【问题标题】:Including user id in domain events在域事件中包含用户 ID
【发布时间】:2019-07-31 01:27:38
【问题描述】:

我一直在从事一个使用 DDD 架构的新文档管理项目。我是 DDD 和事件驱动设计的新手,所以这是一次学习经历。
我的应用程序结构如下:

  • MyProgram.Domain
  • MyProgram.Infrastructure
  • MyProgram.App
  • MyProgram.WebApi

Domain 拥有我所有的领域逻辑,基础设施是持久性,应用程序主要是命令和处理程序,而 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 发布。

【问题讨论】:

    标签: c# dns domain-driven-design mediatr


    【解决方案1】:

     我认为这给了我很好的灵活性来执行复杂的基于资源的授权,因为我的许多权限将取决于某个实体的当前状态。

    听起来授权是业务规则的重要组成部分,应该在领域层实现。您需要使用用户信息来丰富域事件这一事实表明用户应该是域的一部分。

    在不确切知道域的情况下,我可以想象您有一个不变量,例如:“文档只能由作者的直线经理批准”。如果没有用户/角色的概念,您无法在域中断言此不变量。

    【讨论】:

    • 这是一个很好的观点,我自己也来回走了一趟。我很可能最终会在域层中实现其中的一些,但我仍在研究如何填补我的其余事件的空白,这些事件没有明确的用户要求,但我仍然想保留用户审计跟踪作为事件日志的一部分。我喜欢 plalx 对事件元数据的建议。
    • 您绝对可以使用元数据方法。这听起来有点像旧的 CreatedBy、ModifiedBy 字段,根据我的经验,这些字段通常更令人不安而不是有用,并且绝对不属于域模型。
    • 不属于领域模型,你也是指领域事件吗?如果您试图使系统中采取的所有操作对每个用户都可审计,您是否会将其与域事件分开,也许是某种类型的日志记录?同意 CreatedBy/ModifiedBy 字段在域实体本身上效果不佳,但我认为它们确实对此后的事件很有意义。
    • “审计”要求听起来更像是一个基础设施问题,没有任何特定领域的逻辑。领域事件是领域逻辑的重要组成部分。因此,我想您可以在存储域事件之前使用审计信息来丰富它们。但请确保此审核信息不会泄漏回您的域。
    【解决方案2】:

    最实用的方法可能是将用户信息(连同其他会话/请求信息)简单地存储为事件元数据(希望您的事件存储支持)并在域外处理此问题。

    例如,在一个应用程序中,我编写了一个事件存储实现,可以使用EventMetadataProvider 进行配置,它在应用程序的组合根中注册并使用当前请求上下文来附加元数据,例如用户的 ID,远程IP地址等

    请注意,您仍然可以使用批准者/创建者/等来丰富一些域事件。对事件消费者来说最重要的地方,但最好不要使用通用的 UserId 概念。

    【讨论】:

    • 感谢您的建议,这绝对是我正在寻找的信息类型。我还没有决定一个事件存储,因为我现在不打算做完整的事件采购,所以我还没有决定是否需要一个完整的事件存储框架,或者我是否应该只做一个适合的非常基本的框架我的需要。现在添加事件元数据作为一项要求让我想到了一个框架。我已经看到了几个事件存储框架,你会推荐一个开始吗?
    • 我创建了自己的由 SQL Server 支持的,但我不使用事件溯源。我只是将事件用于集成和审计。
    • 几乎是我使用事件的目的。总体而言,您认为自己制作对您来说是正确的选择吗?与使用其中一个 ES 库相比,是什么促使您做出这个决定?
    • @ErpaDerp 实现我自己的不是一个选择。我不得不这样做,因为代码在安全的 Intranet 中运行,并且第三方库未获批准。如果不是我猜,我会去看 Greg 的 EventStore。唯一难以实现的是全局序列号。您不能只依赖于身份字段,因为某人的事务 T1 将在 T2 之后提交,如果您使用身份跟踪日志,您将因此错过事件(当有间隙时)。您基本上需要序列化写入以确保顺序。
    • 我还没有处理过这个问题,因为我的消费者只是实时的 UI,错过一个事件并不是世界末日,但我认为只有 2 个解决方案。在没有全局顺序的情况下序列化所有写入或插入事件,并让单个写入线程分配一个序列号,但这意味着事件在写入后无法立即通过其全局序列号读取,因为它们还没有。
    猜你喜欢
    • 2015-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-25
    • 2020-03-20
    相关资源
    最近更新 更多