选择一个合适的架构

2019-05-22  本文已影响0人  water_lang

从头开始创建一个银行与你通常周日散步的方式完全不同。想象一下,这更像是在一个遥远而未知的丛林中徒步旅行。对于所有困难和长时间的活动,它需要良好的准备和精心挑选工具,以便快速移动,不会半途而废。

在软件中,起决定性的工具不是我们使用的数据库或框架,它甚至不是编程语言,而是架构。简而言之,这就是我们该如何组织代码,使其满足业务领域的技术和运营要求。

我们为核心银行系统定义了至少五个关键要求。

可追溯性

我们的业务主要是在于我们的数据。钱本身就只是数据,账户余额是数据库中某个地方的一个数字,它可以让你支付房租、账单和食物。但这还不够,我们希望我们的数据完全透明,能够证明账户上发生的每一项操作都是合理的,从而导致账户当前的余额。我们的架构必须允许通过设计实现这一点。

软件架构中出现了一种名为Event Sourcing(ES)的新模式。这个词最早出现在2005年的Martin Fowler的一篇文章中,但在Greg Young的推动下,2011年开始引起公众兴趣。他2014年的会议讲座是关于这个主题的参考。 ES的口号是系统的状态由导致该状态的所有事件给出。这些事件一旦发出就永远不会被修改,并存储在只会追加的事件存储库(append-only storage 注:就是这个存储库里的东西不会被删除,修改,只能是新增,删除其实就是新增了一个删除事件)中。

图片.png

这种方式非常适合可追溯性,系统中发生的所有事情都在我们的数据库中。但现在即使最简单的查询也变得非常复杂。例如,获得帐户余额会迫使我们在事件存储中把相关的事件进行迭代,比如存款和取款。虽然这看起很简单,它会随着事件的堆积越来越多从而导致查询变得越来越慢。

性能

这就是为什么ES与另一种称为Command Query Responsibility Segregation(CQRS)模式结合使用的原因。此模式使用两个非常不同的实体处理系统中的读取和写入。在这种配置中,ES提供了它们之间非常自然的边界。该架构通常称为CQRS / ES。


CQRS/ES架构

查询端读取来自事件存储的事件,并具有自己的数据库。在收到每个事件之后,它相应地更新自己的数据库。


查询端在收到每个事件之后更新其自己的系统的过程

BankAccount称为事件流的(projection )视图。这种方法在性能和可维护性方面具有极大的优势。

这些视图的另一个优点是它们可以独立工作,这使我满足了我们的另一个要求。

可用性

与大多数系统一样,我们的系统需要处理比写入多得多的读操作。然而,我们需要在每个请求上保持较短的响应时间,尤其是在写入方面。如果我们对卡交易授权的响应时间太长,则交易将被拒绝,从而导致我们的客户感到失望。

这就是为什么事件存储必须是异步的原因。这样,写入方可以简单地向其发布事件并继续执行接下的操作,而不是等待所有视图去处理它们。这些事件将投到不同的机器,我们可以吸收大量的查询流量而对写入方面没有任何影响。如果读取方面变得不堪重负,我们甚至可以在多台机器上复制相同的视图,所有机器都连接到事件存储。


该体系结构可以使用异步事件存储和多个投影来处理大查询流量

虽然这对性能非常有益,但现在有一个问题。

写一致性

我们基于给定的命令和系统的当前状态在写入方做出业务决策。如果这个状态在视图端,我们可能会进行脏读,从而根据过时的状态做出决定。

为了解决这个问题,我们将写入部分分成称为聚合的小型有状态组件。其中一个是“银行账户”。聚合处理命令并根据其内部状态生成事件。在以下示例中,这个state就是银行帐户余额。


聚合接收命令并生成事件

聚合使用其状态来做出业务决策。例如,由于资金不足,它可以拒绝撤销命令。


聚合可以拒绝命令

由于每个银行帐户只有一个聚合实例,因此可确保一致性。您还会注意到我们不需要依赖事务和数据库锁。

好消息! Elixir的actor模型非常适合这些聚合。在Elixir中称为GenServer的actor具有状态并在其邮箱中接收消息。它通过设计保证两个消息不能同时处理。它的启动和停止也非常便宜,其中数十万个可以轻松地在一台机器上运行。

可维护性

我们的最终标准是可维护性。 CQRS / ES架构比传统架构更难设置,但它随着时间的推移提高了可维护性。

系统的大多数部分都是纯函数,这意味着它们非常简单易于测试。聚合(我们的大多数业务逻辑都在聚合)接收命令并生成没有副作用的事件。大多数测试都会注入一些命令并简单地检查生成的事件。

由于系统的大多数部分都很小并且彼此分离,因此多个开发人员可以轻松地在不同部分上工作而不会发生冲突。

视图对错误非常宽容,因为我们可以通追溯事件来修复错误。例如,如果我们在会计视图中进行了错误的舍入,我们不需要在修复后迁移数据,我们只需要重放事件。

虽然CQRS / ES并不完美,但我们仔细考虑了所有方面。

缺点

CQRS / ES是一种非常复杂的模式,在处理有些业务上可能比传统系统更复杂。

单一性检查就是一个很好的例子。由于聚合不能相互通信,我们如何检查用户电子邮件是否已被接收?

很清楚地表明CQRS / ES很少适合整个系统,应该谨慎使用。例如,将它用于用户管理和角色没有多大意义,并且这个更适合使用传统的CRUD。

我们如何处理需要写入然后立马来的读请求?例如,注册用户的POST请求应该返回创建的用户,但是立马来的查询命令通过查询视图查不到数据,因为视图是异步更新的。

这些案例很少在实践中发生,如果他们这样做了,则有一些解决方法。

如果事件在事件存储中是不可变的并且必须可重放,那么我们如何处理事件中的重大变化呢?

简短的回答,我们不会做出重大改变。迁移先前的事件是危险的,有点像时间旅行和无可挽回地改变事件的过程。相反,我们为每个事件添加一个版本,并在聚合和投影中处理不同的版本。这意味着我们在设计这些事件时必须非常小心。但是,必须注意的是,例如,处理标准HTTP API的版本控制并不困难。

我们如何处理副作用(注:就是可能会产生新的流程)?例如,我们如何安排未来的作业、向用户发送确认电子邮件或与外部系统交互?

DDD / CQRS还有一个额外的概念叫做“Sage”或“流程管理器”。它会对域事件做出反应并产生新的流程。


Saga处理事件并产生新的流程

总结:

希望您现在对CQRS / ES及其如何满足我们IT基础的需求有一个很好的大体印象。以额外的初始复杂性为代价,我们相信它将帮助我们在正确的方向上扩展并实现我们的目标。基于纯粹的功能,特别是非常适合actor,这种选择与我们选择使用Elixir作为语言和平台是一致的。

如果你碰巧在巴黎(或者很乐意搬迁),有一种疯狂和不可抗拒的冲动,需要学习,编写和发扬CQRS / ES架构中的代码:让我们来谈谈吧!
原文:https://medium.com/margobank/choosing-an-architecture-85750e1e5a03

上一篇 下一篇

猜你喜欢

热点阅读