As the name implies, domain-driven development is not just an implementation pattern, but a development method that involves members other than development personnel.
In the design of Domain Driven Development, software is designed in the following three layers.
--Application layer --Domain layer --Infrastructure layer
Why do you make such a division? The mobile app version may suddenly start developing, and the DB may suddenly cease to be an RDB. In such a case, what can be reused between software of the same domain is domain knowledge, and what includes this is the domain layer.
** Application layer **
--Main function --Application services (session management, etc.) --Controller --Presentations such as views, email templates, etc. --Value object for view, DPO
** Domain layer **
--Domain service --Something --Value object --Aggregation -* Repository interface *
** Infrastructure layer **
--Repository
You can see that the implementation class of the repository is the infrastructure layer. But why is the interface a domain layer? This is because if an interface is an infrastructure layer, the implementation of the domain layer that calls it will inevitably depend on the infrastructure layer. In other words, you will not be able to select the infrastructure library to use.
He explained that the interface of the repository is put in the domain layer so that the domain layer does not depend on the infrastructure layer. So where do you initialize an instance of the repository that contains implementation knowledge? Simply thinking, it seems that there is no choice but to initialize it at the application layer and propagate it to the domain service. However, if you use Spring DI and declare an implementation class that inherits the domain service class and interface as a component, Spring DI will solve this problem. There is no need to prepare a Factory pattern or ʻApplicationContextAware`.
@Repository
public class Repository extends IRepository {
...
}
Use the constructor @Autowired
instead of the field @Autowired
to resolve the Dependency. It makes it easier to write unit tests.
It can be written more easily with Lombok's @RequiredArgsConstructor
.
@Service
@RequiredArgsConstructor
public class Service {
private final IRepository repository;
}
IRepository mockRespository = new MockRepository();
var service = new Service(mockRespotory);
I wrote that the software was designed in three layers, but should the Maven module also be divided into three layers? If the software is single, it is enough to divide it into packages.
On the contrary, if you want to divide the module, prepare a parent module.
/pom.xml
<modules>
<module>./web</module>
<module>./mobile</module>
<module>./domain</module>
<module>./infrastructure</module>
</modules>
As
/application/pom.xml
<parent>
<groupId>me.yong_ju.application</groupId>
<artifactId>parent</artifactId>
<version>...</version>
<relativePath>../</relativePath>
</parent>
...
<dependencies>
<dependency>
<groupId>me.yong_ju.application</groupId>
<artifactId>domain</artifactId>
<version>...</version>
</dependency>
<dependency>
<groupId>me.yong_ju.application</groupId>
<artifactId>infrastructure</artifactId>
<version>...</version>
</dependency>
</dependencies>
You can do it. By defining the child module first in the parent module, when that module is added to the dependency, it will be retrieved locally instead of in the Maven repository. (However, the versions must match)
If you divide the package into three layers, you will immediately run into a problem.
That's because we put @ SpringBootApplication
in the application package, so Spring's component scan only injects application layer components.
what to do. You can use scanBasePackages
to change the packages you want to scan to the upper layer, but we don't recommend it.
Here, we have prepared a Configuration class with Component Scan enabled in the domain layer and infrastructure layer, and explicitly @Import
the Configuration used by the application.
@Configuration
@ComponentScan
public class DomainConfiguration {
...
}
@Configuration
@ComponentScan
public class InfrastructureConfiguration {
...
}
@SpringBootApplication
@Import({ DomainConfiguration.class, InfrastructureConfiguration.class })
public class Application {
...
}
A pattern in which a service you often see has become just a repository wrapper.
It should be serviced in meaningful operation units, and if it is not known yet, it is even faster to turn it into a service.
(In connection with this, I often see patterns that have been @Transactional
in a completely meaningless operation unit.)
I personally think that implementation patterns are generally a tool for stopping thinking while avoiding fatal design mistakes in software development with poor planning prospects. Therefore, it is a mess to waste time wandering around seeking best practices for everything. Java and its peripheral frameworks are constantly evolving. In some cases, they can be used for less redundant and safer implementation.
If I have free time, I will focus on what I took time to understand.
Recommended Posts