DDD as Code: How to Interpret Domain-Driven Design with Code?

There are a lot of 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 open several DDD articles on the Internet, although each author says that he designed the architecture according to the DDD idea, careful students will find the structure of each author's DDD article. The descriptions and architectural diagrams are all very different. You will be very surprised. Are these all DDD designs? There is actually a problem here, that is, when describing some abstract concepts through words and diagrams, there will be great differences. Don't make an analogy with the concept of a blind man touching an elephant. Even if the two classmates know DDD very well and have practiced multiple projects for several years, the things they write 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 middleware at the beginning, and it was developed based on the MVC framework of Struts 1.x. 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 length of the architecture design documents is not the same, which is understandable.

So are we to accept the fact that "the interpretation of DDD by authors does not have to be the same", "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 is better and easier to understand. At the same time, how do I know that the DDD I have learned is relatively orthodox and has not been Others are crooked. I'm not saying that playful thinking isn't okay, but from a preaching perspective, respect for theoretical facts is still required.

We all know that when the code expresses some business or logic, it can reflect the real situation very well. Even if it is written by different developers, considering the design pattern, naming convention, development language constraints, etc., the code is generally the same, or it is convenient Understandably, it would be better if there were unit tests and code reviews. This is also when some documents are incomplete, 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 is Everything as Code, such as Terraform of Infrastructure as Code, Kubernetes YAML of Platform as code, PlantUML of Diagram as Code, etc., then can we use the concept of DDD as Code , make our design 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 tactical design and code level of DDD, such as Sculptor[1] and http://fuin.org DDD  DSL[2 ] , it is generally believed that it is a DDD code generator based on Xtext. It takes a lot of effort to learn so much, just to generate some code, and it's just Java code, so the general attention is not much.

Can we make DDD DSL more inclined to strategic design (Strategic Design) in addition to the code generation part, and highlight the idea of ​​design, then DDD as Code will be more comprehensive. Next, we will introduce the framework of ContextMapper.

Explanation of terms: Many students have some doubts about the distinction between DDD's strategic design and tactical design. DDD has a special introduction, as follows:

  • Tactical 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, and the strategic design is larger and more macroscopic. You can understand it as the business and technical direction discussed by the company's top management, the division of labor and cooperation between each team or product; while the tactical design is relatively small, mainly focused on a BoundedContext Internally, such as how to design DDD Entity, Service, Repository, etc., plus the technical selection of possible application development, it can be said that it pays more attention to the technical level.

Introduction to the ContextMapper framework

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

When introducing ContextMapper, let's first explain the project background. Ruhua is an architect who is very familiar with DDD and has practiced DDD in 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. Before the membership line, there are three applications: a large number of REST API services provided by the member center; member registration and login applications; member center, which handles the modification of personal passwords, basic information, SNS third-party binding and payment method binding after member login. Wait.

After Ruhua joined the membership team, I communicated with everyone about the architecture idea based on DDD + MicroServices, and everyone agreed, but how to implement the specific architecture design and documentation, everyone was in trouble. Let's take a 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 no problem, but how to express this idea to others? I can't always paste the DDD design diagram and layered diagram every time, and then say that I designed it according to DDD.

Start with SubDomain

Ruhua started the first step of DDD, that 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 them:

  • Generic Domain: Generic Domain is usually considered to be a problem that has been solved by the industry, such as Logging, Metrics and Tracing of observability in architecture design, various cloud services (Cloud Service), etc., which have been better To implement the plan, you can connect. Of course, there are also in business, such as mature industry solutions, such as ERP, CRM, mature hardware systems, etc., you can buy it.
  • Supporting Domain: Similar to general Domain, but the system is more internal or needs some custom development on a general basis. For example, an e-commerce system, membership, commodities, orders, 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 should pay attention to most.

The overall relationship between the three is as follows: Core is the most distinctive and spends more energy. In the Y dimension of complexity, we need to avoid high-complexity general-purpose and supporting Domains, which will distract your attention, and at the same time invest in A very large amount of energy, and if it is really needed, the way to buy the service is probably the best.

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

Ruhua first divides members into several Sub Domains, such as Account for account-related accounts, UserTag for member marking, PaymentProfile for payment methods, SnsProfile for social platform integration, and one other Profiles. We do not involve Generic here. The planning of Supporting Doman mainly starts from the business core Domain. A student used PPT to explain the division structure and starting point, as follows:

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

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

Back to the starting point of the problem, we just 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 don't know the corresponding DSL code syntax yet, we already know the domain name, domain type, and the domain's vision statement. As for how to display the system domain in the later stage, such as tables, graphics, etc., this can be considered Based on current data. The UserTagDomain type is GENERIC_SUBDOMAIN, which indicates that the marking is a universal Domain. For example, we can cooperate with the product, picture or video team in the later stage, and we can build a marking system together.

Note: Subdomain not only includes type and domainVisionStatement, but also you can add Entity and Service. The purpose is to highlight core features and facilitate your understanding of Domain, such as adding resetPassword and authBySmsCode to Account. I believe most people know this is what does it mean. But be careful not to add other objects to Subdomain, such as VO, Repository, Domain Event, etc. These are 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 a carrier for implementing Domain, such as an HSF service application you write, a web application or mobile app that handles customer requests, or an external SaaS system you rent. For example, there is a SubDomain of blog 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 business or virtual fields, while BoundedContext is a microservice application that specifically supports SubDomain. Of course, one SubDomain may correspond to multiple microservice applications.

Since it is describing each BoundedContext relationship, it will inevitably involve association relationships, such as DDD recommended Partnership([P]<->[P]), 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. For a detailed introduction, you can refer to DDD books. These correspondences have corresponding abbreviations, which are the expressions in parentheses. Here is an illustration of the association relationship Cheat Sheet:

Source: https://github.com/ddd-crew/context-mapping

If you draw your own diagrams to express these relationships, there must be a lot of work, including arrow types, remarks, etc., otherwise it will lead to misunderstandings. Here we go directly 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 it involves the corresponding technical implementation. 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 exposed aggregated information, such as class objects and fields, service interfaces, etc., to facilitate the docking of both parties. We will introduce this in BoundedContext.

BoundedContext

In the ContextMap, we describe the association between them, and then we need 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., which everyone should be familiar with. Here we give a ContextMapper to BoundedContext code, 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 for BoundedContext:

  • The name of the BoundedContext, needless to say, this is the same as the name in the ContextMap.
  • implements AccountDomain: Indicates which SubDomain to implement. We all know that a Subdomain may contain multiple BoundedContexts, and 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 documentation has corresponding instructions.
  • Attribute field of BoundedContext: type indicates the type, such as APPLICATION, SYSTEM, etc. domainVisionStatement describes the responsibilities of the BoundedContext. implementationTechnology represents a specific technology. We mentioned earlier that BoundedContext has involved specific applications and systems, so to explain the implementation of the corresponding technical solution, just describe the core part. responsibilities represents the list of responsibilities of the BoundedContext, only keywords are needed here, for example, Account is responsible for security verification, etc.
  • AccountFacadeAggregate: Represents the aggregation provided to external calls, here the object definition of DTO, the definition of service interface, etc.
  • Aggregate Accounts: This represents the aggregation within the BoundedContext, such as entity, value object, service, etc. Let me explain here that the Aggregate in DDD is an aggregate object of entity and value objects, while the Aggregate in ContextMapper is represented as a collection of resources, such as a Service collection.

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

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

At the beginning of the article, we mentioned 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 the directory where entity, value object, service, and repository are saved. , the generated code may also include certain Annotation or interface, standard fields and so on. Of course, we do not discuss the issue of code generators here, but we hope that everyone's DDD architecture design should still adopt a certain standard directory structure. Here are a few standards to recommend to you:

  • 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 annotation, interface, base class, and of course, the recommended package structure.

Other features of ContextMapper

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

UserStory

Many students have 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 consists of 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 business 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. There is a corresponding UseCase diagram in the UML diagram. The core is actor, interactive action and business 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 an Aggregate aggregation, you can set the useCases property to describe the corresponding UseCase, as follows:

Aggregate Contract {
  useCases = UC1_Example, UC2_Example
}

Benefits from ContextMapper

According to you, we use DSL code to describe DDD, what is the benefit of this?

Standardized Architecture Design

This code method is clear at a glance and very standardized. If you write the code wrong, there will be any problem, of course, the compilation fails, and the IDE will correct it for you. So the 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 template.

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, and other forms of code are generated based on this Code Model, of course.

In addition, ContextMapper also supports other model generation, such as ContextMap graphical display, PlantUML structure diagram, the corresponding code is here [9]. Here are some screenshots for you:

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

Real-life DDD design process

We have DDD DSL to describe our architecture design. Is it comprehensive and sufficient, so we don't have to worry about development? Not yet, we know that before software architecture design and code writing, there are requirements research, customer visits, domain expert communication, requirements analysis, seminars, etc., which are still indispensable in real life, and its purpose is for follow-up. Architectural design provides materials and lays the groundwork. So how do you integrate DDD with these upfront operations? In fact, DDD has content related to this, 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.

Personal suggestion: If you have time, it is strongly recommended to pay attention to ddd-crew [11], which has a very comprehensive latest and practical knowledge and practice related to DDD.

The relationship between DDD and MicroServices

Nothing to do with the DDD DSL, just a little mention. The design of the microservice architecture lies in how to divide the complex business system into closely cooperative microservice applications, and the basis for the 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 type BoundedContext can support multiple SubDomains at the same time, and can achieve application reuse of different business systems. If in the Cloud Native scenario, we hope to use the BoundedContext of the System type more, that is, to reuse the system on the cloud, thereby reducing our own development and maintenance costs. Going back to the BoundedContext of the Appplication type, this is the application you want to develop specifically. You can decide which microservice framework you choose. In the whole process, DDD plays a theoretical basis for application division.

But there is another problem here, which is the communication 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 have to do better. This is not clearly stated, so everyone chooses mixed communication technology stacks such as REST API, gRPC, RPC, Pub/Sub, etc.

About the relationship between BoundedContext DDD has been given (partner ship, c/s, share kernel, etc.), but specific to communication and collaboration, and did not give a good theoretical basis, but this also has some consensus in the DDD community , that is, based on asynchronous message communication + event-driven is a better solution, so you see Vaughn Vernon, the chief evangelist of DDD, repeatedly talking about DDD + Reactive, which 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 about the relationship between MicroServices and DDD, you can refer to "Microservices love Domain Driven Design, why and how?"[12]

Summarize

The DSL concept proposed by ContextMapper is still very good, at least it makes everyone less ambiguous in the understanding of DDD, and it is standardized at the same time. The threshold for DDD beginners is also lowered. Although it cannot reach the level of architectural design, at least it is accessible to reading and understanding. When I wrote this article, the ContextMapper DSL version 5.15.0 has been released, and all related features have been developed, and it is very smooth to use. Of course, in actual development, whether DDD as Code is effective or not, I also hope that students who practice DDD will give valuable opinions.

Of course, my article can't explain ContextMapper very clearly. There are very detailed documents and corresponding related papers on contextmapper [13]. Of course, you can not use the DSL idea, but these ideas and related materials are very important to DDD. Design still helps a lot.

Another person feels that if you are a beginner of DDD, then ContextMapper may be more suitable. DDD is a methodology. Those books are boring to death, and it is almost very difficult to read two chapters without getting stuck. On the contrary, if you learn DDD DSL, it will be much simpler. This DSL will not be more complicated than the programming language you are learning, right? On the contrary, this DSL is very simple. By learning a simple DDD DSL, you will quickly grasp the concepts, ideas and methods. If not, just look at other people's code (DDD DSL examples), which will also help you learn quickly. It is also very good to master these methodologies, and then use books and articles to consolidate them later.

Original link

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

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324142381&siteId=291194637