[Reprinted] Ali technical experts explain the first lecture of DDD series - Domain Primitive

1. Introduction

For an architect, how to reduce system complexity in software development is an eternal challenge, whether it is Design Patterns of GoF in 1994, Martin Fowler's Refactoring in 1999, P of EAA in 2002, or 2003 Enterprise Integration Patterns, through a series of design patterns or paradigms to reduce some common complexity.

But the problem is that the idea of ​​these books is to solve technical problems through technical means, but they do not fundamentally solve business problems. Therefore, the book Domain Driven Design by Eric Evans in 2003, and the subsequent books such as Implementing DDD by Vaughn Vernon and Clean Architecture by Uncle Bob, really start from the business point of view and provide a solid foundation for most of the pure business development in the world. A whole set of architectural ideas.

2. Preface

Since DDD is not a set of frameworks, but an architectural idea, there is a lack of sufficient constraints at the code level, resulting in a high threshold for DDD in practical applications. It can even be said that most people have a biased understanding of DDD . For example, an Anti-pattern described by Martin Fowler in his personal blog, Anemic Domain Model ① (anemic domain model) emerges endlessly in practical applications, and some still hot ORM tools such as Hibernate, Entity Framework actually contribute to anemia Model Diffusion. Similarly, the traditional four-tier application architecture (UI, Business, Data Access, Database) based on database technology and MVC is confused with some concepts of DDD to a certain extent, causing most people to only use DDD in practical applications. The idea of ​​modeling, but its idea of ​​the entire architecture system cannot be implemented.

The first time I came into contact with DDD should be in 2012. At that time, except for large Internet companies, basically commercial applications were still in the stand-alone era, and the service-oriented architecture was still limited to stand-alone + LB. MVC was used to provide a Rest interface for external calls, or SOAP was used Or WebServices to do RPC calls, but in fact more limited to the protocol of external dependencies. What made me pay attention to the idea of ​​DDD is a concept called Anti-Corruption Layer (anti-corruption layer), especially its mechanism of how to isolate core business logic from external dependencies in the case of frequent changes in external dependencies. By 2014, SOA became popular, and the concept of microservices began to emerge, and how to reasonably split a Monolith application into multiple microservices became a hot topic in major forums, and the Bounded Context (Bounded Context) in DDD The idea provides a reasonable framework for microservice splitting. Today, in an era where everything can be called "service" (XAAS), DDD thinking allows us to calm down and think about which things can be split by service, which logic needs to be aggregated, In order to bring the minimum maintenance cost, rather than simply pursuing development efficiency.

So today, I started this series of articles about DDD, hoping to continue to carry forward the idea of ​​​​DDD on the basis of summarizing the predecessors, but through a set of code structure, framework and constraints that I think are reasonable, to reduce the practice threshold of DDD, Improve code quality, testability, security, and robustness.

Future coverage includes:

  • Best Architecture Practices: Core Ideas and Implementation Solutions of Hexagonal Application Architecture/Clean Architecture
  • Continuous Discovery and Delivery: Event Storming > Context Map > Design Heuristics > Modeling
  • Reduce the speed of architecture corruption: a modular solution for integrating third-party libraries through the Anti-Corruption Layer
  • Specifications and boundaries of standard components: Entity, Aggregate, Repository, Domain Service, -Application Service, Event, DTO Assembler, etc.
  • Redefine the boundaries of application services based on Use Case
  • DDD-based micro-service transformation and granular control
  • Transformation and challenges of CQRS architecture
  • Challenges of event-driven architectures
  • Wait,
    today I will bring you the most basic but extremely valuable concept of Domain Primitive.

3. Domain Primitive

Just like when learning any language, the first thing you need to understand is the basic data type. Before fully understanding DDD, first introduce the most basic concept: Domain Primitive (DP).

The definition of Primitive is:

not developed from anything else
rudimentary formation or early stage of growth

Just like Integer and String are the primitives of all programming languages, in DDD, DP can be said to be the basis of all models, methods, and architectures, and just like Integer and String, DP is ubiquitous. Therefore, the first lecture will give a comprehensive introduction and analysis of DP, but let's not talk about the concept first, but start with a case to see why DP is a powerful concept.

4. Case study

Let's look at a simple example first. The business logic of this case is as follows:

A new application is promoted nationwide through salespersons, and a user registration system needs to be built.
At the same time, it is hoped that after the user registers, the salesperson can be given bonuses through the region (area code) of the user's phone (let's assume it is only for landlines).

Don't worry about whether the business logic of sending bonuses based on the user's phone number is reasonable, and don't worry about whether the user should be bound with the salesman when registering. Here we mainly look at how to realize this logic more reasonably. A simple user and user registration code is implemented as follows:

public class User {
    
    
    Long userId;
    String name;
    String phone;
    String address;
    Long repId;
}

public class RegistrationServiceImpl implements RegistrationService {
    
    

    private SalesRepRepository salesRepRepo;
    private UserRepository userRepo;

    public User register(String name, String phone, String address) 
      throws ValidationException {
    
    
        // 校验逻辑
        if (name == null || name.length() == 0) {
    
    
            throw new ValidationException("name");
        }
        if (phone == null || !isValidPhoneNumber(phone)) {
    
    
            throw new ValidationException("phone");
        }
        // 此处省略address的校验逻辑

        // 取电话号里的区号,然后通过区号找到区域内的SalesRep
        String areaCode = null;
        String[] areas = new String[]{
    
    "0571", "021", "010"};
        for (int i = 0; i < phone.length(); i++) {
    
    
            String prefix = phone.substring(0, i);
            if (Arrays.asList(areas).contains(prefix)) {
    
    
                areaCode = prefix;
                break;
            }
        }
        SalesRep rep = salesRepRepo.findRep(areaCode);

        // 最后创建用户,落盘,然后返回
        User user = new User();
        user.name = name;
        user.phone = phone;
        user.address = address;
        if (rep != null) {
    
    
            user.repId = rep.repId;
        }

        return userRepo.save(user);
    }

    private boolean isValidPhoneNumber(String phone) {
    
    
        String pattern = "^0[1-9]{2,3}-?\\d{8}$";
        return phone.matches(pattern);
    }
}

Most of our daily codes and models are actually similar to this. At first glance, there seems to be no problem, but let’s go a step further and analyze it from the following four dimensions: interface clarity (readability), data Validation and error handling, business logic code clarity, and testability.

4.1 Issue 1 - Clarity of the interface

In Java code, for a method, all parameter names are lost at compile time, leaving only a list of parameter types, so let's re-look at the above interface definition, in fact, it is only at runtime:

User register(String, String, String);

So the following code is a bug that the compiler will not report an error at all, and it is difficult to find by looking at the code:

service.register("殷浩", "浙江省杭州市余杭区文三西路969号", "0571-12345678");

Of course, errors will be reported when running in real code, but such bugs are found at runtime, not at compile time. Ordinary Code Review is also difficult to find this kind of problem, it is very likely that the code will be exposed after it goes online. The thinking here is, is there a way to avoid this possible problem when coding?

Another common example, especially in query services, is as follows:

User findByName(String name);
User findByPhone(String phone);
User findByNameAndPhone(String name, String phone);

In this scenario, since the input parameters are all String types, you have to add ByXXX to the method name to distinguish them, and findByNameAndPhone will also fall into the wrong order of the previous input parameters, and different from the previous input parameters, the order of parameters here If you make a mistake, the method will not report an error but return null, and this kind of bug is more difficult to find. The thinking here is, is there a way to make the method input parameters clear at a glance, and avoid bugs caused by incorrect input parameters?

4.2 Issue 2 - Data Validation and Error Handling

In the previous data verification code:

if (phone == null || !isValidPhoneNumber(phone)) {
    
    
    throw new ValidationException("phone");
}

It often appears in daily coding. Generally speaking, this kind of code needs to appear at the forefront of the method to ensure fail-fast. But suppose you have multiple similar interfaces and similar input parameters, this logic will be repeated in each method. What's more serious is that if we want to expand the phone number to include mobile phones in the future, we may need to add the following code:

if (phone == null || !isValidPhoneNumber(phone) || !isValidCellNumber(phone)) {
    
    
    throw new ValidationException("phone");
}

If you have used the phone parameter in many places, but forgot to modify one place, it will cause bugs. This is a problem that often occurs when the DRY principle is violated.

If there is a new requirement that needs to return the reason for the input parameter error, then this code becomes more complicated:

if (phone == null) {
    
    
    throw new ValidationException("phone不能为空");
} else if (!isValidPhoneNumber(phone)) {
    
    
    throw new ValidationException("phone格式错误");
}

It is conceivable that when the code is filled with a large number of similar code blocks, the maintenance cost will be high.

Finally, in this business method, there will be (implicit or explicit) throwing ValidationException, so it is reasonable for the external caller to try/catch and 业务逻辑异常be 数据校验异常mixed together?

In the traditional Java architecture, there are several ways to solve some problems, such as BeanValidation annotation or ValidationUtils class, such as:

// Use Bean Validation
User registerWithBeanValidation(
  @NotNull @NotBlank String name,
  @NotNull @Pattern(regexp = "^0?[1-9]{2,3}-?\\d{8}$") String phone,
  @NotNull String address
);

// Use ValidationUtils:
public User registerWithUtils(String name, String phone, String address) {
    
    
    ValidationUtils.validateName(name); // throws ValidationException
    ValidationUtils.validatePhone(phone);
    ValidationUtils.validateAddress(address);
    ...
}

But these traditional methods also have problems,

BeanValidation:

  • Usually only simple verification logic can be solved, and complex verification logic also needs to write code to implement a custom validator
  • When new verification logic is added, it will also happen that some places forget to add an annotation, and the DRY principle will still be violated

ValidationUtils class:

  • When a large amount of verification logic is concentrated in one class, it violates the Single Responsibility principle, resulting in code confusion and unmaintainability
  • Business exceptions and verification exceptions will still be mixed

So, is there a way to solve all verification problems once and for all and reduce subsequent maintenance costs and exception handling costs?

4.3 Issue 3 - Clarity of business code

In this code:

String areaCode = null;
String[] areas = new String[]{
    
    "0571", "021", "010"};
for (int i = 0; i < phone.length(); i++) {
    
    
    String prefix = phone.substring(0, i);
    if (Arrays.asList(areas).contains(prefix)) {
    
    
        areaCode = prefix;
        break;
    }
}
SalesRep rep = salesRepRepo.findRep(areaCode);

In fact, there is another common situation, which is to extract part of the data from some input parameters, then call an external dependency to obtain more data, and then usually extract part of the data from the new data for other functions. This kind of code is usually called “胶水代码”, and its essence is that the input parameters of externally dependent services do not match our original input parameters. For example, if SalesRepRepositoryyou include a findRepByPhonemethod, most of the above code is unnecessary.

Therefore, a common approach is to extract this code into one or more independent methods:

private static String findAreaCode(String phone) {
    
    
    for (int i = 0; i < phone.length(); i++) {
    
    
        String prefix = phone.substring(0, i);
        if (isAreaCode(prefix)) {
    
    
            return prefix;
        }
    }
    return null;
}

private static boolean isAreaCode(String prefix) {
    
    
    String[] areas = new String[]{
    
    "0571", "021"};
    return Arrays.asList(areas).contains(prefix);
}

Then the original code becomes:

String areaCode = findAreaCode(phone);
SalesRep rep = salesRepRepo.findRep(areaCode);

In order to reuse the above methods, a static tool class may be extracted PhoneUtils. But the thing to think about here is, is the static tool class the best way to implement it? When your project is filled with a large number of static tool classes and the business code is scattered in multiple files, can you still find the core business logic?

4.4 Issue 4 - Testability

In order to ensure code quality, each possible condition of each input parameter in each method must be covered by TC (assuming we don’t test the internal business logic first), so we need the following TC in this method:
insert image description here

If a method has N parameters, and each parameter has M verification logics, there must be at least N * M TCs.

If a new input parameter field fax is added to this method at this time, even if the verification logic of fax and phone is completely consistent, M new TCs are still required to ensure TC coverage.

And assuming that there are P methods that use the phone field, these P methods all need to test the field, that is to say, the overall need:

P * N * M

Only one test case can fully cover all data verification issues. In daily projects, the cost of this test is very high, resulting in a large amount of code not being covered. The code that is not covered by the test is the most likely place for problems.

In this case, reducing the cost of testing == improving code quality, how can we reduce the cost of testing?

5. Solutions

Let's go back and look at the original use case first, and mark the concepts that may be important in it:

A new application is promoted nationwide through local push salesmen, and a user registration system needs to be built.
After the user registers, the salesperson can be given bonuses through the area code of the user's phone number.

After analyzing the use case, it is found that the local salesperson and the user have their own ID attributes, which belong to Entity (entity), and the registration system belongs to Application Service (application service). These concepts already exist. But it was found that the concept of the phone number was completely hidden in the code. We can ask ourselves, does the logic of getting the area code of the phone number belong to the user (user's area code?)? Is it a registered service (registered area code?)? If none of them are very appropriate, it means that this logic should belong to an independent concept. So here we introduce our first principle :

Make Implicit Concepts Explicit
makes implicit concepts explicit

Here, we can see that the original phone number is only a parameter of the user, which is an invisible concept, but in fact the area code of the phone number is the real business logic, and we need to make the concept of the phone number explicit by writing A Value Object :

public class PhoneNumber {
    
    
  
    private final String number;
    public String getNumber() {
    
    
        return number;
    }

    public PhoneNumber(String number) {
    
    
        if (number == null) {
    
    
            throw new ValidationException("number不能为空");
        } else if (isValid(number)) {
    
    
            throw new ValidationException("number格式错误");
        }
        this.number = number;
    }

    public String getAreaCode() {
    
    
        for (int i = 0; i < number.length(); i++) {
    
    
            String prefix = number.substring(0, i);
            if (isAreaCode(prefix)) {
    
    
                return prefix;
            }
        }
        return null;
    }

    private static boolean isAreaCode(String prefix) {
    
    
        String[] areas = new String[]{
    
    "0571", "021", "010"};
        return Arrays.asList(areas).contains(prefix);
    }

    public static boolean isValid(String number) {
    
    
        String pattern = "^0?[1-9]{2,3}-?\\d{8}$";
        return number.matches(pattern);
    }

}

There are several important elements here:

  1. By private final String numberensuring PhoneNumberthat is an ( Immutable ) Value Object. ( Generally speaking, VO is Immutable , here is just to emphasize)
  2. The verification logic is placed in the constructor to ensure that as long as PhoneNumberthe class is created, it must pass the verification.
  3. The previous findAreaCodemethod becomes PhoneNumberin the class getAreaCode, highlighting areaCodethat is PhoneNumbera computed property of .

After doing this, we found that after making PhoneNumberexplicit , a **Type (data type)** and a Class (class) are actually generated :

  • Type refers to the concept that we can PhoneNumberuse to
  • Class means that we can completely collect all the logic related to the phone number into one file

The combination of these two concepts constitutes the Domain Primitive (DP) of the title of this article .

Let's take a look at the effect after fully using DP:

public class User {
    
    
    UserId userId;
    Name name;
    PhoneNumber phone;
    Address address;
    RepId repId;
}

public User register(
  @NotNull Name name,
  @NotNull PhoneNumber phone,
  @NotNull Address address
) {
    
    
    // 找到区域内的SalesRep
    SalesRep rep = salesRepRepo.findRep(phone.getAreaCode());

    // 最后创建用户,落盘,然后返回,这部分代码实际上也能用Builder解决
    User user = new User();
    user.name = name;
    user.phone = phone;
    user.address = address;
    if (rep != null) {
    
    
        user.repId = rep.repId;
    }

    return userRepo.saveUser(user);
}

We can see that after using DP, all the sums disappear, 数据验证逻辑and the rest are all , which can be seen at a glance. Let's re-evaluate the above four dimensions:非业务流程的逻辑核心业务逻辑

5.1 Assessment 1 - Clarity of the interface

The refactored method signature becomes very clear:

public User register(Name, PhoneNumber, Address)

And the bugs that were prone to appear before, if written according to the current method

service.register(new Name("殷浩"), new Address("浙江省杭州市余杭区文三西路969号"), new PhoneNumber("0571-12345678"));

Let the interface API become very clean and easy to expand.

5.2 Assessment 2 - Data Validation and Error Handling

public User register(
  @NotNull Name name,
  @NotNull PhoneNumber phone,
  @NotNull Address address
) // no throws

As shown in the previous code, in the refactored method, there is no logic of data verification at all, and it will not be thrown ValidationException. The reason is because of the characteristics of DP, as long as it can be brought into the input parameter, it must be correct or null (Bean Validation or lombok annotations can solve the problem of null). Therefore, we put the workload of data verification on the caller, and the caller should provide legal data , so it is more appropriate.

Looking at it further, another advantage of using DP is that the code follows the DRY principle and the singleness principle . If you need to PhoneNumbermodify , you only need to modify it in one file, and all PhoneNumberthe places where are used will take effect.

5.3 Assessment 3 - Clarity of Business Code

SalesRep rep = salesRepRepo.findRep(phone.getAreaCode());
User user = xxx;
return userRepo.save(user);

In addition to not needing to verify the data in the business method, the original piece of glue code findAreaCodewas changed to a computed attributePhoneNumber of the class , which greatly improved the clarity of the code. And the glue code is usually not reusable, but after using DP, it becomes reusable and testable code. We can see that after removing the data verification code and glue code, the rest is the core business logic . (Entity-related refactoring will be discussed in later articles, ignore it this time) getAreaCode

5.4 Assessment 4 - Testability

insert image description here

After we PhoneNumberextract , let's look at the TC of the test:

  • First of PhoneNumberall , M test cases are still required, but since we only need to test a single object, the amount of code for each test case will be greatly reduced, and the maintenance cost will be reduced.
  • Each parameter in each method only needs to be overridden as null, and other cases cannot happen (because as long as it is not null, it must be legal)

Therefore, the TC of a single method has changed from N * M to N + M today. Likewise, the number of TCs for multiple methods becomes

N + M + P

This number is generally much lower than the original number N * M * P, which greatly reduces the cost of testing.

5.5 Evaluation Summary

insert image description here

6. Advanced usage

Above I introduced the first principle of DP: making implicit concepts explicit . Here I will introduce two other principles of DP, using a new case.

6.1 Case 1 - Transfer

Assuming that a function is to be implemented now, so that user A can pay x yuan to user B, the possible implementation is as follows:

public void pay(BigDecimal money, Long recipientId) {
    
    
    BankService.transfer(money, "CNY", recipientId);
}

If this is a domestic transfer, and the domestic currency will never change, this method seems to be no problem, but if one day the currency changes (such as the problems that have occurred in the Eurozone), or we need to do cross-border transfers, this method is obvious. bug, because the currency corresponding to money is not necessarily CNY.

In this case, when we say "pay x yuan", in addition to the number of x itself, there is actually an implicit concept that is the currency "yuan". But in the original input parameter, the reason why only is BigDecimalused is that we think that the CNY currency is the default, which is an implicit condition, but when we write code, we need to make all the implicit conditions explicit, and These conditions collectively make up the current context . So the second principle of DP is:

Make Implicit Context Explicit
makes the implicit context explicit

So when we do this payment function, an input parameter is actually required 支付金额 + 支付货币. We can combine these two concepts into a single complete concept: Money.

@Value
public class Money {
    
    
    private BigDecimal amount;
    private Currency currency;
    public Money(BigDecimal amount, Currency currency) {
    
    
        this.amount = amount;
        this.currency = currency;
    }
}

The original code becomes:

public void pay(Money money, Long recipientId) {
    
    
    BankService.transfer(money, recipientId);
}

By making the implicit contextual concept of the default currency explicit and merging it with the amount Money, we can avoid many bugs that are currently invisible but may be thunderous in the future.

6.2 Case 2 - Cross-border transfer

Let’s upgrade the previous case, assuming that the user may do a cross-border transfer from CNY to USD, and the currency exchange rate fluctuates at any time:

public void pay(Money money, Currency targetCurrency, Long recipientId) {
    
    
    if (money.getCurrency().equals(targetCurrency)) {
    
    
        BankService.transfer(money, recipientId);
    } else {
    
    
        BigDecimal rate = ExchangeService.getRate(money.getCurrency(), targetCurrency);
        BigDecimal targetAmount = money.getAmount().multiply(new BigDecimal(rate));
        Money targetMoney = new Money(targetAmount, targetCurrency);
        BankService.transfer(targetMoney, recipientId);
    }
}

In this case, since targetCurrencyis not necessarily consistentmoney with that of , it is necessary to call a service to fetch the exchange rate and then do the calculation. CurrenyFinally, use the calculated result to make the transfer.

The biggest problem with this case is that the calculation of the amount is included in the payment service, and there are 2 objects involved Currency, 2 objects Money , 1 object BigDecimal, and a total of 5 objects. This kind of business logic involving multiple objects needs to be packaged with DP, so here comes the third principle of DP :

Encapsulate Multi-Object Behavior
encapsulates multi-object behavior

In this case, the function of converting the exchange rate can be encapsulated into a DP ExchangeRatecalled :

@Value
public class ExchangeRate {
    
    
    private BigDecimal rate;
    private Currency from;
    private Currency to;

    public ExchangeRate(BigDecimal rate, Currency from, Currency to) {
    
    
        this.rate = rate;
        this.from = from;
        this.to = to;
    }

    public Money exchange(Money fromMoney) {
    
    
        notNull(fromMoney);
        isTrue(this.from.equals(fromMoney.getCurrency()));
        BigDecimal targetAmount = fromMoney.getAmount().multiply(rate);
        return new Money(targetAmount, to);
    }
}

ExchangeRateThe exchange rate object makes the original code extremely simple by encapsulating the amount calculation logic and various verification logic:

public void pay(Money money, Currency targetCurrency, Long recipientId) {
    
    
    ExchangeRate rate = ExchangeService.getRate(money.getCurrency(), targetCurrency);
    Money targetMoney = rate.exchange(money);
    BankService.transfer(targetMoney, recipientId);
}

7. Discussion and conclusion

7.1 Definition of Domain Primitive

Let's redefine Domain Primitive:

Domain Primitive is a Value Object with precise definition, self-verification, and behavior in a specific domain.

  • DP is a Value Object in the traditional sense, with the characteristics of Immutable
  • DP is a complete conceptual whole with precise definition
  • DP uses the native language in the business domain
  • DP can be the smallest component of the business domain, and can also build complex combinations

Note: The concept and naming of Domain Primitive comes from the book Secure by Design by Dan Bergh Johnsson & Daniel Deogun

7.2 Three principles of using Domain Primitive

  • Make implicit concepts explicit
  • Make implicit context explicit
  • Encapsulating Multi-Object Behavior

7.3 Difference between Domain Primitive and Value Object in DDD

In DDD, the concept of Value Object already exists:

  • In Evans' DDD blue book, Value Object is more of a non-Entity value object
  • In Vernon's IDDD Red Book, the author pays more attention to the Immutability, Equals method, Factory method, etc. of Value Object

Domain Primitive is an advanced version of Value Object. On the basis of the original VO, each DP is required to have a concept as a whole, not just a value object. Added Validity and behavior on the basis of VO's Immutable. Of course, the same requirement has no side effects (side-effect free).

7.4 Difference between Domain Primitive and Data Transfer Object (DTO)

Another data structure that is often encountered in daily development is DTO, such as the input and output parameters of methods. The difference between DP and DTO is as follows:
insert image description here

7.5 When should Domain Primitive be used?

Common DP usage scenarios include:

  • String with limited format: such as Name, PhoneNumber, OrderNumber, ZipCode, Address, etc.
  • Restricted Integer: such as OrderId (>0), Percentage (0-100%), Quantity (>=0), etc.
  • Enumerable int: such as Status (generally not Enum because of deserialization problems)
  • Double or BigDecimal: Generally used Double or BigDecimal has business meaning, such as Temperature, Money, Amount, ExchangeRate, Rating, etc.
  • Complex data structures: such as Map<String, List>, etc., try to wrap all operations of Map, and only expose necessary behaviors

8. Actual combat - the process of refactoring old applications

It is relatively simple to use DP in new applications, but to use DP in old applications can be upgraded step by step according to the following process. Take the first case of this article as an example here.

8.1 Step 1 - Create Domain Primitive and collect all DP behaviors

In the previous article, we found that the area code of the phone number is an independent logic that can be put into the PhoneNumber Class. Similarly, in a real project, the codes previously scattered in various services or tool classes can be extracted and placed in DP, and become DP's own behavior or attribute. The principle here is: all extracted methods should be stateless , such as the original static method. If the original method has state changes, it is necessary to separate the part that changes the state from the part that does not change the state, and then integrate the stateless part into DP. Because DP itself cannot have state, all codes that need to change state do not belong to the category of DP .

(Code PhoneNumberreference code, not repeated here)

8.2 Step 2 - Replace data checksum and stateless logic

In order to ensure the compatibility of existing methods, the signature of the interface will not be modified in the second step, but the original verification logic and business logic related to DP will be replaced by code. for example:

public User register(String name, String phone, String address)
        throws ValidationException {
    
    
    if (name == null || name.length() == 0) {
    
    
        throw new ValidationException("name");
    }
    if (phone == null || !isValidPhoneNumber(phone)) {
    
    
        throw new ValidationException("phone");
    }
    
    String areaCode = null;
    String[] areas = new String[]{
    
    "0571", "021", "010"};
    for (int i = 0; i < phone.length(); i++) {
    
    
        String prefix = phone.substring(0, i);
        if (Arrays.asList(areas).contains(prefix)) {
    
    
            areaCode = prefix;
            break;
        }
    }
    SalesRep rep = salesRepRepo.findRep(areaCode);
    // 其他代码...
}

After replacing the code by DP:

public User register(String name, String phone, String address)
        throws ValidationException {
    
    
    
    Name _name = new Name(name);
    PhoneNumber _phone = new PhoneNumber(phone);
    Address _address = new Address(address);
    
    SalesRep rep = salesRepRepo.findRep(_phone.getAreaCode());
    // 其他代码...
}

The original verification code is replaced by the code new PhoneNumber(phone).

The original stateless business logic is replaced by _phone.getAreaCode().

8.3 Step 3 - Create a new interface

Create a new interface and promote the code of DP to the interface parameter layer:

public User register(Name name, PhoneNumber phone, Address address) {
    
    
    SalesRep rep = salesRepRepo.findRep(phone.getAreaCode());
}

8.4 Step 4 - Modify the external call

The external caller needs to modify the calling link, for example:

service.register("殷浩", "0571-12345678", "浙江省杭州市余杭区文三西路969号");

Change to:

service.register(
	new Name("殷浩"), 
	new PhoneNumber("0571-12345678"), 
	new Address("浙江省杭州市余杭区文三西路969号")
);

Through the above 4 steps, you can make your code more concise, elegant, robust, and safe. What are you waiting for? Go try it today!


Link:

https://martinfowler.com/bliki/AnemicDomainModel.html

Guess you like

Origin blog.csdn.net/luo15242208310/article/details/125468930