This article is published in vivo Internet technology wechat public number link: mp.weixin.qq.com/s/gk-Hb84Dt… Author: Zhang Wenbo

Domain Driven Design (DDD) is not a new theory. You can see the original version of Domain Driven Design compiled by Eric Evans in 2003, more than 10 years ago. Compared with the distributed, microservices now, it is definitely an “old guy” who is about to enter middle age.

Until microservices theory was proposed and widely used in the Internet industry in recent years, people seemed to rediscover the value of domain-driven design. So it does seem that domain-driven design is making a comeback thanks to microservices.

However, I found that there were some misconceptions about DDD, which gradually became a “deep metaphysics” and then was put on the shelf. In the past two years, I have read many classic books on DDD, consulted some senior DDders, and practiced in the project.

However, after preliminary study and practice, I re-read the relevant writings and theories with doubts and thoughts. DDD, as an idea, is very close to us.

I write my own learning process, thinking into a series of articles, with you to discuss learning, I hope you can harvest, of course, where is not correct, you are welcome to criticize.

At the same time, I will also cite relevant literature or some good case materials in the article, just as our detailed interpretation of these knowledge, and here to express our gratitude to these DDD predecessors tireless exploration.

(Classic works related to DDD)

Myths about DDD

  1. DDD is to solve large and complex projects. Our current business is relatively simple and not suitable for DDD.
  2. DDD requires a complete code structure that conforms to DDD principles, which can increase code complexity and cause the project to run out of control.
  3. DDD is a framework that should contain aggregate roots, entities, domain events, warehouse definitions, bounded contexts, and everything DDD advocates; Otherwise you are not DDDer.
  4. DDD requires everyone to strictly follow the boundaries of their modules, and there is too much seemingly redundant and useless code due to decoupling, which will reduce the coding efficiency and cause “class inflation”.

DDD is very close to us

What is DDD? After searching for her, I suddenly realized that DDD is an idea that can be used for reference, rather than a methodology that can be strictly followed.

1. Domain model in domain driven design

When we are developing for business, we should think about domain models first rather than how to build tables.

I have heard a lot of business development voices, “interview to build aircraft carriers, work screw”, the daily work is to build tables, add, delete, change and check. The root of this perception lies in the idea of table-driven design rather than domain-driven design.

The former can only increase the number of tables in the database, while the latter can form long-term, business-meaningful models, which make the system more durable. We can also code in an engineering way, moving from coding to business domain development experts.

There are a lot of discussions about domain-driven design that don’t make it clear how we get to the “domain”. Only a reasonable domain model can effectively drive design and development. Therefore, building a good domain model is the key, and thinking about the domain model is as important as upgrading the technical framework. I have shared how to do domain modeling in the Internet department, and I welcome you to share with me. Interested readers can also focus on the relevant chapters of UML and Pattern Application.


2. Architecture and decoupling

Before we talk about DDD, let’s talk about “decoupling,” a word we use a lot in everyday coding. A craftsman programmer will break up some of the most powerful functions or classes in the code review phase to make them more focused and less coupled.

On the other hand, we also value “decoupling” in terms of architecture, because a system with arbitrarily coupled modules is a nightmare for everyone. Therefore, in addition to clean code, we need to focus on clean architecture.

Architecture consists of three elements: modules or components with clear responsibilities, clear relationships between components, constraints, and guidelines. Cohesive components must have well-defined boundaries, and these well-defined boundaries must guide future development as related constraints.

3. Go from layered to hexagonal architecture

3.1 Layered Architecture

Layered architecture is the most widely used architectural pattern. Almost every software system needs to isolate different concerns through layers, so as to cope with the changes of different requirements and make the changes independently. Each layer, and even each component within the same layer, changes at different rates.

By “change at different rates”, we mean change for different reasons, which is the Single Responsibility Principle (SRP). That is, “A class should have only one cause that causes it to change.” In other words, if there are two causes that cause a class to change, they need to be separated.

The single responsibility principle can be understood as an architecture principle, where it is not classes that matter, but hierarchies. For example, network layer 7 protocol is a well defined, classic layered architecture, simple, easy to learn and understand, and finally widely used to greatly promote the development of network communication.

Typically, software systems are divided into several layers: THE UI interface (or access layer), application-specific business logic, domain-wide business logic, databases, and so on.

Next, what are the changes for different reasons? The answer is the business logic itself! Within each layer, different business scenarios change for different reasons and frequency, and different scenarios are defined as business use cases. From this, we can conclude a pattern of slicing the system horizontally into multiple layers while slicing it vertically by use case. The advantage of this is that changes made to a single use case do not affect other use cases.

If we also group the UI and database that support these use cases, then each use case uses its own UI representation and database, thus decoupling from the top down. On the other hand, with hierarchy comes dependency. In the OSI protocol, the upper layer transparently depends on the lower layer. But in software architecture, we put more emphasis on dependency abstraction. In other words, component A depends on the function of B. What we do is to define the interface it needs in A, and let B realize the corresponding interface capability, so that it can be pluggable. In the future, we can replace COMPONENT B with component C that also realizes interface capability without causing any impact on the system.

3.2 Clean Architecture

There is a sense in the layered architecture that each layer is equally important, but a clean architectural style evolves if we focus on the domain layer and shape dependencies into a domain-centric ring in business-by-business order. This is not to say that other layers are unimportant, but simply to highlight the domain capabilities that host the core of the business.


The most important principle of clean architecture is the dependency principle, which defines the level of dependencies for each layer, and the lower the dependency level, the higher the code level. The outer circle code relies on pointing only to the inner circle, which knows nothing about the outer circle. In general, declarations of outer circles (including methods, classes, and variables) cannot be referenced by inner circles. Similarly, the data format used by the outer circle cannot be used by the inner circle.

The main functions of each layer of a clean architecture are as follows:

  • Entities: Implements domain core business logic that encapsulates enterprise-level business rules. An Entity can be an object with methods or a collection of data structures and methods. In general, we recommend creating a congestion model.

  • Use Cases: Realize service composition and orchestration related to user operations. It contains application specific business rules and encapsulates and realizes all Use Cases of the system.

  • Interface Adapters: Converts data suitable for Use Cases and entities to a format suitable for external services, or converts external data to a format suitable for Use Cases and Entities.

  • Frameworks and Drivers: This is where all the front-end business details are implemented, the UI, Tools, Frameworks, etc., as well as the infrastructure such as the database.

3.3 Hexagonal architecture

We integrate the external dependencies of a clean architecture according to their input and output functions and resource types. Expose a port for storage, middleware, integration with other systems, and HTTP calls. This will evolve into the architecture diagram below.

“Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and “Systems can be equally driven by users, other programs, automated tests, or scripts, and can be developed and tested independently of their final runtime devices and databases.” This is the essence of the hex.

The architecture consists of ports and adapters. Ports are the entry and exit points for applications, and in many languages, they exist as interfaces. For example, taking order cancellation as an example, “send order cancellation notification” can be considered as an exit port. The business logic of order cancellation determines when to invoke the port, and the order information determines the port input, while the port hides its implementation details for the upstream order-related services.

There are two types of adapters. The primary Adapter (alias Driving Adapter) represents how the user uses the application. Technically, they receive user input, invoke ports, and return output. Rest API is currently the most common application usage. Take order cancellation as an example. The adapter implements the Endpoint of Rest API and calls the entry port OrderService, which may send OrderCancelled events. The same port may be called by multiple adapters, and the cancellation order in this scenario may also be called by a Driving Adapter that implements the messaging protocol to cancel the order asynchronously.

The secondary Adapter (alias Driven Adapter) implements the exit port of the application to perform operations to external tools, such as executing SQL to MySQL and storing orders; Search for products using Elasticsearch’s API; Send order cancellation notifications by email/SMS. Instead of the traditional layered image, it forms a hexagon, hence the term hexagon.

DDD is an idea

I foolishly thought DDD was business + decoupling. A no-brainer, familiar scenario, because that’s what we’re doing, except maybe we’re too focused on what technical frameworks we’re using, what middleware we’re using, what generic classes we’re writing.

In fact, DDD is just like dialectical materialism, even if we use it in a certain part of a software project, as long as it solves practical problems for us, it is enough. We don’t have to go to DDD for DDD’s sake, we have to go from problem to problem.

What is the use of DDD

DDD can change the way developers think about the business domain, requiring developers to spend a lot of time and energy thinking about the business domain, studying concepts and terminology, and communicating with domain experts to discover, capture, and improve the common language, and even to discover model and system architecture inconsistencies. Of course, it’s possible that you don’t have business experts on your team, and you need to become business experts yourself.

In general, the business value of DDD can be summarized as follows:

  1. You have a very useful domain model;

  2. Your business is more accurately defined and understood;

  3. Domain experts can contribute to software design;

  4. Better user experience;

  5. Clear model boundaries;

  6. Better enterprise architecture;

  7. Agile, iterative, and continuous modeling;

  8. Use of new strategic and tactical tools;

How to DDD

The words “domain model,” “decoupling,” “dependency abstraction,” and “boundaries” must be flashing through your mind. These general methods of analysis must be valid everywhere. So I think you’ve made a step forward in DDD when you think about it in terms of these principles. Let’s take a closer look at bounded context and Repository, two of the most neglected areas of DDD.

After all these steps are done, you can decide how to proceed with your coding development. But I’m sure you’ve learned a lot of high business value in the process.

How to implement it next, you can depend on the actual situation. I think strategic DDD is more important than tactical DDD, and I think that’s the magic of DDD as an idea. Just like Jin Yong’s shaolin classics of pure learning and easy tendon, a set of internal kung fu mind without a clear style can play all over the martial arts.

1. Bounded context

There are also problem Spaces and solution Spaces in the domain. In the problem space, we think about the challenges facing the business, and in the solution space, we think about how to implement software to solve those business challenges.

  • The problem space is part of the domain, and the development of the problem space results in a new core domain. The evaluation of the problem space should consider both existing and additional required subdomains. Therefore, the problem space is a combination of the core domain and other subdomains. Subdomains in the problem space often vary from project to project, each focusing on the current business problem, which makes subdomains useful for problem space evaluation. Subdomains allow us to quickly scan the various aspects of the domain that are necessary to solve a particular problem.

  • The solution space contains one or more bounded contexts, which are a specific set of software models. This is because a bounded context is a specific solution to a problem.

In general, we want to map subfields one-to-one to bounded contexts. This approach explicitly separates the domain model into different business blocks and fuses the problem space and solution space together.

In practice, however, this is not always possible. Imagine anyone who has not maintained a “ball of wool” system. Now we have to use bounds context to safely, reasonably, and quickly straighten out the tangled relationships.

Many books and articles on DDD always highlight how to build the code package structure and what technical framework to use. I don’t think this is entirely true, so I’ll spend a lot of time explaining how to straighten out this “wool ball” with the help of bounded context.

I used the illustration directly from the relevant chapter of Implementing Domain-Driven Design as my own annotation of the diagram.

Legacy of the e-commerce system is a typical “big line group”, we according to the experience to be logically apart for: product catalog subdomains, order subdomains, fa ticket domain, of course you also can disassemble out more subdomains, even will continue to break down catalogue subdomains for category subdomains, commodity subdomains is logical subdomains (dotted line). There is also an inventory system for inventory management and a forecasting system for sales forecasting.

Due to historical reasons, there are logistics related business logic in the e-commerce system, and logistics inevitably functions on the inventory logic. The most difficult thing to grasp is where this part intersects, which is the actual project scenario. We usually merge it into a new performance system, which serves as a supporting sub-domain to assist the main e-commerce system.

, of course, as the business development, our performance patterns (such as city support on that day, the businessman, electricity quotient set the warehouse delivery warehouse delivery, returns, etc.), the types of inventory (transfers in the inventory, the library operation, inventory, defect and so on) is more and more complex, we will consider it down again into the performance system, inventory system 2.0 2.0.

The core is that we can conceptually use multiple subdomains to decompose the larger bounded context, and can also include multiple discrete bounded contexts in a new subdomain, and finally achieve “one-to-one correspondence between subdomains and bounded contexts”. I personally feel that this process is the most test of internal mental skills.

We have already said that we will disassemble new subdomains so that the “clean” boundary context can solve the problem space corresponding to this subdomain one-to-one, but the disassembly will inevitably lead to “association relations”. Because in order to solve the problem space, you have to use the corresponding subdomain, you can take it apart, but it’s always in the dependency net.

The general approach is to define interfaces where they intersect. It is realized by the supported boundary context, and the support context can be switched. Here again, we emphasize “dependency abstraction” and “decoupling”.

2, the Repository

“For each object that requires global access, we should create another object as a provider of those objects, as if we were accessing the collection of objects in memory. Create a global interface for these objects to be accessed by clients. Create add and remove methods for these objects…

In addition, we should provide a way to query these objects according to some specified criteria… Create a repository only for aggregation “from Domain-Driven Design. What is a Repository? What is the difference between DAO and Repository? Why Repository?

First, Repository is a separate layer, between the domain layer and the data mapping layer (data access layer).

Its existence makes the domain layer unaware of the existence of the data access layer, and it provides a collection like interface for the domain layer to access domain objects. A Repository is a Repository manager. The domain layer tells the Repository what it needs and the Repository brings it to it. It does not need to know where it is actually stored. The core of this is “decoupling”, so we should make it clear that the domain layer should only use Repository to retrieve objects.

Next, take a look at how DAO differs from Repository.

As I understand it, you can think of Repository as a DAO, but it is important to note that we should design Repository in a collection oriented way, not a data access oriented way. This helps you look at your domain as a model rather than a CRUD operation; Repository is domain-oriented, Repository definition is not intended to be DB-driven, and Repository manages data at a minimum granularity of aggregate root, which is quite different from DAO.

It is generally recommended that Repository be defined as a collection and only provide set-like interfaces such as Add, Remove, and Get. In short, we want to manipulate aggregate roots with the idea of collections rather than the traditional DB-oriented CRUD approach.

Finally, to see why Repository is needed, I understand it to be “decoupled”. When we think of Repository as a Repository, we don’t care about the persistence behind it, and DDD doesn’t think about it either. We can do it with mysql, mongo, or even Redis. Especially when we are replacing the underlying storage, the domain layer and associated services have no impact.

Here is an example of code:

package zwb.ddd.repository.sample.domain; import zwb.ddd.repository.sample.domain.model.BaseAggregateRoot; import java.util.List; /** * BaseAggregateRoot Domain model base class, BaseSpecification is suitable for more complex query scenarios. * @author wenbo.zhang * @date 2019-11-20 */ public interface IRepository<T extends BaseAggregateRoot, Q extends BaseSpecification> { T ofId(String id); void add(T t); void remove(String id); List<T> querySpecification(Q q); }Copy the code


Implementation class:

package zwb.ddd.repository.sample.infrastructure; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import zwb.ddd.repository.sample.domain.IRepository; import zwb.ddd.repository.sample.domain.BaseSpecification; import zwb.ddd.repository.sample.domain.model.BaseAggregateRoot; import zwb.ddd.repository.sample.domain.model.Customer; import zwb.ddd.repository.sample.domain.model.CustomerSpecification; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author wenbo.zhang * @date 2019-11-20 */ @Component public class CustomerRepository implements IRepository { /** * The implementation of Repository is not aware of the upper layer. If we want to switch to Redis and mysql in the future, we only need to change this layer. */ Map<String, Customer> customerMap = new ConcurrentHashMap<>(); @Override public Customer ofId(String id) {return customerMap.get(id);
    }
 
    @Override
    public void add(BaseAggregateRoot aggregateRoot) {
        if(! (aggregateRoot instanceof Customer)) {return; } Customer customer = (Customer) aggregateRoot; customerMap.put(customer.getId(), customer); } @Override public void remove(String id) { customerMap.remove(id); ** @param Specification; ** @param Specificationreturn
     */
    @Override
    public List<Customer> querySpecification(BaseSpecification specification) {
 
        List<Customer> customers = new ArrayList<>();
        if(! (specification instanceof CustomerSpecification)) {return customers;
        }
        if (CollectionUtils.isEmpty(specification.getIds())) {
            return customers;
        }
        specification.getIds().forEach(id -> {
            if (ofId(id) != null) {
                customers.add(ofId(id));
            }
        });
        returncustomers; }}Copy the code


Mybatis is used in everyday projects, so the DAO of MyBatis is used in Repository. Here is a complex scenario involving ordering.


5. Practice: strategic DDD reconstruction of a franchise business

Let’s take a franchise business to describe the division of boundary context. The business process in the figure below should be relatively clear, but it involves some terms. Therefore, important terms should be clearly defined first to reduce the cognitive difference of everyone.

General terms:

  • Incoming: A term used in finance to refer to the documents that are prepared and submitted to the system of the loan company or bank, called incoming, after which the bank or loan company will begin to review the loan.

  • Charter merchant: A financial term used to refer to the various units of banks, other financial institutions, and finance companies whose credit cards are accepted in circulation as a means of payment and which are willing to service them. In short, it refers to the merchant who has signed an agreement with the bank to accept the card business and agrees to use the bank card for business settlement.


In version 1.0 of the figure above, the card, input and settlement rules all cross the problem domain, so we abstract the “payment” and “contracted merchant” contexts, as shown below.

Some people here may have questions about the relationship between “specially engaged merchants” and “merchants”, and whether the “specially engaged merchants” should be classified as “merchant domain”. This is just a literal similarity. “Specially engaged merchants” is the payment related business formed after the approval of the entry. Of course the Merchant domain will use the capabilities of the franchised merchant.

Because the input logic is complex, we draw such a context with the input as the center. On the other hand, from the perspective of state flow, “bank entry” is an important node, representing some rights and interests of the platform and merchants, so it is necessary to take this as the core.

With the development of takeout group purchase business, we need a performance installation domain with more capabilities in the field, which can carry out community distribution and after-sales maintenance. It will inevitably have a relationship with orders, invoices, inventory, after sales, etc., so the following context is constructed around the order.

Six, the concluding

Due to the length and variety of content, the content related to the domain layer will continue to be explained in the following articles.

This article focuses on the strategic DDD principle, which is relatively abstract, but it is the most internal and most important part of the process.

Again, practicing DDD is not about trying to rewrite your system based on a web code structure, which is bound to fail. It is recommended that you think about your system according to the principles and methods described in this article. When you understand the essence of the system, you will be able to “laugh at the code” and master the internal work mind method to solve the core complexity of software.

For more content, please pay attention to vivo Internet technology wechat public account

Note: To reprint the article, please contact our wechat account: LABs2020.