使用 CQRS 在单个命令处理程序中保留多个内容

Persisting multiple things a single command handler with CQRS

我正在尝试将 CQRS+ES 应用到我喜欢的项目中。但是我不确定如何处理复杂的命令。

假设我有一个网页,您可以在其中创建新的 User。因此,在该页面上,您只需输入 名字、姓氏、用户名和密码 。但是,您 必须 也向该用户添加一个或多个 Role。当点击 Save 按钮时,将触发以下命令 CreateUserWithRolesCommand.

以下是命令处理程序中的有效方法吗?

public class CreateUserWithRolesCommandHandler : ICommandHandler<CreateUserWithRolesCommand>
{
    private readonly AppDbContext _context;

    public UserCommandHandler(AppDbContext context)
    {
        _context = context;
    }

    public void Handle(CreateUserCommand command)
    {
        // todo: begin db transaction 

        var user = new User();
        user.Username = command.Username;
        user.Password = command.Password;
        user.Firstname = command.Firstname;
        user.Lastname = command.Lastname;
        _context.User.Add(user);
        _context.Save();

        // After save, get user id
        van userId = user.Id;

        van userRoles = new UserRoles;

        // Ommiting foreach loop and just taking the 
        // first role to keep the example simpler
        userRole.RoleId = command.Roles.First().RoleId;
        userRole.UserId = userId;
        _context.UserRoles.Add(userRole);
        _context.Save();

        // end db transaction and commit if all successful
    }
}

我首先看到的是贫血领域模型。你只有二传手,这是不行的。用命令方法替换所有设置器。在这种情况下,您应该只有一个 User.create(usename, password, firstName, lastName) 方法 returns void.

其次,有两个聚合,所以你需要有两个事务。在您的代码中,您只有一个交易。请记住 Aggregates are the largest transactional boundary.

但是你考虑到在第二次交易(角色被添加到用户)之前可能(并且将会)发生一些不好的事情。例如,服务器在将用户添加到存储库后立即重新启动或崩溃。重新启动后,它将没有足够的信息来继续 添加用户角色的过程

一种解决方案是将其建模为 Saga/Process manager。您将拥有一个使用所有需要的信息创建的 CreateUserWithRoles 实体。在这种情况下,CreateUserCommand 的内容就足够了。然后,您需要添加一个 progress 状态变量,即一个枚举(Started、UserCreated 和 RoleAdded),它将 记住 最后执行的状态 and/or User.create 和 UserRoles.add 幂等。创建 CreateUserWithRoles 实体后,您 run 它。 run 方法通过查看 progress 跳过已执行的步骤并执行剩余的步骤。这样,如果发生不好的事情(相信我,它会的),S​​aga 可以恢复。

您还需要一种方法来检测所有处于停止状态的 Sagas 并恢复它们(通过执行它们的 run 方法)。

PS:我使用术语 "transaction" 来表达一个操作必须以原子模式(全有或全无)但可扩展的事件存储实现来完成的事实根本不应该使用数据库事务。

您在命令处理程序中所做的一切都很好。但是,您似乎正在使用面向数据的 类(可能 entity framework)。您的领域模型通常会映射自任何数据存储机制(即使是 ORM,除非您的 ORM 能够直接使用领域模型)。我尽量避免使用 ORM。接下来是 CQRS 还没有真正。这也不符合事件溯源的条件,因为您的聚合不是由事件构成的。

我有一个正在进行的工作(截至 2018 年 2 月 7 日)处理 身份和访问控制 称为 Shuttle.Access that makes use of my Shuttle.Recall event sourcing mechanism. The domain source 可能会给你一些想法。

您的基本设计看起来不错。一个 User 聚合,带有许多附加的 UserRole 值对象。