Domain-Driven Architecture-oriented query implementation

Source: Internet
Author: User

In the previous articleArticle《. NET application framework architecture design practices-OverviewIn the comments section, some netizens raised a common problem in the practice of domain-oriented drive architecture:"Ddd uses aggregated root access, such as thoseGeneral QueryHow to implement it? Do they all need to be obtained through multiple steps of the aggregation root? How does DDD implement associated table queries, such as 3 table join queries?"This problem is quite extensive and involves a lot of content. I will introduce my views on this issue in a single article. For the"General Query"-Er, this definition is vague. I can only give some of my ideas or empirical things. My experiences and opinions in this article may not necessarily be 100% suitable for your application scenarios, but I think it should be instructive.

Aggregation and aggregation Root

I think, let's talk about it from the aggregation root. The aggregation root is a concept in DDD. No matter the classic DDD architecture or the event-driven cqrs architecture, most of the concepts are actually the same, for example, entity, value object, service, factory, warehousing, and aggregation/aggregation root. According to my understanding, the aggregate root is an entity, which maintains reference with other entities/value objects and works with these entities/value objects, to express a unique, unambiguous logical concept in a universal language of the domain. For example, the most common "customer", in the "online sales" field, "customer" not only contains the person (or organization) It refers) the name, contact number, and e-mail address of the contact address and the delivery address, here we can regard it as a value object, because we only care about the information contained in the address itself. Here, "customer" is not only an entity, but also an aggregate root of an object set (aggregation) composed of "customer-address.

The objection here is whether the "sales order" should belong to the "customer" aggregation. I think this still depends on whether the "sales order" is necessary information for the "customer" in the current field. In other words, "customer" cannot be "customer" without "sales orders ". I think, in most cases, the "customer" should be an entity that exists independently from the "sales order". In this case, "sales orders" will not be aggregated by "customers.

Now let's look at another part of the "online sales" field: sales orders. Of course, "sales order" is an entity, and it is also an aggregation root composed of the order subject and "sales lines". This is a natural thing, because "sales order" does not have the order details, it will lose the meaning of the order itself. In addition, the "customer" entity is also an integral part of this aggregation, which is well understood. The "sales order" itself is issued by the customer and cannot exist out of the "customer. Therefore, the aggregation based on "sales orders" also includes the "customer" entity and "Order details" (as to whether "Order details" are entities or value objects, this is closely related to the definition of specific fields. For example, if the product item and the quantity of purchased items are involved in discounts, the "Order details" must be handled in Entity mode, otherwise, it can be designed as a "value object" to reduce system overhead. This article bypasses the discussion of this issue ). Before further discussion, let's review the warehousing in DDD. Ddd tells us that warehousing is applied to the aggregation root: In the domain model, the storage and reading of objects are performed in an aggregation unit.

Through the above discussion, we have roughly obtained the following domain model for the "online sales" field (some parts may be omitted in the figure to shorten the length)

the problem arises. What should we do if we need to obtain all orders from a "customer? In the above domain model, the customer entity does not have a certain attribute or method to obtain all of its sales orders. When such a problem occurs, the salesorder warehouse is usually used in combination with the specification to filter out all the sales orders that meet the specific "customer" conditions, then, the warehouse returns the list of sales orders. You may think this practice is not scientific, and you may feel that you should obtain all the sales orders owned by the customer entity through a certain attribute (such as salesorders, this will be more straightforward. However, we have discussed the model in this field above. In our case, customer is an independent entity, and salesorder is not a necessary component. Therefore, to maintain the integrity of the domain model, we need to use the "sales order" warehouse to complete this function. The pseudo Code is as follows:

Public interface ispecification <t> {bool issatisfiedby (t obj);} public abstract class specification <t>: ispecification <t> {public abstract expression <func <t, bool> Expression {Get;} public bool issatisfiedby (t obj) {return this. expression. compile () (OBJ) ;}} public class ordercustomermatchesspecification: Specification <salesorder> {private customer; Public ordercustomermatchesspecificat Ion (customer) {This. customer = Customer;} public override expression <func <salesorder, bool> Expression {get {return P => P. customer. id. equals (customer. ID) ;}} public interface irepository <t> where T: iaggregateroot {void add (T aggregateroot); List <t> getallbyspecification (ispecification <t> spec );} public class memoryrepository <t>: irepository <t> where T: iaggregateroot {private re Adonly list <t> store = new list <t> (); Public void add (T aggregateroot) {If (! This. store. exists (P => P. id. equals (aggregateroot. ID) This. store. add (aggregateroot);} public list <t> getallbyspecification (ispecification <t> spec) {return this. store. where (spec. issatisfiedby ). tolist () ;}}ispecification <salesorder> spec = new ordercustomermatchesspecification (custdaxnet); List <salesorder> daxnetorders = salesorderrepository. getallbyspecification (SPEC );

In the code above, the daxnetorders object stores all sales orders belonging to the customer custdaxnet. Through this example, we can see that when we need some information, we only deal with the aggregation, entity, value object, and warehousing in the domain model, we have no concept of any database, data table, field, record, etc. From the code above, we can see that we can useService Pile(Service stub, poeaaMemory-based storage of mock is unrelated to relational databases. As a matter of fact, our software designers, developers, and field experts reach a consensus on the same thing: domain model. Aggregation, entities, value objects, and so on have become the main components of the domain model, and these objects maintain their own States, that is, the data we need. In the classic DDD architecture style (such as Microsoft nlayerapp), we obtain the information we need through the objects in the domain model and Their Relationships. Therefore, data Query is caused by warehousing, and navigation query is implemented through aggregation. Next, let's introduce a relational database to talk about the "Multi-table join query" problem first proposed in this article.

Domain model vs Relational Database

What I wrote earlier 《Classic application system structure, cqrs, and event TracingThis article discusses the "impedance imbalance" effect between the domain model and the relational data model, so we will not repeat it here, but we must figure out one thing, in the practice of DDD, we must put aside relational databases and even all other data persistence mechanisms, but only focus on Domain Models. Therefore, the domain model itself also needs to block the details of data persistence (we usually call it "Persistence independence", persistence ignorance ). There are two reasons for this: first, DDD is domain-oriented, not data-oriented. The domain model expresses the problem domain, which is also a bridge between software engineers and experts in the field, if the details of data storage are introduced, it is not conducive to communication, but also enables the domain model to rely too much on specific technical implementation solutions, improving the coupling of the system. Secondly, due to the existence of the "impedance imbalance" effect, an intermediary role is needed to solve this imbalance effect. Generally, Orm assumes this role. However, from the perspective of technical implementation, for the same domain model, The ORM can have different processing methods. The specific processing method can be determined by the configuration information of the orm framework (for example, the HBM ing file of nhib.pdf; in this case, the domain model + ORM determines the structure of the relational database, so the discussion of relational databases such as data tables, fields, and records does not make much sense, because the structure of the relational database itself is also uncertain. Now let's take a look at an example to learn how ORM processes models in the same domain. Taking the "customer-address" aggregation mentioned above as an example, Orm can process this aggregation at least (but not limited to) in the following four ways:

  • Foreign key ing mode (foreign key mapping pattern, poeaa)
    In this way, the relationship between objects is mapped to the foreign key Association of the data table. For example, if "customer-address" is aggregated, Orm will generate two tables in the database: the customer table and the address table. The customer table contains the foreign key reference of two address records:

  • Association Table mapping pattern (poeaa)
    This method introduces the third data table to save the primary key association between the other two tables. For example, if "customer-address" is aggregated, Orm generates three tables in the database: Customer table, address table, and customeraddress table:

  • Embedded value pattern (poeaa)
    The embedded value mode maps an object to several fields in another object table. For example, if "customer-address" is aggregated, Orm generates only one table in the database: Customer table, which contains the fields of all attribute values of the address object:

  • Serialization lob Mode(Serialized lob pattern, poeaa)
    This mode serializes the data of another object into a lob (BLOB or clob), and stores the data in the data table corresponding to the current object as a field. For example, if "customer-address" is aggregated, Orm will generate a data table in the database: Customer table, and save the serialized lob data of the "Address" Object in it:

Therefore, in the practice of DDD, we do not have the issue of "how to query joined tables". We are concerned with Domain Models. As for the work of relational databases, let's hand over it to Orm.

Of course, the difference between theory and practice is too large. We also need to analyze specific problems. For example, although the introduction of ORM solves the "impedance imbalance" between the domain model and the relational data model, it also brings performance problems to some extent. For some systems with high performance requirements, using DDD may not be a good choice, but you can also find a way to solve the problem. For example, if a system does not have high performance requirements, the practice of DDD can be adopted, but the efficiency of individual query functions (such as general ledger report generation and data statistics) is required, we can still apply the practical experience of DDD and try to bypass the domain model in these functions to directly use the efficient database query method (such as ADO. net), of course, this is out of the scope of the discussion of DDD, but our goal is to achieve a stable, secure, and efficient system, DDD or not DDD is not the focus, the point is that it is appropriate. I think this is also the responsibility of the architect.

When we use "abnormal means" to slowly bypass the domain model, we will find an interesting phenomenon: in fact, "query" is not part of the domain model at all, "query" can exist independently as a separate system, this "query system" can be integrated into the actual system (such as Microsoft BizTalk Server) to provide the query service for the client. Since "query" can be a separate system, there are a variety of methods to implement this "query" system: You can continue to use ORM to implement queries, you can also directly write SQL statements for query, or even use some existing query frameworks. In short, you only need to provide the required data to the client. "Query" is no longer restrained by domain models. In the context of such a wide range of technical selection, I think it is not difficult to implement a complex and customizable query mechanism.

The domain-driven cqrs (command query responsibility segregation, command query responsibility separation) architecture is like this: it completely separates the "query" part from the domain model.

Cqrs Architecture Model

What I wrote earlier 《Domain-driven design practices of entityframework [extended Reading]: cqrs Architecture ModelThe cqrs architecture model has been introduced and summarized in great detail, here, I will briefly describe the "query" section of this structure.

In cqrs, we can see that the "warehousing" on the aggregation root has been degraded into "Domain repository", and the field warehousing is also used on the aggregation root, but it only has two operations: Save and getbyaggregaterootid. Obviously, the Save function is to save the whole aggregation, while getbyaggregaterootid is to get the whole aggregation through the identity of the aggregation root. Therefore, operations such as "getting all sales orders from a customer" I mentioned above cannot be completed in the command section of cqrs: you cannot pass the specification) to obtain all the orders that "include" a customer. You can only obtain the order information by using the order number. Maybe (I mean maybe), In the cqrs architecture domain model, we don't need to know which customer the order belongs to. OK, remove the "customer" entity from the "sales order" aggregation. I have discussed this issue in the official forum on domain-driven design. The conclusion is that the domain model should only contain the necessary information and all content related to queries, they should all be designed in the "query" section.

In 《CqrsI have provided a structure diagram in the article. Now I will refine this diagram to show the details of the query section:

After completing the operation, the domain model generates domain events. When aggregation is saved to the database, domain events are also published to the event bus. Then, the event dispatching processor (Microsoft BizTalk Server) will distribute the event to different subscription mechanisms, such as the dynamics ax system or a separate query database. In this way, the query database will have a large design space (for example, you can design the table structure of the relational database based on the view model of the client), and the design of the query reader will become very simple. Under such a structure, the implementation of general queries and complex queries is also very simple.

Summary

In short, the domain model can provide certain query capabilities, such as obtaining the required data through warehousing, conventions, and object link navigation. However, the query should not be part of the domain model, it can be separated. For Classic Architecture styles (such as Microsoft nlayerapp), If you need complex query functions, you can directly bypass the domain model and directly access the database from a single system for query, then, the query is returned to the client. After obtaining the query result, the client obtains the domain object through warehousing and updates the domain model based on the modified data. For the architecture style of cqrs, we will get a larger design space for the query part, and the implementation of the query function is no longer a problem.

I hope this article will be helpful to readers who are interested in this aspect.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.