16 Demo of Code Design Based on Ddd Including Ddd Technical Backend Design

16 Demo of Code Design based on DDD including DDD Technical Backend Design #

In my years of experience, I initially worked as a project manager leading a team in software development. Later on, I transitioned into an architect, thinking about the things involved in software development at a higher level. I believe that a mature software development team:

  • Not only relies on improving the development skills of its team members,
  • But also in the accumulation of design methods and technical frameworks, which can be solidified into the underlying technical middle platform.

With such a technical middle platform as support, the development team can have stronger capabilities, develop more products at a faster pace, and adapt quickly to the fiercely competitive and rapidly changing market.

For example, if the team receives a data push requirement, after completing and delivering the functionality to the user, the team can extract commonalities, retain individuality, and incorporate it into the design of the technical middle platform, forming a “data sharing platform.” With this functionality, whenever the team receives similar requirements in the future, they only need to do some configuration or simple development to deliver the product to the user.

In this way, the development capabilities of the team are greatly enhanced. The more features the team develops, the more features are solidified into the technical middle platform, resulting in greater improvement in the team’s development capabilities. Only with such a technical middle platform can the team’s rapid delivery be supported. The key is for someone to consciously organize and cultivate these capabilities. Our team completes this work in “enabler stories.”

Nowadays, more and more teams are adopting Agile development, planning and completing “user stories” within a 2 to 3-week iteration cycle. While user stories are meant to address urgent user requirements, if the team cannot improve its capabilities, it will be like firefighters constantly putting out “fires” of user requirements and be exhausted.

On the other hand, “enabler stories” are aimed at improving our capabilities so that we can respond to user requirements more quickly. As the saying goes, “Sharpening the axe will not delay the cutting of firewood.” “Enabler stories” are like “sharpening the axe.” Although they may consume some time, they enable faster and better “cutting of firewood” in the future, making it well worth it.

Therefore, in each iteration, a mature team should not only complete the “user stories” but also allocate a certain proportion of time to complete the “enabler stories,” enabling faster delivery of future “user stories.”

The technical middle platform that I support, based on DDD and microservices, gradually evolved under this guidance. In the process of practicing DDD and microservices in my team, we encountered many obstacles. Overcoming these obstacles required team members to spend more time learning DDD-related knowledge and to design and develop correctly using the right methods and steps. However, when they actually did it correctly, they found that DDD’s design and development were very tedious. They had to frequently implement various factories, repositories, data supplements, and other development work, leading to developers feeling frustrated with DDD. In the past, project managers could only develop development standards from a management perspective, but such measures were not effective.

In my role as an architect, I designed a technical framework that abstracted the tedious DDD development work and encapsulated it within the technical middle platform, based on the existing code. This not only simplified the design and development, making it easier for DDD to be applied in projects, but also standardized the code, preventing business developers from having the opportunity to write Controller and Dao code. Business code was naturally designed based on the domain model within the Service and Domain Objects. Now, let’s take a look at the design of this framework.

Architecture of the Entire Demonstration Code #

I have shared the entire demonstration code on GitHub, which is divided into several projects:

  • demo-ddd-trade: A monolithic application designed using DDD.

  • demo-parent: The parent project for all microservices in this example.

  • demo-service-eureka: Microservice registration center eureka.

  • demo-service-config: Microservice configuration center config.

  • demo-service-turbine: Microservice circuit breaker monitor turbine.

  • demo-service-zuul: Service gateway zuul.

  • demo-service-parent: The parent project for various business microservices (without database access).

  • demo-service-support: The underlying technical framework for various business microservices (without database access).

  • demo-service-customer: User management microservice (without database access).

  • demo-service-product: Product management microservice (without database access).

  • demo-service-supplier: Supplier management microservice (without database access).

  • demo-service2-parent: The parent project for various business microservices (with database access).

  • demo-service2-support: The underlying technical framework for various business microservices (with database access).

  • demo-service2-customer: User management microservice (with database access).

  • demo-service2-product: Product management microservice (with database access).

  • demo-service2-supplier: Supplier management microservice (with database access).

  • demo-service2-order: Order management microservice (with database access). In summary, there is a monolithic application based on DDD and a complete microservice application. In the microservice application:

    • demo-service-xxx is based on an early framework design, and you can see the original state of our previous design and development.
    • demo-service2-xxx is the microservice design based on DDD that I want to focus on explaining.

The demo-service2-support is the core of this framework, which serves as the underlying technology platform, while the others are specific applications demonstrating its usage.

Design and Implementation of Single Controller #

Unlike before, there are only a few Controllers in the entire system, and they have been migrated to the underlying technology platform demo-service2-support. They include the following parts:

  • OrmController: used for CRUD operations and load/get operations based on key values, typically designed based on DDD.
  • QueryController: used for query analysis reports based on SQL statements, usually not designed based on DDD, but the query results will form domain objects and data will be supplemented based on DDD.
  • Other Controllers, such as ExcelController, for special operations, extending the functionality of the above two classes.

The OrmController handles requests like orm/{bean}/{method}, where bean is a bean configured in Spring and method is the method to be called in the bean. Since this is a basic framework, there are no restrictions on which methods the frontend can call, so actual projects need to add permission verification on top of this. This method can accept both GET and POST requests, so other parameters can be passed according to the respective ways of GET/POST.

The bean mentioned here corresponds to the backend Service. The writing of the Service requires that all methods, if they need to use domain objects, must be placed as the first parameter. If the first parameter is a simple type like number, string, date, etc., it is not a domain object, otherwise it is treated as a domain object and the corresponding data will be retrieved from the JSON uploaded by the frontend to fill it. Collections and domain objects with inheritance relationships are not currently supported, but I will improve this in the future. The judging code is as follows:

/**
* check a parameter whether is a value object.
* @param clazz
* @return yes or no
* @throws IllegalAccessException 
* @throws InstantiationException 
*/
private boolean isValueObject(Class<?> clazz) {
  if(clazz == null) return false;
  if(clazz.equals(long.class) || clazz.equals(int.class) ||
    clazz.equals(double.class) || clazz.equals(float.class)) {
    // ...
  }
  // ...
}
Writer out = new BufferedWriter(new FileWriter("/tmp/foo.out"));
       
  return dao;

 }

    ...

}

Next, configure the business operations service in applicationContext-orm.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" ...>

 <description>The application context for orm</description>

 <bean id="customer" class="com.demo2.trade.service.impl.CustomerServiceImpl">

  <property name="dao" ref="repositoryWithCache"></property>

 </bean>

 <bean id="product" class="com.demo2.trade.service.impl.ProductServiceImpl">

  <property name="dao" ref="repositoryWithCache"></property>

 </bean>

 <bean id="supplier" class="com.demo2.trade.service.impl.SupplierServiceImpl">
<property name="dao" ref="basicDao"></property>
</bean>

<bean id="order" class="com.demo2.trade.service.impl.OrderServiceImpl">
<property name="dao" ref="repository"></property>
</bean>

</beans>

Here, we can see that each service needs to inject a DAO, but different DAOs can be injected depending on the requirements.

  • If the service uses the anemic model, then injecting BasicDao is sufficient.
  • If the service uses the rich model, which includes some aggregate operations, then injecting repository achieves the functionality of a repository and a factory.
  • However, if the repository and factory need to provide caching functionality, then repositoryWithCache should be injected.

For example, in the above case:

  • The SupplierService implements simple functionality, so injecting BasicDao is enough.
  • The OrderService implements the aggregation of orders and details, but due to large data volume, caching is not suitable. Therefore, repository should be injected.
  • The CustomerService implements the aggregation of users and addresses, and caching is required. Thus, repositoryWithCache should be injected.
  • The ProductService, although not aggregating, needs to supplement the supplier when querying a product. Therefore, repositoryWithCache should also be injected.

It is worth noting that the use of caching can also be determined by operations and maintenance personnel in the future by modifying the configuration, thereby improving the maintainability of the system.

Once the configuration is complete, the key is to map the domain model to the programming model. Developers first write the various domain objects. For example, when a product is associated with a supplier, in addition to adding the “supplier_id” attribute, a Supplier property must also be added:

public class Product extends Entity<Long> {

private static final long serialVersionUID = 7149822235159719740L;

private Long id;

private String name;

private Double price;

private String unit;

private Long supplier_id;

private String classify;

private Supplier supplier;

...
}

Note that in this framework, each domain object must implement the Entity interface so that the system knows which field is the primary key.

Next, configure vObj.xml to map the domain objects to the database:

<?xml version="1.0" encoding="UTF-8"?>

<vobjs>

<vo class="com.demo2.trade.entity.Customer" tableName="Customer">

<property name="id" column="id" isPrimaryKey="true"></property>

<property name="name" column="name"></property>

<property name="sex" column="sex"></property>

<property name="birthday" column="birthday"></property>

<property name="identification" column="identification"></property>

<property name="phone_number" column="phone_number"></property>

<join name="addresses" joinKey="customer_id" joinType="oneToMany" isAggregation="true" class="com.demo2.trade.entity.Address"></join>

</vo>

...

</vobjs>

Note that all domain objects that use the “join” or “ref” tags must use the repository or repositoryWithCache in their services in order to automatically supplement data or implement aggregation operations where necessary. Injecting BasicDao cannot achieve these operations.

In addition, the “name” configuration of each property corresponds to the name of the private attribute variable of the domain object, not the name of the GET method. For example, “product_id” is configured in OrderItem, not “productId”. Furthermore, this name must match the database field (as required by MyBatis, much to my chagrin).

With the above configuration, database operations for the services, as well as the laborious caching, repository, factory, aggregation, and supplementation operations in DDD, can be easily implemented. By encapsulating the underlying technology platform, business development personnel can focus on understanding the business, domain modeling, and business development based on the domain model, allowing DDD to be better, faster, and lower risk in actual projects.

Summary #

In this article, I explained the design and development thought process of the technology platform that supports DDD, including how to design a single Controller, a single Dao, and their application in the project.

In the next article, I will further explain how the framework is designed to query a single Service, the design of a generic repository and factory, and their support for microservice architecture.

Click here for the GitHub link to view the source code.