在哪里提出依赖于持久性的域事件 - 服务,存储库或UI?

Jos*_*son 32 asp.net-mvc domain-driven-design nservicebus domain-events asp.net-mvc-3

我的ASP.NET MVC3/NHibernate应用程序需要触发和处理与我的域对象相关的各种事件.例如,Order对象可能包含OrderStatusChanged或等事件NoteCreatedForOrder.在大多数情况下,这些事件会导致发送电子邮件,因此我不能将它们留在MVC应用程序中.

我已经阅读了Udi Dahan的Domain Events以及关于如何做这类事情的许多其他想法,我决定使用一个处理事件消息的基于NServiceBus的主机.我做了一些概念验证测试,这似乎运作良好.

我的问题是应用程序层应该实际引发事件.我不想在有问题的对象成功保留之前触发事件(如果持久性失败,则无法发送创建注释的电子邮件).

另一个问题是,在某些情况下,事件与聚合根下面的对象相关联.在上面的示例中,Note通过将a 添加到Order.Notes集合并保存订单来保存a .这带来了一个问题,因为它很难评估Order保存时应该触发哪些事件.我想避免在保存更新的副本之前必须拉出对象的当前副本并查找差异.

  • 用户界面提出这些事件是否合适?它知道发生了什么事件,并且只有在成功使服务层保存对象后才能触发它们.让控制器触发域事件似乎有些不对劲.

  • 存储库是否应该在成功持久后触发事件?

  • 我是否应该完全分离事件,让存储库存储一个Event对象,然后由轮询器服务接收该对象,然后将其转换为NServiceBus的事件(或直接从轮询器服务处理)?

  • 有一个更好的方法吗?也许让我的域对象排队只有在持久化对象后由服务层触发的事件?

  • 更新:我有一个服务层,但让它通过比较过程以确定在保存给定聚合根时应该触发哪些事件似乎很麻烦和过分.由于其中一些事件是粒状的(例如"订单状态已更改"),我认为我必须检索对象的数据库副本,比较属性以创建事件,保存新对象,然后将事件发送到NServiceBus保存操作成功完成.

更新

在我下面发布的答案(下面的方法)之后,我最终做的是在我的域实体中构建一个EventQueue属性List<IDomainEvent>.然后我添加了事件,因为对域的更改是值得的,这使得我可以将逻辑保留在域中,我认为这是合适的,因为我根据实体内部的内容触发事件.

然后,当我将对象保留在服务层中时,我处理该队列并实际将事件发送到服务总线.最初,我计划使用使用身份PK的遗留数据库,因此我必须对这些事件进行后处理以填充实体的ID,但我最终决定切换到Guid.Comb允许我跳过该步骤的PK.

Yan*_*ian 9

我的解决方案是您在域层和服务层中引发事件.

你的域名:

public class Order
{
    public void ChangeStatus(OrderStatus status)
    {
        // change status
        this.Status = status;
        DomainEvent.Raise(new OrderStatusChanged { OrderId = Id, Status = status });
    }

    public void AddNote(string note)
    {
        // add note
        this.Notes.Add(note)
        DomainEvent.Raise(new NoteCreatedForOrder { OrderId = Id, Note = note });
    }
}
Run Code Online (Sandbox Code Playgroud)

您的服务:

public class OrderService
{
    public void SubmitOrder(int orderId, OrderStatus status, string note)
    {
        OrderStatusChanged orderStatusChanged = null;
        NoteCreatedForOrder noteCreatedForOrder = null;

        DomainEvent.Register<OrderStatusChanged>(x => orderStatusChanged = x);
        DomainEvent.Register<NoteCreatedForOrder>(x => noteCreatedForOrder = x);

        using (var uow = UnitOfWork.Start())
        {
            var order = orderRepository.Load(orderId);
            order.ChangeStatus(status);
            order.AddNote(note);
            uow.Commit(); // commit to persist order
        }

        if (orderStatusChanged != null)
        {
            // something like this
            serviceBus.Publish(orderStatusChanged);
        }

        if (noteCreatedForOrder!= null)
        {
            // something like this
            serviceBus.Publish(noteCreatedForOrder);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Roy*_*tus 7

域事件应该在......域中引发.这就是为什么他们是域事件.

public void ExecuteCommand(MakeCustomerGoldCommand command)
{
    if (ValidateCommand(command) == ValidationResult.OK)
    {
        Customer item = CustomerRepository.GetById(command.CustomerId);
        item.Status = CustomerStatus.Gold;
        CustomerRepository.Update(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

(然后在Customer类中,以下内容):

public CustomerStatus Status
{
    ....
    set
    {
        if (_status != value)
        {
            _status = value;
            switch(_status)
            {
                case CustomerStatus.Gold:
                    DomainEvents.Raise(CustomerIsMadeGold(this));
                    break;
                ...
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Raise方法将事件存储在Event Store中.它还可以执行本地注册的事件处理程序.

  • 这就是我开始的地方,但假设在`CustomerRepository.Update(item)`期间出现错误并且持久性失败.我不希望那个事件被处理.这就是为什么我想知道其他选择是什么.在存储库中执行它是一个坏主意,就像在UI中一样.如果不是因为我不喜欢NH倾向于确保"相同"对象的两个实例是相同的,那么在服务中执行它将是很好的,这使我无法对更改前和更改后的比较进行比较服务中的对象属性. (6认同)

Jos*_*son 1

事实证明,该解决方案基于在 NHibernate 会话对象上实现这些扩展方法。

我可能对问题的措辞有点不清楚。架构问题的全部原因是 NHibernate 对象始终处于相同状态,除非您手动取消代理它们并经历各种阴谋。这是我不想做的事情来确定哪些属性已更改以及因此要触发哪些事件。

在属性设置器中触发这些事件是行不通的,因为这些事件只能在更改持久化后触发,以避免在可能最终失败的操作上触发事件。

所以我所做的就是向我的存储库库添加一些方法:

public bool IsDirtyEntity(T entity)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyEntity(entity);
}

public bool IsDirtyEntityProperty(T entity, string propertyName)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyProperty(entity, propertyName);
}
Run Code Online (Sandbox Code Playgroud)

然后,在我的服务Save方法中,我可以执行类似的操作(请记住我在这里使用 NServiceBus,但如果您使用 Udi Dahan 的域事件静态类,它将以类似的方式工作):

var pendingEvents = new List<IMessage>();
if (_repository.IsDirtyEntityProperty(order, "Status"))
    pendingEvents.Add(new OrderStatusChanged()); // In reality I'd set the properties of this event message object

_repository.Save(order);
_unitOfWork.Commit();

// If we get here then the save operation succeeded
foreach (var message in pendingEvents)
    Bus.Send(message);
Run Code Online (Sandbox Code Playgroud)

因为在某些情况下,Id实体的 id 在保存之前可能不会设置(我使用的是Identity整数列),所以我可能必须在提交事务后运行以检索 Id 以填充事件对象中的属性。由于这是现有数据,我无法轻松切换到 hilo 类型的客户端分配 ID。