微服务和DDD的关系

微服务和DDD的关系

众所周知,微服务架构能让系统的开发与运维管理变得简单高效,还能提高系统的可用性。随着微服务的不断流行,我们开始在自己的业务中落地微服务。

但是当实际执行时,我们才发现就算采用了微服务架构也不能解决问题,反而带来很多开发与运维上的负担。于是我们试着去找解决方案,最后发现其实是自己划分微服务的方法错了,我们应该用 DDD(领域驱动设计) 的思想去指导微服务的实践

简单来说,DDD 的本质是一种软件设计方法,而微服务架构是具体的实现方式微服务架构虽好,但是他并没有给出如何对复杂系统进行分解的具体方法论,而 DDD 正好就是解决方案

DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

中台本质是领域模型微服务是领域模型的系统落地DDD 是一种设计思想,它可以同时指导中台领域建模型和微服务设计,这就是 DDD、中台和微服务的铁三角关系

领域驱动设计和界限上下文

微服务的强大之处在于清晰地定义了它们的职责并划定了它们之间的边界。它的目的是在边界内建立高内聚,在边界外建立低耦合。也就是说,倾向于一起改变的事物应该放在一起。正如现实生活中的许多问题一样,但这说起来容易做起来难,业务在不断发展,设想也随之改变。因此,重构能力是设计系统时考虑的另一项关键问题。
在我们看来,领域驱动设计 (DDD) 是关键,它是设计微服务时必不可少的工具,无论是对单体应用进行拆分还是从头开始构建一个新项目。领域驱动设计因 Eric Evans 的著作而出名,它是一组思想、原则和模式,可以帮助我们基于业务领域的底层模型设计软件系统。开发人员和领域专家一起使用统一的通用语言创建业务模型。然后将这些模型绑定到有意义的系统上,在这些系统和处理这些服务的团队之间建立协作协议。更重要的是,它们设计了系统之间的概念轮廓或边界。
微服务设计从这些概念中汲取了灵感,因为所有这些原理都有助于构建可以独立变更和发展的模块化系统。
领域(Domain): 代表组织所做的工作。例如零售或电子商务。
子域(Subdomain): 组织或组织内的业务部门。一个领域由多个子域组成。
统一语言(Ubiquitous language):这是用于表达模型的语言。在下面的例子中(图 1),Item 是一个模型,它是每个子域的统一语言。开发人员、产品经理、领域专家和业务各涉众方都能就使用这种语言达成一致,并在他们的工件(代码、产品文档等)中使用该语言。
界限上下文(Bounded Contexts):领域驱动设计将界限上下文定义为“一个单词或语句出现时确定其含义的设置”。简而言之,这意味着模型在边界内是有含义的。在上面的例子中(图 1),“Item”在每个上下文中都有不同的含义。在 Catalog 上下文中,Item 表示可出售的产品,而在 Cart 上下文中,它表示客户已添加到购物车中的商品选项。在 Fulfillment 上下文中,它表示将要运送给客户的仓库物料。这些模型各不相同,每个模型都有不同的含义,并且可能包含不同的属性。通过将这些模型分离并将其隔离在各自的边界内,我们就可以自由地表达这些模型,而不会产生歧义。
注意: 必须理解子域和界限上下文之间的区别。子域属于问题空间,即我们的业务要如何看待问题,而界限上下文属于解决方案空间,即我们将如何实施问题的解决方案。理论上,每个子域可能有多个界限上下文,尽管我们努力每个子域只提供一个界限上下文。

微服务和界限上下文如何关联

定价界限上下文有三个不同的模型:价格(Price)、定价项(Priced items) 和折扣(Discounts),分别负责目录项的价格、计算列表项的总价以及各自使用的折扣。

在 DDD 中,这些模型(价格、定价项和折扣)被称为聚合(Aggregates)。聚合是由相关模型组成的自包含模型。我们只能通过已发布的接口来变更聚合的状态,并且聚合可以确保一致性,而且不变量可以始终保持良好状态。
在形式上,聚合是关联对象的集群,被视为数据变更的单元。外部引用仅限于指定聚合的一个成员,即聚合根。在聚合的边界内需应用一组一致性规则。
没有必要将每个聚合都建模为一个不同的微服务。在某些情况下,在单个服务中托管多个聚合可能是有意义的,特别是在我们不完全了解业务领域的情况下。需要注意的一点是,一致性只在单个聚合中才能得到保证,并且聚合只能通过已发布的接口进行修改。任何违反这些规则的行为都有增加应用程序变成一个大泥球的风险。

上下文映射:一种精确划分微服务边界的方法

另一个基本工具是上下文映射,同样,它也是来自领域驱动设计。一个单体应用通常由不同的模型组成,这些模型大多是紧密耦合的,模型之间可能知道彼此的实现细节,变更一个模型可能造成另一个模型的副作用等等。当你分解单体应用时,确定这些模型(在这里是聚合)及其关系是至关重要的。上下文映射可以帮助我们做到这一点。它们用于识别和定义各种界限上下文和聚合之间的关系。在上面的例子中,界限上下文定义了模型的边界(价格、折扣等等)。而上下文映射定义了这些模型之间以及不同上下文之间的关系。在确定了这些依赖关系之后,我们就可以确定下来实现这些服务的团队之间的正确协作模型了。

请记住,微服务架构的成败取决于聚合之间的低耦合以及聚合之内的高内聚

可以将整个上下文及其聚合组成单个微服务。

我们发现这种启发式方法对于有些模糊的领域特别有用,比如组织正在涉足的新业务领域。我们可能对分离的正确边界没有足够的了解,并且任何过早的聚合分解都可能导致昂贵的重构。试想一下,由于数据迁移,不得不将两个数据库合并为一个,因为我们偶然发现两个聚合属于同一类。但是要确保这些聚合通过接口是充分隔离的,这样它们就不知道彼此的复杂细节了。

微服务也是分布式系统。因此,CAP 定理也适用于它们:“一个分布式系统只能提供三个所需特性中的两个:一致性、可用性和分区容错(CAP 中的‘C’——Consistency、‘A’——Availability 和 ‘P’——Partition Tolerance)。”在现实世界的系统中,分区容错是不可协商的——网络是不可靠的、虚拟机可以宕机、区域之间的延迟可能会变得更糟等等。

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

我们最好根据自己的用例来设计应用程序,容忍稍微的不一致,以提高可用性。对于上面的例子,我们可以使所有流程异步,从而达到最终的一致性。
可能需要跨越不同流程边界的两个聚合的强 ACID 式的事务。这是一个重新审视这些聚合并将它们组合成一个聚合的极好迹象。开始在不同流程边界中分解这些聚合之前,事件风暴和上下文映射将有助于我们及早识别这些依赖关系。将两个微服务合并为一个的成本很高,这是我们应该努力避免的。
微服务可以将发生在其聚合上的基本更改发出来,这些称为领域事件(Domain Event),并且任何对这些更改感兴趣的服务都可以监听这些事件并在其领域内执行相应的操作。这种方法避免了任何行为上的耦合(一个领域无需规定其他领域应该做什么)以及时间上的耦合(一个流程的成功完成不依赖于所有系统同时可用)。当然,这将意味着系统最终是一致的。


posted @ 2021-04-11 15:23  delphi中间件  阅读(1326)  评论(0编辑  收藏  举报