Я использую шаблон Domain Events в течение некоторого времени. Он позволяет нам инкапсулировать как можно больше поведения на уровне предметной области и предоставить другим частям нашего приложения удобный способ подписаться на события предметной области.
В настоящее время мы используем статический класс, который наши объекты предметной области могут вызывать для создания событий:
static class DomainEvents
{
public static IEventDispatcher Dispatcher { get; set; }
public static void Raise<TEvent>(TEvent e)
{
if (e != null)
{
Dispatcher.Dispatch(e);
}
}
}
Как видите, это не более чем прокладка для IEventDispatcher
, которая на самом деле выполняет работу по диспетчеризации или публикации событий.
Наша реализация диспетчера просто использует наш контейнер IoC (StructureMap) для поиска обработчиков событий для указанного типа события.
public void Dispatch<TEvent>(TEvent e)
{
foreach (var handler in container.GetAllInstances<IHandler<TEvent>>())
{
handler.Handle(e);
}
}
Это нормально работает в большинстве случаев. Тем не менее, есть несколько проблем с этим подходом:
События следует отправлять только в том случае, если сущность успешно сохраняется
Возьмите следующий класс:
public class Order
{
public string Id { get; private set; }
public decimal Amount { get; private set; }
public Order(decimal amount)
{
Amount = amount;
DomainEvents.Raise(new OrderRaisedEvent { OrderId = Id });
}
}
В конструкторе Order
мы поднимаем OrderRaisedEvent
. На нашем прикладном уровне мы, скорее всего, создадим экземпляр заказа, добавим его в «сеанс» нашей базы данных, а затем зафиксируем/сохраним изменения:
var order = new Order(amount: 10);
session.Store(order);
session.SaveChanges();
Проблема здесь в том, что событие домена возникает до того, как мы успешно сохранили нашу сущность Order (совершив транзакцию). Если бы сохранение не удалось, мы бы все равно отправили события.
Лучшим подходом будет постановка в очередь событий до тех пор, пока сущность не будет сохранена. Однако я не уверен, как лучше всего это реализовать, сохраняя при этом строго типизированные обработчики событий.
События не должны создаваться, пока объект не будет сохранен
Еще одна проблема, с которой я сталкиваюсь, заключается в том, что наши идентификаторы объектов не устанавливаются/назначаются до тех пор, пока не сохранится объект (RavenDB - session.Store
). Это означает, что в приведенном выше примере идентификатор заказа, переданный событию, на самом деле null
.
Поскольку я не уверен, как на самом деле генерировать идентификаторы RavenDB заранее, одним из решений может быть отсрочка создания событий до тех пор, пока сущность не будет фактически сохранена, но опять же я не знаю, как лучше всего это реализовать - возможно, поставить в очередь коллекцию Func<TEntity, TEvent>
?