Why Package Structures Won't Save Your Spring Boot Architecture (And How Maven Enforces It) (Chapter 1)
How We Turned Architectural Guidelines Into Compilation Errors Have you ever found yourself doing a Friday afternoon Code Review, only to discover that someone injected the EntityManager directly into a REST controller, writing raw SQL strings, and mapping rows manually with a loop? Or worse, have

How We Turned Architectural Guidelines Into Compilation Errors Have you ever found yourself doing a Friday afternoon Code Review, only to discover that someone injected the EntityManager directly into a REST controller, writing raw SQL strings, and mapping rows manually with a loop? Or worse, have you seen a @RequestParam accepting org.springframework.data.domain.Pageable while the controller returns a raw Page<Entity> directly to the frontend? Congratulations. You have just exposed your database schema to the entire world and completely bypassed the concept of an Anti-Corruption Layer (ACL). When an application is structured within a single module and we rely strictly on package separation (.controller, .service, .repository), we live in an illusion of architectural control. β οΈ The hard truth: Packages do not stop anyone. In the heat of a tight deadline, when "things just need to work," Java package visibility rules will not prevent a junior or stressed developer from committing architectural crimes that you will be debugging for months. Here is how we solved this problem in our team by breaking down the system into highly specialized Maven modules, effectively turning architectural guidelines into compilation errors. The first step toward true isolation was radical: we extracted the API contract into a completely separate Git repository. Why? Because your API contract version should be independent of your backend implementation. If we fix a bug tomorrow in the core business logic, it makes absolutely no sense to bump the version of the REST/Event contract if nothing changed there. The frontend team and QA engineers need a stable contract to work against, completely shielded from our internal refactoring. Next, we split the main backend project into highly specialized Maven modules. When I first proposed this, the team was highly skeptical: "It's too complex," "Why do we need this? We already have packages." So, I built a quick proof-of-concept. Instead of relying on a developerβs goodwill, we shifted the enforcement of architectural boundaries directly to the compiler. Here is what the real dependency topology looks like: βββ RestService API (Git Repo 1) β Β βββ dto (Jackson & Swagger) β Β βββ events (Event Contracts) β Β βββ rest-api (The API intefaces from which documentation is generated) β βββ Backend (Git Repo 2) βββ domain (Pure Domain Models & Core Logic (No Frameworks)) Β Β βββ business-logic (Core Business Logic (Depends only on domain & APIs)) Β Β βββ dao-api (Database Access Interfaces (No JPA/Spring Data)) Β Β βββ dao-impl (Actual DB Integration (Spring Data JPA, Hibernate)) Β Β βββ bridge-api (External Services Communication APIs) Β Β βββ bridge-impl (Actual Integration with External APIs) Β Β βββ integration-tests (Testing layer via Testcontainers (Docker-based)) Β Β βββ application (Spring Boot Bootstrapper) In this structure, the heart of the system β the business-logic module β depends strictly on the interfaces defined in dao-api and bridge-api. It has zero access to dao-impl or bridge-impl. Your core business logic does not have spring-boot-starter-data-jpa, Hibernate, Kafka, or Redisson in its classpath. Once we introduced this change, it didnβt take long for the team to realize its power. If a developer attempts to inject the EntityManager or write raw SQL queries inside the core business logic tomorrow, the code simply will not compile. The build will break right on their local machine. To circumvent this, they would have to deliberately go into the pom.xml of business-logic and introduce a dependency on the database module β an action that would instantly trigger a massive red flag during any Code Review. No More Cyclic Dependencies: Maven physically forbids module A from depending on B if B already depends on A. Lightning-Fast Unit Tests: Because the business logic is entirely decoupled from infrastructure frameworks, unit tests are written effortlessly. We only mock pure Java interfaces, and the tests execute in milliseconds. No one can use the "tests take too much time" excuse anymore. Pure Infrastructure Interchangeability: The business-logic module interacts solely with the contract in dao-api. It doesn't know β nor does it care β whether the data underneath comes from MySQL (via dao-impl), is cached in Redis, or is being streamed via Kafka (via bridge-impl). The implementations are wired together at the very top layer β in the application module. If you are building a small CRUD app with five tables, this approach is undoubtedly overengineering. But if you are building an Enterprise system designed for long-term maintainability, high team velocity, and strict domain boundaries, you cannot afford to build your house on sand. Relying purely on folder structures means that sooner or later, under pressure, someone will break the rules. Transitioning to a multi-module design requires more initial boilerplate, but it eliminates 70% of long-term architectural decay. In the next part, we will dive into the very foundation of this design β the Domain Module β and discuss how to keep it 100% pure (POJO) without allowing a single JPA or Hibernate annotation to pollute your business models. The entire evolutionary architecture of this project is tracked using strict Git tags. To clone the repository and switch exactly to the baseline state established in Chapter 1, use the following link: GitHub Repository (Tag: chapter-01-baseline): advanced-spring-multimodule Note: All core modules are configured with strict compilation-level boundaries. Compile and run mvn clean install to see the structure in action. Maven version 3.9.* and Java 25 are required. βΆοΈ Read Chapter 2: The Domain module π¨ Liked this architecture blueprint? This article is part of my Evolutionary Architecture series. I publish deep-dive technical pieces every week. Subscribe to my Substack Newsletter to get full source code repositories (Git tags) and new chapters straight to your inbox!
Key Takeaways
- β’How We Turned Architectural Guidelines Into Compilation Errors Have you ever found yourself doing a Friday afternoon Code Review, only to discover that someone injected the EntityManager directly into a REST controller, writing raw SQL strings, and mapping rows manually with a loop? Or worse, have
- β’This story was reported by Dev.to, covering developments in the dev space.
- β’AI advancements continue to reshape industries β read the full article on Dev.to for complete coverage.
π Continue reading the full article:
Read Full Article on Dev.to βShare this article



