Hexagonal Architecture tells us no framework should be present inside the domain to avoid technical accidental complexity and to ease the migration to a new structural framework (or major version) without redeveloping parts of business logics. This means that when you are using Spring, you cannot rely on any stereotype annotations such as @Service or @Component inside your domain.
Beans declaration marathon
As a result, we typically end up with bean factory methods inside Spring Configurations with a lot of boilerplate code for instantiating domain services, repositories and stubs because we think we cannot use the ComponentScan for domain objects.
This article will show you how to take advantage of the component scanning without violating the hexagonal architecture rules with code samples in Koltin. If you are working with Java, don’t worry, the code will be around the same, you’ll have to adjust a bit the syntax. Be aware that the maven part of this article is specific to Koltin, you can ignore it if you are not using this language.
The Secret: Dependency Inversion
Spring component scanning looks for annotated classes with Spring stereotypes to identify the objects you want to register in the ApplicationContext. So these objects will inevitably have a dependency on the Spring Framework, and this is basically an issue inside the domain. To get rid off this limitation, we will use the same trick used by the hexagonal architecture to ensure that your domain will never be dependent on your persistence layer: a dependency inversion.
The trick is simple, create descriptive annotations inside your domain that will identify the objects you want to expose to the ApplicationContext – usually DomainServices and Stubs.
The retention policy is set to RUNTIME to allow Spring to discover them. With the other policies, the annotation would have been discarded at run time, which is not helpful in our case. You can now safely annotate with your custom annotations the domain objects you want to expose.
You can also create descriptive annotations for the other DDD objects such as Aggregate, Entity, ValueType, Repository even if you don’t expose them to the ApplicationContext. This is a great technic to onboard people into Domain Driven Design (with some javadoc, see the example below) or simply to better understand your business and how it was modeled.
This has sometimes been useful to detect some bugs in code reviews. Like a ValueType containing an Entity, a ValueType with side effects… It is sometimes difficult to determine the nature of object we are working on, especially in rich business domains.
We will now tell Spring to treat those objects like @Service, @Component annotated ones, by configuring our custom domain annotation for scanning in the infrastructure. Simply create a DomainConfiguration and annotate as follow:
Beware that the ComponentScan looks by default only in the SpringBootApplication package, including its child packages. Thus, if your domain is not in this inverted hierarchy, you will need to specify a base package class that will identify the root package of your domain (see commented code above). Usually, we use the main domain Aggregate here since a common DDD convention encourages us to name the root domain package after him.
You can also use basePackages and give the package name as a String. But this practice is discouraged because not resilient to package refactoring.
And if you are use Java this is pretty much it, you can go directly to the Limitations section. If you are use Kotlin, you’ll be disappointed if you try to run this code. Kotlin follows the open-close principle, so every class is by default final, which means it cannot be extended. And for some reasons, maybe for bytecode injection, Spring needs bean classes to be open.
We will not use the
open keyword of Kotlin to solve this issue because it would be a workaround within the domain to allow the Spring integration. Instead, we will use an out-of-the box solution using Maven.
Make SOLID lose its ‘O’
The kotlin maven plugin offers a compilation option named all-open. In this option, you can configure the classes that the kotlin compiler will open out-of-the-box and let Spring do its dirty stuff at run time. To do this, in the pom of your domain, specify the following build plugin with the full qualified name of the annotations you created above:
And now it should work with Kotlin.
Since everything comes with a price, here are the limits of this method:
- If you need some conditional or profile-based bean construction, you will have to switch back to a bean factory.
- Subjective: some people dislike annotations.
- If you are using Kotlin, the classes opened by the maven plugin will be now extendable, so it violates the open-close principle.
If you want to go further in the implementation of a Hexagonal Architecture based application using Maven, Koltin and SpringBoot, you can take a look at this repository.