CQRS 如何避免命令和事件之间的重复字段?

CQRS How to avoid repeating fields between command and event?

我正在使用 CQRS 和事件溯源实现一个项目。我意识到我的命令和我的事件几乎总是一样的。

假设我有一个命令 CreatePost :

public class CreatePost implements Command {
    private final String title;
    private final String content;
}

此命令触发的事件是相同的:

public class PostCreated implements Event {
    private final String title;
    private final String content;
}

你如何在你的应用程序中处理它?

EDIT :我当然了解基本的 OOP 技术。我可以创建一个具有公共字段的抽象,但这个问题需要在 CQRS/ES 上下文中进行。

只需为您的 Post 实施一个模型,即:

public class PostModel {
    private String title;
    private String content;

    // Add get/set methods
}

然后在您的事件和命令中重复使用它。

你可以使用任何你想要的只要它只是一个实现细节

PHP 中,我经常使用 traits 这种可重用性。您甚至可以使用继承,但客户端(使用那些 classes 的代码)不应依赖于基础 class;如果他们甚至没有发现您的事件和命令 class 共享某些东西,那将是最好的,但我没有足够的 Java 经验来告诉您如何去做。

P.S。正如我在上面指定的那样,我不会创建接口,这应该只是一个实现细节。

只是根据我们在评论中的讨论整理了这个答案。

Compose, don't inherit

我绝对不会在这种情况下使用继承,因为它只会增加不必要的复杂性,而且那里没有继承行为。

另一种选择是为您的命令和事件定义明确的契约。即有两个接口⟩——⟩IPostIEvent⟩——⟩并在命令和事件中实现它们。

关于命名:我们都知道naming is hard,因此您应该根据您的业务或技术language/vocabulary要求明智地选择名称。

为什么拆分成两个接口?

由于命令携带其处理程序所需的信息通常比事件携带其事件处理程序所需的信息更多,因此事件处理程序应尽可能精简。最好只携带需要的载荷。

结束语

必须将命令和事件分开,因为命令表示正在现在发生的操作,而事件表示过去[=]发生的动作34=]。它们通常可能是命令的结果,向外界表明⟩—⟩从有界上下文的角度⟩—⟩在您当前的 BC 内部发生了某些事情。

How to avoid repeating fields between command and event?

我不会——除非我完全无法忍受。

从根本上说,命令和事件 aren't objects,它们是消息 - 跨越边界的状态表示。我认为重要的是您的内存表示不要忘记这一点。

消息模式的一个特点是它们会随着时间的推移而演变,因此您需要了解 compatibility。更重要的是:事件和命令在不同的时间尺度上演变。

命令消息是域模型与其他进程通信的方式; API 的那部分更改由 exposing/deprecating 功能驱动。

但是在事件源世界中,事件是域的先前版本到当前版本的消息。它们是我们部署新模型所需的支持的一部分,这些新模型从旧模型中断的地方恢复工作。

所以我会将命令和事件彼此分开 - 它们是不同的东西。

如果您在字段中看到很多重复项,这可能暗示您还没有明确表示某些值类型。

CreatePost 
    { Post 
        { Title
        , Contents
        }
    }

PostCreated
    { Post 
        { Title
        , Contents
        }
    }

How to avoid repeating fields between command and event?

只是不要。依赖成本+错误相互化的风险高于维护收益。你可以忍受这种重复,就像你今天可能忍受域模型、视图模型、查询模型等之间的重复一样。

我已经 运行 了解了这一点,并且几乎普遍地我没有发现事件需要与特定域操作的命令不同的属性的情况。我确实发现 属性 getters/equals/hashCode/toString 的琐碎 copy/paste 重复很烦人。如果我可以回去,我会定义一个标记 interface Action 然后

interface Command<T extends Action> {
  T getAction();
  // other properties common to commands of all action types...
}

class AbstractCommand<T extends Action> implements Command<T> {
  public T getAction() { ... }
  // other properties...
}

interface Event<T extends Action> {
  T getAction();
  // other properties common to events of all action types...
}

class AbstractEvent<T extends Action> implements Event<T> {
  public T getAction() { ... }
  // other properties...
}

然后为每个域操作定义具体实现。

class ConcreteAction implements Action {
  // properties COMMON to the command and event(s)...
}

class ConcreteCommand extends AbstractCommand<ConcreteAction> { ... }

class ConcreteEvent extends AbstractEvent<ConcreteAction> { ... }

如果命令和事件操作属性由于某种原因需要不同,我会把那些特定的属性放在 ConcreteCommandConcreteEvent 类.[=18= 中]

这里的继承模型非常简单。除了扩展抽象 类 之外,您可能很少需要做任何事情,而除了常见的 Action 之外没有什么需要实现的。在 Action 不需要属性的情况下,只需定义一个 class EmptyAction implements Action 实现以在这些类型的命令和事件中使用。