设计微服务(一)
Charles Antony爵士Richard Hoare是开发快速排序算法的英国科学家。在1980年接受图灵奖的演讲中,他提到有两种设计软件的方法:一种方法是使软件变得如此简单以至于显然没有缺陷,另一种方法是使软件如此复杂以至于没有明显的缺陷 - 第一种方法要困难得多。在微服务设计中,需要担心其内部和外部架构。内部架构定义了如何设计微服务本身,外部架构讨论了它如何与其他微服务进行通信。除非您使设计简单易行,否则会使系统容易出错并远离关键的微服务设计目标。在任何微服务设计的核心,生产时间,可扩展性,复杂性本地化和弹性都是关键要素。除非你简化设计,否则很难达到这些期望。
域驱动设计(DDD)并不是微服务引入的新概念,并且已经存在了很长时间。 Eric Evans在他的着作“域驱动设计:解决软件核心中的复杂性”一书中创造了“域驱动设计”这一术语。 随着微服务成为主流架构模式,人们开始意识到驱动设计概念在设计微服务中的适用性。 这种设计在确定微服务方面起着关键作用。
图2-2微服务之间的通信
假设这些微服务通过消息相互通信,消息作为事件发送。请求首先命中订单处理微服务,一旦锁定库存中的项目,就会触发ORDER_PROCESSING_COMPLETED事件。事件是微服务之间通信的一种方式。可以有多个其他服务监听ORDER_PROCESSING_COMPLETED事件,一旦通知它们,就开始相应地对其进行操作。如图2-2所示,Billing微服务收到ORDER_PROCESSING_COMPLETED事件并开始处理付款。例如,亚马逊在下订单时不会处理付款,但只有在准备发货时才会处理。与亚马逊一样,订单处理微服务仅在订单准备发货时才会触发ORDER_PROCESSING_COMPLETED事件。此事件本身包含处理结算微服务所需的数据。在该特定示例中,它携带客户ID和支付方法。 Billing微服务将客户付款选项存储在其自己的存储库中,包括信用卡信息,因此它现在可以独立处理付款。
注意:使用事件在微服务之间进行通信是微服务间通信中常用的模式之一。 它消除了微服务之间的点对点连接,并通过消息传递系统进行通信。 每个微服务一旦完成自己的处理,就会向一个主题发布一个事件,而其他微服务,它们自己注册为感兴趣的主题的听众,一旦收到通知就会相应地行动。 微服务集成模式和事件驱动的消息传递模式中使用的消息传递技术将在“服务间通信”,“集成微服务”和“API,事件和流”中介绍。
一旦Billing微服务完成处理付款,它将触发PAYMENT_PROCESSING_COMPLETED事件,并且Delivery微服务将捕获它。此事件包含客户ID,订单ID和发票。现在,交付微服务从其自己的存储库加载客户交付地址,并准备交付订单。即使客户微服务如图2-2所示,它也不会在订单处理流程中使用。当新客户加入系统或现有客户想要更新其个人数据时,将使用客户微服务。
当一个项目的语言断裂时,它将面临严重的问题.-埃里克埃文斯
图2-2中的每个微服务都属于一个业务领域。库存和订单管理是订单处理微服务的领域;客户管理是客户微服务的领域;交付是交付微服务的领域;计费和财务是计费微服务的领域。这些域或部门中的每一个都可以在内部具有自己的通信结构以及其自己的术语来表示业务活动。
每个域都可以独立建模。只要一个人可以独立于其他人,它就可以获得更大的灵活性来自行发展。域驱动设计定义了关于如何为给定域建模的最佳实践和指南。它强调需要使用无处不在的语言来定义域模型。无处不在的语言是一种共享的团队语言,由领域专家和开发人员共享。事实上,无处不在意味着必须在给定的上下文中(或者确切地说,在有限的上下文中,在后面的部分中讨论),从会话到代码,在任何地方使用相同的语言。这填补了域专家和开发人员之间沟通的空白。域专家使用他们自己的术语是彻底的,但对软件开发中使用的技术术语有限或根本没有理解,而开发人员知道如何用技术术语描述系统,但没有或有限的领域专业知识。无处不在的语言填补了这一空白,并将每个人带到同一页面。
由普遍存在的语言定义的术语必须受相应的上下文的限制。上下文与域相关。例如,无处不在的语言可用于定义称为客户的实体。库存和订单管理域中的客户实体的定义不一定需要与客户管理域中的相同。例如,库存和订单管理域中的客户实体可能具有订单历史,未结订单和预定订单等属性,而客户管理域中的客户实体具有诸如名字,姓氏,家庭住址等属性,电子邮件地址,手机号码等。结算和金融域中的客户实体可能具有信用卡号,帐单邮寄地址,帐单历史记录和预定付款等属性。由普遍存在的语言定义的任何术语只能在相应的上下文中解释。
注意:典型的软件项目仅在需求收集阶段涉及领域专家。 业务分析师(BA)将业务用例转换为技术需求规范。 业务分析师完全拥有这些要求,并且没有反馈周期。 正如业务分析师所听到的那样,该模型已经开发出来。 域驱动设计的一个关键是鼓励领域专家和开发人员之间进行更多,更长时间的沟通。 这远远超出了最初的需求收集阶段,最终建立了域专家和开发人员都很好理解的域模型。
深入研究这个例子。 在体系结构中,给定的微服务属于单个业务域,并且微服务之间的通信通过消息传递发生。 消息传递可以基于事件驱动的体系结构,也可以基于HTTP。 从一个微服务到另一个微服务的每条消息都携带域对象。 例如,ORDER_PROCESSING_COMPLETED事件包含订单域对象,而PAYMENT_PROCESSING_COMPLETED事件包含发票域对象(参见图2-2)。 必须通过域驱动设计以及域专家和开发人员之间的协作来仔细地推导出这些域对象的定义。
有界的背景
正如我们所讨论的,微服务设计中最具挑战性的部分之一是确定微服务的范围。这是SOA及其实现在范围上定义不明确的地方。在SOA中,在进行设计时,会考虑整个企业。它不会担心个别业务领域,而是整个企业。它不会担心库存和订单管理,计费和财务,交付和客户管理作为独立的独立域 - 而是将整个系统视为企业电子商务应用程序。
图2-3说明了SOA架构师完成的电子商务应用程序的分层体系结构。对于来自某些SOA背景的人来说,这应该看起来非常熟悉。我们这里有一个单片应用程序。即使服务层将某些功能公开为服务,也没有一个功能彼此分离。服务的范围没有基于它们所属的业务领域来完成。例如,订单处理服务还可以处理计费和交付。在第1章“微服务案例“中,我们讨论了这种单片架构的不足之处。
图2-3A电子商务应用程序的分层体系结构
正如在上一节中讨论的那样,域驱动设计有助于扩展微服务。微服务的范围是围绕有界的上下文完成的。有限的背景是微服务设计的核心。 Eric Evans在他的“域驱动设计:解决软件核心中的复杂性”一书中首次介绍了有界环境作为设计模式。这个想法是任何给定的域由多个有界上下文组成,每个有界上下文将相关的功能封装到域模型中,并定义与其他有界上下文的集成点。换句话说,每个有界上下文都有一个显式接口,它定义了与其他上下文共享的模型。通过明确定义应共享哪些模型,而不共享内部表示,我们可以避免可能导致紧密耦合的潜在缺陷。这些模块化边界是微服务的理想选择。通常,微服务应该干净地对齐有界的上下文。如果服务边界与相应域的有界上下文对齐,并且微服务代表那些有界上下文,则表明微服务松散耦合且具有强大的内聚性。
注意:有界上下文是存在域模型的显式边界。 在边界内,无所不在的语言的所有术语和短语都具有特定的含义,而模型则反映了语言的准确性
用有界上下文扩展之前的例子。在那里确定了四个领域:库存和订单管理,计费和财务,交付和客户管理。设计的每个微服务都附加到其中一个域。即使在微服务和域之间存在一对一的关系,但我们现在知道一个域可以有多个有界上下文,因此不止一个微服务。例如,你采用库存和订单管理域,有订单处理微服务,但也可以有多个其他微服务,基于不同的有界上下文(例如,库存微服务)。为此,我们需要仔细研究清单和订单管理域下提供的关键功能,并确定相应的有界上下文。
注意:建议有界上下文通过各自拥有自己的团队,代码库和数据库模式来保持它们的分离。
企业的库存和订单管理部门负责管理库存,确保现有库存满足客户需求。它还应该知道何时从供应商处订购更多库存以优化销售以及存储设施。每当收到新订单时,它都必须更新库存并锁定相应的物品以便交货。一旦付款完成并由计费部门确认,交付部门必须在其仓库中找到该项目并使其可用于提取和交付。同时,每当商店中商品的可用数量达到某个阈值时,库存和订单管理部门应联系供应商以获得更多,并且一旦收到,就应该更新库存。
域驱动设计的一个关键亮点是领域专家和开发人员之间的协作。要正确理解库存管理部门在企业中的工作方式,否则您永远不会识别相应的有界上下文。由于对库存管理的理解有限,基于之前讨论的内容,可以确定以下三个有限的背景。
订单处理:此有界上下文封装了与处理订单相关的功能,包括按库存中的订单锁定商品,记录客户订单等。
库存:库存本身可以视为有限的上下文。这样可以在收到供应商的物品并发布交货时更新库存。
供应商管理:这个有限的上下文封装了与管理供应商相关的功能。在发布交货项目时,供应商管理检查库存中是否有足够的库存,如果没有,则通知相应的供应商。
图2-4说明了库存和订单管理域下的多个微服务,代表了每个有界上下文。这里,服务边界与相应域的有界上下文对齐。有界上下文之间的通信只能通过消息传递来定义良好的接口。如图2-4所示,Order Processing微服务首先更新Inventory微服务以锁定订单中的项目,然后触发ORDER_PROCESSING_COMPLETED事件。听取ORDER_PROCESSING_COMPLETED事件的Billing微服务执行支付处理,然后触发PAYMENT_PROCESSING_COMPLETED事件。监PAYMENT_PROCESSING_COMPLETED事件的供应商管理微服务检查库存中的物料数量是否高于最低阈值,如果没有通知供应商。监听同一事件的交付微服务执行其操作以查找项目(可能向仓库机器人发送指令),然后将这些项目分组在一起以构建订单并使其准备好交付。完成后,Delivery微服务将触发ORDER_DISPATCHED事件,该事件通知订单处理微服务,并将更新订单状态。
图2-4有界上下文之间的通信
一个好的设计会将一个微服务范围扩展到一个有界的上下文。任何扩展到多个有界上下文的微服务都偏离了最初的目标。当有一个微服务,将业务逻辑封装在一个定义良好的接口后面并表示一个有界的上下文时,引入新的更改将对整个系统没有影响或影响很小。
正如我们所讨论的,微服务之间的通信可以通过事件发生。在域驱动设计下,这些事件称为域事件。域事件是由有界上下文中的状态更改触发的。然后其他有界的上下文可以以松散耦合的方式响应这些事件。触发事件的有界上下文不必担心由于这些事件而应该发生的行为,同时处理此类事件的有限上下文不必担心事件的来源。域事件可以在域内的有界上下文之间或域之间使用。
Vaughn Vernon在他的“实现域驱动设计”一书中介绍了表达上下文映射的多种方法。更简单的方法是提供一个图表来显示两个或多个现有有界上下文之间的映射,如图2-5所示。此外,请记住,图2-5中的每个有界上下文都有相应的微服务。我们在两个有界上下文之间使用了一条线,在每一端有两个标识符,U或D,以显示相应有界上下文之间的关系。 U表示上游,D表示下游。
图2-5上下文映射
在Order Processing有界上下文和Billing有界上下文之间的关系中,Order Processing有界上下文是上游上下文,而Billing是下游上下文。上游上下文可以更好地控制下游上下文。换句话说,上游上下文定义了在两个上下文之间传递的域模型。下游上下文应该清楚地意识到上游上下文发生的任何变化。图2-4显示了在这两个有界上下文之间传递的确切消息。没有直接耦合。 Order Processing有界上下文和Billing有界上下文之间的通信通过事件发生。上游有界上下文或Order Processing有界上下文定义了事件的结构,并且对该事件感兴趣的任何下游有界上下文必须是兼容的。
Billing有界上下文与Supplier Management有界上下文之间的关系也与Order Processing有界上下文和Billing有界上下文之间的关系相同。 Billing是上游环境,而Supplier Management是下游环境。这两个有界上下文之间的通信是通过事件发生的,如图2-4所示。订单处理有界上下文与库存有界上下文之间的通信是同步的。 Inventory是上游上下文,Order Processing是下游上下文。换句话说,订单处理有界上下文与库存有界上下文之间的通信合同由库存有界上下文定义。并非所有关系(如图2-5所示)都需要解释,因为它们不言自明。