Recently, I saw several articles about DDD shared by Alibaba team, which inspired me a lot. Here I will make a summary, record and learn to share. Content is more, specific can see the original text, here to make a summary of the article, convenient we have a global understanding.
DDD: How to avoid writing a running account code? Previous article: DDD domain Drivers – Design aggregation
1. Domain Primitive
1.1. What is Primitive
Definition of Primitive: Primitive
Java primitives, like String, Integer, and Long, are the primitives of the Java programming language. They are the basis of Java.
But these types are at the programming language level, and for the Domain, there’s very little correlation, so you have the definition of Domain Primitive, so what is the basis of Domain, Domain is for complex business, it’s business dependent, so the Primitive should have business attributes, Obviously String and Integer have no business attributes.
So Domain Primitive actually encapsulates, for example, registering a user:
public class User {
Long userId;
String name;
String phone;
String address;
Long repId;
}
public interface RegistrationService {
User register(String name, String phone, String address);
}
// Register ("13312331233", "Zhang SAN "," Beijing ")
Copy the code
The three String types of such input parameter forms are irrelevant to business. In the process of calling, if the fields are passed in wrong order, it is difficult to find them during the coding process. They may only be found after the code is released or even online.
Let’s look at another way:
public class User {
UserId userId;
Name name;
PhoneNumber phone;
Address address;
RepId repId;
}
public class Name {
private finale String name;
public Name(String name) {
if(StringUtils.isBlank(name)) {
throw new ValidationException("Name cannot be empty"); }}public String getName(a) {
returnname; }}public class PhoneNumber {
private final String number;
public String getNumber(a) {
return number;
}
public PhoneNumber(String number) {
if (number == null) {
throw new ValidationException("Number cannot be empty");
} else if (isValid(number)) {
throw new ValidationException("Number format error");
}
this.number = number; }}public interface RegistrationService {
User register(Name name, PhoneNumber phone, Address address);
}
Copy the code
Analyze the way this works:
- Encapsulate the String field as a concrete object, add verification logic to the constructor, so that as long as the object is created successfully, it must be valid;
- Each value of an input parameter has a corresponding object, so there is no problem with passing the parameter incorrectly. If it does, the compiler will find it directly.
This form is Domain Primitive.
1.2. Summary of Domain Primitive
Domain Primitive definition:
- DP is a traditional Value Object, which is Immutable
- DP is a complete conceptual whole with precise definition
- DP uses native languages in the business domain
- DP can be the smallest part of a business domain or can build complex combinations
Three rules for using Domain Primitive:
- Make implicit concepts explicit
- Make the implicit context explicit
- Encapsulate multi-object behavior
2. DDD code layering
Alibaba /COLA: 🥤 COLA: Clean Object-oriented & Layered Architecture (github.com)
2.1. The Interface layer
The interface layer acts as a gateway to the outside world and decouples network protocols from business logic. It can contain functions such as authentication, Session management, traffic limiting, exception handling, and logging. Of course, if a unified gateway service is available, the logic such as authentication, Session, traffic limiting, and logging can be excluded.
The return value
The return value of the interface layer uniformly encapsulates the Response object. For example, in COLA architecture, the return value is divided into four parts: Response/SingleResponse/PageResponse/MultiResponse
The details are not shown; every company probably has a wrapper for such objects, and the implementation details are pretty much the same
public class Response extends DTO {
private static final long serialVersionUID = 1L;
private boolean success;
private String errCode;
private String errMessage;
}
public class SingleResponse<T> extends Response {
private T data;
}
public class PageResponse<T> extends Response {
private static final long serialVersionUID = 1L;
private int totalCount = 0;
private int pageSize = 1;
private int pageIndex = 1;
private Collection<T> data;
}
public class MultiResponse<T> extends Response {
private static final long serialVersionUID = 1L;
private Collection<T> data;
}
Copy the code
Interface Isolation between the number of interfaces and services at the Interface layer
An Interface layer class should be “small and beautiful”, should be oriented to “a single business” or “a class of business with the same requirements”, need to avoid using the same class to meet the requirements of different types of business.
2.2. The Application layer
Application layer core classes:
- Service Application Service: Responsible for orchestration of business processes but not itself responsible for any business logic
- DTO Assembler: Responsible for converting internal domain models into externally available DTO
- Command, Query, Event objects: as input arguments to ApplicationService
- Return DTO: as the outgoing argument to Service
Command, Query, Event objects
- Command: A Command that performs operations on the system, including adding, deleting, and modifying. Usually an explicit return value is required.
- Query: a read-only operation, which contains Query parameters, filtering, and paging conditions.
- Event: Refers to an existing fact that has happened and needs to be changed or responded to by the system. Usually, Event processing involves some write operations. Event handlers typically do not return values and are asynchronous operations.
The CQE specification: ApplicationService interface input can only be a Command, Query, or Event object that represents the semantics of the current method. The only exception is the single-ID Query, which can omit a Query object.
CQE vs DTO
On the surface, both types of objects are simple POJO objects, but there are big differences:
- CQE: Is the input to ApplicationService and has an explicit “intent” that the object needs to guarantee internallycorrectness.
- Every CQE has a clear “intent”, so it should be avoided as far as possible, even if all parameters are the same, as long as the semantics are different, it should not be reused.
- DTO: just a data container, only for external interaction, so it does not contain any logic, just anaemic objects.
Since CQE is “intentional”, the amount of CQE is theoretically infinite. However, as a data container, DTO is corresponding to the model, so it is limited.
ApplicationService
When there are many processes in a domain, each process corresponds to one or more methods. The advantage of converging these methods into a service class is that there is a complete business process and the process is clear. The downside is that this can lead to too much code in the service.
You can reduce the amount of code by using CommandHandler, EventHandler, and do not define private methods in ApplicationService:
@Component
public class CheckoutCommandHandler implements CommandHandler<CheckoutCommand.OrderDTO> {
@Override
public OrderDTO handle(CheckoutCommand cmd) {
/ /...}}// ApplicationService
public class CheckoutServiceImpl implements CheckoutService {
@Resource
private CheckoutCommandHandler checkoutCommandHandler;
@Override
public OrderDTO checkout(@Valid CheckoutCommand cmd) {
returncheckoutCommandHandler.handle(cmd); }}Copy the code
ApplicationService encapsulates business processes and does not handle business logic.
Points to determine if a piece of code is a business process:
- Don’t have if/else branch logic
- Don’t have computational logic
- Some data conversions can be handed over to other objects for DTO Assembler (MapStruct library is recommended)
Common ApplicationService routines:
- Data preparation: Fetching the Entity, VO, and DTOS returned by the external service from the external service or persistent source
- Perform operations: This includes the creation of new objects, assignment of values, and manipulation of them by calling the domain object’s methods. These operations are usually purely in-memory and non-persistent.
- persistence
Anticorrosive coating
In microservice scenarios, applications often reference external services, which may provide HTTP interfaces, RPC interfaces, and FeignApi.
No anti-corrosion layer:
There is an anti-corrosion layer:
The addition of acLs, by converting them to internal objects, masks the classes, methods, and external objects of external services through the FacadeInterface interface class. If the external service changes in the future, you only need to modify the Facade to implement the class and data transformation logic, not the ApplicationService logic.
The advantage of adding anticorrosion layer is that the external services are decoupled and the changes of external services are shielded. But this comes at a cost, in the form of more code for object conversion, more external service encapsulation, more code, and more maintenance costs. But in the long run, the benefits of such costs far outweigh the disadvantages.
2.3. The Domain layer
Encapsulate the core business logic and provide business entities and business logic calculations to the Application layer through Domain Services and Domain Entities. The domain layer is the core of the application, focusing only on the business and not on technical implementation details, so it does not depend on any other layers.
2.4. The Infrastructure layer
Mainly responsible for technical details, such as database CRUD, cache, message service, search engine, RPC, etc.
2.5. Exception handling
The Interface layer handles all exceptions, returns a unified Response object, and catches all exceptions.
The Application layer is not responsible for handling exceptions and can throw exceptions at will, returning Dtos.