简体   繁体   English

DDD 延迟验证或始终有效的聚合根验证

[英]Aggregate root validation on DDD deferred validation or always valid

I'm confused about aggregate root validation on DDD.我对 DDD 上的聚合根验证感到困惑。 Where should i validate the children states when a child depends on another child?当一个孩子依赖另一个孩子时,我应该在哪里验证孩子的状态?

For example, i have a billing context, which has the bill aggregate root.例如,我有一个计费上下文,其中包含帐单聚合根。 This root has a list of items, a list of taxes and a list of invoices (a bill can have one or more invoices).这个根有一个项目清单、一个税金清单和一个发票清单(一张账单可以有一张或多张发票)。 The sum of taxes should be lower or equal than the sum of items and the sum of invoices should be lower or equal than the sum of items - taxes.税款总和应低于或等于项目总和,发票总和应低于或等于项目总和 - 税费。 And i can't have two invoices with same date and duplicated items.而且我不能有两张具有相同日期和重复项目的发票。

I read that exists two approaches for the aggregate root state validation, the always valid and the deferred validation:我读到存在两种方法用于聚合根 state 验证,始终有效和延迟验证:

I thought of implementing an deferred validation using:我想过使用以下方法实现延迟验证:

class BillService {
    public Bill createBill(BillRequest billRequest) {
        Bill bill = new Bill();
        billRequest.getItems().forEach(item -> bill.addItem(item));
        billRequest.getTaxes().forEach(tax -> bill.addTax(tax));
        billRequest.getInvoices().forEach(invoice -> bill.addInvoice(invoice));
        if (bill.isInvoicesSumValid()) {
            throw new ...
        }

        return bill;
    }
}

class Bill {
    public void addItem(String itemId) {
        if (invoices.stream().anyMatch(i -> i.getId().equals(itemId))) {
            throw new ...
        }
        items.add(new BillItem(itemId, amount));
    }
    public void addInvoice(BigDecimal amount, LocalDate date) {
        if (invoices.stream().anyMatch(i -> i.getDate().equals(date))) {
            throw new ...
        }
        invoices.add(new BillInvoice(amount, date))
    }
}

or the always valid approach:或始终有效的方法:

class Bill {
    private List<BillItem> billItems;
    private List<BillTax> billTaxes;
    private List<BillInvoices> billInvoices;
    public Bill(..., List<BillItem> billItems, List<BillTax> billTaxes, List<BillInvoices> billInvoices) {
        ... //setting many other attributes
        this.billItems = billItems;
        this.billTaxes = billTaxes;
        this.billInvoices = billInvoices;

        validateDuplicatedItem();
        validateDuplicatedDateInvoice();
        validateInvoiceSum();
    }
}

Using the deferred validation will make the aggregate root be on an invalid state, but seems easier to understand.使用延迟验证将使聚合根位于无效的 state 上,但似乎更容易理解。 Using the always valid approach will make the constructor giant and harder to understand.使用始终有效的方法将使构造函数变得庞大且难以理解。

Is there another way to solve this?还有其他方法可以解决这个问题吗?

Both of these are technically valid, but there is a subtle difference between the two samples.这两个在技术上都是有效的,但两个样本之间存在细微差别。 In the always valid approach it is impossible to create a bill without all the proper information.在始终有效的方法中,如果没有所有适当的信息,就不可能创建账单。 In the deferred validation approach it is impossible for the BillService to create a bill with invalid info, but something else could.在延迟验证方法中,BillService 不可能创建包含无效信息的账单,但其他方式可以。 Your business rules should tell you which way to go.您的业务规则应该告诉您通往 go 的方式。

If the many params in the Bill constructor bothers you, there's nothing stopping you from passing in the BillRequest to the Bill constructor.如果 Bill 构造函数中的许多参数困扰您,那么没有什么能阻止您将 BillRequest 传递给 Bill 构造函数。 In fact that approach would be easier for callers to work with because you could have an IsValid method on the BillRequest which callers could use to make sure their request is valid before trying to create the bill.事实上,调用者可以更轻松地使用这种方法,因为您可以在 BillRequest 上使用 IsValid 方法,调用者可以使用该方法在尝试创建账单之前确保他们的请求有效。 It's better to validate the incoming data as close to the source as possible.最好在尽可能靠近源的地方验证传入数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM