background
The purpose of layering is to separate the domain from the non-domain. According to the logic of layering, the responsibilities between layers are clearer. There are traditional four-tier architectures, inverted four-tier architectures, and hexagon architectures.
As customers of domain objects, application services are very different from domain services, and their roles are not the same. Do not confuse the two concepts.
Domain events, useful…
First, the traditional four-tier architecture
Infrastructure layer: Repository implementation (persistence), message-oriented middleware (message processing), external interface interaction implementation, access caching, etc.
Domain layer: Entity, Value Object, Domain Service, Repository interface (excluding implementation), Factory, etc., is the most core business layer of the project.
Application layer: business task allocation, service orchestration, transaction control, etc., independent of business. Application Service is the client of domain layer objects.
UI layer: it is mainly the interaction layer with the system user. In fact, it is generally not needed for applications with separate front and back ends. If Spring MVC is used, the controller can be directly placed in the application layer.
Two, rely on the inverted four-tier architecture
The infrastructure layer relies on the domain layer, where an interface like Repository can be placed, whereas the domain layer only needs to use the interface and its implementation is placed in the infrastructure layer, so the concept of layering seems to disappear, as if layers are flattened out.
Three, hexagonal architecture
Hexagonal architecture allows us to take a higher view of the system architecture. The adapters in the upper left corner can be understood as restful interfaces, WebService interfaces, etc., the triangular adapters on the left side can be understood as receiving message interfaces (MQ messages, domain events, etc.), and the adapters on the right side can be understood as persistent, memory access interfaces. The adapter in the lower right corner can be understood as sending domain events or sending MQ messages, etc., all around the domain model (domain layer).
There is no standard for which architecture to use, as long as it is appropriate. Of course, in addition to the three mentioned above, there are also CQRS and event-driven architecture. Generally speaking, I think the above three architectures can handle business.
This is a typical hexagonal architecture. Application serves as the application layer and the client of the domain layer, while domain layer only has the domain model.
Port. Adapter is the port and adapter of the above hexagonal architecture, put the adapter below the port, the port can better express the interaction with the outside. Under the Adapter, there are two. Obviously, there is no infrastructure layer here either. In fact, the layered architecture, or how simple how to come.
4. Application services
Application services exist in the application layer, as the client of domain objects, mainly responsible for: task coordination, transaction control, code examples:
public class ProductApplicationService {
private TimeConstrainedProcessTrackerRepository processTrackerRepository;
private ProductOwnerRepository productOwnerRepository;
privateProductRepository productRepository; .// TODO: additional APIs / student assignment
public void initiateDiscussion(InitiateDiscussionCommand aCommand) {
ApplicationServiceLifeCycle.begin();
try {
Product product =
this.productRepository()
.productOfId(
new TenantId(aCommand.getTenantId()),
new ProductId(aCommand.getProductId()));
if (product == null) {
throw new IllegalStateException(
"Unknown product of tenant id: "
+ aCommand.getTenantId()
+ " and product id: "
+ aCommand.getProductId());
}
product.initiateDiscussion(new DiscussionDescriptor(aCommand.getDiscussionId()));
this.productRepository().save(product);
ProcessId processId = ProcessId.existingProcessId(product.discussionInitiationId());
TimeConstrainedProcessTracker tracker =
this.processTrackerRepository()
.trackerOfProcessId(aCommand.getTenantId(), processId);
tracker.completed();
this.processTrackerRepository().save(tracker);
ApplicationServiceLifeCycle.success();
} catch(RuntimeException e) { ApplicationServiceLifeCycle.fail(e); }}... }Copy the code
In the initiateDiscussion method of product application services, it can be seen that task coordination is the main task rather than business logic. Can readers have a question, is this not business logic? Still not really, the above code, first create the product through the repository, and then through the product. The initiateDiscussion initialize the Discussion object, initialization here – the business logic to the aggregation of the product, and then update the aggregation, in simple terms, We rarely see if-else in application services. Of course, the above judgement is not ~.
InitiateDiscussionCommand is actually the object, using the Command pattern, let a concept more clear.
If need things here, either through a similar ApplicationServiceLifeCycle class implements, either through annotations, such as Spring @ Transactional annotation.
In addition, application services can handle security-related operations.
5. Domain Events
Domain events are a powerful modeling tool, which means that when we model, we pay attention to important information such as:
When……
If it happens…
Happen… when
These are valuable and should be part of the common language.
In fact, modeling domain events, we can simply think of it as the observer pattern in the design pattern, because domain events themselves really need event senders, event subscribers.
public class Product extends Entity {...public BacklogItem planBacklogItem( BacklogItemId aNewBacklogItemId, String aSummary, String aCategory, BacklogItemType aType, StoryPoints aStoryPoints) {
BacklogItem backlogItem =
new BacklogItem(
this.tenantId(),
this.productId(),
aNewBacklogItemId,
aSummary,
aCategory,
aType,
BacklogItemStatus.PLANNED,
aStoryPoints);
DomainEventPublisher
.instance()
.publish(new ProductBacklogItemPlanned(
backlogItem.tenantId(),
backlogItem.productId(),
backlogItem.backlogItemId(),
backlogItem.summary(),
backlogItem.category(),
backlogItem.type(),
backlogItem.storyPoints()));
returnbacklogItem; }... }Copy the code
When the backlog is created, send the Create Backlog field event.
Domain event subscribers, so this is publish-subscribe, and if you are using the Spring framework, you can use Spring’s ApplicationEvent directly.
Domain events are useful for achieving consistency between aggregations, between models, and even between contexts.
ProductBacklogItemPlanned itself is part of the domain modeling, so it will be stored in a domain layer, and the Entity.
Reference:
1. Implementation of domain-driven design Vaughn Vernon
2. github.com/vaughnverno…