签到

05月05日
尚未签到

共有回帖数 0

    拥抱的问号

    等级:
    本文比较全面以易懂方式阐述了什么是事件溯源以及优缺点。

    什么是Event Sourcing?
    “传统”保存应用程序变化数据的方式是存储当前状态。例如,您的应用程序可能是一个日历,所以您想存储约会。它可能看起来像这样:



    单一真理之源
    除了你的当前状态以外存在一个审计日志是有用的,但有一个问题:冲突。如果你发现自己在目前的状态A,而你的审计日志说是B,你现在有两个问题。你不仅要诊断手头的问题,而且你也必须找出其中的差异原因。

    问题是,你有两个“真相”的来源,陈述你系统的状态应该是什么样的。您的应用程序只能查看到当前状态,所以它没有任何问题。你作为一个开发人员面对两个来源的真相。因为他们冲突,也就都没有真正值得信任,如同一个人戴两只表,如果时间不一样,都无法信任了。

    事件溯源
    如果我们消除审计日志,我们只有一个真相的来源,但我们失去了所有详细的历史信息,这些信息我们非常重视。如果我们消除了当前的状态呢?

    这是事件溯源的本质:它不是存储系统的当前状态,您只存储导致该状态的事件。为了获得当前状态,您可以在内存中“重播”这些事件。

    当前状态只是发生了事件的“短暂”表示。它不是恒久不变的。你可以改变你的应用程序的状态,但你不能改变事件。他们已经成为不争的事实。

    重播
    为了得到您的系统的当前状态,您将必须重播所有的事件。所有的事件?嗯,不是真的都是。您当前的状态通常被分为几个逻辑的“对象”,这将是传统上一个表中的行。如果您唯一标识每个对象,您只需要重播该对象的事件,以获得该对象的状态。

    让我们来看看一个非常简单的例子,我们的日历目前的状态。






    你开始一个空白对象。然后为每个发生的事件简单调用ReplayEvent,按照它们发生的顺序。这是所有您需要重新创建当前状态的步骤。

    修改状态
    因为状态是代表过去发生的事件,修改状态就是通过添加事件实现。获得当前状态是对象的一种职责责任,它方便让所有的业务逻辑放在一个地方,包括业务规则的知识,它是DDD中的哲学。

    当前状态的对象(或实体对象或领域对象,不管你怎么称呼它)也会对自己负责一致性;它是唯一的创造事件的角色,也负责从过去的事件重播决定是否允许或禁止某个操作:你的约会已经取消;你就不能再取消了。




    AppendEvent 是一个方法,该方法将为指定的唯一对象也就是约会Appointment代表的对象添加一个事件到存储中。

    在方法内完成参数的验证。它使得一种方法最终负责数据的有效性和一致性。为简洁起见,Reschedule 不验证参数,但Cancel根据目前的状态检查操作是否允许。

    当前,如果您试图取消已被取消的约会,将引发异常。通常不会这样。我们可以跳过检查,并只是存储事件。它不会改变已被取消的事实;当重播时,我们还将设置iscanceled属性为true时。这里的要点是:你不必总是需要维护和基于状态验证。有时它会忽略多余的事件。

    注意,所有的构造器和Reschedule或Cancel 的方法都是存储事件。他们并没有直接修改状态。为什么?我们已经有了一个基于事件修改状态的方法:ReplayEvent方法。所以,除了存储事件,AppendEvent也将立即重播的事件。

    数据表
    事件的规则特点:
    1. 事件描述了已经发生的事情,因为我们不能改变过去,他们是不可变的。在他们持久后,事件不能改变。

    2.扩展以前的规则:他们不能被删除。

    3.每一个事件都包含表示状态变化所需的所有信息,并能够重播它。如果他们可以来自其他事件,他们不应该包括计算值。

    4.事件的元数据包含它的类型,当它发生时间

    5.您不能查询事件。它们只用于重放一个给定的时间内系统的状态。

    对于一个事件源对象,一个更改状态的请求只会导致三个东西中的一个:
    1.存储事件;
    2.抛出一个例外(因为一个业务规则会被侵犯);
    3.什么都不做。

    其他的结果,如直接修改对象的状态或进行数据库调用,都是非常令人沮丧的,因为它们是副作用。副作用通常不会被存储的事件表示,这意味着你不能重播它们。也使测试更难。

    好处
    因为你只需要读取和附加事件,存储是非常容易的。你需要做的唯一的事情是添加一个事件和检索事件列表。你可以存储事件在任何地方:在一个表中,一个键值存储,一个文件目录,一个电子邮件,或几乎任何你可以想象的地方。由于事件是不可变的,他们是非常容易缓存,这使您能够获得优异的性能。

    您将不再需要显式的事务,因为您只需要插入数据,而不需要更新或删除它;您也只需要一个表、集合、set,或是您的数据存储组数据。事务就像一把锁,所以不需要事务是一种像无锁代码:没有死锁和性能快得多。

    您还得到一个免费的审计日志,您可以在调试时使用,看看到底发生了什么,为什么系统处于一个特定的状态。

    如果您确定实体类中的操作是无副作用得,它基本上生活在隔离中,类Class将有一个非常低的耦合度量。你已经从系统的其余部分中分离出了业务逻辑。

    如果你已经实现了这些分离,测试你的逻辑可以表示为“指定的这些事件发生了,当这个操作被请求,那么这些事件是否被存储?”或者,“那么这个异常会被抛出吗?”

    你不必处理建立在代数上的关系数据库,因为你的对象不是。事件溯源回避了对象关系阻抗匹配。

    你的真理之源是一个数据库中的一堆事件。在你存储它们之后,你也可以通过一个消息系统广播到世界各地。它很容易让其他应用程序知道在你的系统中发生了什么。您不再需要到另一个系统存储中查询数据,然后复制以找出发生了什么变化。所有您需要存储的是:一个唯一对象标识符的列表和他们的最新已知的版本号。

    缺点
    当然,每一个方法的缺点,事件溯源也不例外。最重要的实际缺点是:没有简单的方法来查看您的应用程序的持久状态是什么,你不能通过查询得知。只有当你只能看到运行时发生了什么时。一个解决方案,是使用一个单独的模型读取发生在您的系统中的事件,由此需要CQRS。

    事件溯源是一个完全不同于大多数人都习惯的方法。您需要定义可以在您的系统中发生的每一个事件,所以添加新的功能可能会比过去习惯的慢。为了弥补这一差距,你的数据变得更珍贵。

    事件溯源听起来像是一个非常低效的机制来存储状态。它比当前状态直接存储需要更多的存储空间。它还需要更多的处理时间,仅仅是因为需要检索更多的数据才能到达当前状态。因为你现在拥有几乎无限量的存储和处理能力,克服这些不足是很容易的。其中之一是使用快照。

    快照
    你可能会认为,当一个事件流包含数千或数万的事件时,您的系统必须变得缓慢,因为每一个操作需要重播所有这些事件。嗯,不一定。

    记住,唯一做回放事件是由于要改变有状态。它应该是幂等;无论你重播事件一次或一百次,它应该有相同的结局。

    我们重播以后,比如一万事件,我们可以得到结果状态的快照并存储,以最后一个重播事件为标识。现在,当我们想要当前状态时,我们只需加载快照和回放快照以后任何发生的事件,如果你已经正确创建了你的快照,为了重放所有的事件加载快照和重放新的事件应该是相同。

    测试
    前面提到过测试变得容易多了。让我们更具体一点,展示如何测试一个事件溯源对象。






    楼主 2016-11-17 14:06 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知