background

Where there are people, there are rivers and lakes, and where there are designs, there will be structures. If you’re a veteran of the software industry, you’ve probably experienced something like this: At the beginning of a business, ordinary CRUD is enough and the lines of business are short. At this point, everything in the system looks nice, but as iterations evolve and business logic becomes more complex, our system becomes more and more complex, with modules linked to each other, and no one can even describe every detail. When new requirements change a feature, it often takes a long time just to review the process involved in the feature, not to mention the unpredictable impact of the change. So RD will add a switch, carefully cut flow line, a problem quickly closed switch.

Faced with such a scenario, you either run or reconstruct. Refactoring is overcome smorgasbord in evolutionary design, through a separate class and do a series of small step to complete the method level, we can easily reconstructed a separate class to put some general logic, however, you will find it is hard to give it a implications for business, can only give a technical dimension of meaning. You’re rebuilding and digging holes for posterity.

In the context of “baby steps, iterative trial and error” in Internet development, DDD seems to be an “old and slow” idea. However, as Internet companies have gradually penetrated into the real economy and their businesses have become increasingly complex, we have encountered more and more problems in software development of traditional industries.

How to solve this problem? In fact, the magic weapon is today’s theme, domain driven design! I believe you will be inspired by this article.

DDD introduced

DDD is the whole process of domain-driven Design, which is called domain-driven Design in Chinese. It is a set of object-oriented modeling methodology for complex software system analysis and Design.

The previous system analysis and design are separate, resulting in requirements and finished products are very prone to deviation, the two are relatively independent, but also lead to communication difficulties, DDD broke this barrier, proposed the concept of domain model, unified analysis and design programming, so that software can be more flexible and quickly follow requirements change.



(Public Account: Architecture Progress)

DDD history

I’m sure you’ve heard of domain driver (DDD) before. Will you be dazzled by the variety of concepts? Does abstract logic feel like a lack of practice? Perhaps this is why DDD has never caught on.

There was OOP in 1967, OOAD (Object-oriented Analysis and Design) in 1982, a full-fledged VERSION of OOP aimed at solving complex business scenarios, and a domain-driven trend of thought developed, and by 2003, Eric Evans has released a work domain-Driven Design: Complexities in the Heart of Software, formally defining the concept of territory and starting the DDD era. That adds up to nearly 20 years, but it’s not as easy as Eric Evans imagined, and DDD hasn’t been a “global hit.”

In 2013, Vaughn Vernon wrote a book on Implementing Domain-Driven Design, which further defined the field direction of DDD and gave a lot of implementation guidance, which made people a step closer to DDD.

At the same time, with the rise of the Internet, Rod Johnson stole the limelight with his lightweight, minimalist Spring Cloud. Spring’s blood loss model isn’t OOP’s royal blood, but who cares? After all, it’s the cost of simplifying development that counts.

As we use this mouth silent Spring, we are aware of a serious problem, when we deal with complex business scenarios, Spring doesn’t seem to give a more reasonable solution, so the idea of divide and rule should be bore micro service, changes the past single multiple sub application, suddenly let a person shine at the moment, So we split the service day and night, plus the registry, circuit breaker, flow limiting solutions provided by micro services, we have a lot of fun.

After stepping on the pit of many split services (too fine split leads to service explosion, unreasonable split leads to frequency reconfiguration, etc.), people start to deadlock reasons. Is there a methodology that can guide people to split services more reasonably? Thousands of times in the search for him, BUT DDD in the lights dim, DDD guidance, coupled with micro service events, is the perfect framework.

Relationship between DDD and microservices

In the background, we said that DDD guidance, coupled with microservices events, is the perfect architecture, and here are the details of their relationship.

The complexity of the system is more and more to high is the inevitable trend, the reason may come from the evolution of their business, it is possible that the technical innovation, however, a team of people and the cognition of complexity are limited, like a server performance limit, the solution only divide and rule, will be big problem apart for small problems, eventually break through the limit. Microservices provides theoretical guidance and best practices in this area, such as registries, circuit breakers, and flow limiting solutions, but microservices does not provide a reasonable solution to the problem of “dealing with complex business scenarios”, because the focus of microservices is governance, not distribution.

As we all know, when building a system, we should consider the following aspects:

  1. Feature dimension
  2. Quality dimensions (including performance and availability)
  3. The engineering dimension
Well done in the second, but not enough in the first and third dimensions. This gives DDD a “window of opportunity” to provide a poor guide to microserver functionality. So they are complementary when dealing with complex problems and building systems.

From the perspective of architecture, services in microservices focus on the domain layer in the hexagonal architecture advocated by DDD, and the Entity and Use cases layer in the clean architecture. As shown below:



How do DDD and microservices work together


It’s not enough to know DDD and microservices, we also need to know how they work together.

A system (or a company) and the scope of business activities in this scope, known as fields, field is faced with the problem domain, in real life has nothing to do with the software system, the field can be divided into subdomains, such as electricity field can be divided into subdomains, order subdomains of commodity, invoice subdomains, inventory subdomains, etc., in different subdomains, Different concepts have different meanings, so we must have a clear boundary when modeling. This boundary is called bounded context in DDD, and it is a boundary within the system architecture, as described in the Neat Way:

The architecture of a system is defined by the architectural boundaries within the system and the dependencies between those boundaries, regardless of how components in the system are invoked. The so-called service itself is just a form of partitioning application behavior that is slightly more expensive than function calls, regardless of system architecture.
Therefore, the first element of the division of complex systems is to divide the internal architectural boundaries of the system, that is, to divide the context, and to clarify the relationships between them. This corresponds to the first dimension (functional dimension) mentioned earlier, and this is where DDD comes in. Second, we consider how to divide the non-functional dimension, and this is where microservices come into their own.

Suppose we divide the service into ABC contexts:





We can deploy monomer used within A process, can also be done through remote call function calls, this is the current service way, more time we is A mixture of two kinds of ways, such as A and B within A unit of deployment, C deployment alone, this is because the C is very important, or the concurrency value is bigger, or demand change more frequently, There are several advantages to C standalone deployment in this case:

  1. C Independent deployment: Resources are properly tilted to expand or shrink capacity independently.
  2. Elastic service: retry, fusing, degradation, etc., has reached fault isolation.
  3. Technology stack independence: C can be written in other languages, making it more suitable for personalized team technology stacks.
  4. Team independence: Different teams can be responsible for it.
Architecture can evolve, so need to consider the structure of split phase, early, pay more attention to business logic border late need to consider more aspects, such as the amount of data, such as complexity, but even with this policy, also often, no one can be at once defined boundary right, actually there is no clear right or wrong here.

Even if boundaries are poorly defined, aggregating roots enable us to evolve a more appropriate context within which domain concepts are modeled through entities and value objects, a set of which are assigned to an aggregating root.

According to DDD constraints:

  • First, aggregate root to ensure the correctness of internal entity rules and data consistency;
  • Second, external objects can only refer to the aggregate root by id, not entities inside the aggregate root.
  • Third, aggregation roots cannot share a database transaction with each other, and data consistency between them needs to be guaranteed through final consistency.
With aggregate roots, and based on these constraints, it will be easier in the future to upgrade aggregate roots to context and even break them down into microservices as needed.

DDD related terms and basic concepts

After discussing the macro concepts, let’s take a look at some of the DDD concepts. For each concept, I have found a mapping concept developed in The Spring pattern for you to understand, but only as an understanding, don’t rely on too much.

In addition, you may need to combine this with the following code several times before it can be integrated into the actual work.

field

Mapping concept: Shelled services.

The domain is the scope. The focus of scope is on boundaries. The core idea of the domain, which is at the heart of the DDD discussion, is to subdivide the problem level by level to reduce business and system complexity.

subdomain

Mapping concept: sub-services.

Domains can be further divided into subdomains, or subdomains. This is a design idea that deals with highly complex domains and attempts to isolate the complexity of technical implementations. This split is found in many architectures, such as C4.

The core domain

Mapping concept: core services.

In the process of domain division, molecular domains are constantly divided, and subdomains are divided into three categories according to the importance: core domain, general domain, and support domain.

The sub-domain that decides the core competitiveness of the product is the core domain, without too much personalized appeal.

In the case of peach trees, there are six sub-domains such as root, stem, leaf, flower, fruit and seed. Different people understand the core domain differently. For example, in the orchard, the core domain is the fruit, while in the park, the core domain is the flower. Sometimes common and supporting domains (stems, leaves, etc.) are cut to provide nutrients to the core domain.

General domain

Mapping concept: Middleware services or third-party services.

A common function used by multiple subdomains is a common domain without many enterprise features, such as permission authentication.

Support domain

Mapping concept: Enterprise common services.

In terms of functions, it must exist, but it does not affect the core competitiveness of the product, nor does it contain universal functions. It has enterprise characteristics and does not have universality, such as the digital dictionary system of data code class.

A unified language

Mapping concept: Unified concept.

Define the meaning of context. Its value is that it can overcome communication barriers, whether you are RD, PM, QA, etc., and let each team use a common language (concept) to communicate, even more readable code.

The common language contains both belong and use case scenarios and can be reflected directly in the code.

You can use event storms (meetings) to unify languages, even chinese-English mappings, business and code model mappings, etc. You can use a table to keep track.

Bounded context

Mapping concept: boundaries of service responsibility division.

Define context boundaries. Domain models exist within boundaries. The same concept can be interpreted differently in different contexts. For example, a commodity is called a commodity in the selling stage and a commodity in the transportation stage.





In theory, the boundary of a bounding context is the boundary of a microservice, so understanding the bounding context is very important in design.

The aggregation

Mapping concept: packages.

The aggregation concept is similar to the concept of packages, where each package contains a class of entities or behaviors. It helps diffuse system complexity and is a high-level abstraction that simplifies the understanding of domain models.

Split entities can’t all be in one service, which involves splitting, so where there’s splitting, there’s aggregation. Aggregation is a problem to ensure consistency between objects in a domain.

When defining aggregates, we should observe the invariant constraint rule:

  1. What information must exist within an aggregation boundary, without which it cannot be called an effective aggregation;
  2. The state of some objects within the aggregation must satisfy a business rule:
  • An aggregate has only one aggregate root, which can exist independently and other entities or value objects in the aggregate depend on the aggregate root.
  • Only the aggregate root, which maintains the internal consistency of the aggregate, is externally accessible.

Aggregate root

Mapping concept: packages.

A context may contain multiple aggregations, each of which has a root entity called an aggregate root, and each aggregate has only one aggregate root.

entity

Mapping concept: Domain or Entity.

According to the book “Domain-Driven Design Patterns, Principles and Practices”, entity is a domain concept with identity and coherence. It can be seen that entity is also a special domain, and we need to pay attention to two points: unique identification (identity) and continuity. One is indispensable.

As you can imagine, articles could be entities and authors could be entities because they have ids as a unique identifier.

The value object

Mapping concept: Domain or Entity.

In order to better display the relationship between domain models, an object formulated is also an entity in nature, but relative to an entity, it has no status and identity identification. Its purpose of existence is to represent a value, and value objects are usually used to convey the form of quantity.

For example, money, having an ID is obviously not reasonable, and you can’t query a money by id.

Defining a value object is context-specific. You can even think of an Author in an Article as a value object, but be aware that if the Author exists independently as an entity, or if the Author is used for complex business logic, the Author will also be upgraded to an aggregate root.

Finally, a graph from the network is given, which is relatively complete and directly copied to facilitate your macro review of DDD related concepts:



Four Domain modes

In addition to obscure concepts, perhaps the most difficult thing for us to accept is the application of the model. In Spring, Domain is just the carrier of data, and all activities are wrapped in the Domain and then flow in the Service, while OOP pays attention to an object dimension to execute business, so, Objects in DDD are behavioral (it is important to understand this).

Here I’ve summarized all four domain patterns for you to distinguish and understand:

  1. Blood loss model
  2. Anemia model
  3. Congestion model
  4. Expanding blood model

background

First, to explain the example background, because the company’s project can not be leaked, I here simulate an article management system (this system is relatively simple, theoretically can not use DDD, here only for example), the business requirements are: publish articles, modify articles, article classification search and display.

If you’re developing with Spring, you probably have the following code in mind.

Article class: Article

public class Article implements Serializable {
    private Integer id;
    private String title;
    private Integer classId;
    private Integer authorId;
    private String authorName;
    private String content;
    private Date pubDate;
    //getter/setter/toString
}Copy the code
DAO class: ArticleDao/ArticleImpl

public interface ArticleDao extends BaseDao<Article>{
    //...
}

Repository("articleDao")
public class ArticleDaoImpl implements ArticleDao{
    //...
}Copy the code
Service types: ArticleService

public interface ArticleService extends BaseService<Article>{
    //...
}

@Service(value="articleService")
public class ArticleServiceImpl implements ArticleService {
    //...
}Copy the code
Controller class: omitted.

Four pattern examples

Blood loss model

Domain Objects are pure data classes with getter/setter methods for properties, and all business logic is done entirely by Business Objects.

public class Article implements Serializable {
    private Integer id;
    private String title;
    private Integer classId;
    private Integer authorId;
    private String authorName;
    private String content;
    private Date pubDate;
    //getter/setter/toString
}

public interface ArticleDao {
     public Article getArticleById(Integer id);
     public Article findAll();
     public void updateArticle(Article article);
}Copy the code

Anemia model

In simple terms, Domain Objects contain Domain logic that does not depend on persistence, and those that do rely on persistence are separated into the Service layer.

public class Article implements Serializable { private Integer id; private String title; private Integer classId; private Integer authorId; private String authorName; private String content; private Date pubDate; Public Boolean isHotClass(Article Article){//getter/setter/toStringreturnStream of (57102). AnyMatch (every - > every. Equals (article. GetClassId ())); } public Article changeClass(Article Article, ArticleClass ac){return article.setClassId(ac.getId());
    }
}

@Repository("articleDao")
public class ArticleDaoImpl implements ArticleDao{
    @Resource
    private ArticleDao articleDao;
    public void changeClass(Article article, ArticleClass ac){
        article.changeClass(article, ac);
        articleDao.update(article)
    }
}Copy the code
Note that this pattern does not rely on DAOs in the Domain layer. Persistence also needs to be done in a DAO or Service.

The pros and cons of doing this

Advantages: each layer one-way dependence, clear structure.

Disadvantages:

  • The parts of the Domain Object that are more closely dependent on persistent Domain Logic are separated into the Service layer and are not OO enough
  • The Service layer is too thick

Congestion model

The congestion model is similar to the second model, but the difference is that most of the business logic is placed in a Domain. A Service is a thin layer that encapsulates a small amount of business logic and does not deal with DAOs:

Service (transaction Encapsulation) — > Domain Object < — > DAO
public class Article implements Serializable { @Resource private static ArticleDao articleDao; private Integer id; private String title; private Integer classId; private Integer authorId; private String authorName; private String content; private Date pubDate; //getter/setter/toString // public List<Article>findAll() {returnarticleDao.findAll(); } public Boolean isHotClass(Article Article){public Boolean isHotClass(Article Article){returnStream of (57102). AnyMatch (every - > every. Equals (article. GetClassId ())); } public Article changeClass(Article Article, ArticleClass ac){returnarticle.setClassId(ac.getId()); }}Copy the code
All business logic is in domains, and transaction management is implemented in Items. The advantages and disadvantages of this are as follows.

Advantages:

  • More aligned with OO principles;
  • The Service layer is thin and acts as a Facade rather than a DAO.
Disadvantages:

  • Daos and Domain Objects form bidirectional dependencies, and complex bidirectional dependencies can lead to many potential problems.
  • The division of Service layer logic and Domain layer logic can be very ambiguous, and in real projects, the overall structure can be chaotic due to differences in the level of designers and developers.

Expanding blood model

Based on the third shortcoming of the congestion model, some students suggested that the Service layer should be cancelled altogether, leaving only Domain Object and DAO layers, and encapsulating transactions on Domain Logic of Domain Object.

Domain Object (Transaction Encapsulation, Business Logic) < — > DAO
Ruby on Rails seems to be such a model, even merging Domain Objects and DAOs.

Pros and cons of doing this:

  • Simplified layering
  • It’s also OO
Disadvantages of this model:

  • Many Service logics that are not Domain Logic are also forced into Domain Object, which causes the instability of Domain Object model.
  • Domain Objects expose too much information to the Web layer, which can cause unexpected side effects.

Practice of using DDD to transform existing old system

If you are a team Leader or architect, when you take on the task of maintaining and refactoring an old system, how do you change it? Do you feel like everything is wrong but you don’t know where to start because you’re unfamiliar with the business? In fact, I can teach you a way to deal with this dilemma.

Here’s what you should do:

1. Sort out the call relationship between systems through the public platform (generally, companies above medium level have RPC and HTTP call relationship, so they can query each system without thinking). The drawing may be messy or clear, but this is the current situation.



2. Assign team members to each claim several projects to sort out the project dimension relationships, including: detailed descriptions of external interfaces, interactions, use cases, MQ, etc. Individual core systems can be drawn with internal entities or aggregate roots.


3. Hold group meetings and review the business concepts of each system one by one to achieve the unified language within the group.



4. According to the above information, we can see which unreasonable call relationships (such as circular call, non-standard call, etc.), and even unreasonable stratification.


5. Subdivide the domain from top to bottom according to the main line of business, and limit the context. This process may overturn the previous system partition.

6. Specify domain model according to business complexity, and select anemia or congestion model. It is best to implement uniform habits within the team to avoid excessive handover costs.

7. Divide the work for development and set the deadline. Pay attention not to set a single deadline, but set the intermediate check time, for example, Dealline is January 20th, and set two check times to communicate the code style and boundary responsibilities respectively. Lest the deadline be delayed.

The perfect combination of DDD and the Spring family

Using the article management system mentioned earlier, LET me show you the focus of DDD development.

Module

Module is explicitly mentioned in DDD as a means to control the bounded context. In our projects, we generally try to use a Module to represent the bounded context of a domain.

As shown in the code, packages in a typical project are organized as {com. company name. Organizational structure. Business. *}, which explicitly limits a context inside a package.

import com.company.team.bussiness.counter.*; / / count the context import com.com pany. Team. Bussiness. Category. *; / / classification context import com.company.team.bussiness.com ment. *; // Comment on contextCopy the code
As for the organizational structure within the module, we generally define it in accordance with the organizational modes of domain object, domain service, domain resource library and anti-corrosion layer.

import com.company.team.bussiness.cms.domain.valobj.*; / / domain objects - value objects import pany. Com.com team. Bussiness. CMS. Domain. The entity. *; / / domain objects - the import entity com.com pany. Team. The bussiness. CMS. Domain. Aggregate. *; / / root domain objects - polymerization import pany. Com.com team. Bussiness. CMS. Service. *; / / field service import com.com pany. Team. Bussiness. CMS. Repo. *; / / field repository import com.com pany. Team. Bussiness. CMS. The facade. *; // Field anticorrosion coatingCopy the code

Domain object

One of the most important problems that domain drivers solve is the anemia of objects, and domain objects most directly reflect this ability.

We can define aggregate roots (articles) and value objects (counters) as examples. The aggregate root holds the id of the article and the count data of the article, and the reason the counter is listed as a value object rather than an attribute of the entity is because the counter is made up of several parts, such as actual read, promoted read, and so on.

In the article domain object, we need to define a method to get the count of articles for display on the page. This logic can be complicated, involving factors such as coverage, column writer level, publication time, etc.

package com.company.team.bussiness.domain.aggregate; import ... ; public class Article { @Resource private CategoryRepository categoryRepository; private int articleId; // article id... private ArticleCount articleCount; // getters & setters // query counts display quantity, this is a bit of a simple logic, even does not fit the actual business scenario, this is not important, just for the purpose of intuitiongetShowArticleCount() {
            if(this.articleCount == null){
            return 0;
        }
        returnthis.articleCount.realCount + categoryRepository.getCategoryWeight(this.category) + (this.articleCount.adCount * DayUtils.calDaysByNow(this.articleCount.deadDays)); }}Copy the code
Instead of just getters and setters, domain objects have behavior and are more fleshly. At the same time, the domain functionality is more cohesive and its responsibilities more specific than writing this logic inside a Service, such as a Service.

The repository

Domain objects need resource storage, which can be understood as DAO, but it is broader than DAO. Storage means can be diversified, such as database, distributed cache, local cache, etc. A Repository is an object that centrally manages storage and access for a domain.

In the system, we organize the repository in the following way.

import com.company.team.bussiness.repo.dao.ArticleDao; / / database access object - article import com.company.team.bussiness.repo.dao.Com mentDao; / / database access object - comment on import com.com pany. Team. The bussiness. Repo. Dao. Po. ArticlePO; / / database persistent object - article import com.company.team.bussiness.repo.dao.po.Com mentPO; / / database persistent objects - comment on import com.com pany. Team. Bussiness. Repo. Cache. ArticleObj; // Distributed cache access object - article cache accessCopy the code
Overall access to a Repository is provided by Repository, which aggregates data from each Repository and takes over the storage logic (such as cache update mechanisms).

In the repository, we block direct access to the underlying prize pool and prizes, and instead manage only the aggregate root of the article. The method for retrieving resources (the most common Cache Aside Pattern) is shown in the code sample.

package com.company.team.bussiness.repo; import ... ; @Repository public class ArticleRepository { @Autowired private ArticleDao articleDao; @AutoWired private articleDaoCacheAccessObj articleCacheAccessObj; public Article getArticleById(int articleId) { Article article = articleCacheAccessObj.get(articleId);if(article! =null){return article;
        }
        article = getArticleFromDB(articleId);
        articleCacheAccessObj.add(articleId, article);
        return article;
    }

    private Article getArticleFromDB(int articleId) {...}
}Copy the code
Compared with the previous practice of putting resource management in services, the responsibility of resource management by the repository is clearer, and the code is more readable and maintainable.

Anticorrosive coating

Also known as adaptive layer. In a context where access to the external context is sometimes required, the concept of an anticorrosion layer is often introduced to escape access to the external context once.

The introduction of anticorrosion coating will be considered in the following situations:

  • Models in external context need to be translated into models understood by the context.
  • For the team collaboration between different contexts, if it is an enshrine relationship, it is recommended to introduce an anti-corrosion layer to avoid the erosion of the local context by external context changes.
  • This access is used extensively in this context to avoid overreaching changes.
package com.company.team.bussiness.facade; import ... ; @Component public class ArticleFacade { @Resource private ArticleService articleService; public Article getArticle(ArticleContext context) { ArticleResponse resp = articleService.getArticle(context.getArticleId());return buildArticle(resp);
    }

    private Article buildArticle(ArticleResponse resp) {...}
}Copy the code
If multiple internal contexts need access to external contexts, consider placing them in a generic context.

Field service

In the previous section, we encapsulated domain behavior in domain objects, resource management behavior in repository, and external context interaction behavior in a preservative layer. At this point, when we look back at domain services, we can see that domain services themselves are responsible for providing interactive interfaces to other contexts by connecting the behavior of a series of domain objects, repositories, and preservative layers.

package com.company.team.bussiness.service.impl import ... ; @Service public class CommentServiceImpl implements CommentService { @Resource private CommentFacade commentFacade; @Resource private ArticleRepository articleRepo; @Resource private ArticleService articleService; @Override public CommentResponse commentArticle(CommentContext commentContext) { Article article = articleRepo.getArticleById(commentContext.getArticleId()); // Get the article aggregate root Commentfacade.docomment (commentContext); // Add count informationreturnbuildCommentResponse(commentContext,article); } private CommentResponse buildCommentResponse(CommentContext CommentContext, Article Article) {... }}Copy the code
You can see that the logic of domain services is clear enough after omitting some of the defensive logic (exception handling, null-value determination, and so on).

Demonstration package structure



Reflection thinking


DDD subdivides the domain layer, which is the biggest highlight of DDD comparison MVC framework.

DDD can do this mainly because DDD subdivides the domain layer. For example, domain objects have entities and aggregations, actions and operations are called domain services, capabilities are called domain capabilities, etc. MVC architecture does not subdivide business elements. All businesses are services. This makes it hard to define technical constraints at the Controller and Service layers, because both are Services, and you don’t know if the Service is describing an object or a business operation.

In terms of future business expansion, aggregation root upgrading into context or even splitting into micro-services is also an important means to deal with complex problems.

Entities and value objects are the biggest changes to existing programming habits, but don’t get too focused on the relationships between domain objects.

DDD itself is a methodology, is to provide theoretical guidance, so do not expect Spring to give you a Demo to write, hope readers after reading a lot of reflection.

(Public Account: Architecture Progress)