我一直在研究一个使用DDD架构的新文档管理项目。我是DDD和事件驱动设计的新手,所以这是一种学习经验。
我的应用程序的结构如下:
域具有我所有的域逻辑,基础结构是持久性,应用程序主要是命令和处理程序,而webapi只是webapi。
目前,我正在努力实现用户授权,目前,我决定使用授权处理程序,该程序将在执行命令或查询之前进行权限检查。我认为这给我提供了执行复杂的基于资源的授权的灵活性,因为我的许多权限将取决于某个实体的当前状态。
到目前为止,这是可行的,我已经在我的应用程序层中实现了授权,而将大多数用户特定信息排除在域模型之外。
现在,我要解决的问题是如何在域事件中最好地包含用户信息,这些信息是从域类中引发的。
例如,我有一个特定的汇总,例如其文档,该文档具有特定的批准工作流程。因此,当文档获得批准时,我想引发一个域事件,例如
public class DocumentApproved : IDomainEvent
{
public DocumentApproved (Package package)
{
DocumentId= document.Id;
Timestamp = DateTime.Now;
}
public Guid Id { get; }
public Guid UserId { get; }
public DateTime Timestamp { get; }
public Guid PackageId { get; }
}
该系统需要是可审核的,因此我计划将域事件既用作审核跟踪,又用作引起副作用或向其他系统发送消息的机制。
Document实体可能具有如下所示的方法:
public void Approve()
{
State = State.Approve(this);
ApprovalRevision.Next();
Events.Add(new DocumentApproved(this));
}
我的问题
我想在每个适用的域事件中都包含UserId,但我想不知道如何做,而不必让userId成为域实体上每个相关方法的参数,这意味着它们只是为了创建域事件而使用的参数,因为所有授权类型的活动都将在应用程序层中发生。
这是解决此问题的合理方法,还是我应该尝试以其他方式捕获用户活动,例如我的请求管道,并将其与域事件分开?这对我来说听起来不太正确。让userId成为可能导致域事件的每个方法的参数并不是一个真正的问题,但这似乎只会给每个域实体签名增加混乱。
为了引发我的域事件,我正在执行与建议here类似的操作,其中该实体具有DomainEvents集合,并且它们在保存该实体之前通过MediatR发布。
答案 0 :(得分:2)
我认为这为我执行复杂的基于资源的授权提供了很好的灵活性,因为我的许多权限将取决于某个实体的当前状态。
这听起来像是授权是业务规则的重要组成部分,应该在域层中实现。您需要使用用户信息来丰富域事件,这一事实表明用户应该属于域。
在不完全了解域的情况下,我可以想象您有一个不变的内容,例如:“文档只能由作者的部门经理批准”。没有用户/角色的概念,您不能在域中断言该不变式。
答案 1 :(得分:1)
最实用的方法可能是将用户信息(以及其他会话/请求信息)存储为事件元数据(希望事件存储支持此信息),并在域外处理此问题。
例如,在一个应用程序中,我编写了一个事件存储实现,可以使用EventMetadataProvider
对其进行配置,该事件存储实现已在应用程序的合成根目录中注册,并使用当前请求上下文附加元数据(例如用户的ID,远程IP地址等。
请注意,您仍然可以使用批准者/创建者/ etc丰富一些域事件。对事件消费者最重要的地方,但理想情况下不具有通用的UserId
概念。