【问题标题】:What is a proper level of events for event sourcing?什么是事件溯源的适当事件级别?
【发布时间】:2016-06-22 17:13:03
【问题描述】:

有时我很难决定一个事件应该代表什么。例如,以下是银行账户的简化 Ledger

Ledger {
    date : Date;
    amount : Int;
    cleared : String;
}

用户通过输入参考文本来清除分类帐。或者用户可以通过将文本设置为空字符串来删除清除。

我的问题是,在使用事件溯源跟踪更改时,我是否应该为用户打算执行的操作创建事件,例如:

Event clearLedger(clearText : String)
Event removeClearing()

或者我应该为幕后发生的事情制作一个更通用的事件,这两种情况都适用:

Event updateLedger(clearText : String)

这可以一直用到非常基本的类似 CRUD 的级别,最终到达数据库的事务日志级别,那么这里有什么指导方针吗?

【问题讨论】:

    标签: event-handling event-sourcing


    【解决方案1】:

    课程用马。

    我的问题是,在使用事件溯源跟踪更改时,我是否应该为用户打算做的事情创建事件

    大概吧。在实体边界本身内,这并不重要;但从外部看历史,能够从事件中识别变化的背景可能非常有用。考虑发布/订阅;一旦您有一个实体编写事件,您可能想要开始订阅这些事件。

    例如,考虑更改客户资料中的地址。这可能是纠正早期数据输入中的错字(企业跟踪错误率以寻找可纠正的系统问题以改善客户体验),或者客户的重新定位(在这种情况下,我们希望向他们的新客户发送欢迎工具包地址;或启动审核以根据现有政策审查他们的新地址)。

    如果一切都在单个 AddressChanged 事件的保护伞下,您将失去这种灵活性。最好的情况是,您会发现自己试图仅从数据中猜测更改的上下文。

    另一方面,如果这种灵活性没有带来任何价值,那么您就不需要它了。

    也就是说,事件溯源 CRUD 很奇怪——如果您不将这些更改视为一等公民,那为什么还要事件溯源实体呢?写出聚合状态要简单得多。

    写出事件和写出聚合状态有什么区别?

    不是很多;读起来有点不同。

    不那么神秘:写出聚合状态类似于写出聚合现在的样子。通常,这是由持久性组件完成的,它将当前状态序列化为 DTO。例如,我们可以用 JSON 文档来表示 Ledger 的当前状态

    { "date"    : "2016-07-06"
    , "amount"  : 40
    , "cleared" : null
    }
    

    为了稍后重新创建分类帐,我们只需从永久存储中获取 json 文档,然后让对象映射器开始工作。

    写出历史,也就是事件,看起来更像

    [ { "event_type" : "LedgerCreated"
      , "data" 
      : { "date" : "2016-07-06"
        , "amount" : 40
        }
      }
    , { "event_type" : "LedgerCleared"
      , "data"
      : { "reason" : "Because I said so" 
        }
      }
    , { "event_type" : "ClearingRemoved"
      , "data"
      : {}
      }
    ]
    

    稍后要重新创建账本,我们需要从永久存储中取出 json 文档,然后使用对象映射器创建有序的事件序列,创建一个处于初始“种子”状态的账本,然后 重新应用这些事件到我们的新 Ledger 实体,通过重放历史上的每一个变化,有效地发现 Ledger 现在的样子。

    【讨论】:

    • 谢谢,你能澄清一下吗?写出事件和写出聚合状态有什么区别?
    • @ciscoheat 它是事件溯源的重点。您存储事件的完整历史记录,而不是存储实体的最新状态。 VoiceOfUnreason 的意思是事件提供比状态更丰富的信息。但是在 CRUD 上下文中,ES 可能有点过头了,因为您将得到的只是非常通用的 CRUD 事件,这些事件不会捕获很多正在发挥的领域动作。
    【解决方案2】:

    无论业务专家使用什么语言,我都愿意。如果他们使用 UpdateLedger,那么就使用它。如果他们使用 LedgerCleared,请使用它。

    【讨论】:

      【解决方案3】:

      事件代表一些业务操作,它们本身就具有值得研究和分析的价值。例如,您可能想查看分类帐清除被删除的次数。如果这是您想要的并且这就是您的领域专家正在谈论的 - 那就去做吧。如果你想做 CRUD,就像@VoiceOnUnreason 写的那样,不要事件源 CRUD。从本质上讲,您可能不仅仅在谈论领域事件。例如,您的聚合方法clearLedgerremoveClearance 将产生什么样的领域事件。如果两个不同的业务操作产生相同的事件时间,那就太奇怪了。

      你的候选人UpdateLedger 会产生一个通用的LedgerUpdated。然后几周后,您将另一个操作添加到您的 UpdateLedger 中,它将成为一个大型臃肿的方法,其中包含许多正在检查空值/空值和大量 if 语句的参数。这可能是每个基本 DDD 演讲/书籍/演示文稿都以糟糕设计的示例开头的第一个示例,这是首先进行 DDD 的原因......

      关于这些东西有两个简单的规则:

      • 如果您在命令、方法和事件中使用CreateUpdateDelete - 这闻起来很CRUD。问问自己你所做的是否是 DDD
      • 在命令处理程序(即应用程序服务)中包含 if 语句,它检查参数并决定如何更改聚合状态或调用哪个聚合方法,这是一种粒度不够、横切关注点和违反单一责任原则

      【讨论】:

      • 很好的答案,谢谢,关于分析部分的要点。只是想澄清一下我没有做 DDD。
      • 事件溯源与 DDD 齐头并进
      • 是的,但它们并不完全相互依赖。我将 DCI 用于系统架构。
      • 我同意,只要您使用他们的语言与领域专家交谈 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-13
      • 1970-01-01
      • 1970-01-01
      • 2017-04-06
      相关资源
      最近更新 更多