In the previous article, we covered concepts and design strategies for domain-driven development, using flaws in Spring Web applications to lead to improvements. This article focuses on several types of domain models and simple practice cases of DDD.

Architectural style

Several architectural styles are mentioned in The book Implementing Domain-Driven Design: hexagonal architecture, REST architecture, CQRS, and event-driven. In practice, the landing architecture is not a pure one, and it is likely to be a combination of the above architectural styles.

Layered architecture

An important principle of layered architecture is that each layer can only be coupled to the layer below it. Layered architecture can be divided into two types: strictly layered architecture and loosely layered architecture. In a strictly layered architecture, a layer can only be coupled to the layer directly below it, whereas in a loosely layered architecture, a layer is allowed to be coupled to any layer directly below it. There are three classic patterns in DDD hierarchical architecture: four-tier architecture, five-tier architecture and hexagonal architecture.

Four layers architecture

Eric Evans, in his book Domain-Driven Design: Tackling Core Complexity in Software, proposes the traditional four-tier architecture pattern:

  • The User Interface is the User Interface layer (or presentation layer), which is responsible for displaying information to users and interpreting User commands. The user can be another computer system, not necessarily the person using the user interface.
  • Application is an Application layer that defines the tasks the software is to accomplish and directs objects that express domain concepts to solve problems. This layer is responsible for work that is significant to the business and a necessary channel for interacting with the application layers of other systems. The application layer should be as simple as possible, containing no business rules or knowledge, but only coordinating tasks and assigning work to domain objects in the next layer to make them work together. It does not reflect the state of the business situation, but it can have another state that shows the progress of a task to the user or program.
  • Domains are the Domain layer (or model layer), which is responsible for expressing business concepts, business state information, and business rules. Although the technical details of preserving the business state are implemented by the infrastructure layer, the state reflecting the business state is controlled and used by this layer. The domain layer is the core of the business software, and the domain model is located in this layer.
  • The Infrastructure layer is the underlying implementation layer that provides common technical capabilities to other layers: passing messages to the application layer, providing persistence mechanisms to the domain layer, drawing screen components to the user interface layer, and so on. The infrastructure layer can also support interaction patterns between the four layers through an architectural framework.

The traditional four-tier architecture is a restricted loosely layered architecture, that is, any upper layer of the Infrastructure layer can access the layer (” L “type), while the other layers adhere to a strictly layered architecture.

Five layers architecture

The five-tier architecture is a summary of the DCI architecture patterns mentioned in DCI Architecture: New Ideas for Object-oriented Programming. DCI architecture (Data, Context, and Interactive three-tier architecture) :

  • The Data layer describes the domain concepts of the system and their relations. This layer focuses on the establishment of domain objects and the life cycle management and relations of these objects, allowing programmers to think about the system from the perspective of objects, so that “what is the system” can be understood more easily.
  • Context layer: is the thinnest layer possible. Context is often implemented stateless, just find the appropriate roles and let them interact to complete the business logic. But simplicity does not mean insignificance, and the explicit Context layer provides the entry point and thread for understanding software business processes.
  • The Interactive layer is mainly embodied in the modeling of roles, which are the real executor of complex business logic in each context and reflect “what the system does”. What a role does is model behavior by connecting context and domain objects. Since the behavior of the system is complex and changeable, role separates the stable domain model layer from the changeable system behavior layer, and role focuses on modeling the system behavior. This layer tends to focus on the scalability of the system, which is more close to software engineering practice. In object-oriented, it is more thought and design from the perspective of classes.

DCI is now widely seen as an evolution and complement to DDD for object-oriented domain modeling. The five-tier architecture is defined as follows:

  • The User Interface is the User Interface layer. It is mainly used to process Restful requests sent by users, parse configuration files entered by users, and transfer information to the Interface of the Application layer.
  • The Application layer is the Application layer, which is responsible for multi-process management and scheduling, multi-thread management and scheduling, multi-coroutine scheduling and maintaining the state model of business instances. When the scheduling layer receives the request from the user interface layer, it entrusts the Context layer and the relevant Context of the current service to process it.
  • Context refers to the environment layer. The Domain objects of the Domain layer are cast into appropriate roles so that the roles can interact to complete the business logic.
  • Domain layer is the Domain layer, which defines the Domain model, including not only the modeling of Domain objects and their relationships, but also the explicit modeling of the role of the object.
  • The Infrastructure layer is the basic implementation layer that provides common technical capabilities for other layers: business platforms, programming frameworks, persistence mechanisms, messaging mechanisms, encapsulation of third-party libraries, common algorithms, and so on.
Hexagonal structure

The Hexagonal Architecture, also known as the port and adapter style, was first proposed by Alistair Cockburn. Developed and popularized in the DDD community, the six morphs are used to highlight that this is a flat architecture, with equal weight on each edge.

As we know, the classical hierarchical architecture is divided into three layers (presentation layer, application layer, data access layer), while the hexagonal architecture can be divided into another three layers:

  • Domain Layer: The innermost, pure core business logic that generally does not contain any technical implementations or references.
  • Ports Layer: Outside of the domain Layer, it is responsible for receiving all requests related to use cases, which are responsible for coordinating work in the domain Layer. The port layer acts as the boundary of the domain layer inside the port and as an external entity outside the port.
  • Adapters Layer: Outside of the port Layer, Adapters are responsible for receiving inputs and generating outputs in a format. For example, for AN HTTP user request, the adapter translates it into a call to the domain layer and marshals the response back from the domain layer back to the calling client over HTTP. There is no domain logic at the adapter layer, whose sole responsibility is to perform technical transformations between the outside world and the domain layer. An adapter can be associated with and used by a protocol of a port, multiple adapters can use the same port, and when switching to a new user interface, the new interface and the old interface can use the same port at the same time.
Picture transferred from the network

The advantage is to make the business more clear boundary, so as to get better extensibility, in addition, the complexity of business and technology complexity separation, is the important foundation of DDD, core domain layer can focus on business logic rather than to rely on technology, external interface when invoked by consumer also need not to care about how business internal implementation.

REST architecture

A RESTful architecture puts resources first, with each resource having a URI that corresponds to it and treating it as an entity in DDD. RESTful uses messages with self-description function to achieve stateless communication and improve system availability. As for which attributes of a resource can be exposed, RESTful uses existing HTTP methods to perform resource operations: GET, PUT, POST, and DELETE.

In the implementation of DDD, we can design the external services as RESTful services, taking entity/value object/domain services as resources to provide external add, delete, change and check services. However, it is not recommended to expose the entity directly, because some privacy attributes of the entity cannot be exposed, and some resource acquisition scenarios cannot be satisfied by one entity. Therefore, we added a role of DTO to the domain model in practical practice. DTO can combine the resources of multiple entity/value objects and expose them externally.

CQRS

CQRS is commonly referred to as read/write separation. The purpose of CQRS is to improve query performance and achieve read/write decoupling. By combining DDD and CQRS, we can model read and write data separately. The query model is usually a de-normalized data model that does not reflect domain behavior but is only used for data display. The command model implements the domain behavior, and finds a way to notify the query model when the domain behavior execution is complete.

So how does the command model inform the query model? This step can be avoided if the query model and domain model share the data source. If there is no shared data source, it can be notified to the query model via Messaging Patterns, resulting in Eventual Consistency.

Martin pointed out in his blog that CQRS is suitable for a very small number of complex business domains, and if it is not suitable, it can increase the complexity; Another scenario is to obtain high-performance services.

The domain model

In the above section, several architectural styles of domain-driven design are explained. In the following section, we can see the division of domain models based on simple examples, which can be preliminarily divided into four categories:

  1. Blood loss model
  2. Anemia model
  3. Congestion model
  4. Expanding blood model

Let’s look at the specifics of these domain models, as well as their strengths and weaknesses.

Blood loss model

In simple terms, the blood loss model is a pure data class of domain Object with only getter/setter methods for properties, and all business logic is completely completed by Business Object (also called TransactionScript). Martin Fowler called the Domain object under this model “anaemic Domain object”. As follows:

  • An entity class is called Item

    public class Item implements Serializable {   
     private Long id = null;   
     private int version;   
     private String name;   
     private User seller;   
     // ...  
     // Getter /setter methods are omitted to avoid being too long
    Copy the code

} ` ` `

  • A DAO interface class is called ItemDao

    public interface ItemDao {   
     public Item getItemById(Long id);   
     public Collection findAll(a);   
     public void updateItem(Item item);   
    Copy the code

} ` ` `

  • A DAO interface implementation class is called ItemDaoHibernateImpl

    public class ItemDaoImpl implements ItemDao extends DaoSupport {   
     public Item getItemById(Long id) {   
         return (Item) getHibernateTemplate().load(Item.class, id);   
     }   
     public Collection findAll(a) {   
         return (List) getHibernateTemplate().find("from Item");   
     }   
     public void updateItem(Item item) {   
         getHibernateTemplate().update(item);   
     }   
    Copy the code

} ` ` `

  • A business logic class is called ItemManager(or ItemService)

    Copy the code

public class ItemManager { private ItemDao itemDao; public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao; } public Bid loadItemById(Long id) { itemDao.loadItemById(id); } public Collection listAllItems() { return itemDao.findAll(); } public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, Bid currentMaxBid, Bid currentMinBid) throws BusinessException { if (currentMaxBid ! = null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { throw new BusinessException(“Bid too low.”); }

  // ...  
Copy the code

} ` ` `

The above is a complete sample code for the blood loss model. In this example, the loadItemById, findAll, and other business logic is implemented in the ItemManager, and Item only has getter/setter methods.

Anemia model

Simply put, Domain ojbect contains domain logic that does not depend on persistence, and those that do rely on persistence are separated into the Service layer.

Service(business logic, transaction encapsulation) –> DAO –> Domain Object

  • An entity class with business logic, that is, domain Object is Item
  • A DAO interface ItemDao
  • A DAO implements ItemDaoHibernateImpl
  • A business logic object, ItemManager

Advantages of this model:

  1. Each layer is unidirectional dependent, the structure is clear, easy to implement and maintain
  2. The design is simple and the underlying model is very stable

Disadvantages:

  1. The parts of the Domain Object that are more closely dependent on persistent Domain Logic are separated into the Service layer and are not OO enough
  2. The Service layer is too thick

The specific code is relatively simple and will not be shown.

Congestion model

The congestion model is similar to the second model, but the difference is how to divide the business logic, that is, most of the business logic should be placed in the Domain Object (including the persistence logic), while the Service layer should be a very thin layer, only encapsulating transactions and a small amount of logic, and not dealing with the DAO layer.

Service(transaction Encapsulation) –> Domain Object <– > DAO

This model combines domain Object and Business object of the second model. There is no need for ItemManager. Under this model, there are only three classes:

  • Item: contains the entity class information, as well as all the business logic
  • ItemDao: Persistent DAO interface class
  • ItemDaoHibernateImpl: Implementation class for the DAO interface

In this model, all business logic is in Item, and transaction management is implemented in Item. Advantages of this model:

  1. More in line with OO principles
  2. The Service layer is thin and acts as a Facade rather than a DAO.

Disadvantages of this model:

  1. Daos and Domain Objects form bidirectional dependencies, and complex bidirectional dependencies can lead to many potential problems.
  2. The division of Service layer logic and domain layer logic can be very ambiguous, and in real projects, the overall structure can be chaotic due to differences in the level of designers and developers.
  3. Considering the transaction encapsulation characteristics of the Service layer, the Service layer must provide the corresponding transaction encapsulation method for all domain object logic. The result is that the Service completely redefines all domain logic, which is very cumbersome. And transactional encapsulation of services is equivalent to translating OO domain Logic into procedural Service TransactionScript.

Expanding blood model

Based on the third shortcoming of the congestion model, some students suggested that the Service layer should be cancelled altogether, leaving only domain Object and DAO layers, and encapsulating transactions on domain Logic of Domain Object.

Domain Object (transaction Encapsulation, business logic) <– > DAO

Ruby on Rails seems to be such a model, even merging domain Objects and DAOs.

Advantages of this model:

  1. Simplified layering
  2. It’s also OO

Disadvantages of this model:

  1. A lot of service logic that is not domain Logic is also forced into domain Object, causing the instability of Domain Ojbect model
  2. Domain Objects expose too much information to the Web layer, which can cause unexpected side effects.

summary

Among the four models, blood loss model and blood distension model should not be advocated. The anemia model and the hyperemia model are both technically feasible. Which is better, anaemic model or hyperemic model? There was a long and fruitless debate on this subject. The debate over on my bold above two sentences, is whether the domain model depends on the persistence layer, because rely on persistence layer means that the unit tests on to more difficult (not out of the framework, the discussion of the original here specifically to Hibernate), domain layer is more difficult to separate, and much harder to spin off from the application in the future, The advantage, of course, is that the business logic does not have to be mixed in different layers, making the single responsibility better. Supporters (hyperemia model) believe that the difficulty of testing can be reduced as long as the persistence layer is abstracting, and the application of hyperemia model after all brings a lot of convenience in development. Besides relying on the persistence layer, hyperemia model with more benefits is still worth choosing. In the end, no one was able to convince anyone that the choice between an anaemic model and a hyperemic model depends more on the specific business scenario than one is better than the other. Design patterns have always been an open question.

Personally, I prefer to use the congestion model because it is more like a well-designed system architecture. Fortunately, there are many IOC and DI frameworks in the computer world, and the only defect dependency persistence layer can be bypased by various workarounds. As the technology advances, some defects will be gradually solved. My idea is to abstract the persistence layer as an interface, and then inject the persistence layer into the domain model through the service layer, so that the domain model only depends on the interface of the persistence layer. This interface, however, can be abstracted using existing framework techniques.

reference

  1. [DDD] Business modeling practice — Delete posts
  2. Explanation of anemia, hyperemia model and some experience