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).
The Illusion of Package Control
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 Architectural Blueprint: Divide, Conquer, and Version
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.
The Dependency Topology
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)
Enter fullscreen mode Exit fullscreen mode
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.
Let Maven Keep Your Code Reviews Clean
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.
Immediate Benefits in an Enterprise Environment
- 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-logicmodule interacts solely with the contract indao-api. It doesn’t know — nor does it care — whether the data underneath comes from MySQL (viadao-impl), is cached in Redis, or is being streamed via Kafka (viabridge-impl). The implementations are wired together at the very top layer — in theapplicationmodule.
Conclusion: Is it Overengineering?
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.
What’s Next?
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.
Codebase & Architecture Blueprint
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!
답글 남기기