Package by Component with Clean Modules in Java
Let's find the best combination of two good architectural approaches: Package by component and Clean architecture.
Software architecture is about tradeoffs. Even when the theory is good the implementation details can break it.
Package by component, as proposed by Simon Brown in his Missing chapter, is an architectural approach to organizing code based on bundling together everything related to a component. If done properly, it enforces architectural rules, such as not-bypassing the business logic in the infrastructure layer, by using only standard Java compiler mechanisms.
Enforced Separation of Concepts
In my experience, the absence of a strict mechanism like this will lead to skipping the rules eventually, especially when working under time pressure. Such a stress-driven architecture results in the unfamous Big ball of mud.
Package by component
Package by component hides code private for the component under the package accessor and exposes API code with the
public accessor. This makes details inaccessible from other components that don’t share the same package. In the example above, there is no way
OrderController could access
OrderRepository, which is exactly what we want.
Unfortunately, there is still no reliable mechanism to prevent
OrderRepositoryJdbc infrastructure adapter from accessing
OrderServiceImpl domain object directly. The obvious ease of using the implementation directly is very tempting. It seems that in the Package by component approach is enforcement of concepts separation not sufficient.
Modules to the Rescue
Fortunately, Java packages are not the only mechanism to separate concepts on the level of code. Build tools such as Maven and Gradle comes with their own mechanisms for creating code modules (projects). The direction of the artifact dependencies defines the separation of the outside from the inside explicitly.
Package by component with modules
As you can see in the picture, there are no changes in the packages structure. We’re still following the Package by component approach, but this time the domain and infrastructure parts are both separated into individual modules. Now, the domain artifacts has no dependencies on any infrastructure artifacts which makes accessing
OderServiceImpl cause a compilation error.
We don’t have to stick with two modules only. A finer modules structure like
db, and similar would be plausible.
As a non-trivila example, we create a simple web application with a product catalog, a shopping cart and an ordering process.
Spring framework is used to implement the web part and Spring Boot to glue all components together.
Package by component with clean modules
Modules create the familiar onion structure:
- On the top there is the Spring Boot bootstrap layer - it represents a deployment scenario. Alternatively, there could be a WAR module for a web-container such as WildFly, an EAR module, and similar.
- Spring framework (not Spring Boot), web UI, and a database implementation create the second infrastructure layer - technical details.
- The domain layer lies always in the most inner circle - it containes business objects and policies.
Clean onion structure
One more benefit emerges. With modules we can get rid of unnecessary dependencies in a declarative way. For example, if we want to add another implementation of the
OrderRepository interface, such as
OrderRepositoryJpa, we can split the
db module into two separate modules
db-jpa. Then, the implementation can be easily changed just by adding or removing dependencies. Spring Boot starters use this approach very successfully.
Warning: Albeit clean modules are nice, they required additional maintenance. In extreme cases, this could lead to an explosion of modules and rapid increase of development costs. Therefore, use with caution!
The source code could be found on GitHub.
Implementation note: The purpose of the example code is to show the Package by component with modules approach. Nevertheless, it is not a perfect Domain-driven design code. Following the DDD principles seriously, we should see packages such as
com.ttulka.myshop.cart, classes such as
OrderItem, and, of course, tests!