03 How Ddd Is Implemented in Database Design

03 How DDD is implemented in Database Design #

In the past, software design was centered around database design. Once the requirements were finalized, teams would start with database design. Since the database served as the sole interface for different modules, once the database design was finalized, each module could be developed independently, as shown in the diagram below.

Drawing 1.png

In the above process, in order to increase the development speed of the team, it’s preferred that the modules don’t interact with each other, thus achieving independent development for each module. However, as the system scale grows larger and the business logic becomes more complex, it becomes increasingly difficult to ensure that the modules remain independent and don’t interact with each other.

With the continuous development of the software industry, software systems have become increasingly complex, and the interaction between different modules has become more frequent. At this point, the previous design process is no longer sufficient. This is because if database design is performed first, it can only describe the data structure and not the processing of these data structures by the system. Therefore, during the initial system analysis process, only the data structure of the system can be outlined, forming the database design. Afterwards, the entire system needs to be analyzed again to examine the processing of these data structures by the system, forming the program design. Why can’t we outline the entire system’s design at once?

Drawing 3.png

Nowadays, we follow the object-oriented software design process to analyze and design systems. During the requirements analysis phase, the first step is to design the use case model to analyze the functionalities that the system should implement. Then, the domain model is designed to analyze the business entities of the system. In the domain model analysis, class diagrams are used, where each class can describe the data structure with its attributes, and describe the processing of this data structure by adding methods. Therefore, in the process of domain model design, both the organization of data structures and the processing of these data structures by the system can be determined, thus completing these two tasks at once.

In this design process, the core is the design of the domain model. By using the domain model as a core, it can guide the database design and program design of the system. At this point, database design is weakened to just one way of implementing the persistence design of the domain objects.

The Idea of Domain Object Persistence #

What does domain object persistence mean? In today’s mainstream software architecture design, object-oriented design has become the mainstream. Throughout the system operation, all data exists in the form of domain objects. For example:

  • To insert a record, a domain object is created;
  • To update a record, the corresponding domain object is modified based on the key value;
  • To delete data, the domain object is destroyed.

If our server is a super powerful server, we wouldn’t need any database. We could simply manipulate these domain objects. However, in the real world, there are no such powerful servers. Therefore, it is necessary to persistently store the domain objects that are not currently in use onto the disk, and the database is just one way to implement this persistence storage.

Following this design idea, we persistently store the domain objects that are not currently in use from memory to disk. When we need to use a domain object again in the future, we search for the corresponding record in the database based on its key value, and then restore it to a domain object, allowing the application to continue using it. This is the design idea of domain object persistence. So, today’s database design is actually the process of transforming the design of domain objects into the design of a database based on some corresponding relationship. At the same time, with the transformation of the entire industry into big data, the future database design thinking will also undergo tremendous changes. It is possible that databases may not necessarily be relational databases anymore, but could be NoSQL databases or big data platforms. The design of the database may not necessarily follow the third normal form (3NF) anymore, and there may be more redundancy and even wide tables.

The database design is undergoing drastic changes, but the only constant is the domain objects. This way, when the system undergoes the transformation into big data, the business code can remain unchanged, while the data access layer (DAO) can change. This will lower the cost of future big data transformations and allow us to keep up with the rapid pace of technological development.

Design of the Domain Model #

In addition, there is an interesting question worth exploring: whose responsibility is the design of the domain model, the requirements analyst or the designer/developer? I believe it is a result of the collaboration between both roles. And as agile development organizations form in the future, teams will become more flattened. In the past, requirements analysts would perform requirements analysis and then hand it over to designers/developers for design and development, which resulted in low-quality software design and bloated structures. In the future, the mindset of “front-end dominance” will support more designers/developers directly participating in requirements analysis, achieving an integrated organizational form from requirements analysis to design and development. In this way, the domain model becomes a powerful tool for designers/developers to quickly understand requirements.

Drawing 4.png

In conclusion, the database design of DDD has actually become the process of transforming domain models into database designs, with the domain model at the core. So how do we perform this transformation? In the domain model, we have classes, while in the database design, we have tables, so it is the process of converting classes into tables.

Drawing 6.png

The above figure depicts a domain model for a performance appraisal system. This performance appraisal system first automatically appraises and identifies a batch of faults, and then gives the person responsible for these faults an opportunity to defend against them. At this point, the person responsible for the faults can fill out a defense application form, and within the defense application form, there are multiple details, with each detail corresponding to a fault behavior, and each fault behavior corresponds to a fault type. Thus, a domain model is formed.

Next, how do we convert this domain model into a database design? Obviously, a class in the domain model can be converted into a table in the database, and the attributes in the class can be converted into fields in the table. But the key here is how to handle the relationships between classes, and how to convert them into relationships between tables. This is where the 5 types of relationships come in, including the traditional 4 types of relationships plus the inheritance relationship.

The Traditional 4 Types of Relationships #

The traditional relationships include one-to-one, many-to-one, one-to-many, and many-to-many, and they exist between classes as well as between tables, so they can be converted directly.

1. One-to-One Relationship

In the example above, the “defense application form detail” and the “fault behavior” have a one-to-one relationship. In this relationship, a “defense application form detail” must correspond to one “fault behavior”, and without a corresponding “fault behavior”, it cannot be a “defense application form detail”. This constraint can be implemented in database design using a foreign key. However, there is another constraint in the one-to-one relationship, which is that one “fault behavior” can have at most one “defense application form detail” corresponding to it.

In other words, a “fault behavior” can have no “defense application form detail” corresponding to it, but if it does have one, it can have at most one “defense application form detail” corresponding to it. This constraint implies a uniqueness constraint. Therefore, the primary key of the fault behavior table is used as the foreign key in the defense application form detail table, and this field is promoted as the primary key of the defense application form detail table.

Drawing 8.png 2. One-to-Many Relationship

This is the most common type of relationship in daily analysis and design. In the above case, one misconduct corresponds to one tax officer, one taxpayer, and one misconduct type. At the same time, one tax officer, taxpayer, or misconduct type can correspond to multiple misconducts. They form a “one-to-many” relationship. In database design, this “one-to-many” relationship can be established through foreign keys. Therefore, we have designed the following database:

Drawing 10.png

The one-to-many relationship is relatively simple in database design, but when it comes to program design, it needs to be discussed thoroughly. For example, in the above case, after designing in this way, when querying, it is often necessary to display the tax officer, taxpayer, and misconduct type corresponding to the misconduct at the same time. The traditional approach is to add a join statement for this purpose. However, such design will greatly affect the query performance as the data volume increases.

In other words, the join operation is often one of the biggest bottlenecks for relational databases when dealing with big data. Therefore, a better solution is to first query the misconduct table, paginate, and then fill in the other related information of the current page. At this time, it is necessary to add references to the tax officer, taxpayer, and misconduct type, etc. in the misconduct value object through attribute variables.

3. Many-to-One Relationship

This relationship often represents a “parent-child table” relationship. For example, in the above case, the “appeal application” and “appeal application details” have a “one-to-many” relationship. In addition, orders and order details, forms and form details are also one-to-many relationships. One-to-many relationship is relatively simple in database design, just add a foreign key in the child table to reference the primary key in the parent table. For example, in this case, the appeal application details table references the primary key in the appeal application table through a foreign key, as shown in the following diagram.

Drawing 12.png

In addition, in the value object design of the program, the main object should also have a collection attribute variable to reference the sub object. In this case, the “appeal application” value object has a collection attribute to reference the “appeal application details”. This way, when a certain appeal application is found by the appeal application number, all its appeal application details can be obtained at the same time, as shown in the following code:

public class Sbsqd {

    private Set<SbsqdMx> sbsqdMxes;
    
    public void setSbsqdMxes(Set<SbsqdMx> sbsqdMxes){
    
          this.sbsqdMxes = sbsqdMxes;
    
    }
    
    public Set<SbsqdMx> getSbsqdMxes(){
    
          return this.sbsqdMxes;
    
    }
    
}

4. Many-to-Many Relationship

A typical example is the “user role” and “function permission”. A “user role” can apply for multiple “function permissions”, and a “function permission” can be assigned to multiple “user roles”, resulting in a “many-to-many” relationship. In object design, this many-to-many relationship can be described in detail through a “function-role association class”. Therefore, in database design, a “role-function association table” can be added, and the primary key of this table is the combination of the primary keys of both sides of the relationship, as shown in the following diagram:

Drawing 14.png

The above are the four types of relationships that exist in both the domain model and the database. Therefore, when designing the database, the corresponding relationships can be directly translated into database design. At the same time, they should be further refined in the database design. For example, in the domain model, both objects and attributes are named in Chinese, which is beneficial for communication and understanding. However, when it comes to database design, they need to be refined into English names or the initials of the Chinese Pinyin, while also determining their field types and whether they are nullable, etc.

Three Designs for Inheritance Relationships #

The fifth type of relationship is different: inheritance relationship exists in the domain model design, but not in the database design. So how can the inheritance relationship in the domain model be converted into database design? There are three options to choose.

1. First Design for Inheritance Relationship

First, let’s look at the above case. The “enforcement action” is divided into “correct action” and “misconduct” through inheritance. If there are not many subclasses in this inheritance relationship (generally 2 to 3), and each subclass does not have many personalized fields (less than 3), then a single table can be used to record the entire inheritance relationship. In this table, there is an identification field in the middle, which indicates which subclass each record in the table belongs to. The fields of the parent class are listed before this identification field, and the personalized fields of each subclass are listed afterwards.

Drawing 16.png

The advantage of using this design is that it is simple, and all the data of the inheritance relationship is stored in this table. However, it will cause “table sparsity”. In this case, if it is a record of “correct action”, the fields “misconduct type” and “deduction” will always be empty; if it is a record of “misconduct”, the field “bonus” will always be empty. If there are many personalized fields in this inheritance relationship, it will cause many empty fields in this table, called “table sparsity”. In a relational database, the empty fields consume space. Therefore, this “table sparsity” not only wastes a lot of storage space, but also affects query speed, so it should be avoided. Therefore, when there are many subclasses or many personalized fields in the inheritance relationship, this design (the first design) is not suitable.

2. Second Design for Inheritance Relationship

If the enforcement actions are inherited by the type of assessment indicators and divided into “assessment indicator 1”, “assessment indicator 2”, “assessment indicator 3”, etc., as shown in the following diagram: Drawing 24.png

所以,在设计 NoSQL 数据库时,需要根据具体业务需求和数据查询模式来决定如何设计表结构,尽量避免 join 操作,并在单表中存储更多的字段。需要注意的是,NoSQL 数据库的设计不需要完全遵循第三范式,允许存在冗余数据,以提高查询性能。但是,对于数据的一致性和完整性仍然需要进行考虑和保证。

{
  qdbz: '00',
  wp_mc: 'Bluetooth headset Car Talker S1 Bluetooth headset',
  sl: 2,
  dj: 68.37,
  ...
},
{
  qdbz: '00',
  wp_mc: 'Car charger New Online',
  sl: 1,
  dj: 11.11,
  ...
},
{
  qdbz: '00',
  wp_mc: 'Protective case non-ni film belongs to iPhone6 electroplating case',
  sl: 1,
  dj: 24,
  ...
}
]

}

In this case, for the "one-to-one" and "many-to-one" relationships, a field of type "object" is used to store the information in the invoice information table, such as the "purchaser's taxpayer (gfnsr)" and the "seller's taxpayer (xfnsr)" fields. For "one-to-many" and "many-to-many" relationships, a field of type "array of objects" is used to store the information, such as the "item details (spmx)" field. With this invoice information table, all invoice queries can be completed without any further join operations.

Similarly, how can we design inheritance relationships with NoSQL databases? Due to the characteristics of NoSQL databases, there is no need to worry about "table sparsity", and join operations can be avoided. Therefore, the first approach, which is to include the entire inheritance relationship in the same table, is more suitable. In this case, each record in the NoSQL database can have fields that are not necessarily identical. It can be designed like this:

{
  _id: ObjectId(79878ad8902c),
  name: 'Jack',
  type: 'parent',
  partner: 'Elizabeth',
  children: [
    { name: 'Tom', gender: 'male' },
    { name: 'Mary', gender: 'female'}
  ]
},
{
  _id: ObjectId(79878ad8903d),
  name: 'Bob',
  type: 'kid',
  mother: 'Anna',
  father: 'David'
}

In the above example, there are two records in a user profile table: Jack and Bob. However, Jack's type is "parent", so his personalized fields are "partner" and "children"; while Bob's type is "kid", so his personalized fields are "father" and "mother". Clearly, designing NoSQL databases can be more flexible.

Summary

Implementing domain models in system design involves two parts, and this lecture demonstrated the entire process of the first part - from DDD to database design:

- The traditional four types of relationships can be directly converted.
- There are three design approaches for inheritance relationships.
- Converting to NoSQL databases requires a completely different approach.

With the guidance of DDD, we can better understand the relationships between data and their operations. Moreover, it will make us more capable in dealing with future big data transformations.