DDD中的产品,类别,属性建模

问题描述

我正在尝试使用域驱动设计为在线商店目录建模。

我现在有三个主要概念:产品,类别,属性

属性是产品的特征。例如颜色,重量,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感兴趣。您已经猜到了,可以使用域服务。但是,请添加特定的验证器,以确保不会混淆“职责”。