The patterns in DDD – Business rules are managed using the Specification

The reason many developers want to use DDD in their projects is to manage the complexity of the business and avoid code and architecture corruption that becomes difficult to maintain as the business rules become more complex. System complexity is reflected in multiple levels, such as tedious processes, complex validation rules, data diversity, etc. DDD provides different modes to deal with different levels of complexity. Today’s article will focus on how to use the Specification pattern to solve the complexity related to “business rules”.

Business rules

Before introducing the Specification pattern, let’s clarify what a “business rule” is. As a developer, these are some of the most common things you do on a daily basis.

  • Verify that certain states of the business object are valid, such as whether the current account is enabled, whether the account balance is sufficient, and whether the accident date is within the valid time of the policy.
  • Filter out the qualified result set from the collection of business objects, such as a record of purchasing a discounted product from a user’s transaction history.
  • Check whether a newly created business object meets some business criteria, such as a newly created order, which corresponds to a customer and merchant who are legitimate system users.

For the sake of subsequent discussion, we narrow the concept of business rules into the three types mentioned above. The next question is how are these business rules implemented in the system?

The original approach was to write a number of small methods to implement the validation or filtering logic, distributed among the Domain Service classes. The disadvantages of this approach are obvious. One is that it is difficult to manage, as more and more business rules become available, these scattered methods cannot be reused, and developers cannot manage them centrally. Secondly, the business knowledge is lost. Most of these methods are short and simple, but these rules actually contain a lot of business knowledge. If they are scattered in different Domain services, it is easy to lose these business knowledge in the subsequent development process.

Another approach that builds on this, and is used more often in real projects, is to write a variety of Validator classes, each of which contains a large number of validation methods for domain objects. This approach addresses the first problem to some extent by centralizing validation methods in a specific class, which makes it easier for developers to maintain and extend. Here’s a sample code for a typical Validator:

Public class CustomerValidator {public static Boolean isVIP(Customer Customer) {... }}

But it doesn’t do much to transfer business knowledge. The Validator classes are more like utility classes and have nothing to do with the domain layer. Validator support is not so good when it comes to reusing these validators, especially when several validation rules are grouped together in a logical relationship such as AND, OR.

Another approach is to implement these validation rules in the domain object as a method of the domain object. See the following code:

Public class Customer {public Boolean isVIP() {... }}

The advantage of this approach is that the validation rules are so tightly tied to the domain objects that developers can easily see them. The drawback is that your domain objects become increasingly bloated with lots of validation methods like this that obscure the core business rules.

Is there a better way to do it? The Specification pattern provides a good alternative.

Specification model

DDD considers these rules to be pure “verbs” and therefore requires a separate model, which should be simple “value objects”. An example of a Specification interface is as follows:

public interface Specification<T> {
    boolean isMatch(T domainObject);
}

Then we can realize the verification of VIP customers:

Public class VipCustomerSpecification <Customer> {@Override public Boolean isMatch(Customer Customer) {... }}

By implementing the Specification interface, we can extend different validation logic to different domain objects, and these classes are all reusable. At the same time, these specifications can be used as basic elements for any combination, combining more complex validation rules and screening logic. For example, in the example below, we encapsulate all of the higher level domain logic in CustomerSpecifications, which provides specific functionality by combining individual specifications.

public class CustomerSpecification { public boolean isSpecialCustomer(List<Specification<Customer>> specifications, The Customer the Customer) {... }}

In the above method of isSpecialCustomer, a series of specifications required for validation can be passed in and verified successively. This method is also convenient for expansion and reuse, and it is more closely combined with the domain model.

Filter the data using the Specification mode

Screening qualified results from a data set is also a business rule that often needs to be implemented in daily development. So what do we do in general?

Suppose we need to filter out the order records for a customer’s name from January to February. One of the simplest and most common ways to do this is through SQL(assuming the data is stored in a relational database). For example, through the following SQL query:

select * from t_order where t_order.customer_id = ? and t_order.created_at >= ? and t_order.created_at <= ?

It seems natural to use SQL to directly implement filtering logic in queries, but the problem is that this part of logic should belong to the domain layer, but now leaked to the data layer, resulting in the difficulty of maintenance greatly increased, many business systems to the later stage are struggling with large sections of SQL. Instead of writing a logical Service layer, the Domain layer becomes a facade, degenerated into pure data objects, passed around, no different from DTOs. The Specification pattern provides a good solution.

We can have the following code:

public class OrderSpecifications { public Specification<Order> inPeriod(LocalDateTime beginTime, LocalDateTime endTime) {... }}

Instead of filtering the data directly, OrderSpecifications creates a specific Specification object with input parameters. The OrderRepository object then accepts the Specification as a parameter to perform the actual data filtering. This separates the query logic from the filtering logic.

This issue to consider is the performance, if according to the practice of SQL, then on the database side will complete the data query and filter, returned to the application of the data volume not very big, but if you use the Specification model, then, is the filter in the memory, in the case of large amount of data will inevitably encounter the problem of performance. I have indeed encountered similar problems in the project before. Due to the large amount of query result data and the need for paging display, all these were completed in memory, which caused a large amount of concurrency, memory consumption and response speed became very poor.

There are two ways to do this. One, as described in the DDD book, is to provide an asSQL() -like method on the Specification interface that converts the current Specification object into an SQL statement. In my practice of some projects, this approach is more troublesome, which can be considered as a different way of splicing SQL, but the effect is not good and difficult to deal with. The other is the ability to use an ORM framework or other advanced framework. Spring Data JPA, for example, provides a JPA-based Specification pattern for querying, which is very easy to use and is something I recommend you try on your project.

summary

Specification pattern is a very practical pattern, which can easily help developers manage and abstract business rules such as state check and data filtering. Moreover, the difficulty of actual operation is low, and there is little dependence on the external. Therefore, it is a highly recommended DDD best practice.

Feel free to follow my WeChat ID blog for more high quality articles