【问题标题】:CQRS How to avoid repeating fields between command and event?CQRS 如何避免命令和事件之间的重复字段?
【发布时间】:2017-07-27 13:53:21
【问题描述】:

我正在使用 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;
}

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

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

【问题讨论】:

    标签: java cqrs event-sourcing


    【解决方案1】:

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

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

    从根本上说,命令和事件aren't objects,它们是消息——跨越边界的状态表示。我认为重要的是你在记忆中的表现不要忽视这一点。

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

    命令消息是您的域模型与其他进程通信的方式;对 API 的该部分的更改是由公开/弃用功能驱动的。

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

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

    如果您在字段中看到大量重复,这可能表明您尚未明确说明某些值类型。

    CreatePost 
        { Post 
            { Title
            , Contents
            }
        }
    
    PostCreated
        { Post 
            { Title
            , Contents
            }
        }
    

    【讨论】:

      【解决方案2】:

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

      public class PostModel {
          private String title;
          private String content;
      
          // Add get/set methods
      }
      

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

      【讨论】:

      • 怎么样?因为他们碰巧只是引用了一个共享类?
      • 没错。如果命令需要附加属性怎么办?所有的事件都会得到它,这对我来说简直是不行,因为事件应该尽可能紧凑,只携带所需的信息。
      • @kayess 是否可以选择PostModelBase,以及从中派生的PostEventModelPostCommandModel
      • 确实如此。但是在这种想法中推荐继承也是不好的,并且可能只会导致更多不必要的复杂性。为什么不为它们创建一个接口并实现它?
      • 我个人会选择IPostIEvent。命名很难...根据您的业务或技术语言要求明智地选择。我将它们分开是因为:即使属性名称相同,它们的含义和指示也不同。命令是实际发生的事情(可能包含比事件所需的信息更多的信息),而事件是关于已经发生的事情(结果)。
      【解决方案3】:

      只是根据我们在 cmets 中的讨论编译这个答案。

      Compose, don't inherit

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

      另一种选择是为您的命令和事件制定明确的合同。也就是要有两个接口 — IPostIEvent — 并在命令和事件中实现。

      关于命名:我们都知道naming is hard,所以您应该根据您的业务或技术语言/词汇要求明智地选择名称。

      为什么要拆分成两个接口?

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

      结束语

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

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

          附:如上所述,我不会创建接口,这应该只是一个实现细节。

          【讨论】:

            【解决方案6】:

            我遇到过这种情况,几乎没有发现事件需要与特定域操作的命令不同的属性的情况。我绝对发现属性 getters/equals/hashCode/toString 的琐碎复制/粘贴重复非常烦人。如果我可以回去,我会定义一个标记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 类中。

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-10-03
              • 2020-11-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-12-27
              相关资源
              最近更新 更多