事件采购 - Domain Logic适合哪些地方?

时间:2017-01-12 11:37:47

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

我一直在观看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);
}

然后结果不会在任何地方结束,并且读取模型必须执行计算。

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

4 个答案:

答案 0 :(得分:3)

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

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

  • 购买X%折扣,无论是否有最低购买量
  • $ X关闭购买,无论是否有最低购买量

我们也可以做一些假设:

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

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

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

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

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

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

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

答案 1 :(得分:1)

例如,您可以举出第二个事件,例如包含计算结果的PromotionalCodeApplied

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

答案 2 :(得分:1)

  

我理解" 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   
}

答案 3 :(得分:0)

这是我喜欢从命令方法返回事件的地方。正如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;
}

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