In 2004, when Eric Evans published his article "domain-driven design-the way to cope with software core complexity" (hereinafter referred to as "domain-driven design"), I was still in high school, it has been eight years since I came into contact with the domain-driven design (DDD. At that time, I was planning to go further on the road to software development. After my colleagues introduced me, I started to contact DDD.
I think most experienced program developers should have heard of DDD and tried to apply it to their own projects. I wonder if you have ever encountered such a scenario: You have created a resource library (repository), but after a while, you will find that this resource library is becoming more and more similar to the traditional Dao, are you right to reflect on your own implementations? Or, if you create an aggregation and find that the aggregation is so large, why does it reference so many objects? Is it wrong?
In fact, you are not alone. I believe most of my colleagues have encountered similar problems. Not long ago, one of my colleagues showed me the domain-driven design he had bought in 2007, which he had already compiled by Wei. He told me that after reading it several times, he still does not know how to put DDD into practice. Eric's book is good and undeniable, but we programmers always hope to see some practical examples to effectively implement DDD to guide our daily development.
So nearly 10 years after Eric's book was published, we had Vaughn Vernon's implementation field-driven design. As a translator of this book, I had the honor to read it and benefited a lot, the conclusion is that good software should be of DDD.
Just like an intellectual property (intellectual property) in the Microelectronics field, DDD integrates the core business functions of a software system into a core domain, it contains concepts such as entity, value object, domain service, resource library, and aggregation. On this basis, DDD puts forward a complete set of infrastructure to support such core fields. At this time, DDD is no longer as simple as "Object Oriented advanced", but evolved into a system engineering.
The so-called domain is the Business Development Method of an organization, and the business value is reflected in it. For a long time, our programmers have been good technical thinkers. We are always good at solving project problems from a technical perspective. However, whether a software system is truly available is reflected by the business value it provides. Therefore, instead of sticking to technologies that are never enough to learn every day, why not think about our focus in the direction of business value provided by software systems, this is exactly what DDD is trying to solve.
In DDD, code is the design itself. You no longer need design documents that are complex and never updated in real time. The coders and field experts no longer need translation to understand what the other party expresses.
Ddd is divided into strategic design and tactical design. Strategic Design mainly looks down on our software system from the top, helping us precisely divide fields and deal with the relationships between various fields; tactical design teaches us how to implement DDD in terms of technical implementation.
Ddd
Strategic Design
It should be pointed out that DDD is not a simple set of technical tools, but many programmers do think so and use DDD with such ideas. Too much technical implementation will lead to ddd-lite. Simply put, DDD-lite will lead to poor domain objects, because we ignore the benefits of DDD strategic modeling.
The Strategic Design of DDD mainly includes domain/subdomain, general language, restriction context and architectural style.
Domain/subdomain)
Since it is a domain-driven design, our main focus should be on how to design the domain model and how to divide the domain model.
The field is not a very advanced concept. For example, an insurance company's field contains insurance policies, claims and reinsurance; an e-commerce website contains the product catalog, orders, invoices, inventory, and logistics concepts. Here, I will mainly talk about the division of fields, that is, dividing a large field into several subdomains.
In daily development, we usually split a large software system into several subsystems. This Division may be based on architecture or infrastructure. But in DDD, we divide the system based on the domain, that is, the business.
So the question also comes: first, which concepts should be modeled in which subsystems? We may find that conceptual modeling in a domain is possible in subsystem A, and modeling in subsystem B seems reasonable. The second question is, how should subsystems be integrated? Some people may say that this is not as simple as the client calls the server? The problem is that the integration between two systems involves the translation of infrastructure and concepts of different fields between two systems. If you do not pay attention to them, these concepts will cause pollution to our well-developed Domain Models.
How can this problem be solved? The answer is: bounded context and context ing.
Bounded Context)
In a domain/subdomain, we create a conceptual domain boundary. In this boundary, any domain object only represents the exact meaning specific to the boundary. In this way, the boundary is called the restriction context. The restriction context and field have a one-to-one relationship.
For example, in a book, the concepts expressed in the publishing and sales stages are different. In the publishing stage, we mainly focus on the publication date, number of words, publishing houses and printing plants, and in the sales stage, we are mainly concerned with the concepts of price, logistics and invoice. What should we do? Put all these concepts in a single book object? This is not the practice of DDD. The context of the DDD finite field separates these two concepts.
Physically, a restriction context can eventually be a DLL (.. Net) file or jar (Java) file, or even all objects in a namespace (such as a Java package. However, technology itself should not be used to define the definition context.
By aggregating all concepts in a restricted context, including nouns, verbs, and adjectives, we create a general language for the restricted context. A common language is the language used by all members of a team to communicate with each other. business analysts, coders, and testers should communicate directly in a common language.
The integration problem between subdomains mentioned above is also the integration problem between restricted contexts. During integration, we mainly care about the relationship between Domain Models and integration methods. For example, to integrate with a rest resource, you need to provide infrastructure (such as resttemplate in spring), but these facilities are not part of your core domain model. What should you do? The answer is the anti-corrosion layer, which is responsible for dealing with external service providers and translating external concepts into concepts that can be understood in their core fields. Of course, the anti-corrosion layer is only one of the many integration methods between the limited context. In addition, there are also shared kernels and open host services. For details, see the original book "implement domain drive design. The integration relationship between restricted contexts can also be understood as the ing relationship between domain concepts in different contexts. Therefore, integration between restricted contexts is also called a context map.
Architecture)
Ddd does not require a specific architectural style because it is neutral to the architecture. You can use a traditional three-tier architecture, rest architecture, and event-driven architecture. However, in the Implementation field-driven design, the author prefers the event-driven architecture and the hexagonal (hexagonal) architecture.
At present, the interface-Oriented Programming and dependency injection principles are already revolutionizing the traditional layered architecture. Further, we will get a hexagonal architecture, also known as ports and adapters ). In a hexagonal architecture, the concept of hierarchy does not exist, and all components are equal. This is mainly because of the advantages of software abstraction, that is, the interaction between each component is completely completed through the interface, rather than the specific implementation details. As Robert C. Martin said:
Abstraction should not depend on details, but on abstraction.
There are many combinations of ports and adapters in a system with hexagonal architecture. A port represents the input and output of a software system, while an adapter accesses each port. For example, in a web application, the HTTP protocol can be used as a port, which provides HTML pages to users and accepts forms submitted by users. servlet (for Java) or the Controller in spring is the adapter corresponding to the HTTP protocol. For another example, to make data persistent, the database system can be regarded as a port, and the driver accessing the database is the database adapter. To add a new access method to the system, you only need to add a corresponding port and adapter for the access method.
Then, how does our domain model interact with ports and adapters?
As mentioned above, the true value of a software system is to provide business functions. We will divide all business functions into several business use cases, each business use case represents an atomic operation on the software system. Therefore, the software system should have such components. Their role is to expose the business functions of the system to the outside world in the unit of Business Use Cases. In DDD, such a component is called an application layer ).
After the application layer is available, the interaction between the software system and the external environment becomes the interaction between the adapter and the application layer, as shown in.
It can be seen that the domain model is at the core of the application. The interaction between the outside and the domain model is completed through the application layer, and the application layer is the direct customer of the domain model. However, the application layer should not contain business logic; otherwise, it will lead to the leakage of domain logic. Instead, it should be a thin layer, mainly playing a coordinating role, all it does is delegate business operations to our domain model. At the same time, if our business operations require transactions, the management of transactions should be placed at the application layer, because transactions are also measured in Business Use Cases.
Although the application layer is thin, it is very important because the domain logic of the software system is exposed through it. At this time, the application layer plays the role of the system facade (facade.
Ddd
Tactical Design
Strategic Design provides us with a high-level perspective to examine our software systems, while tactical design specifically and in detail focuses on implementation at the technical level, it is also the most practical place for our programmers.
Domain objects with full Behaviors
We hope that the domain object can accurately express the business intent, but most of the time we see the domain object full of getter and setter. At this time, the domain object is no longer the domain object, martin Fowler calls it the anemia object.
In the Java World, Java Bean norms have tempted programmers to create countless anemia objects in a "natural and reasonable" manner for many years, some frameworks also stipulate that the object must provide the getter and setter methods, such as early versions of hibernate. So what are the disadvantages of anemia objects? Let's look at an example: to modify the email address of a customer, when using the setter method:
public class Customer { private String email; public void setEmail(String email) { this.email = email; }}
Although the above Code can complete the "Modify email address" function, when you read this code, can you predict that there must be a "Modify email address" Service case in the system?
You may say that you can create a changecustomeremail () method in another service class, and then call the setemailaddress () method of customer in this method. Is the business intention clear? The problem is that the responsibility for modifying the email address should be placed on the customer instead of the service and customer. The fundamental object principle, such as information encapsulation, is the most basic literacy for implementing DDD.
It is not difficult to create domain objects with full behaviors. We need to change our mindset and regard domain objects as service providers rather than data containers. Think about what actions a domain object can provide, instead of data.
Functional programming, which has become popular in recent years, can also help us write business code with more business expressiveness. For example, C # and Java 8 both provide Lambda functions, it also includes most dynamic languages (such as Ruby and groovy ). Furthermore, we can use DSL to implement Domain Models.
I have imagined such a software system: its core functions are completely exposed to the outside by a DSL, and all business operations are carried out through this DSL, business rules in this field can be configured through a rule engine, so this DSL can be sold on the market like the Intellectual Property core mentioned above. At this time, our core domain is strictly encapsulated in this DSL, and no external pollution is allowed.
Entity vs value object (entity vs value object)
In a software system, entities represent things that have lifecycles and will change in their lifecycles, while value objects represent descriptive and replaceable concepts. The same concept is modeled as an entity in a software system, but may be a value object in another system. For example, in a currency transaction, we model it as a value object, because we spent 20 yuan to buy a book, and we only care about the number of currencies, instead of worrying about which 20 yuan bill is used, two 20 yuan bills can be exchanged. However, if the People's Bank of China needs to establish a system to manage all issued currencies and want to track each currency, then the currency becomes an entity, and has a unique identity ). In this system, even if both bills are 20 yuan, they still represent two different entities.
At the implementation level, the value object does not have a unique identifier. Its equals () method (for example, in Java) can be implemented using its descriptive attribute fields. However, for entities, the equals () method can only be implemented through a unique identifier, because even if the two entities have the same state, they are still different entities, it's like two people are named Zhang San, but they are two different individuals.
We found that most domain concepts can be modeled as value objects rather than entities. A value object is like a passthrough in a software system and has the "No matter after creation" feature. Therefore, we do not need to care about issues such as lifecycle and persistence as we care about entities.
Aggregate)
Aggregation may be the most difficult to understand concept in DDD. It is called aggregation because the objects contained in the aggregation are closely related and they are cohesive. For example, a car contains engine, wheel, tank, and other components. An aggregation can contain multiple entities and value objects. Therefore, aggregation is also called a root entity. Aggregation is the basic unit of persistence. It has a one-to-one correspondence with the resource library (see below.
Since aggregation can accommodate objects in other fields, How big should aggregation be designed? This is also one of the difficulties in designing aggregation. For example, in a blog system, a user can create Multiple blogs, and a blog can contain multiple posts ). During modeling, we normally include a set of blogs in the user object, and then each Blog contains a set of post. Do you really need to do this? If you need to modify the basic information of a user, all the blogs and posts need to be loaded when loading the user, which will cause high performance loss. It is true that we can solve the problem through delayed loading, but delayed loading is only a technical implementation. The underlying cause of the above problems is actually in our design. We found that users are mostly related to authentication and authorization, but they are not closely related to blogs, therefore, there is no need to maintain the set of blogs in the user. After separating the user and the blog, the blog and the user become an aggregation, which has its own resource library. The problem arises again: Since the user and the blog are separated, what should we do if we need to reference the user in the blog? Directly referencing another aggregation in one aggregation is not encouraged by DDD, but we can reference another aggregation by ID. For example, we can maintain an userid instance variable in the blog.
As the creator of the blog, the user can become the blog factory. Put it in DDD, and the function of creating a blog can only be completed by the user.
To sum up, we can use the following methods to create a blog:
public class BlogApplicatioinService { @Transactional public void createBlog(String blogName, String userId) { User user = userRepository.userById(userId); Blog blog = user.createBlog(blogName); blogRepository.save(blog); }}
In the preceding example, the business use case is completed through the blogapplicationservice application service. In the createblog () method of the use case, first obtain a user from the user's resource library, and then call the factory method createblog () in the user () method To create a blog, and then use blogrepository to persist the blog. The whole process constitutes a transaction, so the createblog () method is marked with @ transactional as the transaction boundary.
The first principle of using aggregation is that at most one aggregation state can be changed in a transaction. If a business operation involves changes to multiple aggregation states, you should notify the corresponding aggregation by publishing domain events (refer to the following. At this time, the data consistency changes from transaction consistency to final consistency (eventual consistency ).
Domain Service)
Have you ever encountered such a problem: it is inappropriate to model a domain concept, put it on an object, and put it on a value object, then you think about whether your modeling method is faulty. Congratulations! Congratulations, you have no problem with modeling techniques, but you have not yet reached the domain service concept, because domain services are originally used to deal with such scenarios. For example, to encrypt the password, we can create a passwordencryptservice to take care of it.
It is worth mentioning that domain services are different from the application services mentioned above. Domain services are part of the domain model, and application services are not. Application Service is a customer of domain services. It turns the domain model into a software system available to the outside world.
Domain services cannot be abused, because if we put too many domain logic on domain services, entities and value objects will become anemia objects.
Resource Library (repository)
The resource library is used to save and obtain aggregate objects. In this regard, the resource library has some similarities with Dao. However, there is a significant difference between the resource library and Dao. Dao is only a thin encapsulation of the database, and the resource library is more domain-specific. In addition, all entities can have corresponding Dao, but not all entities have a resource library. Only aggregation has a corresponding resource library.
There are two types of resource libraries: Collection-based and persistence-based. As the name suggests, a collection-based resource library has the characteristics of integrating programming languages. For example, in Java, we extract an element from a list. After modifying the element, we do not explicitly Save the element to the list. Therefore, the SAVE () method does not exist in the collection-oriented resource library. For example, for the above user, its resource library can be designed:
public interface CollectionOrientedUserRepository { public void add(User user); public User userById(String userId); public List allUsers(); public void remove(User user); }
For a persistent-oriented resource library, after the aggregation is modified, We need to explicitly call the Sava () method to update it to the resource library. It is still a user. The resource library is as follows:
public interface PersistenceOrientedUserRepository { public void save(User user); public User userById(String userId); public List<User> allUsers(); public void remove(User user); }
In the resource library implemented by the above two methods, although the add () method is changed to the SAVE () method, it is different in use. When using a collection-oriented resource library, the add () method is only used to add new aggregates to the resource library. In a persistence-oriented resource library, the SAVE () method is not only used to add new aggregates, it is also used to explicitly update existing aggregation.
Domain event)
Domain events have not been mentioned in Eric's domain-driven design. Domain events have been added to the DDD ecosystem in recent years.
In traditional software systems, data consistency is handled through transactions, including local transactions and global transactions. However, an important principle of DDD is that a transaction can only update one aggregation instance. However, there is indeed a need to modify multiple aggregate service cases. What should we do at this time?
In addition, in the recently popular microservice architecture, the entire system is divided into many lightweight program modules, data Consistency between them is not easily achieved through transaction consistency. What should we do now?
In DDD, domain events can be used to deal with the above problems. At this time, final consistency replaces transaction consistency and achieves data consistency between various components through domain events.
Domain events are named in the English format of "noun + Verb past word segmentation", indicating a previous event. For example, the ordersubmitted event is published after the buyer submits the product order, and the emailaddresschanged event is published after the user changes the email address.
It should be noted that since it is a domain event, they should be released from the domain model. The final receiver of a domain event can be a component in the context of the current restriction, or another restriction context.
The additional benefit of a domain event is that it can record all important changes that occur in the software system, which can well support program debugging and business intelligence. In addition, in the software system of cqrs architecture, domain events are also used for data synchronization between write and read models. Further development, the event-driven architecture can evolve into event sourcing, that is, the acquisition of aggregation is not achieved by loading the instantaneous state in the database, instead, it is done by replaying all domain events that occur in the aggregation lifecycle.
Summary
Ddd is divided into strategic design and tactical design. over-emphasizing the technical nature of DDD will lead us to miss the benefits of strategic design. Therefore, when implementing DDD, we should also put strategic design in an important position. Strategic Design helps us observe and review the software system from a macro perspective. The restriction context and context ing graph help us to divide each subdomain (system) correctly ). The tactical Design of DDD focuses more on technical implementation. It provides us with a complete set of technical tools, including entities, value objects, domain services and resource libraries. Although the concept of DDD has been put forward for nearly 10 years, we still have a long way to go on how to implement it.