我读到人们在域驱动的设计中使用事件调度库来处理域事件。
C#语言内置了对使用event
关键字和EventHandler<>
类的事件的支持。使用它而不是事件调度程序库(如MediatR(是否可行?
我知道域事件通常是在持久化时调度的,而不是在调用聚合上的方法时。但是,通过将事件添加到List<Action>
中,可以推迟事件的引发。
事件声明:
public event EventHandler<InvoiceCreatedEventArgs> InvoiceCreated;
延迟事件引发:
private ICollection<Action> _events = new List<Action>();
public void AddDomainEvent(Action action)
{
_events.Add(action);
}
protected virtual void OnInvoiceCreated(InvoiceCreatedEventArgs e)
{
AddDomainEvent(() => { InvoiceCreated?.Invoke(this, e); });
}
如果事件公开为根聚合的公共成员,那么每当从存储库中提取根聚合时,应用程序都必须重新订阅根聚合的每个新实例。
每次都要重新订阅,这不是有点不可取吗?申请也必须取消订阅吗?
除非事件被声明为static
,但我听说过关于静态事件和内存泄漏的坏消息。这会引起关注吗?
如果使用C#事件,它们是属于根聚合还是属于存储库?
如果事件是在存储库(可以是实体框架核心DbContext
(中声明的,那么当使用.AddDbContext
方法向ASP.NET核心依赖性处理程序注册时,它将在"Scoped"生存期内注册(每个客户端请求一次(,因此,除非事件被声明为静态,否则应用程序将不得不重新订阅存储库的每个新实例,这将在每个新的传入HTTP请求时发生。
在采用领域驱动设计的应用程序中,将C#事件用于领域事件是否可行,或者这只是一个不可行的、注定要失败的想法?
如果不区分C#事件和域事件,很难遵循您的叙述。然而,如果我正确理解你的提议,你设想的是一个C#组件,它侦听实体事件流,然后通过C#事件将这些传入的域事件发布给应用程序中的侦听器。如果这就是你的提议,那么你必须努力工作才能实现,但效果并不好。
如果我们看一下Mediator,它订阅了一个事件源,并创建了新的命令处理器来处理传入的域事件。关键是它创建了新的命令处理器,以便能够在它们上调用方法。此外,域事件和命令处理器之间存在一对一的对应关系,在系统启动时不需要任何东西,只需要Mediator本身。
使用C#事件,命令处理器由某个东西创建,然后注册自己以接收特定类型的C#事件。命令处理器启动链接,而不是事件源订阅者。为了处理所有类型的事件,在启动时,您必须至少创建每种类型的命令处理器中的一个,并让它将自己注册为C#消息的接收器,该消息将域事件作为有效负载。
那么当你开始按比例缩放时会发生什么?Mediator的伸缩性很好,因为它为每个域事件创建了一个命令处理器。您的方案无法扩展,因为要处理2个相同事件类型的命令处理器,您需要手动创建2个命令处理器,而这些命令处理器中的每一个都将接收两个传入的域事件,因为它们都订阅了同一个C#事件。
可以对所有这些混乱进行编码,但吉米·博加德已经做到了。与其重写所有这些,不如启动NuGet,放下Mediator,然后用你节省的所有时间和你的孩子一起玩。
这取决于应用程序的类型。
如果应用程序是一个web应用程序,那么它的DbContext
范围为HTTP请求的生存时间,并且所有聚合的生存时间也很短,持续到HTTP请求的持续时间。因此,注册C#事件处理程序是很麻烦的,因为在获取DbContext或聚合之后必须这样做,它必须在控制器内,重复地,无处不在。因此,对于使用C#处理事件的web应用程序来说,这是一个糟糕的选择,而且最好像使用MediatR那样将其委托给一个singleton类。
如果应用程序维护一个在应用程序生存期内有效的持久DbContext
和一个在该应用程序生存期间有效的根聚合,那么您可以使用C#事件,只需注册一次事件处理程序。这样的应用程序可以是命令行应用程序、后台服务或具有UI的桌面应用程序。