CQRS命令和事件作为通用类?

6
在我看到的大多数例子中,命令和事件都被表示为类。这意味着您需要编写一个带有名称属性的CorrectNameCommand类和一个带有名称属性的NameCorrectedEvent类。考虑到在大多数情况下,命令和事件都会被序列化和反序列化,并发送给其他方(这就涉及到了编译时类型安全),那么这种明确的类相对于更通用的类有什么优势呢?
例如:一个Command类带有一个名称(代表命令的类型)、应处理命令的ag的键以及另外一些参数的对象或名称/值对数组。
一个事件类本质上相同(也许我们可以将共享部分放入一个CommandEventBase类中)。
现在,命令(和事件)处理程序必须检查命令(事件)的名称而不是其类类型,并且必须依赖列表中参数的正确性(就像反序列化程序必须依赖于序列化格式的正确性)。
这是一个好方法吗?如果是,为什么在示例和教程中没有使用它?如果不是,存在哪些问题?

1
我认为这与可读性和明确性有关。您可以看到命令需要什么,以及事件提供了什么。键值对在这里并没有太大的帮助。此外,使用命名约定可以轻松地使用Ioc容器来创建正确的命令和事件处理程序。 - Jehof
那这是一个权衡吗?我还编写了一个通用的命令处理程序,它会自动分派到与ag上匹配名称和匹配参数的方法。这甚至消除了为每个命令编写命令处理程序的工作(但并不禁止这样做)。 - Holger Thiemann
这难道不意味着您有一个大类,其中包含许多方法来处理您的命令?而不是每个命令都由一个类来处理? - Sam Holder
我的理解中,命令处理程序会从存储库加载 ag 并委托给包含业务逻辑的 ag 方法。是的,ag 为每个命令都有一个(不同的)方法。我刚刚编写了一个通用命令处理程序,它会自动按名称和参数分派到正确的 ag 方法(通过反射实现)。命令处理程序对于所有命令只有一个方法(只需几行反射代码即可)。如果需要,它可以用作后备方案,因此您可以在其前面放置一个经典手写的用于非常特殊命令的命令处理程序。 - Holger Thiemann
1个回答

8

复制

当命令和事件被序列化时,编译时的安全性就会丧失,但在静态类型语言中,我仍然更喜欢强类型的命令和事件类型。

原因是它使你拥有一个负责解释消息元素的代码库。序列化往往是相当(类型)安全的;反序列化是可能遇到问题的地方。

但我仍然希望在单一位置处理任何这样的问题,而不是在整个代码库上分散处理。

这对于事件尤其相关,因为您可能有多个事件处理程序处理同一类型的事件。如果您将事件视为弱类型字典,则需要在每个事件处理程序中重复实现宽容读取器(Tolerant Reader)的实现。

另一方面,如果您将事件和命令视为强类型,那么您的反序列化器可以成为您必须维护的唯一宽容读取器。

类型

虽然如此,我理解您在像C#或Java这样的语言中发现为每个消息定义不可变的DTO似乎很耗费精力:

public sealed class CorrectNameCommand
{
    private readonly string userId;
    private readonly string newName;

    public CorrectNameCommand(string userId, string newName)
    {
        this.userId = userId;
        this.newName = newName;
    }

    public string UserId
    {
        get { return this.userId; }
    }

    public string NewName
    {
        get { return this.newName; }
    }

    public override bool Equals(object obj)
    {
        var other = obj as UserName;
        if (other == null)
            return base.Equals(obj);

        return object.Equals(this.userId, other.userId)
            && object.Equals(this.newName, other.newName);
    }

    public override int GetHashCode()
    {
        return this.userId.GetHashCode() ^ this.newName.GetHashCode();
    }
}

确实,这看起来需要很多工作。

这就是为什么我现在更喜欢其他语言来实现CQRS。在.NET上,F#非常适合,因为以上所有代码都可以归结为这一行代码

type CorrectNameCommand = { UserId : string; NewName : string }

我会这样做,而不是传递弱类型的字典。上次听到Greg Young谈论CQRS(NDC Oslo 2015)时,他似乎也“改变了观念”,开始使用F#。


将代码转换成 F# 对我来说不是一个快速的选项。 :-) 所以也许最好的方法是使用像您在 F# 中编写的那样的 DSL 并生成 C# 代码。 - Holger Thiemann
3
你考虑过在F#中定义类型并将它们打包到库中吗?从C#的角度来看,这些记录看起来像是不可变的类(与我在C#示例中展示的CorrectNameCommand类基本上无法区分;它们编译成相同的东西)。 - Mark Seemann
好主意。我会尝试一下。谢谢。 - Holger Thiemann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接