微服务中的事件、流程和长时间运行业务


本文是讨论微服务和领域事件架构下一些需要长时间运行服务的设计问题,这些长时间运行的服务任务是因为有人工流程介入导致,比如请假需要所在部门和人事部门等两个部门领导批准,那么请假这个服务就可能需要一两天时间才能完成,因为需要两个部门领导都在电脑前且点按了批准按钮,这个请假服务流程才结束。

本文关键点:

1. 围绕微服务架构的讨论促进了EDA事件驱动架构思想的发展,EDA能有效地解耦服务。领域事件概念属于EDA一种。

2. 领域事件非常适合分散的数据管理,能够根据写模型产生用于读取的数据模型(CQRS读写分离)或解决业务上很多交叉耦合问题。 但是,您不应该执行复杂的点对点事件链。可以使用“命令”这个概念来协调其他服务能够进一步减少耦合。

3.集中管理的ESB不适合微服务架构。微服务中推荐智能端点和哑管模型。但是,不要因为害怕引入中央控制而放弃服务的协调:重要的业务能力需要一个总控点。

4.过去,BPM和工作流引擎在战略方向上有问题,所以市场上出现许多可怕的所谓“零代码”的可视化流程开发工具却吓跑了开发者。现在出现了轻量级且易于使用的流程框架了,其中有许多框架是开源的。 不要花时间编写自己的流程状态机,而是要利用现有的工作流工具。这将帮助您避免意外的复杂性。

如果您一直关注最近围绕微服务架构风格的讨论,您可能会听到这样的建议:“为了有效地分离微服务,您必须创建一个事件驱动架构”。

这个想法得到了领域驱动设计(DDD)社区的支持,这里介绍如何使用领域事件以及它们是如何改变我们对系统的看法。

虽然我们普遍支持事件驱动导向,但是如果没有进一步的思考就会增加使用风险。为了回答这个问题,我们回顾了三个常见的假设:

1. 事件减少耦合
2. 中央控制事件总线ESB需要避免
3. 工作流引擎是痛苦的


我们假设我们有四个有界的上下文产生四个专用的服务(可能是微服务,自包含系统或其他形式的服务):

1. Checkout结账服务
2. Payment支付服务
3. Inventory库存服务
4. shipment货运服务


如何使用事件实现解耦

让我们假设上述Checkout服务在“如果一个物品有货可以马上运送”的情况下应该给用户一个反馈,。Checkout服务可以使用请求/响应向库存服务询问库存数量,但是这将使得Checkout和Inventory耦合起来(就可用性、响应时间等而言)。

另一种方法是,库存发布对库存物品数量的任何变化作为广播事件,让其他模块能够知道这种变化。结帐可以监听这些变化事件,并将当前库存量储存在自己的内部,然后在本地处理相关功能。这些本地信息显然是一个副本,可能与库存中数据并不完全一致。但是,某种程度的最终一致性通常是可以在分布式系统中进行必要的权衡的(CAP定理)。

(第二种办法就是引入领域事件的事件驱动方式)

另一个使用事件驱动用例是能够解耦核心与非核心功能,如两者业务交叉的问题。例如,在您的订单完成后你想发送通知给客户。通知服务以完全自主的方式实施并存储有关客户的通知偏好和联系人等数据。然后它能够发送有关“收到付款”或“发货”等特定事件的客户电子邮件,这些无需在其他服务中进行任何更改。这里如果引入领域事件,将使得发送通知的非核心功能从订单服务中解耦出来,事件驱动架构(EDA)能够让添加新服务或扩展现有服务变得非常简单。

点对点事件链的风险

一旦团队开始使用事件驱动架构,他们往往会迷恋事件 - 事件提供了惊人的解耦,所以尽情地使用它们!当您通过点对点事件链实施端到端的流程(如订单履行业务流程)时,问题开始出现。我们假设一个相当微不足道的流程。它可以通过事件链中的下一个服务总是知道什么时候需要什么服务做什么来实现,事件链服务如下:

Checkout ----> Payment -----> Inventory ----->Shipment


上述流程是支付好以后到库存中取货,然后发货,现在,假如在付款Payment之前执行需要先取货:

Checkout ----> Inventory -----> Payment ----->Shipment


您必须调整和重新部署多个微服务,才能更改业务流程。(原文认为这通常是微服务环境下的反模式,因为这种架构风格的核心设计原则是争取更少的耦合和更多的服务自主权,但是微服务独立部署独立重启正是微服务的一个重要特点)。因此,我们建议您在使用事件实施点对点事件链之前要三思而行,特别是在期待相当复杂的情况下。

使用命令,但不需要中央控制

解决这个问题的一个更明智的方法是在一个专门的Order服务中实施它。该服务可以充当协调员角色,并向其他服务发送命令,这种方式可以描述为:“Order服务协调编排付款payment、库存Inventory和货运Shipmengt等服务以完成客户的业务需求”。

然而,一旦我们谈论协调编排,有些人就会想到“神奇”的企业服务总线(ESB)或集中式业务流程建模(BPM)解决方案。这些工具过去存在不好使用的印象,因为这通常意味着你不得不放弃易于测试或部署自动化的专有工具。所以,James Lewis和Martin Fowler建议在微服务中使用“ 智能终端和哑管道 ” 。

但是,上面引入协调者的设计并没有提出一个聪明管道(哑管的反模式)。编配服务处理作为一流公民执行订单实现,并在一个专门服务order中执行它。这样的服务可以用任何你喜欢的方式,用你喜欢的任何技术堆栈来实现。现在只是你有一个专门的地方,让你可以了解流程,并通过改变一个服务改变流程。

Sam Newman在“建立微服务”一书中描述了另一种风险,就是这种编排订单服务- 随着时间的推移,这会发展成吸纳了所有业务逻辑的“上帝服务”,而其他服务则降级为“贫血”服务,或者变得更糟CRUD式的“实体”服务。这是否会在这里发生呢?不,让我们快速再次回顾Martin Fowler的“智能终端”概念。

什么是智能终端?这是其实是一种关于良好的API设计。对于支付服务,您可以设计一个有效的粗粒度API,它可以接受“请求支付”命令,并发出“已收到付款”或“付款失败”事件(这里有一个模式:命令 -->服务 --->事件,命令是服务的输入,事件是服务的输出)。支付服务中其实内置了复杂的业务逻辑处理比如客户信用卡验证等。在这种情况下,支付服务不会因为它在某种其他环境中被编排(或者更简单地说,“使用”)而变成贫血。


长时间运行的服务

为了设计智能终端并为您的客户提供有价值的API,您必须认识到许多服务可能会长期运行,因为他们需要解决幕后的业务问题(人工审核介入)。假设在信用卡到期的情况下,我们会给客户一个更新修改的机会,如果将该职责功能保留在付款Payment服务内,等待客户提供新的信用卡信息意味着付款仍然可能被请求失败。如果payment支付服务不关心这个客户更新信用卡更新的功能,则会将该职责推给Order服务。Payment支付服务则会更清洁,更符合有界上下文的DDD想法。。支付API变得非常清晰,而且服务易于使用。但是,在某些情况下,对于客户更新信用卡信息,我们可能需要两周的时间才能获得其业务响应。这就是我们所说的“长时间运行”的业务流程。

长时间运行的服务需要以某种方式持久存储状态。这里如果还没有完成付款,那么订单还没有完成,并处于等待该付款的状态。这些状态必须在系统重新启动之后还能够获得。当然,处理持久状态不是一个新问题,有两个典型的解决方案:

1. 实现你自己的持久性“实体”,如数据库实体,持久性框架等,问问自己,你是否曾经建立过一个名为status状态的列表?

2.你在这利用状态机或工作流引擎。
市场上有很多工具和框架,其中一些已经非常成熟。最近在这个领域还有一些创新,例如NetflixUber开发自己的开源项目。


根据我们自己的经验,实现您自己的状态处理持久机制往往会导致本地的状态机。往往面临后续的需求,例如超时处理(“嘿,让我们给游戏添加一个调度器”),状态可见性和报告(“为什么业务人员不能用SQL来查询信息? “),或者如果出现错误(”嗯“)的监视操作。

自己实现状态机如此常见的原因不仅是因为“Not-Invented-Here”综合症,还因为围绕工作流的概念和市场上老式的BPM工具。许多开发人员对通常定位为“零代码”的工具感到痛苦。这样的工具首先被卖给了业务部门,他们往往想要摆脱开发人员,这当然还没有发生。相反,这些工具后来被移交给IT部门,在那里仍然处于“外星人”地位。这些工具通常是重量级和专有的,开发人员会遇到我们称之为“death-by-properties-panel因属性面板而死亡”的问题。

轻量级状态机和工作流引擎

轻量级和灵活的业务流程引擎确实存在,可以像其他所有库一样使用 - 只需很少的代码。他们不把自己定位为“零代码”,而是作为开发者工具箱中的工具。他们解决了状态机的难题,许多项目早期就能看到回报。

这样的工具允许您用ISO标准BPMN图形化流程,或者通常基于JSON,YAML或语言相关DSL(如Java或Golang)的其他流程语言来定义流程。一个重要的方面是流程定义是有效的源代码,因为它们将被直接执行。执行意味着状态机知道如何从一个状态转换到另一个状态。

成熟的流程语言(如BPMN)提供了相当强大的概念,例如处理时间和超时,或复杂的业务事务。因为有很多项目利用BPMN,所以我们知道我们可以用它来解决棘手的需求。

在上面的例子中,工作流实例等待一个Goods Fetched取货事件,直到某个超时触发为止。如果发生这种情况,业务交易将得到补偿,即所有的补偿活动都将被执行,在这种情况下,付款将被退还。状态机跟踪已经执行的活动,因此能够触发所有必要的补偿动作。这实现了状态机协调业务的事务交易 - 底层设计也被称为Saga模式。

利用图形符号来定义这样的流程还增加了活文档的思想 - 文档与运行系统的排头并齐,使其不会与实际行为不同步。一些工具为单元测试包括长时间运行的特定情况提供了特殊的支持。例如在Camunda中,每个测试运行都会生成一个HTML输出,该输出突出显示已执行的场景,这些内容可以轻松挂接到正常的持续集成(CI)报告中。这样的图形模型增加了更多的价值:


工作流程位于服务边界内部

一个非常重要的概念是使用业务工作流框架和工具是每个服务团队做出的分散决策。状态机应该是一个在服务外部不可见的实现细节。不需要任何中央工作流程工具,而且状态机应该只是一个库,用来更容易地使你的一些服务的长期运行行为。


另一种看待这个问题的方式是这样一个工作流引擎是你的服务的逻辑部分。根据您选择的工具,它可以使用简单的语言客户端(例如使用Java或Go和Zeebe)作为独立的进程运行嵌入到您的应用程序过程(例如,使用Java,Spring和Camunda),也可以由REST API (例如使用Camunda或Netflix指挥)。拥有这种基础设施可以使服务免受实施状态处理本身的负担,以专注于业务逻辑。您可以设计出良好的服务API和真正的智能端点,因为您可以轻松决定让服务长期运行。


我们已经使用Java开源组件(Spring Boot,Camunda,Apache Kafka)开发了这样案例:按这里

原文:
Events, Flows and Long-Running Services: A Modern