The way of practice growth -- Design Pattern Practice I: what is the difference between congestion model DDD and anemia model MVC? How to use DDD to develop a virtual wallet system?

Keywords: Java mvc


Most engineers are engaged in business development, so the actual project we are talking about today is also a typical business system development case. As we all know, many business systems are developed based on MVC three-tier architecture. In fact, to be more precise, this is an MVC three-tier architecture development model based on anemia model.

Although this development mode has become the standard development mode of Web projects, it violates the object-oriented programming style and is a thorough process-oriented programming style. Therefore, it is called anti pattern by some people. Especially after the prevalence of Domain Driven Design (DDD), this traditional development model based on anemia model has been criticized. DDD development model based on congestion model is more and more advocated.

Considering that you may not understand the concepts I just mentioned, before officially entering the explanation of the actual project, I will take you to clarify the following questions:

  • What is anemia model? What is a hyperemia model?
  • Why does the traditional development model based on anemia model violate OOP?
  • Since the traditional development model based on anemia model violates OOP, why is it so popular?
  • Under what circumstances should we consider using DDD development model based on congestion model?

What is the traditional development model based on anemia model?

I believe that MVC three-tier architecture is no stranger to most back-end development engineers. However, in order to unify our understanding of MVC, I'd like to take you to review what is the MVC three-tier architecture.

In the MVC three-tier architecture, M represents Model, V represents View, and C represents Controller. It divides the whole project into three layers: presentation layer, logic layer and data layer. MVC three-tier development architecture is a relatively general layering method. When implemented to the specific development level, many projects will not 100% comply with the fixed layering method of MVC, but will make appropriate adjustments according to the specific project requirements.

For example, many Web or App projects are now separated from the front end and the back end is responsible for exposing the interface to the front end. In this case, we generally divide the back-end project into Repository layer, Service layer and Controller layer. The Repository layer is responsible for data access, the Service layer is responsible for business logic, and the Controller layer is responsible for exposing interfaces. Of course, this is just one way of layering and naming. Different projects and teams may adjust this. However, all changes are inseparable. As long as it is a Web project relying on database development, the basic layering idea is not bad.

Just now we reviewed the MVC three-tier development architecture. Now, let's look again. What is an anemia model?

In fact, you may have been developing anemia models, but you don't know it. It is no exaggeration to say that as far as I know, almost all business back-end systems are based on anemia model. Let me give you a simple example to explain.

// Controller+VO(View Object) //
public class UserController {
  private UserService userService; //Injection through constructor or IOC framework
  
  public UserVo getUserById(Long userId) {
    UserBo userBo = userService.getUserById(userId);
    UserVo userVo = [...convert userBo to userVo...];
    return userVo;
  }
}

public class UserVo {//Omit other properties and get/set/construct methods
  private Long id;
  private String name;
  private String cellphone;
}

// Service+BO(Business Object) //
public class UserService {
  private UserRepository userRepository; //Injection through constructor or IOC framework
  
  public UserBo getUserById(Long userId) {
    UserEntity userEntity = userRepository.getUserById(userId);
    UserBo userBo = [...convert userEntity to userBo...];
    return userBo;
  }
}

public class UserBo {//Omit other properties and get/set/construct methods
  private Long id;
  private String name;
  private String cellphone;
}

// Repository+Entity //
public class UserRepository {
  public UserEntity getUserById(Long userId) { //... }
}

public class UserEntity {//Omit other properties and get/set/construct methods
  private Long id;
  private String name;
  private String cellphone;
}

When we usually develop Web backend projects, we basically organize the code in this way. Among them, UserEntity and UserRepository constitute the data access layer, UserBo and UserService constitute the business logic layer, and UserVo and UserController belong to the interface layer here.

From the code, we can find that UserBo is a pure data structure that contains only data and no business logic. Business logic is centralized in UserService. We operate UserBo through UserService. In other words, the data and business logic of the Service layer are divided into two classes: BO and Service. Classes like UserBo that contain only data but no business logic are called Anemic Domain Model. Similarly, UserEntity and UserVo are designed based on anemia model. This anemia model separates data from operation, destroys the encapsulation characteristics of object-oriented, and is a typical process oriented programming style.

What is DDD development model based on congestion model?

Just now we talked about the traditional development model based on anemia model. Now let's talk about another recently more respected development model: DDD development model based on congestion model.

First, let's take a look at what is the congestion model?

In the anemia model, data and business logic are divided into different classes. In contrast to the Rich Domain Model, the data and corresponding business logic are encapsulated in the same class. Therefore, this congestion model meets the object-oriented encapsulation characteristics and is a typical object-oriented programming style.

Next, let's take a look at what is Domain Driven Design?

Domain Driven Design, or DDD, is mainly used to guide how to decouple business systems, divide business modules, and define business domain models and their interactions. The concept of domain driven design is not novel. It was put forward as early as 2004. It has a history of more than ten years. However, it is well known by the public and is based on the rise of another concept, that is, microservice.

We know that in addition to the development of service governance systems such as monitoring, call chain tracking and API gateway, microservices also have another more important work, that is, reasonably splitting microservices for the company's business. Domain driven design is just used to guide the division of services. Therefore, microservices have accelerated the prevalence of domain driven design.

However, I personally think that domain driven design is a bit similar to agile development, SOA, PAAS and other concepts. It sounds very tall, but in fact it is only worth "five cents". Even if you haven't heard of Domain Driven Design and know nothing about this concept, you are more or less using it as long as you are developing business systems. The key to doing domain driven design well is to see your familiarity with your business, not your mastery of the concept of domain driven design itself. Even if you know more about the domain driver, you are not familiar with the business and may not be able to make a reasonable domain design. Therefore, don't regard Domain Driven Design as a silver bullet and don't spend too much time studying it too much.

In fact, the code implemented by DDD development mode based on congestion model is also layered according to MVC three-tier architecture. The Controller layer is still responsible for exposing interfaces, the Repository layer is still responsible for data access, and the Service layer is responsible for core business logic. The difference between it and the traditional development model based on anemia model is mainly in the Service layer.

In the traditional development model based on anemia model, the Service layer includes Service class and BO class. BO is an anemia model, which only contains data and does not contain specific business logic. The business logic is concentrated in the Service class. In the DDD development pattern based on congestion model, the Service layer includes two parts: Service class and domain class. Domain is equivalent to BO in anemia model. However, the difference between domain and BO is that it is developed based on Congestion Model and contains both data and business logic. The Service class becomes very thin. To sum up, the traditional development model based on anemia model pays more attention to Service than BO; DDD development mode based on congestion model, light Service and heavy domain.

Why is the traditional development model based on anemia model so popular?

As we mentioned earlier, the traditional development model based on anemia model separates data from business logic, which violates the encapsulation characteristics of OOP. In fact, it is a process oriented programming style. However, now almost all Web projects are based on this anemia model, and even the official demo of Java Spring framework is written according to this development model.

As we mentioned earlier, process oriented programming style has various disadvantages. For example, after the separation of data and operation, the operation of data itself is not limited. Any code can modify the data at will. Since the traditional development model based on anemia model is process oriented programming style, why is it accepted by programmers? On this issue, I summarize the following three reasons.

The first reason is that in most cases, the system business we develop may be relatively simple, as simple as SQL based CRUD operation. Therefore, we don't need to use our brains to carefully design the congestion model. The anemia model is enough to cope with the development of this simple business. In addition, because the business is relatively simple, even if we use the congestion model, the business logic contained in the model itself will not be many, and the designed domain model will be relatively thin, similar to the anemia model, which is of little significance.

The second reason is that the design of congestion model is more difficult than anemia model. Because congestion model is an object-oriented programming style. From the beginning, we should design what operations should be exposed and what business logic should be defined for the data. Instead of the anemia model, we only need to define the data, and then we define the operation in the Service layer according to the function development requirements, without much design in advance.

The third reason is that thinking has been solidified and transformation has costs. The traditional development model based on anemia model has been popular and used to for so many years. If you ask an older colleague nearby, basically all the Web projects he participated in in in the past should be based on this development model, and there have been no major problems. If we turn to congestion model and domain driven design, there will be a certain learning cost and transformation cost. Many people are unwilling to do this without encountering the pain point of development.

What projects should consider using the congestion model-based DDD development model?

The traditional development model based on anemia model is more suitable for system development with simple business. Correspondingly, DDD development mode based on congestion model is more suitable for complex business system development. For example, a financial system containing various interest calculation models, repayment models and other complex businesses.

**You may have some questions. These two development modes are implemented at the code level. The difference is that one puts the business logic into the Service class and the other puts the business logic into the Domain domain model** Why can't the traditional development model based on anemia model deal with the development of complex business systems? What about DDD development mode based on congestion model?

In fact, in addition to the differences we can see at the code level (one business logic is placed in the Service layer and the other in the domain model), there is another very important difference, that is, two different development modes will lead to different development processes. The development process of DDD development mode based on congestion model has more advantages in dealing with the development of complex business systems. Why do you say that? Let's first recall how our traditional development model based on anemia model realizes a functional requirement.

It is no exaggeration to say that most of our usual development is SQL driven development mode. When we receive the development requirements of a back-end interface, we will look at the data required by the interface corresponding to the database, which table or tables we need, and then think about how to write SQL statements to obtain data. After that, define Entity, BO and VO, and then add code to the corresponding Repository, Service and Controller classes in a template manner.

The business logic is wrapped in a large SQL statement, and the Service layer can do very little. SQL is written for specific business functions with poor reusability. When I want to develop another business function, I can only rewrite an SQL statement to meet the new requirements, which may lead to all kinds of SQL statements with similar length and little difference.

Therefore, in this process, few people will apply the concept of domain model and OOP, and have little awareness of code reuse. For simple business systems, this development method is not a problem. However, for the development of complex business systems, such a development method will make the code more and more chaotic, and eventually lead to inability to maintain.

If we apply the DDD development mode based on Congestion Model in the project, the corresponding development process will be completely different. In this development mode, we need to clarify all businesses in advance and define the attributes and methods contained in the domain model. The domain model is equivalent to a reusable business middle layer. The development of new functional requirements is based on the previously defined domain models.

We know that the more complex the system, the higher the requirements for code reusability and maintainability, and the more time and energy we should spend on preliminary design. The DDD development model based on congestion model just needs us to do a lot of business research and domain model design in the early stage, so it is more suitable for the development of this complex system.

How to develop a virtual wallet system using DDD based on congestion model?

WALLET business background

Many applications with payment and purchase functions (such as Taobao, Didi travel, geek time, etc.) support the function of wallet. The application opens a virtual wallet account in the system for each user, and supports user recharge, withdrawal, payment, freezing, overdraft, gift transfer, account balance query, transaction flow query and other operations. The following figure is a typical wallet function interface, which you can intuitively feel.

Generally speaking, each virtual wallet account corresponds to a user's real payment account, which may be a bank card account or a three party payment account (such as Alipay, WeChat wallet). In order to facilitate the follow-up explanation, we limit the wallet to only support the five core functions of recharge, withdrawal, payment, balance query and transaction flow query. We will not consider other infrequent functions such as freezing, overdraft and gift transfer. In order to let you understand how these five core functions work, let's take a look at their business implementation processes.

1. Recharge

Users recharge the money in their bank card account into the virtual wallet account through three-party payment channels. The whole process can be divided into three main operation processes:

The first operation is to transfer from the user's bank card account to the application's public bank card account; The second operation is to add the user's recharge amount to the balance of the virtual wallet; The third operation is to record the transaction flow.


2. Payment

The user uses the balance in the wallet to pay for the goods in the application. In fact, the payment process is a transfer process, from the user's virtual wallet account to the merchant's virtual wallet account. In addition, we also need to record the transaction flow information of this payment.


3. Withdrawal

In addition to recharge and payment, users can also withdraw the balance in the virtual wallet to their bank card. This process is actually to deduct the balance in the user's virtual wallet and trigger the real bank transfer operation to transfer money from the application's public bank account to the user's bank account. Similarly, we also need to record the transaction flow information of this withdrawal.


4. Query balance

The balance query function is relatively simple. Let's take a look at the balance number in the virtual wallet.

5. Query transaction flow

Querying transaction flow is also relatively simple. We only support three types of transaction flow: recharge, payment and withdrawal. When users recharge, pay and withdraw cash, we will record the corresponding transaction information. When we need to query, we only need to filter the previously recorded transaction flow according to time, type and other conditions and display it.

Design idea of wallet system

According to the business implementation process and data flow diagram just mentioned, we can divide the business of the whole wallet system into two parts, one of which is simply dealing with the virtual wallet account in the application, and the other is simply dealing with the bank account. Based on such a business division, we decouple the system and divide the whole wallet system into two subsystems: virtual wallet system and third-party payment system.


In order to explain today's content thoroughly in a limited space, we will only focus on the design and implementation of the virtual wallet system. We will not explain the design and implementation of the third-party payment system and the whole wallet system. You can think for yourself.

Now let's look at the corresponding operations of the virtual wallet system if we want to support these five core functions of the wallet. I drew a diagram to list the operations of the virtual wallet corresponding to these five functions. Note that for the record and query of transaction flow, I temporarily put a question mark in the figure. That's because it's special. We'll talk about it later.


As can be seen from the figure, the operation supported by the virtual wallet system is very simple, that is, the addition and subtraction of the balance. Among them, the three functions of recharge, withdrawal and balance query only involve the addition and subtraction of the balance of one account, while the payment function involves the addition and subtraction of the balance of two accounts: one account minus the balance and the other account plus the balance.

Now, let's take another look at the question mark in the figure below, * * that is, how to record and query the transaction flow** Let's take a look at what information the transaction flow needs to contain. I think the following information must be included.


From the figure, we can see that the data format of transaction flow includes two wallet accounts, one is the posting wallet account and the other is the outgoing wallet account. Why have two account information? This is mainly to be compatible with the transaction type of payment involving two accounts. However, for the two transaction types of recharge and withdrawal, we only need to record one wallet account information.

The design idea of the whole virtual wallet is over. Next, let's see how to use the traditional development mode based on anemia model and the DDD development mode based on congestion model to realize such a virtual wallet system?

Traditional development model based on anemia model

In fact, if you have some experience in Web project development and understand the design ideas I just talked about, it should be a very simple thing for you to use the traditional development mode based on anemia model to implement such a system. However, in order to compare the two development modes, I'll take you to implement them together.

This is a three-tier structure of a typical Web backend project. Among them, the Controller and VO are responsible for exposing the interface, and the specific code implementation is as follows. Note that in the Controller, the interface implementation is relatively simple, mainly calling the Service method. Therefore, I have omitted the specific code implementation.

public class VirtualWalletController {
  // Injection through constructor or IOC framework
  private VirtualWalletService virtualWalletService;
  
  public BigDecimal getBalance(Long walletId) { ... } //Check the balance
  public void debit(Long walletId, BigDecimal amount) { ... } //Out of account
  public void credit(Long walletId, BigDecimal amount) { ... } //Entry
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { ...} //transfer accounts
  //Omit the interface for querying transaction
}

Service and BO are responsible for core business logic, while Repository and Entity are responsible for data access. The code implementation of the Repository layer is relatively simple, which is not the focus of our explanation, so I have omitted it. The code of the service layer is as follows. Note that I have omitted some unimportant verification codes here, such as verifying whether the amount is less than 0, whether the wallet exists, and so on.

public class VirtualWalletBo {//Omit getter/setter/constructor methods
  private Long id;
  private Long createTime;
  private BigDecimal balance;
}

public Enum TransactionType {
  DEBIT,
  CREDIT,
  TRANSFER;
}

public class VirtualWalletService {
  // Injection through constructor or IOC framework
  private VirtualWalletRepository walletRepo;
  private VirtualWalletTransactionRepository transactionRepo;
  
  public VirtualWalletBo getVirtualWallet(Long walletId) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWalletBo walletBo = convert(walletEntity);
    return walletBo;
  }
  
  public BigDecimal getBalance(Long walletId) {
    return walletRepo.getBalance(walletId);
  }

  @Transactional
  public void debit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    BigDecimal balance = walletEntity.getBalance();
    if (balance.compareTo(amount) < 0) {
      throw new NoSufficientBalanceException(...);
    }
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.DEBIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, balance.subtract(amount));
  }

  @Transactional
  public void credit(Long walletId, BigDecimal amount) {
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.CREDIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    BigDecimal balance = walletEntity.getBalance();
    walletRepo.updateBalance(walletId, balance.add(amount));
  }

  @Transactional
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.TRANSFER);
    transactionEntity.setFromWalletId(fromWalletId);
    transactionEntity.setToWalletId(toWalletId);
    transactionRepo.saveTransaction(transactionEntity);
    debit(fromWalletId, amount);
    credit(toWalletId, amount);
  }
}

DDD development mode based on Congestion Model

We just talked about how to use the traditional development mode based on anemia model to realize the virtual wallet system. Now, let's take a look at how to use the DDD development mode based on congestion model to realize the system?

The main difference between the DDD development mode based on Congestion Model and the traditional development mode based on anemia model is that the codes of Service layer, Controller layer and Repository layer are basically the same. Therefore, let's focus on how to implement the Service layer according to the DDD development mode based on congestion model.

In this development mode, we design the virtual wallet VirtualWallet class as a congested Domain domain model, and move some business logic originally in the Service class to the VirtualWallet class, so that the implementation of the Service class depends on the VirtualWallet class. The specific code implementation is as follows:

public class VirtualWallet { // Domain domain model (congestion model)
  private Long id;
  private Long createTime = System.currentTimeMillis();;
  private BigDecimal balance = BigDecimal.ZERO;
  
  public VirtualWallet(Long preAllocatedId) {
    this.id = preAllocatedId;
  }
  
  public BigDecimal balance() {
    return this.balance;
  }
  
  public void debit(BigDecimal amount) {
    if (this.balance.compareTo(amount) < 0) {
      throw new InsufficientBalanceException(...);
    }
    this.balance = this.balance.subtract(amount);
  }
  
  public void credit(BigDecimal amount) {
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException(...);
    }
    this.balance = this.balance.add(amount);
  }
}

public class VirtualWalletService {
  // Injection through constructor or IOC framework
  private VirtualWalletRepository walletRepo;
  private VirtualWalletTransactionRepository transactionRepo;
  
  public VirtualWallet getVirtualWallet(Long walletId) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    return wallet;
  }
  
  public BigDecimal getBalance(Long walletId) {
    return walletRepo.getBalance(walletId);
  }
  
  @Transactional
  public void debit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    wallet.debit(amount);
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.DEBIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, wallet.balance());
  }
  
  @Transactional
  public void credit(Long walletId, BigDecimal amount) {
    VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
    VirtualWallet wallet = convert(walletEntity);
    wallet.credit(amount);
    VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
    transactionEntity.setAmount(amount);
    transactionEntity.setCreateTime(System.currentTimeMillis());
    transactionEntity.setType(TransactionType.CREDIT);
    transactionEntity.setFromWalletId(walletId);
    transactionRepo.saveTransaction(transactionEntity);
    walletRepo.updateBalance(walletId, wallet.balance());
  }

  @Transactional
  public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
    //... just like the code of traditional development model based on anemia model
  }
}

After reading the above code, you may say that the domain model VirtualWallet class is very thin and contains simple business logic. Compared with the original design idea of anemia model, this design idea of congestion model does not seem to have much advantage. You're right! This is also the reason why most business systems are developed based on anemia model. However, if the virtual wallet system needs to support more complex business logic, the advantages of the congestion model appear. For example, we should support the function of overdraft a certain amount and freezing part of the balance. At this time, let's revisit the implementation code of the VirtualWallet class.

public class VirtualWallet {
  private Long id;
  private Long createTime = System.currentTimeMillis();;
  private BigDecimal balance = BigDecimal.ZERO;
  private boolean isAllowedOverdraft = true;
  private BigDecimal overdraftAmount = BigDecimal.ZERO;
  private BigDecimal frozenAmount = BigDecimal.ZERO;
  
  public VirtualWallet(Long preAllocatedId) {
    this.id = preAllocatedId;
  }
  
  public void freeze(BigDecimal amount) { ... }
  public void unfreeze(BigDecimal amount) { ...}
  public void increaseOverdraftAmount(BigDecimal amount) { ... }
  public void decreaseOverdraftAmount(BigDecimal amount) { ... }
  public void closeOverdraft() { ... }
  public void openOverdraft() { ... }
  
  public BigDecimal balance() {
    return this.balance;
  }
  
  public BigDecimal getAvaliableBalance() {
    BigDecimal totalAvaliableBalance = this.balance.subtract(this.frozenAmount);
    if (isAllowedOverdraft) {
      totalAvaliableBalance += this.overdraftAmount;
    }
    return totalAvaliableBalance;
  }
  
  public void debit(BigDecimal amount) {
    BigDecimal totalAvaliableBalance = getAvaliableBalance();
    if (totoalAvaliableBalance.compareTo(amount) < 0) {
      throw new InsufficientBalanceException(...);
    }
    this.balance = this.balance.subtract(amount);
  }
  
  public void credit(BigDecimal amount) {
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException(...);
    }
    this.balance = this.balance.add(amount);
  }
}

After the domain model VirtualWallet class adds simple freezing and overdraft logic, the functions seem to be much richer and the code is not so thin. If the function continues to evolve, we can add more detailed freezing strategy, overdraft strategy, logic that supports automatic generation of wallet account (VirtualWallet id field) (ID is automatically generated through distributed ID generation algorithm instead of external ID through constructor), etc. The business logic of the VirtualWallet class will become more and more complex, which is worth designing into a congestion model.

Dialectical thinking and flexible application

The first question to be discussed is: in the DDD development mode based on congestion model, when the business logic is moved to the Domain, the Service class becomes very thin, but in our code design and implementation, the Service class is not completely removed. Why? Or, what is the responsibility of the Service class in this case? What function logic will be put into the Service class?

Different from the responsibilities of Domain, Service class mainly has the following responsibilities.

  1. The Service class is responsible for communicating with the repository. In my design and code implementation, the VirtualWalletService class is responsible for dealing with the Repository layer, calling the Respository class method, getting the data in the database, transforming it into domain model VirtualWallet, then completing the business logic by the domain model VirtualWallet, and then calling the Repository class method to return the data back to the database.

Let me explain a little more here. The reason why we let the VirtualWalletService class deal with the Repository rather than the domain model VirtualWallet deal with the Repository is that we want to maintain the independence of the domain model and do not couple with the code of any other layer (the code of the Repository layer) or development framework (such as Spring and MyBatis), Decouple the process code logic (such as fetching data from DB and mapping data) from the business logic of the domain model to make the domain model more reusable.

  1. The Service class is responsible for the business aggregation function of the cross domain model. The transfer() transfer function in the VirtualWalletService class will involve the operation of two wallets, so this part of business logic cannot be placed in the VirtualWalletService class. Therefore, let's put the transfer business into the VirtualWalletService class for the time being. Of course, although the function evolution makes the transfer business more complex, we can also extract the transfer business and design it into an independent domain model.
  2. The Service class is responsible for some non functionality and interaction with the third-party system. For example, idempotent, transaction, sending e-mail, sending messages, logging, calling RPC interfaces of other systems, etc. can be put into the Service class.

The second question to be discussed is: in the DDD development model based on congestion model, although the Service layer is transformed into congestion model, the Controller layer and Repository layer are still anemia models. Is it necessary to model congestion domain?

The answer is not necessary. The Controller layer is mainly responsible for exposing the interface, and the Repository layer is mainly responsible for dealing with the database. These two layers do not contain much business logic. As we mentioned earlier, if the business logic is relatively simple, there is no need to do congestion modeling. Even if it is designed as congestion model, the class is very thin and looks strange.

Although this design is a process oriented programming style, we can still develop excellent software as long as we control the side effects of process oriented programming style. How to control the side effects here?

Take the Entity of Repository for example. Even if it is designed as an anemia model, violates the encapsulation characteristics of object-oriented programming, and has the risk of data modification by arbitrary code, the life cycle of Entity is limited. Generally speaking, after we pass it to the Service layer, it will be transformed into BO or Domain to continue the following business logic. The life cycle of Entity is now over, so it will not be arbitrarily modified everywhere.

Let's talk about VO in Controller layer. In fact, VO is a DTO (Data Transfer Object). It is mainly used as the data transmission carrier of the interface to send data to other systems. Functionally speaking, it should not contain business logic but only data. Therefore, it is reasonable for us to design it as an anemia model.

review

Compared with the traditional development model based on anemia model, the DDD development model based on congestion model is mainly different in the Service layer. In the development mode based on congestion model, we move part of the business logic originally in the Service class to a congested Domain domain model, so that the implementation of the Service class depends on this Domain class.

In the DDD development mode based on congestion model, the Service class will not be completely removed, but is responsible for some functions that are not suitable to be placed in the Domain class. For example, it is responsible for dealing with the Repository layer, business aggregation function of cross Domain model, idempotent transaction and other non functional work.

Compared with the traditional development mode based on anemia model, the codes of Controller layer and Repository layer are basically the same. This is because the Entity life cycle of the Repository layer is limited, and the VO of the Controller layer is only used as a DTO. The business logic of both parts will not be too complex. Business logic is mainly concentrated in the Service layer. Therefore, there is no problem that the Repository layer and Controller layer continue to follow the design idea of anemia model.

Posted by ziesje on Tue, 21 Sep 2021 19:21:02 -0700