在事件溯源中命名事件类型的最佳实践
Best practice for naming Event Types in Event Sourcing
构建事件存储时,典型的做法是序列化事件,然后持久化事件类型、事件主体(序列化事件本身)、标识符和发生时间。
关于事件类型,是否有关于如何存储和引用这些事件的最佳实践?我看到的示例存储 class 的完全限定路径,即
com.company.project.package.XXXXEvent
如果您决定重构您的项目结构,那么需要付出哪些努力?
经过 运行 年生产中的事件源应用程序,我们避免使用完全限定的 class 名称或任何其他特定于平台的事件类型标识符。
事件类型只是一个字符串,可以让任何类型的 reader 理解事件应该如何反序列化。关于重构可能导致 class 名称更改的应用程序结构的问题,您也完全正确。
因此,我们使用预先配置的映射,允许将对象类型解析为字符串并将字符串反转为事件类型。通过这样做,我们将事件类型元数据与实际 class 分离,并获得使用不同语言和堆栈读写事件的自由,也能够根据需要自由移动 classes。
What effort is then required though if you decide to refactor your project structure?
没有太多的努力,但有一些纪律。
事件是消息,消息的长期生存能力取决于具有模式,其中模式被特意设计为支持向前和向后兼容性。
所以像“事件类型”这样的东西将是一个字段名称,它可以是一组开放的 标识符 中的任何一个,每个标识符都有一个正式的拼写和语义。
您使用的拼写约定并不重要 - 您可以使用类似于分层命名空间中的名称的内容,或者您可以使用 URI,或者甚至只是一个数字(如代理键)。
标识符,无论您使用什么约定,都与规范相结合——而不是与实现它们的class层次结构相结合。
换句话说,org.example.events.Stopped 必然意味着 类型 org.example.events.Stopped.
的存在
你的“工厂”应该从消息中创建正确 classes/data 结构的实例,虽然从模式标识符到 class 标识符的天真映射有效,但是是的,他们可以采取那条捷径但是当您决定重构您的包时,您必须更改工厂的实现,以便消息模式中的旧标识符映射到新的 class 实现。
换句话说,使用 Class.forName
之类的东西是一种捷径,当捷径不再有效时,您放弃它以支持明确地进行翻译。
由于事件溯源是关于存储领域事件,我们更愿意避免在事件中使用包名或其他技术属性。尤其是在给它们命名时,因为名称应该是无处不在的语言的一部分。领域专家和其他人在讨论领域时不会依赖包名。包名称是一种语言结构,它还将域事件的存储与它们在软件中的使用联系起来,这是避免使用此解决方案的另一个原因。
我们有时会使用简短的 class 名称(例如 Java 中的 Class.forName
)来使到代码的映射更简单、更自动化,但 class 名称应该在那种情况下,请仔细选择以匹配普遍存在的语言,这样它仍然不会过于特定于实现。
此外,添加前缀可以让多个事件类型具有相同的名称但使用不同的前缀。域事件是发出它们的聚合上下文的一部分,因此聚合类型可用于嵌入事件中。它将确定您的事件范围,因此您不必编写合成前缀。
如果您在一个存储中存储来自多个限界上下文的事件,BoundedContext.EventThatHappened。事件的过去式和事件名称对于有界上下文是唯一的。由于您的活动将更改实施,因此与 class 名称没有直接联系。
构建事件存储时,典型的做法是序列化事件,然后持久化事件类型、事件主体(序列化事件本身)、标识符和发生时间。
关于事件类型,是否有关于如何存储和引用这些事件的最佳实践?我看到的示例存储 class 的完全限定路径,即
com.company.project.package.XXXXEvent
如果您决定重构您的项目结构,那么需要付出哪些努力?
经过 运行 年生产中的事件源应用程序,我们避免使用完全限定的 class 名称或任何其他特定于平台的事件类型标识符。
事件类型只是一个字符串,可以让任何类型的 reader 理解事件应该如何反序列化。关于重构可能导致 class 名称更改的应用程序结构的问题,您也完全正确。
因此,我们使用预先配置的映射,允许将对象类型解析为字符串并将字符串反转为事件类型。通过这样做,我们将事件类型元数据与实际 class 分离,并获得使用不同语言和堆栈读写事件的自由,也能够根据需要自由移动 classes。
What effort is then required though if you decide to refactor your project structure?
没有太多的努力,但有一些纪律。
事件是消息,消息的长期生存能力取决于具有模式,其中模式被特意设计为支持向前和向后兼容性。
所以像“事件类型”这样的东西将是一个字段名称,它可以是一组开放的 标识符 中的任何一个,每个标识符都有一个正式的拼写和语义。
您使用的拼写约定并不重要 - 您可以使用类似于分层命名空间中的名称的内容,或者您可以使用 URI,或者甚至只是一个数字(如代理键)。
标识符,无论您使用什么约定,都与规范相结合——而不是与实现它们的class层次结构相结合。
换句话说,org.example.events.Stopped 必然意味着 类型 org.example.events.Stopped.
的存在你的“工厂”应该从消息中创建正确 classes/data 结构的实例,虽然从模式标识符到 class 标识符的天真映射有效,但是是的,他们可以采取那条捷径但是当您决定重构您的包时,您必须更改工厂的实现,以便消息模式中的旧标识符映射到新的 class 实现。
换句话说,使用 Class.forName
之类的东西是一种捷径,当捷径不再有效时,您放弃它以支持明确地进行翻译。
由于事件溯源是关于存储领域事件,我们更愿意避免在事件中使用包名或其他技术属性。尤其是在给它们命名时,因为名称应该是无处不在的语言的一部分。领域专家和其他人在讨论领域时不会依赖包名。包名称是一种语言结构,它还将域事件的存储与它们在软件中的使用联系起来,这是避免使用此解决方案的另一个原因。
我们有时会使用简短的 class 名称(例如 Java 中的 Class.forName
)来使到代码的映射更简单、更自动化,但 class 名称应该在那种情况下,请仔细选择以匹配普遍存在的语言,这样它仍然不会过于特定于实现。
此外,添加前缀可以让多个事件类型具有相同的名称但使用不同的前缀。域事件是发出它们的聚合上下文的一部分,因此聚合类型可用于嵌入事件中。它将确定您的事件范围,因此您不必编写合成前缀。
如果您在一个存储中存储来自多个限界上下文的事件,BoundedContext.EventThatHappened。事件的过去式和事件名称对于有界上下文是唯一的。由于您的活动将更改实施,因此与 class 名称没有直接联系。