问题描述
我正在尝试使用域驱动设计为在线商店目录建模。
我现在有三个主要概念:产品,类别,属性。
属性是产品的特征。例如颜色,重量,cpu内核数等。有些属性的值是固定的,例如“ condition”(条件)-可以是新的也可以使用。其中一些值在一定范围内,例如“ cpu核心数”。有些是自由创建的,例如“颜色”。
类别具有该类别中的每个产品都必须具有的必填属性,以及可选属性。类别可以具有父类别。
产品属于一个类别,必须是叶子类别(无子类别)。
现在,我的问题是将这三个概念建模为聚合。
产品将具有其属性值(每个都有其父ID到属性AR)。属性将具有不同的类型(固定,自由选择,范围)。类别将具有所需属性ID的列表,以及ID的列表
这里的问题是,每当我需要创建新产品时,都需要检查它是否具有所有必需的属性,检查值,然后存储产品。该验证将跨越三个聚合。应该去哪里?应该是域服务吗?
其他选项是拥有2个AR。类别及其产品和属性。这里的问题还是验证添加到产品中的单个属性的正确值。我在这里看到的另一个巨大问题是,我应该从存储库中获取整个集合。鉴于该类别可以包含数百种产品,所以我认为这不是一个好主意。但是,从概念上讲,这是很有意义的,因为如果我想删除一个类别,则还应该删除其所有产品。
我在这里缺少什么?
解决方法
这里的问题是,每当我需要创建新产品时,都需要检查它是否具有所有必需的属性,检查值,然后存储产品。该验证将跨越三个聚合。应该去哪里?应该是域服务吗?
通常的答案是信息的检索(又名I / O)是在应用服务中完成的。然后,像其他输入一样,将这些信息的副本传递到域模型中。
当我们从不同位置获取输入时,单个“事务”可能包括对聚合方法的多次调用。
这些信息副本通常被视为data on the outside-我们在这里有数据的未锁定副本;当我们使用该副本时,权威副本可能正在更改。
如果您发现自己“我在这里使用数据时,那里的数据的权威副本不允许更改”-这是一个很大的危险信号,即(a)您实际上并不了解您的真实信息数据限制或(b)您未正确绘制聚合边界。
现实世界中的大多数数据都是外部数据(Bob的帐单地址可能会在未经您允许的情况下发生更改-数据库中的内容是Bob帐单地址在过去某个时间的缓存副本)。 / p>
,在“实施域驱动的设计” 中, Vaugh Vernon 使用“规范模式”来处理实体/集合验证。在不引用整个章节的情况下,您会有不同的可能性:(在我的示例中使用了Java,希望您能获得总体思路)
验证属性/属性
如果是逐个字段进行简单的验证过程,则在setter方法中分别验证每个属性。
class Product {
String name;
public Product(String name) {
setName(name);
}
public void setName(String name) {
if(name == null) {
throw new IllegalArgumentException("name cannot be null");
}
if(name.length() == 0) {
throw new IllegalArgumentException("name cannot be empty");
}
this.name = name;
}
}
验证整个对象
如果必须验证整个对象,则可以使用一种规范来帮助您。为了避免实体承担过多的责任(应对状态并进行验证),可以使用验证器。
a. Create a generic Validator class,and implement it for your Product Validator. Use a NotificationHandler to deal with your validation error (exception,event,accumulating errors and then sending them ? up to you) :
public abstract class Validator {
private ValidationNotificationHandler notificationHandler;
public Validator(ValidationNotificationHandler aHandler) {
super();
this.setNotificationHandler(aHandler);
}
public abstract void validate();
protected ValidationNotificationHandler notificationHandler() {
return this.notificationHandler;
}
private void setNotificationHandler(ValidationNotificationHandler aHandler) {
this.notificationHandler = aHandler;
}
}
NotificationHandler
是一个接口,您可以根据验证错误处理方面的要求来实现该接口。这是Vaugh Vernon提出的界面:
public interface ValidationNotificationHandler {
public void handleError(String aNotificationMessage);
public void handleError(String aNotification,Object anObject);
public void handleInfo(String aNotificationMessage);
public void handleInfo(String aNotification,Object anObject);
public void handleWarning(String aNotificationMessage);
public void handleWarning(String aNotification,Object anObject);
}
b。使用特定的验证器ProductValidator
实现此类:
public class ProductValidator extends Validator {
private Product product;
public ProductValidator(Product product,ValidationNotificationHandler aHandler) {
super(aHandler);
this.setProduct(product);
}
private void setProduct(Product product) {
this.product = product;
}
@Override
public void validate() {
this.checkForCompletness();
}
private void checkForCompletness() {
if(product.getName().equals("bad name") && anotherCondition()) {
notificationHandler().handleError("This specific validation failed");
}
...
}
}
然后,您可以使用validate方法更新实体,该方法将调用此验证器来验证整个对象:
public class Product {
private String name;
public Product(String name) {
setName(name);
}
private void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
if (name.length() == 0) {
throw new IllegalArgumentException("Name cannot be empty");
}
this.name = name;
}
// Here is the new method to validate your object
public void validate(ValidationNotificationHandler aHandler) {
(new ProductValidator(this,aHandler)).validate();
}
}
验证多个聚合
最后,这是您的直接关注点,如果您想验证多个聚合以使其具有连贯性,则建议创建一个域服务和一个特定的验证器。域服务既可以注入存储库以查找不同的聚合,也可以一切都由应用程序层创建,然后将不同的聚合作为方法参数注入:
public class ProductCategoryValidator extends Validator {
private Product product;
private Category category;
public ProductCategoryValidator(Product product,Category category,ValidationNotificationHandler aHandler) {
super(aHandler);
this.setProduct(product);
this.setCategory(category);
}
private void setCategory(Category category) {
this.category = category;
}
private void setProduct(Product product) {
this.product = product;
}
@Override
public void validate() {
this.checkForCompletness();
}
private void checkForCompletness() {
// Count number of attributes,check for correctness...
}
}
以及将调用验证程序的域服务
public class ProductService {
// Use this is you can pass the parameters from the client
public void validateProductWithCategory(Product product,ValidationNotificationHandler handler) {
(new ProductCategoryValidator(product,category,handler)).validate();
}
// Use This is you need to retrieve data from persistent layer
private ProductRepository productRepository;
private CategoryReposiory categoryReposiory;
public ProductService(ProductRepository productRepository,CategoryReposiory categoryReposiory) {
this.productRepository = productRepository;
this.categoryReposiory = categoryReposiory;
}
public void validate(String productId,ValidationNotificationHandler handler) {
Product product = productRepository.findById(productId);
Category category = categoryReposiory.categoryOfProductId(productId);
(new ProductCategoryValidator(product,handler)).validate();
}
}
就像我说的那样,我认为您可能对解决方案3感兴趣。您已经猜到了,可以使用域服务。但是,请添加特定的验证器,以确保不会混淆“职责”。