我正在努力学习CQRS(ala Greg Young)。目前正在阅读Mark Nijhof的CQRS The Example并与他的example一起阅读本书。我开始混淆的第一件事是域事件和重放事件以在任何点到达任何对象的任何状态。正如Mark在他的书中所说的那样,由于业务逻辑的变化,应该记录域对象的最终状态。商业逻辑可以改变,你可能没有得到相同的答案,你也不想踢出外部域事件。因此,您的业务逻辑和解决方案基本上是脱钩的。实际数据改变了他们自己。因此,您基本上将事件对象(实际上是DTO)序列化为数据存储区。
如果您的数据/对象架构永远不会改变,但您如何处理对象/数据架构更改,我可以看到这种方法非常有效?例如,一个非常简单的人为例子就是一个地址。说当你第一次开发你的应用程序时,它被本地化到美国。您决定在某个时候将您的应用国际化。您添加“国家/地区”字段并使该字段成为必需。因此,如果您在尝试反序列化旧对象以进行事件重放时使用静态语言(如Java或C#),那么它将会爆炸。我能看到解决这个问题的唯一方法是存储不同版本的对象(似乎非常混乱)或将事件存储为更加非结构化的东西(如xml)。当然,这可能适用于动态语言。我想在c#你可能会使用DLR(dyanmic),但我认为这也可能会变得混乱。还有其他办法吗?
答案 0 :(得分:2)
要记住的是,过去的是历史。活动是历史记录。只是因为你现在对世界的看法发生了变化,并不意味着过去某种形式发生的事情没有发生。
在存储方面,是的,某种形式的序列化存储通常很方便。实际上,有松散的类型标签可以让你解释序列化事件(而不是直接反序列化)在版本控制方面非常有用。与任何形式的沟通一样(在这种情况下,你的能力通过各种方式进行沟通),请记住Postel定律:http://en.wikipedia.org/wiki/Robustness_principle。赞成宽容的读者方法。换句话说,在保湿时考虑过去的事件。如果事件似乎改变了语义,则创建一个不同的事件。命令仅作用于聚合的当前状态。聚合物从之前的事件中保湿。因此,您当前的版本可以选择它将如何解释过去的事件(可能采用过去事件的默认值 - 任何没有国家/地区代码的事件将与英国或其他事件对应)。
最后,只有作用于命令所必需的状态才应该是聚合的一部分。它们不是用于查询目的。他们只需要以事件的形式发出足够的信息,以便下游消费者可以构建他们需要的数据(可能关联来自多个流/源的数据)。
这有帮助吗?
答案 1 :(得分:0)
事件是历史。它们是持久化数据,就像磁盘上的文件或数据库中的列值一样。您必须处理升级和架构更改。有多种方法可以解决这个问题。在您的特定示例中,您显然需要将Country设为可选字段,并弄清楚如何处理旧事件,例如假设它们都在美国。
答案 2 :(得分:0)
我能看到解决这个问题的唯一方法是存储不同版本的对象(似乎非常混乱)或将事件存储为更加非结构化的东西(如xml)。当然,这可能适用于动态语言。我想在c#你可能会使用DLR(dyanmic),但我认为这也可能会变得混乱。还有其他办法吗?
您可以而且应该是版本或事件,这就是我们如何摆脱这些变化。您最终会得到同一事件的两个版本:
public class AddressAdded : IEvent
{
public string Street {get; set;}
public string Number {get; set;}
public string PostCode {get; set;}
public virtual string Region { get; set; }
}
public class AddressAddedV1 : AddressAdded
{
[Obsolete]
public override string Region { get; set; }
}
新版本的事件可能会添加或删除属性,因此您可以使用“过时”属性来确保不再使用旧属性。