如何构建基于 DDD 领域驱动的微服务?(2)

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 如何构建基于 DDD 领域驱动的微服务?

事件风暴-识别服务边界的另一种技术


事件风暴是识别系统中的聚合(以及微服务)的另一种必不可少的技术。这对于破坏整体结构以及设计复杂的微服务生态系统都是有用的工具。我们已经使用这种技术分解了一个复杂的应用程序,并且打算在单独的博客中介绍Event Storming的经验。对于此博客的范围,我们想给出一个快速的高级概述。如果您有兴趣进一步探索,请观看Alberto Brandelloni的视频。


简而言之,事件风暴是在应用程序团队(在我们的情况下为整体)中进行的头脑风暴,以识别系统中发生的各种领域事件和流程。团队还确定这些事件影响及其后续影响的汇总或模型。在团队进行此练习时,他们会确定不同的重叠概念,模棱两可的领域语言以及相互冲突的业务流程。他们将相关模型分组,重新定义聚合并确定重复的过程。随着这些工作的进行,这些集合所属的有限上下文变得清晰起来。如果所有团队都在同一个房间(物理或虚拟)中,并开始在Scrum风格的白板上绘制事件,命令和过程的映射,那么Event Storming研讨会将非常有用。在本练习结束时,


  1. 重新定义的聚合列表。这些可能成为新的微服务
  2. 这些微服务之间需要流动的域事件
  3. 直接从其他应用程序或用户调用的命令

我们在一场Event Storming研讨会结束时展示了一个示例板。对于团队来说,这是一次很棒的协作活动,以就正确的聚合和有限的上下文达成一致。除了进行出色的团队建设活动外,团队在本次会议中脱颖而出,对领域,通用语言和精确的服务边界有着共同的理解。


微服务之间的通信


一个整体在一个流程边界内托管了多个聚合体。因此,在此边界内可以管理聚合体的事务一致性,例如,如果客户下订单,我们可以减少项目的库存,并向客户发送电子邮件,全部都在一次交易事务中。所有操作都会成功,或者全部都会失败。但是,当我们打破整体并将聚合散布到不同的环境中时,我们将拥有数十甚至数百个微服务。迄今为止,在整体结构的单个边界内存在的流程现在分布在多个分布式系统中。要在所有这些分布式系统上实现事务的完整性和一致性非常困难,而且要付出代价-系统的可用性。


微服务也是分布式系统。因此,CAP定理也适用于它们- “分布式系统只能提供三个所需特征中的两个:一致性,可用性和分区容限(CAP中的“ C”,“ A”和“ P”)。” 在现实世界的系统中,分区容忍度是无法商议的- 网络不可靠,虚拟机可能宕机,区域之间的延迟会变得更糟,等等。


因此,我们可以选择“可用性”或“一致性”。现在,我们知道在任何现代应用程序中,牺牲可用性也不是一个好主意。


围绕最终一致性设计应用程序


如果您尝试跨多个分布式系统构建事务,那么您将再次陷入困境。变成最糟糕的一种分布式整体事务。如果任何一个系统点不可用,则整个流程将不可用,通常会导致令人沮丧的客户体验、失去保障失败的承诺等。


此外,对一项服务的更改通常可能需要对另一项服务进行更改,从而导致复杂而昂贵的部署。因此,我们最好根据自己的用例来设计应用程序,以容忍一点点的不一致性,以提高可用性。对于上面的示例,我们可以使所有进程异步,从而最终保持一致。我们可以异步发送电子邮件,而与其他流程无关。


如果承诺的物品以后在仓库中不可用,该项目可能被延期订购,或者我们可以停止接受超过某个阈值的项目的订单。


有时,您可能会遇到一个场景,该场景可能需要跨越不同流程边界的两个聚合中的强ACID样式事务。这是重新查看这些聚合并将它们合并为一个极好的标志。在我们开始在不同过程边界中分解这些聚合之前,事件风暴和上下文映射将有助于及早识别这些依赖性。将两个微服务合并为一个成本很高,这是我们应该努力避免的事情。


支持事件驱动的架构


微服务可能会在其聚合上发出本质上的变化。这些称为领域事件,并且对这些更改感兴趣的任何服务都可以侦听这些事件并在其域内采取相应的操作。这种方法避免了任何行为上的耦合:一个域不规定其他域应该做什么,以及时间耦合-一个过程的成功完成不依赖于同时可用的所有系统。当然,这将意味着系统最终将保持一致。


在上面的示例中,订单服务发布了一个事件-订单已取消。订阅该事件的其他服务处理其各自的域功能:付款服务退还款项,库存服务调整项目的库存,依此类推。要确保此集成的可靠性和弹性的几点注意事项:


  • 生产者应确保至少生产一次事件。如果这样做失败,则应确保存在回退机制以重新触发事件
  • 消费者应确保以幂等的方式消费事件。如果再次发生同一事件,那么在用户端不应有任何副作用。事件也可能不按顺序到达。消费者可以使用时间戳记或版本号字段来保证事件的唯一性。

由于某些用例的性质,不一定总是可以使用基于事件的集成。请查看购物车服务和付款服务之间的集成。这是一个同步集成,因此我们需要注意一些事项。这是行为耦合的一个示例-Cart服务可能从Payment服务中调用REST API,并指示其授权订单付款,而时间耦合则需要Payment服务用于Cart服务才能接受订单。这种耦合减少了这些上下文的自治性,并且可能减少了不希望的依赖性。有几种方法可以避免这种耦合,但是使用所有这些选项,我们将失去向客户提供即时反馈的能力。


  • 将REST API转换为基于事件的集成。但是,如果支付服务仅公开REST API,则此选项可能不可用
  • 购物车服务立即接受订单,并且有一个批处理作业来接管订单并调用支付服务API
  • 购物车服务会产生一个本地事件,然后调用付款服务API

在失败和上游依赖项(付款服务)不可用的情况下,将上述内容与重试结合使用可以使设计更具弹性。例如,在发生故障的情况下,可以通过事件或基于批次的重试来备份购物车和付款服务之间的同步集成。这种方法会对客户体验产生额外的影响:客户可能输入了不正确的付款明细,并且当我们离线处理付款时,我们不会将其在线。否则,收回失败的付款可能会增加业务成本。但是,很有可能,购物车服务对于支付服务的不可用性或故障具有弹性,其缺点胜于缺点。例如,如果我们无法离线收款,我们可以通知客户。


避免针对特定消费者数据需求的服务之间的编排


任何面向服务的体系结构中的反模式之一是,这些服务都可以满足消费者的特定访问模式。通常,这发生在消费者团队与服务团队紧密合作时。如果团队正在开发整体应用程序,则他们通常会创建一个跨不同聚合边界的单一API,从而紧密耦合这些聚合。


让我们考虑一个例子。说Web中的“订单详细信息”页面,移动应用程序需要在单个页面上同时显示订单的详细信息和针对该订单处理的退款的详细信息。在整体应用程序中,Order GET API(假设它是REST API)一起查询Orders和Refunds,合并两个聚合,然后将复合响应发送给调用方。由于聚合属于相同的过程边界,因此无需太多开销即可执行此操作。因此,消费者可以在一个调用中获得所有必要的数据。


如果订单和退款是不同上下文的一部分,则数据不再存在于单个微服务或聚合边界内。为消费者保留相同功能的一种选择是使订单服务负责调用退款服务并创建复合响应。此方法引起以下问题:


  1. 订单服务现在与另一个服务集成在一起,纯粹是为了支持需要退款数据和订单数据的消费者。现在,订单服务的自治性降低了,因为退款总额中的任何更改都将导致订单总额中的更改。
  2. 订单服务具有另一个集成,因此要考虑另一个故障点-如果退款服务出现故障,订购服务是否仍可以发送部分数据,并且消费者可以正常地故障吗?
  3. 如果消费者需要更改以从“退款”聚合中获取更多数据,则现在需要两个团队来进行更改
  4. 如果在整个平台上都遵循这种模式,则可能导致各种域服务之间的依存关系错综复杂,所有这些都是因为这些服务满足了调用者的特定访问模式。
  5. 前端的后端BFF


减轻这种风险的一种方法是让消费者团队管理各种域服务之间的编排。毕竟,呼叫者会更好地了解访问模式,并且可以完全控制对这些模式的任何更改。这种方法将域服务与表示层分离开来,使它们专注于核心业务流程。但是,如果Web和移动应用程序开始直接调用不同的服务而不是从整体中调用一个复合API,则可能会导致这些应用程序的性能开销–通过较低带宽网络进行多次调用,处理和合并来自不同API的数据,等等。 。


相反,可以使用另一种称为前端的后端的模式。在这种设计模式下,由消费者(在本例中为Web和移动团队)创建和管理的后端服务负责跨多个域服务的集成,纯粹是为了向客户提供前端体验。Web和移动团队现在可以根据他们的用例设计数据合同。他们甚至可以使用GraphQL而不是REST API来灵活地查询并准确获取所需的信息。


重要的是要注意,此服务是由使用者团队拥有和维护的,而不是由拥有域服务的团队拥有和维护的。前端团队现在可以根据自己的需求进行优化-移动应用程序可以请求更小的有效负载,减少来自移动应用程序的呼叫次数等等。查看下面的业务流程的修订视图。


结论


在此博客中,我们触及了各种概念,策略和设计启发法,以便在我们进入微服务领域时,尤其是在尝试将整体式服务拆分为基于域的微服务时,加以考虑。其中许多主题本身就是广阔的主题,我认为我们没有做出足够的公正性来详细解释它们,但是我们想介绍一些关键主题以及我们采用这些主题的经验。进一步阅读(链接)部分提供了一些参考资料和一些有用的内容,供任何希望采用此方法的人使用。


原文:https://medium.com/walmartglobaltech/building-domain-driven-microservices-af688aa1b1b8

译文:jdon.com/54558


相关文章
|
弹性计算 运维 数据挖掘
DDD与微服务架构浅析
主要介绍DDD,微服务架构,以及两者之间的关系
DDD与微服务架构浅析
|
2月前
|
消息中间件 测试技术 API
深入解析微服务架构的设计与实践
在软件工程领域,"分而治之"的策略一直是解决复杂问题的有效方法。微服务架构作为这一策略的现代体现,它通过将大型应用程序分解为一组小的、独立的服务来简化开发与部署。本文将带你了解微服务的核心概念,探讨设计时的关键考虑因素,并分享实践中的一些经验教训,旨在帮助开发者更好地构建和维护可扩展的系统。
|
2月前
|
消息中间件 负载均衡 持续交付
构建可扩展的微服务架构:从设计到实现
在微服务架构的世界里,设计和实现可扩展性是至关重要的。然而,开发者往往面临着如何在系统复杂性和性能之间取得平衡的问题。本文通过深入探讨微服务架构的关键设计原则和实践,展示了如何从初期设计到最终实现,构建一个既高效又可扩展的系统架构。
|
2月前
|
前端开发 API 数据库
构建领域驱动的微服务
构建领域驱动的微服务
40 3
|
2月前
|
监控 Java API
构建微服务架构的简易指南
【8月更文挑战第31天】在这篇文章中,我们将探索如何从零开始搭建一个微服务架构。通过简单易懂的语言和步骤,我们将一起学习设计、实现和部署微服务的最佳实践。无论你是后端开发的新手还是希望提升现有技能,本文都将为你提供有价值的指导。
|
设计模式 前端开发 测试技术
如何构建基于 DDD 领域驱动的微服务?(2)
如何构建基于 DDD 领域驱动的微服务?
136 0
|
测试技术 持续交付 定位技术
如何构建基于 DDD 领域驱动的微服务?(1)
如何构建基于 DDD 领域驱动的微服务?
128 0
|
设计模式 运维 测试技术
为什么在做微服务设计的时候需要DDD?
回到主题,我们要了解的是微服务和DDD到底有什么关系呢? 因为在互联网时代,软件所面临的问题域比以往要复杂得多,这种复杂性来源于不断扩展的问题域自身,也来源于创新变化,以及这种规模性增长所
为什么在做微服务设计的时候需要DDD?
|
运维 监控 微服务
DDD领域驱动设计实战-微服务架构演进的关键:边界(下)
微服务的设计要涉及到逻辑边界、物理边界和代码边界等。 逻辑边界:微服务内聚合之间的边界是逻辑边界。它是一个虚拟的边界,强调业务的内聚,可根据需要变成物理边界,也就是说聚合也可以独立为微服务。 物理边界:微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。 代码边界:不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。
260 0
DDD领域驱动设计实战-微服务架构演进的关键:边界(下)
|
存储 前端开发 NoSQL
[半翻] 设计面向DDD的微服务
在DDD中,应用层依赖于领域和基础设施层,而基础设施依赖于领域层,但是领域层不依赖于任何层。 只在领域层编写业务规则和通用的领域知识,而应用层负责针对软件的目标来组合、协调领域层的业务规则。 领域层的领域实体、值类型、聚合根反映了真实业务的核心,需要用一种通用的语言来定义,这样不管应用层多么复杂,核心领域层自岿然不动。 领域层不能直接依赖与基础设施层,现代ORM框架一般都提出仓储模型来帮助领域层和技术设施层解耦。
[半翻] 设计面向DDD的微服务
下一篇
无影云桌面