DDD as Code: How to interpret domain-driven design with code?

There are many articles about DDD on the Internet, which is of course a good thing. Everyone wants to master good design methods to solve problems in software development. But there are also some problems. If you just open a few DDD articles on the Internet, although every author says that he is architected according to the DDD idea, careful students will find the structure of each author’s DDD article The description and drawing of the architecture diagrams are very different, you will be very surprised, are these all DDD designs? In fact, there is a problem here, that is, when describing some abstract concepts through words and icons, there will be a big difference. Don't use the concept of blind people to touch the elephant as an analogy. This is not appropriate. Even if two students are very familiar with DDD and have practiced multiple projects for several years, the things they wrote are still different. I started Java a little earlier. Of course, you can say that I am old and conservative. I remember that there were not so many middlewares at the beginning, and they were developed based on the Struts 1.x MVC framework. The design documents written by different students are also very different. Such a simple MVC architecture can have different architecture design documents, and DDD is relatively more abstract and more difficult to understand, so the architecture design documents are not the same in length, which is understandable.

So do we have to accept the fact that "the interpretation of DDD by each author does not have to be the same", and that "DDD design documents can be presented in different forms"? If this is the case, then students who want to learn DDD will have a very heavy burden. Which design expression method is better, is easier to understand, and how do I know that the DDD I learn is relatively orthodox and has not been Others are crooked. I'm not saying that it's not possible to think through playfulness, but from the perspective of evangelism, it is still necessary to respect theoretical facts.

We all know that the code can reflect the real situation when expressing some business or logic. Even if it is written by different developers, taking into account the compliance with Design Pattern, naming conventions, development language constraints, etc., the code is generally the same, and it is convenient Understand, it would be better if there are unit tests and code reviews. This is also when some documents are not perfect, many students choose to read the code, and some students say, "Look at the code directly, don't look at their PPT and documents, you will be misled, otherwise you will not know how to die." In addition, we all know that a very good practice now is Everything as Code, such as Terraform for Infrastructure as Code, Kubernetes YAML for Platform as code, PlantUML for Diagram as Code, etc., then can we use the concept of DDD as Code , Let our design be more unified, more convenient to express design ideas, and easier to be understood by others.

DDD DSL

Using DSL is to express DDD in code. This has been around for a long time, but it is more inclined to the Tactic Design and code level of  DDD , such as Sculptor[1] and http://fuin.org DDD DSL[2 ], everyone generally believes that it is a DDD code generator based on Xtext. It takes so much effort to learn, just to generate some code, and it is just Java code, so the general attention is not high.

Can we put DDD DSL in addition to the code generation part, and more inclined to strategic design (Strategic Design), highlighting the design ideas, then DDD as Code is much more comprehensive. Next we will introduce the ContextMapper framework.

Explain the terms: Many students have some doubts about the distinction between DDD's strategic design (Strategic Design) and tactics (Tactic Design). DDD has a special introduction, as follows:

  • Tactic DDD: Entity, Value Object; Aggregate, Root Entity, Service, Domain Event; Factory, Repository.
  • 战略设计(Strategic DDD):Bounded Context, Context Map; Published Language, Shared Kernel, Open Host Service, Customer-Supplier, Conformist, Anti Corruption Layer (context relationship types)。

In fact, it is relatively simple. The strategic design is larger and more macroscopic. You can understand it as the business and technical directions discussed by the company's senior management, the division of labor and cooperation of various teams or products; while the tactical design is relatively small, mainly concentrated in a BoundedContext Internally, such as how to design the Entity, Service, Repository, etc. of DDD, plus the technical selection of possible application development, it can be said that more attention is paid to the technical level.

Introduction to ContextMapper framework

ContextMapper is an open source project[3], mainly to provide DSL support for DDD design, such as DDD strategy (Strategic) design, Context mapping (Context Mapping), BoundedContext modeling and service decoupling (Service Decomposition), then we Take a look at how to complete a project based on the expression of DDD DSL based on ContextMapper.

When introducing ContextMapper, let's first explain the background of the project. Ruhua is an architect and is very familiar with DDD, and has practiced DDD on several projects. Recently, he joined the membership line and is responsible for completing the transformation of the membership system to better match the company's microservice design ideas. There are three applications before the membership line: a large number of REST API services provided by the member center; member registration and login applications; the member center, which handles the modification of personal passwords, basic information, SNS third-party binding and payment method binding after member login Wait.

After joining the member team, Ruhua communicated with everyone based on the architecture ideas based on DDD + MicroServices, and everyone agreed, but how to implement the specific architecture design and documentation is difficult for everyone. Let us look at the most typical DDD design diagram:

The concepts in it, such as SubDomain, BoundedContext, Entity, ValueObject, Service, Repository, Domain Event, and Context Mapping, are all right, but how to express this idea to others? You can't always paste the DDD design drawings and layered drawings every time, and then say that I design according to DDD.

Start from SubDomain

Ruhua started the first step of DDD, which is the division of Subdomain. Of course, DDD includes three types of SubDomains, namely Generic, Supporting, and Core. Here is a little explanation of the differences between these:

  • Generic Domain: Generic Domain is usually considered to be a problem that has been solved by the industry, such as the observability of Logging, Metrics and Tracing in architecture design, various cloud services (Cloud Service), etc., these have been relatively good Realize the plan, just connect. Of course, there are also business, such as mature industry solutions, such as ERP, CRM, mature hardware systems, etc., you can buy them.
  • Supporting Domain: Similar to the general domain, but the system is more internal or requires some custom development on a general basis. For example, an e-commerce system, membership, merchandise, order, logistics and other business systems, and of course some internally developed technical support systems.
  • Core Domain: This is what we often call the business core. Of course, if it is a technical product, it is the technical core. This is what you need to pay most attention to.

The overall relationship between these three is as follows: Core is the most distinctive and consumes more energy. In the complexity Y dimension, we must avoid high-complexity general purpose and support domains, which will distract your attention and also invest in A lot of energy, if you really need it, the way to buy the service may be the best.

Image source: https://github.com/ddd-crew/ddd-starter-modelling-process

Ruhua first divides members into several Sub Domains, such as processing account-related Account, processing UserTag for member marking, Processing PaymentProfile for processing payment methods, processing SnsProfile for social platform integration, and one other Profiles. Here we don’t involve Generic. The planning of supporting Doman and Supporting Doman mainly starts from the core domain of the business. A student used PPT to explain the division structure and starting point, as follows:

However, some students said whether the UML Component diagram is better, and it is convenient to be unified with the UML diagram behind, as follows:

Of course, there are many other graphical tools such as Visio for displaying structure diagrams. The first step of DDD: the division and presentation of SubDomain, there are different ways of understanding, there are many differences in how to describe and how to display it graphically.

Back to the starting point of the question, we want to divide the SubDomain, so is the following DSL code also possible:

Domain User {
    domainVisionStatement = "User domain to manage account, tags, profiles and payment profile."
    Subdomain AccountDomain {
       type = CORE_DOMAIN
       domainVisionStatement = "Account domain to save sensitive data and authentication"
    }
    Subdomain UserTagDomain {
       type = GENERIC_SUBDOMAIN
       domainVisionStatement = "UserTag domain manage user's KV and Boolean tag"
    }
    Subdomain PaymentProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User payment profile domain to manage credit/debit card, Alipay payment information"
    }
    Subdomain SnsProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User Sns profile domain to manage user Sns profile for Weibo, Wechat, Facebook and Twitter."
    }
    Subdomain ProfilesDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User profiles domain to manage user basic profile, interest profile etc"
    }
}

Although we do not yet know the corresponding DSL code syntax, we already know the domain name, domain type, and domain vision statement (visionStatement). As for how to display the system domain, such as tables, graphics, etc., this can be considered Display based on current data. Among them, the UserTagDomain type is GENERIC_SUBDOMAIN, which means that the marking is a universal domain. For example, we can cooperate with the product, picture or video team in the later stage, and everyone can build a marking system together.

Note: Subdomain not only simply includes type and domainVisionStatement, but you can also add Entity and Service. Its purpose is to highlight the core features and facilitate your understanding of the Domain. For example, add resetPassword and authBySmsCode to Account. I believe most people know this. What does it mean. But be careful not to add other objects to the Subdomain, such as VO, Repository, Domain Event, etc. These are all auxiliary development and should be used in BoundedContext.

Subdomain AccountDomain {
       type = CORE_DOMAIN
       domainVisionStatement = "Account domain to save sensitive data and authentication"
       Entity Account {
         long id
         String nick
         String mobile
         String ^email
         String name
         String salt
         String passwd
         int status
         Date createdAt
         Date updatedAt
       }
      Service AccountService {
          void updatePassword(long accountId, String oldPassword, String newPassword);
          void resetPassword(long acountId);
          boolean authByEmail(String email, String password);
          boolean authBySmsCode(String mobile, String code);
      }
    }

Context Map

ContextMap mainly describes the relationship between each BoundedContext in each Domain. You can understand it as the topology map of BoundedContext. We will not introduce BoundedContext in detail here. You only need to understand it as the carrier that implements the Domain, such as the HSF service application you write, a web application or mobile app that processes customer requests, or an external SaaS system that you rent. For example, you have a blog SubDomain in your system, you can develop it yourself, you can also set up a WordPress, or use Medium to implement Blog. Back to the microservice scenario, how to divide the microservice application? SubDomain corresponds to a business or virtual domain, while BoundedContext is a microservice application that specifically supports SubDomain. Of course, one SubDomain may correspond to multiple microservice applications.

Since each BoundedContext relationship is described, it will inevitably involve the association relationship, such as Partnership ([P]<->[P]) recommended by DDD, Shared Kernel ([SK]<->[SK]), Customer/Supplier([ C]<-[S]), Conformist(D,CF]<-[U,OHS,PL]), Open Host Service, Anticorruption Layer([D,ACL]<-[U,OHS,PL]), Published Language, etc., you can refer to DDD books for detailed introduction. These corresponding relations have corresponding abbreviations, which are the expression methods in parentheses. Here is an explanatory diagram of the association relationship Cheat Sheet:

Image source: https://github.com/ddd-crew/context-mapping

If you draw a picture yourself to express these relationships, there must be a lot of work, as detailed as the arrow type, remarks, etc., otherwise it will lead to misunderstandings. Here we directly go to the description of ContextMap by ContextMapper DSL, the code is as follows:

ContextMap UserContextMap {
   type = SYSTEM_LANDSCAPE
   state = TO_BE
   contains AccountContext
   contains UserTagContext
   contains PaymentProfileContext
   contains SnsProfileContext
   contains ProfilesContext
   contains UserLoginContext
   contains UserRegistrationContext
    UserLoginContext [D]<-[U] AccountContext {
        implementationTechnology = "RSocket"
        exposedAggregates = AccountFacadeAggregate
    }
    ProfilesContext [D]<-[U] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] SnsProfileContext {
        implementationTechnology = "RSocket"
    }
}

You can see the names of each BoundedContext contained in the Map, and then describe the relationship between them. In the description of the association relationship, the corresponding description is involved. Earlier we explained that BoundedContext is the bearer of the specific system and application of the Domain, so the corresponding technical implementation is involved. Such as HTTP REST API, RPC, Pub/Sub, etc., if the blog system is Medium, then implementationTechnology = "REST API". There are also exposedAggregates, which represent the exposed aggregate information, such as class objects and fields, service interfaces, etc., to facilitate the communication between the two parties. We will introduce this in BoundedContext.

BoundedContext

In ContextMap, we describe the relationship between them, and then we have to define the BoundedContext in detail. I believe most students know the content contained in BoundedContext, such as Entity, ValueObject, Aggregate, Service, Repository, DomainEvent, etc. Everyone should be familiar with this. Here we give a ContextMapper code for BoundedContext, as follows:

BoundedContext AccountContext implements AccountDomain {
    type = APPLICATION
    domainVisionStatement = "Managing account basic data"
    implementationTechnology = "Kotlin, Spring Boot, MySQL, Memcached"
        responsibilities = "Account", "Authentication"
    Aggregate AccountFacadeAggregate {
       ValueObject AccountDTO {
          long id
          String nick
          String name
          int status
          Date createdAt
          def toJson();
       }
       /* AccountFacade as Application Service */
       Service AccountFacade {
          @AccountDTO findById(Integer id);
       }
    }
    Aggregate Accounts {
         Entity Account {
            long id
            String nick
            String mobile
            String ^email
            String name
            String salt
            String passwd
            int status
            Date createdAt
            Date updatedAt
         }
   }
}

Here is another explanation of BoundedContext:

  • The name of the BoundedContext, needless to say, this is the same as the name in the ContextMap.
  • implements AccountDomain: Indicates which SubDomain is to be implemented. We all know that a Subdomain may contain multiple BoundedContexts. These BoundedContexts work together to complete the business requirements of the Subdomain. ContextMap also provides refines to indicate that BoundedContext needs to implement some user cases, and the official documents have corresponding instructions.
  • The attribute field of BoundedContext: type represents the type, such as APPLICATION, SYSTEM, etc. domainVisionStatement describes the responsibilities of BoundedContext. ImplementationTechnology represents specific technology. We mentioned earlier that BoundedContext has involved specific applications and systems, so we need to explain the implementation of the corresponding technical solutions, and just describe the core part. responsibilities represents the list of responsibilities of the BoundedContext, and only keywords are needed here, such as Account is responsible for security verification and so on.
  • AccountFacadeAggregate: Represents the aggregation provided to external calls, where the definition of the object of the DTO, the definition of the service interface, etc.
  • Aggregate Accounts: This represents the internal aggregation of BoundedContext, such as entity, value object, service, etc. Here to explain, the Aggregate in DDD is an aggregate object of entity and value objects, and the Aggregate in ContextMapper represents a collection of some resources, such as a Service collection.

For more information about BoundedContext, you can refer to the sculptor document [4], according to the actual situation, you can add corresponding parts, such as DomainEvent, Repository, etc.

Personally, I feel that BoundedContext has not yet involved Ubiquitous Language, it still needs the corresponding auxiliary design documents, and needs to explain the relevant project background, technical decisions and so on. Personally, I recommend "Visualise, document and explore your software architecture" [5] written by the author of the C4 architecture design, which is very practical. As a DDD architecture design document, there is no problem at all.

At the beginning of the article, we said that the previous DDD DSL is more of a code generator. If it is a code generator, then the generated code must have corresponding specifications and structures, such as entity, value object, service, and the directory saved by the repository. , The generated code may also include a certain Annotation or interface, standard fields and so on. Of course, we will not discuss the issue of code generators here, but we hope that everyone’s DDD architecture design still adopts a certain standardized directory structure. Here are a few standards for everyone to recommend:

  • ddd-4-java: Base classes for DDD with Java[6]
  • jDDD:Libraries to help developers express DDD building blocks in Java code[7]
  • ddd-base: DDD base package for java[8]

In fact, the starting points of these three are the same, that is, to describe DDD at the code level. The core is some annotations, interfaces, base classes, and of course the recommended package structure.

Other features of ContextMapper

Speaking of this, in fact, DDD as a whole, we have explained clearly: Domain division, BoundedContext topology diagram and association relationship of the overall Domain, BoundedContext specific definition and architecture design document specification. But ContextMapper also provides the DSL corresponding to UserStory and UseCase, let's take a look.

UserStory

Many students asked how to write UserStory. With this DSL, students no longer have to worry about how to write UserStory. This DSL is relatively clear and mainly has three elements: as "aaa", I hope to be able to "xxx", I hope to be able to "yyyy" so that "zzz" is also in line with the typical three elements of UserStory: role, activity and commercial value.

UserStory Customers {
    As a "Login User"
        I want to update a "Avatar"
        I want to update an "Address"
    so that "I can manage the personal data."
}

UseCase

Use Case is a way to describe requirements. UML diagrams have corresponding UseCase diagrams. The core is actor, interactive actions and commercial value. The corresponding DSL code is as follows:

UseCase UC1_Example {
  actor = "Insurance Employee"
  interactions = create a "Customer", update a "Customer", "offer" a "Contract"
  benefit = "I am able to manage the customers data and offer them insurance contracts."
}

In Aggregate, you can set the useCases property to describe the corresponding UseCase, as follows:

Aggregate Contract {
  useCases = UC1_Example, UC2_Example
}

Benefits of ContextMapper

According to your statement, we use DSL code to describe DDD. What benefits does this have?

Architecture design standardization

This code method is clear at a glance and very standardized. If your code is wrong, there will be any problems, of course, the compilation fails, and the IDE will help you correct it. So DDD DSL is the same, completely unambiguous. Currently, ContextMapper DSL includes Eclipse and VS Code plug-ins. IntelliJ IDEA can help you write cml files by customizing File Types and Live templates.

Generators support

Earlier we talked about the DDD DSL support code generator, which can help you generate code. I believe everyone can understand this, because the DDD DSL code is standard. Generate other forms of code based on this Code Model, of course this is fine.

In addition, ContextMapper also supports the generation of other models, such as graphical display of ContextMap and the structure diagram of PlantUML. The corresponding code is here [9]. Let me give you some screenshots:

Of course, ContextMapper also provides a general generator, which is based on the DDD DSL model, plus Freemarker templates, and then you can generate all kinds of output you want, such as generating JHipster Domain Language (JDL) for quickly creating file scaffolding. . I believe that many Java programmers are familiar with this. We use Freemarker to generate HTML when we develop web applications. For more details visit here [10].

The actual DDD design process

We have DDD DSL to describe our architecture design, is it comprehensive and sufficient, and development is not a problem? Not yet, we know that before software architecture design and coding, there are demand research, customer visits, domain expert communication, demand analysis, seminars, etc. This is still indispensable in real life, and its purpose is for follow-up Architecture design provides materials and pave the way. So how to integrate DDD with these preliminary operations? In fact, DDD has content related to this aspect, such as EventStorming cards:

Bounded Context Canvas card:

If you pay attention to the use of these DDD cards in the requirements analysis stage, then the subsequent DDD design will have better materials, of course, UserStory and Use Case, etc.

Personal suggestion: If you have time, I strongly recommend to pay attention to ddd-crew[11] for a very comprehensive latest and practical knowledge and practice related to DDD.

The relationship between DDD and MicroServices

It has nothing to do with DDD DSL, just a little mention. The microservice architecture design lies in how to divide a complex business system into closely cooperating microservice applications. The basis for division is very important. SubDomain divides the business boundary from the perspective of business, while BoundedContext focuses on the application bearer corresponding to the business domain. The Generic BoundedContext can support multiple SubDomains at the same time, enabling application reuse of different business systems. If in the Cloud Native scenario, we hope to use System type BoundedContext more, that is, reuse the system on the cloud, thereby reducing our own development and maintenance costs. Back to the BoundedContext of Appplication type, this is the application you want to develop specifically, which microservice framework you choose, you can decide for yourself. Throughout the process, DDD plays the role of the theoretical basis for application division.

But there is another problem here, which is the communication problem between microservices. You can repeatedly emphasize that we need to build powerful distributed applications, but what is the recommended technology stack? How to do it And we need to do better. This is not clearly stated, so everyone chooses a mixed communication technology stack such as REST API, gRPC, RPC, Pub/Sub, etc.

DDD has been given about the association relationship between BoundedContext (partner ship, c/s, share kernel, etc.), but specific to communication and collaboration, it does not give a good theoretical basis, but there is also some consensus in the DDD community , Is based on asynchronous message communication + event-driven is a better solution, so you see DDD chief evangelist Vaughn Vernon repeatedly talked about DDD + Reactive, it is to solve the communication problem of ContextMapping.

Having said that, if you see that ContextMapper supports the output of MDSL (Micro-)Service Contracts Generator, then it is not surprising, and it is a matter of course.

For more information about the relationship between MicroServices and DDD, you can refer to "Microservices love Domain Driven Design, why and how?"[12]

to sum up

The DSL concept proposed by ContextMapper is still very good. At least it allows everyone to have less ambiguity in the understanding of DDD, and at the same time it is also standardized. The threshold for DDD beginners is also lowered. Although it cannot reach the point of architecture design, at least it is barrier-free to read and understand. At the time of writing this article, ContextMapper DSL version 5.15.0 has been released, all related features have been developed, and it is still very smooth to use. Of course, in actual development, whether DDD as Code is effective or not, I also hope that students who do DDD practice will give valuable opinions.

Of course, my article cannot explain ContextMapper very clearly. There are very detailed documents and corresponding related papers on contextmapper[13]. Of course, you don’t need to use the DSL set of ideas, but these ideas and related materials are important to DDD. The design is still very helpful.

In addition, I personally feel that if you are a beginner in DDD, then ContextMapper may be more suitable. DDD is a methodology. Those books are boring to death. It is almost very difficult to watch two chapters without getting sleepy. On the contrary, if you learn DDD DSL, it is much simpler. No matter how complicated this DSL is, it will not be more complicated than the programming language you learn, right? On the contrary, this DSL is very simple. By learning the simple DDD DSL, you will quickly grasp the concepts, ideas and methods. If it doesn’t work, just take a look at other people’s codes (DDD DSL examples), which will also help you learn quickly. It is also very good to master these methodologies and use books and articles to consolidate them later.

Original link

This article is the original content of Alibaba Cloud and may not be reproduced without permission.

Guess you like

Origin blog.csdn.net/weixin_43970890/article/details/114686527