命令处理程序或事件处理程序中的域逻辑?

Domain logic in command handler or event handler?

我正在使用 cqrs 和 ddd 来构建我的应用程序。

我有一个帐户实体、一个交易实体和一个交易行实体。一个交易包含多个交易行。每个 transactionLine 都有一个金额并指向一个帐户。

如果用户在已经有一个交易线指向与新交易线相同的帐户的交易中添加一个交易线,我想简单地将新交易线金额添加到现有交易线,以防止交易有两个指向同一个帐户的交易行。

例如:

Before command :
    transaction
        transactionLine1(amount=100, account=2)
        transactionLine2(amount=50, account=1)

Command :
    addNewTransaction(amount=25, account=1)

Desired result :
    transaction
        transactionLine1(amount=100, account=2)
        transactionLine2(amount=75, account=1) // Add amount (50+25) instead of two different transactionLines

instead of

transaction
    transactionLine1(amount=100, account=2)
    transactionLine2(amount=50, account=1)
    transactionLine3(amount=25, account=1) // Error, two different transactionLines point to the same account

但我想知道最好是在命令或事件处理程序中处理这个问题。

如果这种情况由命令处理程序处理

Before command :
    transaction
        transactionLine1(amount=100, account=2)
        transactionLine2(amount=50, account=1)

Command :
    addNewTransaction(amount=25, account=1)  // Detects the case

Dispatches event
    transactionLineAmountChanged(transactionLine=2, amount=75)
  1. 收到 AddTransactionLine 命令

  2. 检查同一账户的新transactionLine的交易中是否存在transactionLine

  3. 如果是,发出 transactionAmountChangedEvt 事件

  4. 否则,发出 transactionAddedEvt 事件

  5. 相应的事件处理程序处理正确的事件

如果这种情况由事件处理程序处理

Before command :
    transaction
        transactionLine1(amount=100, account=2)
        transactionLine2(amount=50, account=1)

Command :
    addNewTransaction(amount=25, account=1)

Dispatches event
    transactionLineAdded(transactionLine=3, amount=25)

Handler  // Detects the case
    transactionLine2.amount = 75 
  1. 收到 AddTransactionLine 命令

  2. 已调度 TransactionLineAdded 事件

  3. 已处理 TransactionLineAdded

  4. 检查添加的交易的 transactionLine 是否指向与此帐户中现有 transactionLine 相同的帐户

  5. 如果是这样,只需将新交易行的金额添加到现有交易行

  6. 否则,添加一个新的交易行

命令和事件都不应该包含域逻辑,只有域应该包含域逻辑。在您的域中,聚合根代表事务边界(不是您的事务实体,而是逻辑事务)。在命令或事件中处理逻辑会绕过这些边界并使您的系统非常脆弱。

该逻辑的正确位置是事务实体。

所以最好的方法是

AddTransactionCommand finds the correct transaction entity and calls
Transaction.AddLine(...), which does the logic and publishes events of what happened
TransactionLineAddedEvent or TransactionLineChangedEvent depending on what happened.

将命令和事件视为 'containers'、'dtos' 您将需要的数据,以便滋润您的 AggregateRoots 或发送到世界(事件)以供其他限界上下文使用消耗它们。而已。除了您的 AggregateRoots、Entities 和 Value Objects 之外,与您的域严格相关的任何其他操作都没有位置。

您可以通过使用 DataAnnotations 或您自己的验证方法实现向您的命令添加一些 'validation'。

public interface ICommand
{
    void Validate();
}

public class ChangeCustomerName : ICommand
{
    public string Name {get;set;}

    public void Validate()
    {
        if(Name == "No one")
        {
            throw new InvalidOperationException("Sorry Aria Stark... we need a name here!");
        }
    }
}