background
Evens DDD emphasizes Model Driven Design, that is, model-driven Design. So in the real world, what do we model? – Model building blocks.
I. Entity
Entities reflect objects in the real world. These objects must meet two conditions (1) have unique identifiers (2) have variability unique identifiers are measures used to distinguish other entities. For example, for Person objects, ID cards can be used as unique identifiers. The product code can be used as a unique identification. Mutability means that the object has state. When the state changes, it can be said that it has mutability, such as Order object Order, Order state has unpaid, paid, waiting for delivery, etc. These states will change
In our daily development, we often use table structures to generate one-to-one Entity classes through code tools. These classes only have properties and no behavior, so they are called anaemic models. These models cannot reflect the real world, but put the behavior logic in Service classes, which is completely transactional scripting development.
public class Product extends Entity {
private Set<ProductBacklogItem> backlogItems;
private String description;
private ProductDiscussion discussion;
private String discussionInitiationId;
private String name;
private ProductId productId;
private ProductOwnerId productOwnerId;
privateTenantId tenantId; .// Behavior => initializes a discussion
public void initiateDiscussion(DiscussionDescriptor aDescriptor) {
if (aDescriptor == null) {
throw new IllegalArgumentException("The descriptor must not be null.");
}
if (this.discussion().availability().isRequested()) {
this.setDiscussion(this.discussion().nowReady(aDescriptor));
DomainEventPublisher
.instance()
.publish(new ProductDiscussionInitiated(
this.tenantId(),
this.productId(),
this.discussion())); }}... }Copy the code
Value Object
The value object is also an object that reflects the real world, but it is essentially different from Entity in that the value object has no unique identification and no state variability. From these two points, it can be shown that the value object and the object represented by Entity are different.
Value object, has the following conditions: (1) invariance. Value objects have no state, only properties, and when the properties change, the whole is replaced, i.e. (2) integrity. For the Product entity in the above code, when the properties of ProductDiscussion (which is a value object) change, instead of updating the properties of ProductDiscussion, we take the perspective of the Product entity. First remove the old ProductDiscussion, and then create a new ProductDiscussion and replace it. (3) No side effect behavior. CQRS in flexible design describes this as a query method, which simply values the behavior of the object without changing the state of the object
Domain Service
What is not domain service?
In our normal development, the business logic is placed in the Service class, including transaction control, task assignment and other actions, which should be the application Service (application Service, more about later), so the domain service and application service are confused.
In general, domain services focus on domain-specific businesses and are stateless, making it difficult to tell whether to use a domain service or not. Implementing Domain-Driven Design lists 3 points that can be used in this case:
(1) Perform a significant business operation; (2) Transform domain objects; (3) Multiple domain objects are computed as input, resulting in a value object
The second and third points are easy to understand, object conversion and creating value objects. But the first point is “significant” business operations, what is “significant”?
public class AuthenticationService extends AssertionConcern {
private EncryptionService encryptionService;
private TenantRepository tenantRepository;
privateUserRepository userRepository; .public UserDescriptor authenticate( TenantId aTenantId, String aUsername, String aPassword) {
this.assertArgumentNotNull(aTenantId, "TenantId must not be null.");
this.assertArgumentNotEmpty(aUsername, "Username must be provided.");
this.assertArgumentNotEmpty(aPassword, "Password must be provided.");
UserDescriptor userDescriptor = UserDescriptor.nullDescriptorInstance();
Tenant tenant = this.tenantRepository().tenantOfId(aTenantId);
if(tenant ! =null && tenant.isActive()) {
String encryptedPassword = this.encryptionService().encryptedValue(aPassword);
User user =
this.userRepository()
.userFromAuthenticCredentials(
aTenantId,
aUsername,
encryptedPassword);
if(user ! =null&& user.isEnabled()) { userDescriptor = user.userDescriptor(); }}returnuserDescriptor; }... }Copy the code
Let’s look at an example where the system must authenticate the User, and only when Tenant is active, using the domain service. First, it’s a business process, and second, it’s not a good business process for User, it’s not a good business process for Tenant, and as for “significant,” it’s a bit of a matter of opinion, at least through constant scrutiny and reflection on the business domain.
Therefore, I personally think that the domain service should be used by some business process and can be considered if it is suitable for any Entity or Value Object.
Iv. Aggregate
Aggregation is composed of entity and value objects within consistency boundaries. Consistency is an important concept of aggregation.
As shown above, Product is an aggregation, and the access between aggregation and aggregation is dependent on a unique identifier. Here, ProductId can be passed, for example, an Order aggregation Order. If Order refers to Product, it can refer to ProductId, which can reduce the size of the aggregation. You can also reduce the number of garbage collected objects in memory.
For consistency, an example of order integrity is given in the Events DDD:
Have a purchase order, purchase there is a limit, such as purchase just $1000, so a few people want to buy n items (also called purchasing items), price of each item is not the same, no matter how many quantity and how much these people purchase amount of goods, but must satisfy a condition, the amount of the purchase items, cannot be greater than the purchase amount, So there’s a consistency rule, or a fixed rule.
Design aggregation has the following principles:
(1) Design small aggregation, but how much should be measured depends on the actual situation; (2) Reference other aggregations through unique identifiers; (3) Use final consistency outside the boundary. The consistency rule mentioned above is actually guaranteed within the scope of one thing, that is, one aggregation corresponds to one thing. Strong consistency needs to be guaranteed within one aggregation, but final consistency is used between different aggregations, which can be achieved by using domain events (to be analyzed in the following article).
5. Module
Modules, also known as packages in Java, should themselves be part of the common language and should have high cohesion within the module.
ackage com.saasovation.identityaccess;
package com.saasovation.collaboration;
package com.saasovation.agilepm;
Copy the code
V. Repository
The concept of repository is also encountered in daily development, such as Spring’s @Repository annotation. However, There is a difference between Repository and DAO. DAO views problems from the perspective of database tables and provides CRUD operations. Resource repositories look at things from a domain perspective and are more object oriented.
public interface ProductRepository {
public Collection<Product> allProductsOfTenant(TenantId aTenantId);
public ProductId nextIdentity(a);
public Product productOfDiscussionInitiationId(TenantId aTenantId, String aDiscussionInitiationId);
public Product productOfId(TenantId aTenantId, ProductId aProductId);
public void remove(Product aProduct);
public void removeAll(Collection<Product> aProductCollection);
public void save(Product aProduct);
public void saveAll(Collection<Product> aProductCollection);
}
Copy the code
Not all domain objects need repository. Strictly speaking, only aggregation needs repository. There is a big difference between DDD and transactional scripting. In DDD, however, all update operations are aggregated via repository to save products, i.e. ProductReposity. Save (product), which is a unified way to update or add objects or attributes.
Vi. Factory
Factories should be the most familiar, such as abstract Factory patterns, factory methods, and Builder patterns in design patterns. In DDD, the factory is part of the domain design, responsible for creating complex objects and aggregations, and the factory itself does not take care of the business logic.
public class NotificationLogFactory {
privateEventStore eventStore; ...public NotificationLog createCurrentNotificationLog(a) {
return this.createNotificationLog(
this.calculateCurrentNotificationLogId(eventStore));
}
public NotificationLog createNotificationLog( NotificationLogId aNotificationLogId) {
long count = this.eventStore().countStoredEvents();
NotificationLogInfo info = new NotificationLogInfo(aNotificationLogId, count);
return this.createNotificationLog(info);
}
Copy the code
The relationship between the building blocks
Reference:
1. Eric Evans, Domain-Driven Design: Tackling Software Core Complexity
2. Vaughn Vernon, Implementing Domain-Driven Design
- Github.com/VaughnVerno…