【问题标题】:Event Sourcing - where does Domain Logic fit in?事件溯源——领域逻辑在哪里适合?
【发布时间】:2017-01-12 11:37:46
【问题描述】:

我一直在看 Greg Youngs 关于事件溯源的演讲,但我对业务逻辑的适用范围感到困惑。一个简单的例子:

1) Shopping Cart Created 
2) Item Added
3) Item Added
4) Promotional Code - 20% Off

促销代码是根据购物车项目计算得出的,并将结果存储为事件。我知道“PromotionalCodeAddedEvent”可能有意义,但数学在哪里发生?我在想:

public void AddPromotionalCode(PromotionalCode code)
{
    //perform calculation against shopping cart items. 
    //if valid
    ApplyChanges(cmd);
}

然后结果不会在任何地方结束,读取模型将不得不执行计算。

我没有完全理解这个概念,任何帮助都会很棒。

【问题讨论】:

标签: domain-driven-design business-logic event-sourcing


【解决方案1】:

像促销代码这样看似简单的事情实际上可能是一个相当复杂的用例。这主要是因为促销代码是(通常)由一个或多个业务用户维护的逻辑,而它本身也属于域内部。从这个意义上说,它是非传统的。有多种方法可以解决这个问题,我将概述的只是我个人的方法。

为了论证起见,假设您有一系列简单的已知促销代码,例如:

  • X% 折扣购买,有或没有最低购买量
  • $X 折扣购买,有或没有最低购买量

我们也可以做一些假设:

  • 促销代码有一个开始日期
  • 促销代码有结束日期

促销代码的应用可能很棘手。考虑我们定义的两个场景。 “$X Off Purchase”比较简单,因为它是一个固定的金额。然而,“X% Off Purchase”更为复杂。如果我们只有固定金额,我们可以在达到任何阈值后立即将折扣应用于购物车。对于基于百分比的折扣,如果用户要添加两件商品,添加促销代码,然后再添加另一件商品,则促销已经“应用”了。

因此,我个人只会将促销代码“附加”到购物车上。为什么?原因是在结账时,我可能会假设购物车将用于生成订单。在那之前,购物车的内容是流动的。假设折扣金额不固定,用户对购物车的操作将改变购物车的总价值以及总折扣。如果用户从购物车中移除一件或多件商品并且购物车的总价值低于应用折扣的阈值,它还可以使任一折扣无效。

所以,我实际上会涉及多个命令。基本上任何影响购物车价值的命令都可能改变折扣金额。为此,我会寻找要为以下命令重新计算的折扣金额:

  • 将商品添加到购物车
  • 从购物车中移除商品
  • 更改购物车中的商品数量
  • 将促销代码添加到购物车
  • 更改附加到购物车的促销代码

由于这些都是针对购物车的操作,我将在促销代码本身中计算折扣,并加入购物车中包含的数据。 感觉就像一个促销代码将是一个聚合,沿着这条路走下去。因此,我会让命令处理程序调用一个域服务,该服务可以为我的购物车提供所需的信息。该域服务将加载促销代码,我将能够传递该购物车中的行项目,以便促销代码告诉我计算出的折扣是多少。然后,我将生成事件,其中包含购物车的新值以及调整后的值(折扣)。沿着这条路走,根据购物车中的行项目计算折扣的逻辑是促销代码的责任。

相反,您可以将此责任放入购物车。不过,就个人而言,我觉得将域逻辑封装在促销代码本身中更有意义。我曾提到,您很可能会从购物车生成订单。通过将促销代码作为一个集合,并且它包含根据订单项应用折扣的逻辑,我们有一个事实,即我们如何计算订单项的折扣 - 无论是根据购物车还是在订单条款。

【讨论】:

  • Joseph,感谢您的回复,这是一个好主意,让我深思。
【解决方案2】:

例如,您可以引发第二个事件,例如 PromotionalCodeApplied,其中包含计算结果。

然后读取模型只需要使用预先计算的结果。

【讨论】:

  • 嗨,Alexander,添加每个商品后,ItemAddedEvent 是否也包含购物车的全价?
  • 是的,这可行,或者您可能想要使用单独的“ShoppingCardTotalPriceChanged”事件,该事件在添加商品或促销代码时引发。
【解决方案3】:

我知道“PromotionalCodeAddedEvent”可能有意义,但数学是在哪里发生的?

它应该发生在对购物车执行修改的命令中。每个这样的命令都会调用像 RecalculateTotals() 这样的方法,所有业务逻辑都将在其中托管。

考虑以下伪代码:

public void AddPromotionalCode(PromotionalCode code)
{
    var @event = new PromotionalCodeAdded(code);
    var amount = RecalculateTotalAmount(extraEvent: @event);
    @event.TotalAmount = amount;
    _eventStore.Publish(@event);
}

decimal RecalculateTotalAmount(IEvent extraEvent)
{
    var relatedEventTypes = new[] { typeof(PromotionalCodeAdded), typeof(ShoppingCartCreated), typeof(ItemAdded) };
    var events = _eventStore.ReadEventsOfTypes(relatedEventTypes);
    var events = events.Concat(new[] { extraEvent });

    //calculation logic goes here based on all related events   
}

【讨论】:

    【解决方案4】:

    这是我喜欢从命令方法返回事件的地方。正如 Alexander Langer 所说,您将应用“数学”并返回相关事件:

    public PromotionalCodeApplied AddPromotionalCode(PromotionalCode code)
    {
        //perform calculation against shopping cart items. 
        var promotionalCodeApplied = new PromotionalCodeApplied(code.VoucherNumber, discountAmount, DateTime.Now);
    
        On(promotionalCodeApplied);
    
        return promotionalCodeApplied;
    }
    
    public void On(PromotionalCodeApplied promotionalCodeApplied)
    {
        _voucherNumber = promotionalCodeApplied.VoucherNumber;
        _discountAmount = promotionalCodeApplied.DiscountAmount;
        _discountDate = promotionalCodeApplied.DateAppllied;
    }
    

    现在您的读取模型可以访问相关值。

    【讨论】:

      猜你喜欢
      • 2021-07-25
      • 2017-11-06
      • 1970-01-01
      • 2015-08-29
      • 2018-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-08
      相关资源
      最近更新 更多