Practice of domain-driven Design (DDD) in iQiyi reward business

· High availability architecture

Domain-driven Design (DDD) started more than 30 years ago, 17 years ago, when Eirc Evans defined the concept of domain-driven Design. DDD has been providing software design methodologies for software engineers in traditional industries, but it is rarely used in the Internet industry. Until the last few years, DDD was rerecognized in the Internet industry and became popular. There are two reasons for this:

  • The business of Internet industry is more and more complex, and it faces the same problem of traditional industry software.
  • The popularity of microservices has led DDD to solve the microservice unassembly problem.

This article mainly explains the first point, “the solution to software complexity”.

The value of

Before we go into more detail, let’s talk about the value that DDD brings to the tipping business.

The efficiency of the member business department has been significantly improved after DDD practice in tipping business:

  • New demand access development cost savings of 20%;
  • Replace the underlying middleware development cost savings of 20%;
  • Familiar with project cost savings of 30% (basic understanding of DDD is prerequisite);
  • Exponential reduction in single-test development costs;
  • On-line risk and cost reduction.

With the background to DDD’s popularity and business value in mind, let’s take a look at what DDD is, what its benefits are, how it works in projects, and a few key questions.

What is domain-driven design

Before we discuss what domain-driven design is, let’s look at the following code:

This is a definition of tipping interface. There is no problem with this interface alone. Users choose stars and props to tip based on activities.

The business logic is fine, but we’ll find some bad code smells.

  • Method parameter names are lost after code is compiled.

In the code if the star and props of the parameters of the wrong order can be compiled, only run time will report business errors. Of course, this kind of problem detection cost is not very high, if it is discovered during compilation, it will avoid some risks and reduce the troubleshooting cost. The codes in the above method parameters can uniquely identify the entity, but lose the actual business domain meaning of the original entity to reward a certain item for a certain star.

The rewards logic is mixed with a lot of pre-validation logic unrelated to the core logic of the business, which affects the readability of the code. All the logic is stacked in one method, increasing the complexity of test case writing.

For the above code, the root cause of the problem is that we do not have a clear division of the business domain, but only realize an operation process, which is the direct realization of requirements, and lacks domain abstraction. The parameter definition of the method lacks the meaning of business domain. The essence of activity verification is some attribute judgment of activity, and the validity of activity is determined by the attribute of activity itself. You can abstract the activity validation class ActivityValidate, add validate methods to the entity, or go one step further and place the validation logic directly in the activity constructor, both for validation purposes and to avoid missing validation. For single-test writing, if we can split a large piece of logic into multiple logical units, we will undoubtedly reduce the number of use cases. The optimized code looks like this:

For activity validation, constructor validation is used, so there is no need to validate the activity in the reward method. Active validations are placed in the pre-constructor, reducing the number of test cases.

Back to the original question, what is domain-driven design?

Domain-driven design is based on domain modeling rather than data modeling

In the example above, the activity entity had only basic properties and get/set methods, known as the “blood loss model,” before the code was refactored. As a result, the activity domain object was reduced to a data object and used only as a CRUD for the ORM component. The blood loss model was everywhere in the project code. The reason is directly related to the popularity of object-relational mapping (ORM, such as Hibernate) persistence mechanisms, which use ORM to map each class to a data table and cruD through entity objects. Over time, entity has become a special term of ORM framework, namely the loss of domain capability. When designing a project, we should think in terms of the business domain, not the database, as we will discuss in detail in the strategic Design section.

2, meet the hexagonal architecture design

The Hexagonal architecture is described in more detail later, but the Onion and Clean architecture are similar to the hexagonal architecture.

Satisfy the above two points, and map some DDD concepts to practice, then your system is DDD compliant. In conclusion, DDD is not a set of new special architecture, it is the end of any project code after refactoring to meet high maintainability, high scalability, high testability, code structure is clear.

DDD tip business practice

1. Brief introduction of tipping business

  • When watching the video, choose stars and gifts to reward;
  • There are bubbles on the screen after tipping;
  • Tip data is displayed in the leaderboard;
  • Accumulate a certain amount of tips to get some kind of reward.

2. Strategic design

When it comes to strategic design, it is necessary to mention several core concepts of strategic design: domain, subdomain, bounded context and architectural hierarchy.

Domain: In a broad sense, domain is what an organization does and everything contained within it. Every company or organization has its own scope of business and way of doing things. This scope of business and the activities carried out within it are domains. When you develop software for a company, you’re dealing with that company’s territory. The field should be clear to you because you work in it.

In the business of tipping, the tipping itself is the realm, the realm of tipping. Whether your reward object is an anchor, a movie, or a blog post, whether your reward object is RMB, virtual currency, rockets, etc., reward is the core of this field.

Subdomain: For domain models to include the word “domain,” we might think of the entire business system as creating a single, cohesive, all-in-one model. However, this is not our goal with DDD. Conversely, in DDD, a domain is divided into smaller domains, which are called subdomains. In fact, when developing a domain model, we usually focus on only one aspect of the business system, and splitting the domain will help us succeed.

Bounded context: A special responsibility limited by display boundaries. Domain models exist within boundaries, within which each model concept, including its properties and operations, has a particular meaning.

At the beginning of the establishment of the reward system, the requirements were relatively simple. With the development of business, the requirements became more and more complex. The process of iteration and domain separation was as follows:

  • The operation and product requirements are very simple, as long as you implement free tipping and reward bubbles in the interface, based on this, there is only one area;
  • After a stage of water testing, the activity effect is very good, need to support multiple reward activities at the same time, increase the activity support sub-domain;
  • The demand side has a new idea. After the user completes certain tipping, some prizes will be given to the user, and reward subdomain will be introduced.
  • To improve user participation, add the ranking function and introduce ranking subdomains.

The final domain division is shown below:

  • Tipping core subdomain: The tipping operation is complete.
  • Notification subdomain: interface bubble notification capability.
  • Reward subdomain: Reward strategy matching, reward distribution.
  • Ranking subdomains: The ranking function is complete.
  • Activity subdomain: activity, star, prop management.
  • User subdomain: provides common functions such as user query and verification.

The process of splitting the domain is not as smooth as described above, and there are many iterations through which our understanding of the domain becomes deeper and more consistent with domain modeling.

Architectural layering: An important principle of hierarchical architecture is that each layer can only aggregate with the layer below it.

To decouple the interface definition from the implementation, the interface definition is at the domain layer and the implementation definition is at the infrastructure layer. But this violates the top – to – bottom single dependency principle. To solve this problem, we use the dependency inversion principle here, the content of the dependency inversion principle — high-level modules should not depend on low-level modules, but both should depend on abstractions. Abstraction should not depend on details, details should depend on abstractions. In accordance with this principle, the structural adjustments are as follows:

We put the infrastructure layer on top of all the layers so that it can implement the interfaces defined by all the other layers.

When we apply the dependency inversion principle to layered architectures, we may find that there is virtually no such thing as layered architectures. Both high and low, they rely only on abstraction, as if the whole layer has been bulldozed. After bulldozing, customers interact with the system in an “equal” manner. Adding new customers is just a different input, output, and presentation, which is the other architecture we’re going to look at, the hexagon architecture.

Hexagonal structure

In our code, there are many direct external dependencies and implementation details. Such as Mybatis mapper class, HttpClient injection, RocketMQ listening, direct operation of cache and so on. There are two obvious problems with this implementation. One is that when the underlying component is replaced, the business logic will be directly affected, and the change momentum and testing scope of the replacement code will be greatly increased. Second, it is not conducive to the reuse of functions, if other businesses have similar logic, can not do direct transplant reuse.

In 2005 Alistair Cockburn proposed the hexagonal architecture, also known as the port and adapter architecture. Looking at the figure above, we see that other underlying dependencies or implementations can be abstracted into input and output classes for the core application and domain model. The organizational relationship becomes a two-dimensional internal and external relationship rather than a top-down structure. Each IO is isolated from the application by an adapter, and each outermost edge is a port. The system based on hexagonal architecture is the ultimate form of DDD. Hexagonal architecture practices are explained in the “ADVANTAGES of DDD” section.

After the practice based on hexagonal architecture is first given, the project module structure is:

# module # description
# Admin-api Configure background dependencies
# api # External user interface
# application # application
# domain # field
# infrastructure # Infrastructure
# query # query module
# task # Is related to the middleware used and can be ignored
# worker # Handle event message module
# common # base package

3. Tactical design

After strategic design, the domain has clear boundaries. Let’s talk about tactical design. Start with business mapping of several basic concepts of DDD.

Entity: Consists of attributes and behaviors that have a unique identity.

When designing systems, we tend to focus on the data rather than the domain. The same is true for DDD developers, as databases still dominate software development. The first consideration is the attributes and associations of the data, rather than the notion of a behavioral domain. The result is that the data model is reflected directly on the object model, resulting in entities containing only GET/SET methods, which is not DDD practice.

Only get/ SET entities need to be used with Services, and the cohesion, maintainability, and cost of reuse and migration are significantly higher than DDD.

Value objects: Objects that have no unique identity, are measurable or describable, and satisfy immutability.

We should try to model value objects rather than entity objects because they are much easier to create, test, use, optimize, and maintain than entities.

For the first implementation, users must know that they need to use both amount and currency, and they should know how to use both properties because they do not form a conceptual whole. The definition of the PropName value object allows extensibility, such as the need to convert the case of the Prop name. This operation can be implemented internally in the PropName, and the logic of the external name leaks into the Prop.

Domain service: A domain service represents a stateless operation that implements a domain-specific task.

When an operation is not suitable for aggregation and value objects, it is best to use domain services.

For example, “user authentication”, one way is that we can simply put the authentication operation on the entity. There are two problems with this design. First, the user class needs to know some authentication details, and second, this approach does not express the common language. Here we are asking if a User is “authenticated”, rather than expressing the “authentication” process. Whenever possible, we should try to use modeling terminology to directly express the language of communication.

Domain events: Events occurring in a domain that are of interest to domain experts. We often use domain events to maintain event consistency, which eliminates two-phase commits (global transactions).

Aggregation: An aggregation is a group of related objects that are accessed as a whole. The root of the aggregation is the root node of the aggregation.

Aggregation is a very important concept, and core domains are often expressed in terms of aggregation. Second, aggregation is also technically very valuable and can guide detailed design. Aggregation consists of root entities, entities, and value objects.

Factory: A factory provides an interface for creating objects that encapsulates the complex process of creating objects without requiring customers to reference the actual objects being created.

We’ll explain why the method entry is passed to the repository object in the above example in the hexagonal architecture section below.

The advantage of DDD

The system applying DDD conforms to the hexagonal architecture, and we achieve the following objectives:

  • Framework-independent: An architecture should not depend on an external library or framework, and should not be tied to the structure of the framework;
  • UI independence: The style presented in the foreground may change over time, but the underlying architecture should not change with it;
  • Independence from underlying data sources: Software architectures should not change dramatically with different underlying data stores;
  • Independence from external dependencies: No matter how external dependencies change or upgrade, the core logic of the business should not change significantly.

Hexagonal architecture (onion architecture, clean architecture are similar) is a good choice to achieve the above goals. Here is how to achieve these goals in combination with specific business implementation of rewards.

First, the code package structure of a module of the project is given:

Resource library: For resource library, our practice is that resource library acts as an isolation layer between business and data, shielding the details of the underlying data table, and completing the transformation of PO and DO at the same time. The benefit of the DO/PO conversion is that the domain layer is not directly dependent on the underlying implementation, facilitating subsequent replacement of the underlying implementation or function migration. The repository interfaces are defined at the domain layer and implemented at the infrastructure layer.

RPC: The structural breakdown of the RPC part is similar to that of the repository, except for the presence of domain services. The interface definition is in the domain layer and the implementation is in the infrastructure layer.

Following the general principle of hexagonal architecture, the separation of the other sides also becomes clear and simple, which will not be described here.

A few key questions

1, the transaction

In the “aggregation” section above, we described transactions in which only one aggregation instance is operated within a transaction. If you find too much logic in a transaction, you can consider stripping out separate aggregations and adopting final consistency. Based on this foundation, the best layer to declare transactions is the application layer.

2, the query

CQRS is a frequently mentioned pattern in DDD. Its purpose is to separate the domain model from the query function, so that some complex queries can get rid of the limitations of the domain model and present the query results in a simpler DTO form. At the same time, different data storage structure is separated, so that developers can choose data storage engine more freely according to the function and requirements of query, and the specific practice of CQRS can search for information by themselves.

3. Frame irrelevant

Based on the hexagonal architecture design, has been achieved with the underlying implementation, framework, middleware independent. But the biggest framework relies on Spring, and we do this by decoupling domain-level frameworks by passing arguments to Spring beans used in the domain.

4, cost,

Cost is a very important issue that we need to consider when we practice DDD. Learning cost, renovation cost, compatibility cost and so on all need special attention. It is recommended that costs be assessed first before hands-on practice.

conclusion

DDD is not a new special architecture, but a methodology for dealing with software complexity. Domain-oriented modeling, based on hexagonal architecture, the project code is reconfigured to meet high maintainability, high scalability, high testability and clear code structure.

DDD lacks authoritative practice guidance and code constraints, so there will be many problems in the application process. Iqiyi member team has accumulated certain experience through several months of practice, welcome to exchange.