Still in the single application is hierarchical architecture, we use the most is the three-tier architecture. Now it is the era of microservices. There are several commonly used models of microservices architecture, such as: clean architecture, CQRS (command query separation) and hexagonal architecture. Each architectural model has its own application scenario, but at its core is the principle of “high cohesion and low coupling”. The application of domain-driven design (DDD) to address the impact of accelerating business changes on the architecture is consistent with the design philosophy of microservices architecture. Layered architecture based on domain-Driven Design (DDD) has become the best practice for microservices architecture practices.
What is DDD hierarchical architecture
1. Traditional three-tier architecture
To understand the DDD layered architecture, first look at the traditional three-tier architecture.
Traditional three-tier architecture process:
- The first step is to consider the database design, how to build the data table, how to design the relationship between the tables
- The second step is to set up the data access layer, such as selecting an ORM framework or concatenating SQL operations
- The third step is the implementation of business logic, because we design the database first, our thinking will be around the database, thinking about how to write to the data correctly in the database, then there is the standard practice of the CRUD, also is not too much consider object-oriented, decoupling, such code is more and more difficult for the daily maintenance of nature
- The fourth step represents the user-oriented output of the layer
2. DDD layered architecture
In order to solve the problem of high coupling and easily cope with future system changes, we propose the concept of domain-driven design to design the architecture.
This paragraph is part summarize the DDD practice class is derived from the innovation of the 07 | DDD layered architecture: effectively reduce the dependence between layer and layer of substance
1) Domain layer
First of all, we put aside the trouble of the database, start from the business logic, design no longer consider the implementation of the database. Split the former business logic layer (BLL) into domain layer and application layer.
The domain layer focuses on the business logic implementation of business objects and reflects the logical changes of real-world business. It is used to express business concepts, business states, and business rules. For business analysis, see “Analyzing Business with Domain-Driven Design.”
2) Application layer
The application layer is the upper layer of the domain layer and depends on the domain layer. It is the coordination and arrangement of various aggregations and does not include any business logic in principle. It provides support for front-end interfaces with coarse-grained closure. In addition to providing upper-level calls, you can also include subscriptions to events and messages.
3) User interface layer
At the user interface layer, the data inbound interface for users can be implemented differently in different scenarios. Web provides services in HTTP restful mode, adding functions such as security authentication, permission verification, and log recording. Microservices-oriented services can provide services in RPC mode and add functions such as traffic limiting and fusing.
4) Infrastructure layer
The infrastructure layer is the outgoing interface to the data, encapsulating the technical details of the data invocation. Any other layer can be served, but the dependency inversion principle is used to solve the coupling problem. The other layers rely only on the infrastructure’s interfaces, separated from the implementation.
Second, DDD layered code implementation
1. Structural model
2. Directory structure
. ├ ─ ─ pom. XML └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ fun │ │ └ ─ ─ barryhome │ │ └ ─ ─ DDD │ │ ├ ─ ─ WalletApplication. Java │ │ ├── Application │ │ ├─ ├─ ├.java │ ├─ ├.java │ ├.java │ ├.java │ ├─ │ ├─ ├─ ├─ All exercises, ├─ all exercises, all exercises, all exercises, all exercises │ ├─ ├─ Java │ ├─ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ │ ├─ Tradeservice.java │ │ ├─ enums │ │ ├─ ├.java │ │ │ ├─ TradeType │ │ │ │ └ ─ ─ WalletStatus. Java │ │ │ ├ ─ ─ event │ │ │ │ └ ─ ─ TradeEvent. Java │ │ │ ├ ─ ─ model │ │ │ │ ├ ─ ─ BaseEntity. Java │ │ │ │ ├ ─ ─ TradeRecord. Java │ │ │ │ └ ─ ─ Wallet. Java │ │ │ └ ─ ─ the repository │ │ │ ├ ─ ─ TradeRepository. Java │ │ │ └ ─ ─ WalletRepository. Java │ │ └ ─ ─ proceeds │ │ ├ ─ ─ TradeRepositoryImpl. Java │ │ ├ ─ ─ WalletRepositoryImpl. Java │ │ ├ ─ ─ Cache │ │ │ └ ─ ─ Redis. Java │ │ ├ ─ ─ client │ │ │ ├ ─ ─ AuthFeignClient. Java │ │ │ └ ─ ─ LocalAuthClient. Java │ │ ├ ─ ─ jpa │ │ │ ├─ ├─ ├─ Java │ ├─ ├─ Java │ ├─ garbage │ ├─ ├─ manule.txt, manule.txt, manule.txt, manule.txt, manule.txt, manule.txtCopy the code
This structure is a simple structure of a single microservice, with all layers in the same module.
In the development process of large projects, in order to achieve the permission control of core modules or better flexibility, the structure can be adjusted appropriately, please refer to the system structure of Digital Wallet System
3. Domain Layer Implementation
After business analysis (Analyzing Business with Domain-Driven Design), start writing code, starting with the domain layer, creating domain objects and domain service interfaces
1) Domain objects
Domain objects include entity objects and value objects.
Entity object: an object with a unique identity that can exist independently and be changeable
Value object: an object that cannot exist alone or at the logical level alone, meaningless, and immutable
Aggregation: A collection of multiple objects as a whole externally
Aggregate root: The entity object in the aggregate that represents the entire business operation, provides external access operations, maintains data consistency within the aggregate, and is the manager of the objects in the aggregate
Code examples:
/ / deal
public class TradeRecord extends BaseEntity {
/** ** transaction number */
@Column(unique = true)
private String tradeNumber;
/** * Transaction amount */
private BigDecimal tradeAmount;
/** * Transaction type */
@Enumerated(EnumType.STRING)
private TradeType tradeType;
/** * Transaction balance */
private BigDecimal balance;
/** * wallet */
@ManyToOne
private Wallet wallet;
/** * Transaction status */
@Enumerated(EnumType.STRING)
private TradeStatus tradeStatus;
@DomainEvents
public List<Object> domainEvents(a) {
return Collections.singletonList(new TradeEvent(this)); }}/ / wallet
public class Wallet extends BaseEntity {
/** * wallet ID */
@Id
private String walletId;
/** * Password */
private String password;
/** * state */
@Enumerated(EnumType.STRING)
private WalletStatus walletStatus = WalletStatus.AVAILABLE;
/** * user Id */
private Integer userId;
/** ** balance */
private BigDecimal balance = BigDecimal.ZERO;
}
Copy the code
-
From the system design of wallet transaction example, any operation of wallet, such as recharge and message, drives the change of wallet balance through the transaction object
-
Both transaction object and wallet object are entity objects and constitute aggregation relationship. Transaction object is the aggregation root of wallet transaction business model and provides call service externally on behalf of aggregation
-
After analysis, the transaction object and the wallet object are one-to-many (@manytoone). Here, JPA is used to do ORM architecture. For more JPA practices, please refer to >>
-
The domain modeling here uses the anemia model, which has simple structure, single responsibility and good isolation from each other but lacks object-oriented design thought. For domain modeling, please refer to “Anemia Model and Hyperemia Model in Domain Modeling”.
-
DomainEvents () is an implementation of the publication of domainEvents. Any data operation on a transaction object triggers the publication of an event. The event driven design model can be implemented in conjunction with the event subscription
2) Field services
/** * Created on 2020/9/7 11:40 am **@authorBarry * Description: Transaction services */
public interface TradeService {
/** * recharge **@param tradeRecord
* @return* /
TradeRecord recharge(TradeRecord tradeRecord);
/** * consumption **@param tradeRecord
* @return* /
TradeRecord consume(TradeRecord tradeRecord);
}
Copy the code
- Define the service interface first. The definition of the interface should follow the operation of the real business. Do not use program logic or database logic to design and define the increase, deletion, change and check
- The main direction of thinking is what services can be provided externally by the object of the transaction. The definition of such services is coarse-grained and highly cohesive, and it is not necessary to define methods at the implementation level of some specific code
- The input and output parameters of interfaces should be in the form of objects, which is compatible with various scenarios
- The complex query methods required by the front end are not defined here; in general, the query is not a domain service with no data changes and can be handled separately
- The implementation of domain services focuses on logical implementation and should not include technical base class code such as caching implementation, database implementation, remote invocation, etc
3) Infrastructure interface
public interface TradeRepository {
/** * save *@param tradeRecord
* @return* /
TradeRecord save(TradeRecord tradeRecord);
/** **@param tradeNumber
* @return* /
TradeRecord findByTradeNumber(String tradeNumber);
/** * Sends MQ event messages *@param tradeEvent
*/
void sendMQEvent(TradeEvent tradeEvent);
/** * get all *@return* /
List<TradeRecord> findAll(a);
}
Copy the code
- The main purpose of infrastructure interfaces placed at the domain layer is to reduce the dependency of the domain layer on the infrastructure layer
- Interfaces are designed so that technical details of implementation are not exposed, such as the inability to take assembled SQL as parameters
4. Implementation of application Layer
// Transaction services
@Component
public class TradeManager {
private final TradeService tradeService;
public TradeManager(TradeService tradeService) {
this.tradeService = tradeService;
}
/ / prepaid phone
@Transactional(rollbackFor = Exception.class)
public TradeRecord recharge(TradeRecord tradeRecord) {
return tradeService.recharge(tradeRecord);
}
/ / consumption
@Transactional(rollbackFor = Exception.class)
public TradeRecord consume(TradeRecord tradeRecord) {
returntradeService.consume(tradeRecord); }}// Trade event subscriptions
@Component
public class TradeEventProcessor {
@Autowired
private TradeRepository tradeRepository;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "# tradeEvent.tradeStatus.name() == 'SUCCEED'")
public void TradeSucceed(TradeEvent tradeEvent) { tradeRepository.sendMQEvent(tradeEvent); }}// Trade message subscription
@Component
public class TradeMQReceiver {
@RabbitListener(queues = "ddd-trade-succeed")
public void receiveTradeMessage(TradeEvent tradeEvent){
System.err.println("========MQ Receiver============"); System.err.println(tradeEvent); }}Copy the code
Application services:
- The application layer is a thin layer that is used to invoke and compose domain services and does not contain any business logic
- Can include a small number of process parameter judgments
- Since multiple domain services may combine operation calls, add **@Transactional** transaction control if atomicity requires it
Event Subscription:
- Event subscription is an implementation of the decoupling of collaboration between operations in multiple domains within a process, and it is also the entry point for all subsequent operations within a process
- It is different from the combined operation of application services. The combined operation can be increased or decreased according to the requirements of the scenario, but the operation after the event subscription is relatively fixed, mainly to meet the requirements of logical consistency
- AFTER_COMMIT the transactionPhase.after_COMMIT configuration is invoked after the transaction of the previous action has completed to reduce the impact of subsequent actions on the previous action
- Event subscriptions can have multiple message bodies and are best handled in a single class for ease of management
- MQ message publishing is typically placed in an event subscription
Message subscription:
- Message subscription is an asynchronous implementation of collaborative decoupling between multiple microservices
- The message body should be wrapped as a unified object to reduce the difficulty of processing heterogeneous objects
5. Infrastructure
@Repository
public class TradeRepositoryImpl implements TradeRepository {
private final JpaTradeRepository jpaTradeRepository;
private final RabbitMQSender rabbitMQSender;
private final Redis redis;
public TradeRepositoryImpl(JpaTradeRepository jpaTradeRepository, RabbitMQSender rabbitMQSender, Redis redis) {
this.jpaTradeRepository = jpaTradeRepository;
this.rabbitMQSender = rabbitMQSender;
this.redis = redis;
}
@Override
public TradeRecord save(TradeRecord tradeRecord) {
return jpaTradeRepository.save(tradeRecord);
}
/** ** order */
@Override
public TradeRecord findByTradeNumber(String tradeNumber) {
TradeRecord tradeRecord = redis.getTrade(tradeNumber);
if (tradeRecord == null){
tradeRecord = jpaTradeRepository.findFirstByTradeNumber(tradeNumber);
/ / cache
redis.cacheTrade(tradeRecord);
}
return tradeRecord;
}
/** * Send event message *@param tradeEvent
*/
@Override
public void sendMQEvent(TradeEvent tradeEvent) {
// Send a message
rabbitMQSender.sendMQTradeEvent(tradeEvent);
}
/** * get all */
@Override
public List<TradeRecord> findAll(a) {
returnjpaTradeRepository.findAll(); }}Copy the code
-
Infrastructure layer is the output direction of data, mainly including database, cache, message queue, remote access and other technical implementation
-
The basic design layer hides the technical implementation details and provides coarse-grained data output services
-
Database operations: The domain layer passes data objects, which can be split and implemented in the same way as data tables
6. User Interface Layer (Controller)
@RequestMapping("/trade")
@RestController
public class TradeController {
@Autowired
private TradeManager tradeManager;
@Autowired
private TradeRepository tradeRepository;
@PostMapping(path = "/recharge")
public TradeDTO recharge(@RequestBody TradeDTO tradeDTO) {
return TradeDTO.toDto(tradeManager.recharge(tradeDTO.toEntity()));
}
@PostMapping(path = "/consume")
public TradeDTO consume(@RequestBody TradeDTO tradeDTO) {
return TradeDTO.toDto(tradeManager.consume(tradeDTO.toEntity()));
}
@GetMapping(path = "/{tradeNumber}")
public TradeDTO findByTradeNumber(@PathVariable("tradeNumber") String tradeNumber){
returnTradeDTO.toDto(tradeRepository.findByTradeNumber(tradeNumber)); }}Copy the code
- The user interface layer provides service support for terminals
- A separate module can be used according to different scenarios to provide HTTP restful Web oriented and RPG support for API calls between services
- For the Web end to provide identity authentication and authority verification services, VO data conversion
- For THE API end to provide current limiting and fusing services, DTO data conversion
- Transferring data from the application layer to the user interface layer facilitates the change of requirements in different scenarios and ensures the uniformity of data formats at the application layer
7. Complex data query
This does not involve complex data queries, which do not involve business logic and should not be dealt with at the domain level.
- This method can be used if there are many requirements for complex data query
CQRS
Mode, the query will be processed by a separate module. If less, the infrastructure layer can do the data query, the application layer can do the data encapsulation, and the user interface layer can do the data call - JPA is not suitable for database query operations with multiple table associations, and other flexible ORM architectures can be used
- In the case of large data concurrency, multi-table association seriously affects database performance. Therefore, you can perform broad-table query
Third, review
DDD layer mainly solves the problem of coupling degree between each layer, so that each layer does not affect each other. The design of domain layer is the center of the whole system and can best embody the core idea of domain driven design. Its good design is to ensure the sustainability and maintainability of the future architecture.
Iv. Source code
The code in this article due to the length of reasons have some omission is not complete logic, if interested please Fork source code gitee.com/hypier/barr…