Editor’s note:

What software engineers do is take things from the real world and put them on computers to increase productivity through informatization. One point that should not be overlooked in this process is **[built-in quality of the system]**.

Well-designed systems: clear in concept, well-structured, understandable and maintainable even with a large code base;

Poorly designed system: “Carved in shit”.

Among them, the lack of domain concept and domain model is the main culprit causing this difference.

01. Concept interpretation


Domain-driven Design (DDD) is a software development methodology to solve complex business problems based on Domain knowledge. Its essence is to divide a big business to be done into many cohesive fields after deducting and abstracting.

It has the following three focuses:

Work closely with Domain experts to define Domain scope and solutions

Split the field into several sub-areas and focus on the core sub-areas

Transform domain knowledge into corresponding program models through a series of design patterns

Domains can be large or small, corresponding to the boundaries of large and small business problems, the division and control of boundaries is the core idea of domain-driven design.

02. Changes made by DDD


Say “No!” to the Anemic Model.

I introduce you to a well-known anti-pattern: the Anemic Model. This pattern generally refers to models that have only getters and setters. The lack of behavioral representation of these models results in the user having to compose the desired functionality each time.

“The anemia model is like teaching a child, and it’s easy to forget every command and every action; Models with behavioral articulatory abilities perform as many commands as adults in one action.”

For example: the data-centric approach requires that the client code must know how to properly commit a backlog item to the sprint. At this point, changing sprintId incorrectly or having another attribute that needs to be set requires the developer to carefully analyze the client code to complete the mapping from the client data to the BacklogItem attribute. Such a model is not a domain model.

public class BacklogItem extends Entity { private SprintId sprintId; private BacklogItemStatusType status; . public void setSprintId(SprintId sprintId) { this.sprintId = sprintId; } public void setStatus(BacklogItemStatusType status) { this.status = status; }... } // The client submits a BacklogItem to the Sprint by setting sprintId and status. Backlogitem.setsprintid (sprintId); backlogItem.setStatus(BacklogItemStatusType.COMMITTED);Copy the code

Encapsulate program behavior through a business language

DDD focuses on introducing business language into program models to encapsulate key business behaviors. Rather than encapsulating code arbitrarily, the act of binding program models to business logic ensures that code ADAPTS to business changes. When modeling, domain experts discuss the following requirements:

  • Allows each pending item to be committed to a sprint and only if one of the pending items is in a Release plan

  • If a pending item has been committed to another sprint, it is reclaimed first

  • Inform the relevant client when submission is complete

The client code does not need to know the implementation details of the BacklogItem submission, because the logic of the implementation code just describes the business behavior.

public class BacklogItem extends Entity { private SprintId sprintId; private BacklogItemStatusType status; . public void commitTo(Sprint sprint) { if (! this.isScheduledForRelease()) { throw new IllegalStateException("Must be scheduled for release to commit to sprint."); } if (this.isComittedToSprint()) { if (! sprint.sprintId().equals(this.sprintId())) { this.uncommitFromSprint(); } } this.elevateStatusWith(BacklogItemStatus.COMMITTED); this.setSprintId(sprint.sprintId()); DomainEventPublisher.instance() .publish(new BacklogItemCommitted( this.tenantId(), this.backlogItemId(), this.sprintId() )); Backlogitem.mitto (Sprint); backlogItem.mitto (Sprint);Copy the code

03. DDD details


To illustrate DDD:

Suppose we are making a simple data statistics system now, its operation logic is as follows: the salesman inputs the name and mobile phone number of the customer, the system divides the customer groups according to the location and carrier of the customer’s mobile phone number, and assigns them to the corresponding sales group, which will follow up the follow-up business.

` `

The code above, most people write it this way, and it doesn’t seem to be a problem, for a small project or short offline system, this can be called fast and good; But putting it all together in a large, iterative project leaves some pitfalls:

Hazard 1: The semantics of the interface are not clear

The bug with the Register method is that it supports one type with two sets of parameters (username, mobile number). When the parameters of the user registration system change, such as using an ID card to Register, the Register method is changed to RegisterByPhone and RegisterByIdCard.

Since internal validation only preserves the parameter type and not the parameter name, changing the parameter means a new interface and validation again, which is not the desired goal. What we expect is that the semantic interface should be unambiguous, extensible, and self-checking.

Interface semantic modification goal: clear semantic unambiguous, strong expansibility, with a certain degree of self-inspection

Risk 2: Parameter verification logic is complex

If there are more than one similar method, and each method has to be validated at the beginning, there is bound to be a lot of duplicate code. If one type of parameter verification logic needs to be modified, then it needs to be modified everywhere, which clearly does not comply with the “on/off principle”. Even if it is encapsulated into a tool for reuse, there are still two bugs: 1. It is not reasonable to mix parameter exceptions with business logic exceptions in business methods; 2. 2. With the increasing number of parameter types, the verification logic in the tool class will continue to expand, and the subsequent maintenance is not a small amount of work.

Parameter verification modification objective: Improve the reusability of the verification logic. Parameter verification exceptions are decoupled from service logic exceptions

Hazard 3: Lack of clarity in core business logic

The modified code is more elegant but not “pure”. RegistrationService is a service used to register users, and its responsibility should be limited to “register” only. The essential act of registration is to “get the user’s information and store it”. The two behaviors in this code, “obtaining the home code of the mobile phone number” and “obtaining the carrier code”, are obviously not applicable to the business logic of “registration”.

The question is: why do we write this logic in the Register method? In order to adapt the interface of findRep to process the original parameters, it is like taking glue to sew the “glue logic”?

How to modify the glue logic to make sense?

Two ideas:

1. Modify the input parameter of findRep interface

This makes sense in the abstract, so you don’t have to do the glue operation inside the Register method

2, “get mobile phone number home code” & “get carrier code” into the type of mobile phone number

Both of these behaviors are attributes associated with retrieving the phone number, and cohesiveness within the phone number type makes sense in the abstract.

From this point of view, the method of cohesion and core domain editing can make the registration method logic clearer.

What logic should belong to which business domain is the understanding of “domain”, just as how to delimit the boundaries of microservices, different understanding angles can lead to different domain model divisions.

One caveat: Many students struggle with writing unit tests: it is difficult to write high coverage; If you do not write, you will not only run CI, but also panic…… Not afraid! By cohesioning PhoneNumber logic and simplifying business logic, kids can write unit tests more efficiently. Changes to PhoneNumber are less frequent. Once a test case has been written, it will be reused more frequently. In this way, the subsequent business logic will become more complex, but the maintenance cost of the unit test logic will not increase.


Article from: LigaAI – intelligent r&d collaboration platform | intelligent project collaboration (ligai. Cn)

By Nerd4Me