4

在为聚合重放事件时,您如何将Apply这些事件发送到子(非根)实体。

到目前为止,我对如何解决这个问题有两个想法。

  1. 获取聚合根以将事件路由到适当的实体
  2. 有一个聚合加载器,它通过 id 加载实体并直接应用它们的事件

你采取了哪些方法,哪些有效,哪些无效?


在我的搜索中,我只找到了两个讨论这个问题的链接(都采用了第一种方法):

复杂的聚合结构 (4.2.3.)
在事件溯源系统中协调其实体的聚合根

4

1 回答 1

7

作为上述讨论的参与者之一,我可以分享一些见解。如果您查看像 NCQRS 这样的项目,它以一种相当明确的方式将实体的构造和水合方式正式化,您会注意到这种方法具有一定的刚性。对于实体,我发现我将它们的存储作为聚合状态从简单的字段、列表或字典发展为专用的集合类,具体取决于维护它们的分散行为。较少的刚性带来了自由,在聚合边界内选择建模。我非常重视这一点。

再水化时的事件路由发生在聚合体的内部。这不是应该外化的IMO。有多种方法可以解决。在我自己的项目中,这就是我以非常轻量级的方式对其进行形式化的方式(此处仅显示实体):

/// <summary>
/// Base class for aggregate entities that need some basic infrastructure for tracking state changes on their aggregate root entity.
/// </summary>
public abstract class Entity : IInstanceEventRouter
{
    readonly Action<object> _applier;
    readonly InstanceEventRouter _router;

    /// <summary>
    /// Initializes a new instance of the <see cref="Entity"/> class.
    /// </summary>
    /// <param name="applier">The event player and recorder.</param>
    /// <exception cref="System.ArgumentNullException">Thrown when the <paramref name="applier"/> is null.</exception>
    protected Entity(Action<object> applier)
    {
        if (applier == null) throw new ArgumentNullException("applier");
        _applier = applier;
        _router = new InstanceEventRouter();
    }

    /// <summary>
    /// Registers the state handler to be invoked when the specified event is applied.
    /// </summary>
    /// <typeparam name="TEvent">The type of the event to register the handler for.</typeparam>
    /// <param name="handler">The state handler.</param>
    /// <exception cref="System.ArgumentNullException">Thrown when the <paramref name="handler"/> is null.</exception>
    protected void Register<TEvent>(Action<TEvent> handler)
    {
        if (handler == null) throw new ArgumentNullException("handler");
        _router.ConfigureRoute(handler);
    }

    /// <summary>
    /// Routes the specified <paramref name="event"/> to a configured state handler, if any.
    /// </summary>
    /// <param name="event">The event to route.</param>
    /// <exception cref="ArgumentNullException">Thrown when the <paramref name="event"/> is null.</exception>
    public void Route(object @event)
    {
        if (@event == null) throw new ArgumentNullException("event");
        _router.Route(@event);
    }

    /// <summary>
    /// Applies the specified event to this instance and invokes the associated state handler.
    /// </summary>
    /// <param name="event">The event to apply.</param>
    protected void Apply(object @event)
    {
        if (@event == null) throw new ArgumentNullException("event");
        _applier(@event);
    }
}

行为执行期间的事件路由遵循实体生命周期的轮廓:创建、修改和删除。在创建期间,Apply 方法会创建一个新实体(请记住,这是我们唯一可以更改状态的地方)并将其分配给字段,将其添加到列表、字典或自定义集合中。Apply 方法中的修改通常涉及查找受影响的一个或多个实体,或者将事件路由到实体,或者在实体上使用专用的内部方法来使用来自事件的数据应用更改。删除时,Apply 方法会进行查找并删除受影响的一个或多个实体。请注意这些 Apply 方法如何与补水阶段混合以达到相同的状态。

现在,重要的是要了解可能存在影响实体但不“属于”任何特定实体的其他行为(可以说没有一对一的映射)。这只是发生的事情,对一个或多个实体有副作用。正是这些东西让你欣赏实体设计的灵活性。

于 2013-11-02T11:21:08.510 回答