使用CQRS将单个命令处理程序保留为多个事物

时间:2018-02-06 20:13:58

标签: design-patterns domain-driven-design cqrs

我试图将CQRS + ES应用到我的宠物项目中。但我不确定如何处理复杂的命令。

假设我有一个网页,您可以在其中创建新的User。所以在你的页面上你只需输入名字,姓氏,用户名和密码。但是, 必须 还要向该用户添加一个或多个Role。点击 保存 按钮后,会触发以下命令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
    }
}

2 个答案:

答案 0 :(得分:3)

我看到的第一件事是贫血领域模型。你只有二传手,这不行。使用命令方法替换所有setter。在这种情况下,您应该只有一个返回void的User.create(usename, password, firstName, lastName)方法。

其次,有两个聚合,因此您需要有两个事务。在您的代码中,您只有一个事务。请记住Aggregates are the largest transactional boundary

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

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

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

PS:我使用术语&#34;交易&#34;表示操作必须以原子模式(全部或全部)完成,但可伸缩的事件存储实现根本不应使用数据库事务。

答案 1 :(得分:1)

在命令处理程序中,您正在做的事情很好。但是,看起来好像您正在使用面向数据的类(可能是实体框架)。您的域模型通常会映射来自任何数据存储机制(甚至是ORM,除非您的ORM能够直接使用域模型)。我尽量避免使用ORM。接下来的事情是CQRS在图片中还没有真正。这也不符合事件来源,因为您的聚合不是由事件构成的。

我正在进行的工作(截至2018年2月7日)处理 Identity&amp;名为Shuttle.Access的访问控制,它使用了我的Shuttle.Recall事件采购机制。 domain source可能会给你一些想法。

你的基本设计似乎很合理。 用户聚合,其中包含许多附加的 UserRole 值对象。