事件采购:将事件重新聚合到子实体

时间:2013-10-14 10:19:50

标签: c# event-sourcing

当涉及为聚合重播事件时,您如何Apply这些事件到子(非根)实体。

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

  1. 获取聚合根以将事件路由到适当的实体
  2. 拥有一个按ID加载实体并直接应用其事件的聚合加载器
  3. 您采取了哪些方法,哪些方法有效,哪些无效?


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

    Complex Aggregate structures (4.2.3.)
    Aggregate roots coordinating their entities in an event sourcing system

1 个答案:

答案 0 :(得分: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方法如何与再水化阶段混合以达到相同的状态。

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