【TICA解读】架构设计的本质:如何确定服务的边界

简介: 【TICA解读】架构设计的本质:如何确定服务的边界

阿里QA导读:领域驱动设计,微服务架构,业务中台,云原生架构等概念贯穿了企业级分布式系统的全流程中,并且每个概念或者工具作用的阶段不一样,导致如果我们不站在端到端的角度看这些概念,就会出现盲人摸象,只知其然,不知其所以然的情况。无论是作为总控TM,还是作为项目交付的1号位,或者说其他的角色,如果我们没有办法体系化的答复客户关于这几个概念以及关系的疑问,客户就会对我们出现信任方面的疑虑。而对于B端的生意,是个对相互之间信任要求非常高的业务,如果客户发现我们无法给他们提供有效的帮助,那么可能当前合同继续推进,后续就会转向其他公司。我们需要从更加抽象的层面,对数字化转型,DDD,云原生,业务中台等概念有深刻的认知,才能应对客户无论是从业务,技术,甚至是流程管理层面的问题。因此笔者想借着聊微服务划分的边界,来系统性的阐述这些概念之间的关系,最后聚焦于微服务的边界具体应该和领域驱动设计中哪个概念对齐,并回答为什么。

服务及微服务概述



   微服务本质上就是服务,大概在2010年的时候逐渐变得流行起来,微服务架构的出现是为了解决单体应用遇到的灵活性,稳定性等问题。因此很多企业都在IT规划上做了战略性的投入,引入微服务架构来应对企业当前和未来可能出现的业务发展,但很多企业将单体应用拆分为多个”服务“之后,发现投入产生的结果和预期不符,从系统形态上讲,把单体应用拆分成了一个分布式单体应用,除了巨大的投入之外,整个系统的稳定性和可运行性竟然变差了。为了解决这个问题,特别是如何”合理“的为服务划分边界,领域驱动设计中的很多关于边界设计的工具和概念被逐渐拿出来,试图来解答微服务划分边界这个难题。

   笔者在很多场合反复听到所谓的微服务设计专家,信誓旦旦的告诉客户和团队,微服务的边界应该和限界上下文对齐,甚至在这样的项目和团队中,微服务和限界上下文已经变成统一语言,两个概念在日常沟通中可以互换使用。但是微服务的边界真的和领域驱动设计中限界上下文相同吗?咱们接下来就来花费一些时间深入探讨一下。不过在进入这个话题之前,咱们先来对服务做一下规范性定义,避免读者对后续的内容的理解产生歧义。

   维基百科关于”服务“定义可以概括为(笔者针对软件服务做了适当的调整):服务是通过预设的”接口“对外提供访问能力的软件组件,对外提供访问的接口没有特殊的限制,可以是同步的,也可以是异步的,如下图所示,两个服务通过对外暴露的接口进行交互:

image.png    笔者很喜欢把服务暴露的接口称作服务的“大门”,所有进出服务的数据都必须经过这些接口。

   了解完服务的概念,接下来我们来介绍一下什么是“微服务”。由于微服务本质上也是服务,因此咱们前边对于服务的定义依然有效,只不过微服务由于多了“微”这个定语,因此微服务更多强调的是缩减服务对外暴露接口,来提升服务的独立性以及稳定性。由于服务对外提供的服务接口数量减少,因此服务由于外部因素导致的变更的可能性降低,服务的独立部署性,独立新版本发布等都得到了提升。由于我们缩减了服务的接口,那么从边界的角度看,对于限界上下文,聚合(一组对象),一组聚合,对象这几种领域驱动设计上边界的概念,和微服务的边界到底是什么关系。我们来做个极限推导,因为从边界的角度,服务对外暴露的接口的数量,其实可以作为衡量的一种依据。

   如果单纯从“微”的角度来理解微服务,那么一个极限的推导就是一个接口作为一个服务,咱们来基于这个思路,分拆一下笔者在华南某头部直销行业客户的场景,如下图所示:


image.png

   如上所示,按照极限推导的思路,咱们以接口为边界来定义微服务的边界,由于每个微服务都有自己独立的数据库,因此为了让库存中心能够实现预期的功能,我们就需要在这些以每个接口为粒度的微服务间建立复杂的协作机制,来实现库存中心的服务能力。因此除了对外提供的这些库存扣减,还原的接口之外,每个服务还需要设计服务间协作的接口,最终的的设计不难想象,服务间有着极其复杂的相互调用,我们对这种类型的服务设计有个专有名词,分布式单体应用,如下图所示:

image.png   如果每个接口都需要依赖于剩余接口工作,并且之间还需要相互同步数据,那么你可以想象上边的这张图有多复杂。虽然说上边的这种拆分对于每个接口来说问题不大,但是在这些接口之上的协调服务,将变得异常复杂,我们需要花费大量的时间和资源来将这些分布的服务组合起来,对外提供满足业务要求的服务能力。如果继续拿”门“来比喻,那么这些为了服务能正常工作所增加的接口,就像增加”员工专属“入口一样,用来解决协作的问题。

   由于这种粒度的划分会造成额外的负担,因此实际项目中,这种拆法肯定不现实。虽然说每个服务中只包含一个接口使得服务本身更加简单,但是整个系统因此会变得更加复杂。因此从这个极限推导的案例上,我们可以发现系统复杂度的两个维度:本地复杂度和全局复杂度。其中本地复杂度描述的是组成系统的每个服务的复杂度,而全局复杂度描述的是整个系统的复杂度。本地复杂度主要由每个服务本身的能力和实现方式决定,而全局复杂度取决于服务之间的交互频率和相互依赖程度。

   降低全局复杂度其实很容易,我们将所有的业务功能都在同一个服务的边界内实现就行,这其实也就是我们经常挂在嘴边的单体应用。不过单体应用本身并没有什么问题,在特定的业务场景下还是适用的。咱们前边也通过极限推导分析过另外一个极端,结果就是分布式单体应用。这两种复杂度相互之间的关系如下图所示:


image.png 从理论上来说,合理的微服务架构系统必须同时消除本地复杂度和全局复杂度。而在实际的系统设计过程中,降低整个系统的复杂度以及本地复杂度的核心部分,就在于每个服务对外暴露接口上,如果通过数字量化,就是每个服务对外暴露的功能数量。这个服务对外暴露的接口数量是平衡全局复杂度和本地复杂度的关键,接下来咱们来引入”服务深度“的概念,来看看微服务架构下,通过降低服务对外暴露的接口数量,来如何降低本地复杂度以及全局复杂度。

   软件设计哲理这本书的作者John Ousterhout提出了衡量软件设计优劣的一种评估标准:深度。具体来说Ousterhout建议将软件的模块可视化为矩形,如下图所示。其中矩形顶上的边表示模块(服务)对外提供的功能,或者说服务对外提供服务的复杂度。因此宽的上边表示模块对外提供更多的功能,而窄的上”边“表示服务对外提供有限的能力接口,最后矩形的面积表示服务实现的业务逻辑,或者说服务的功能实现复杂度。


image.png

根据Ousterhout提出的这个理论,设计有效的服务应该属于”深“服务并对外提供有限的接口。而设计有缺陷的服务通常都是”浅“服务,比如服务提供两个数相加,由于方法名和入口参数,包括业务逻辑都非常简单,因此设计AddTwoNumbers这样的一个服务属于”过渡设计“,引入了额外的负责度,虽然说本地复杂度不高,但是增加了全局复杂度,最终造成整个系统的复杂度的升高。

   读者需要注意的是,笔者过往的经验显示,大部分微服务架构的系统未达到设计预期,主要是由于过多的这种”浅“服务造成,并且行业中充斥着大量的拆分微服务最佳实践:1)服务设计不超过xx行;2)服务应该不费吹灰之力就可以重写等等,这些设计理念本身没有问题,但是有非常重要的缺失:忽略了系统复杂度。

   那么我们应该如何把握这个度呢?或者如何来衡量服务的深浅,来降低系统整体的复杂度呢?咱们以单体应用拆分为例,当我们将单体应用拆分为多个微服务后,整体上看在系统应对变更所需要的工作量在降低,但是当我们继续细化服务的粒度,当超过微服务的临界值后,由于从这个临界点后服务的深度越来越浅,因此为了满足集成的需求,需要引入额外的接口,因此全局复杂度会越来越高,进而让我们的应用逐渐向分布式单体靠近,如下图所示:

image.png

具体该如何做呢?



   微服务架构设计和领域驱动设计从本质上来讲,要解决的问题是类似:确定软件系统的边界。比如限界上下文确定的是领域模型的边界;子域确定的是业务能力的边界;聚合(一组实体)和值对象确定的是事务的边界,聚合内部强一致,聚合之间最终一致,那么从系统设计的角度,微服务的边界到底应该和领域驱动设计的子域对齐,还是和限界上下文对齐,亦或是聚合对齐,这是领域驱动设计(业务架构)和微服务架构(技术架构)之间需要解决的核心问题。

限界上下文(Bounded Context)

   限界上下文经常和微服务被互换使用,笔者在很多项目上,默认情况下限界上下文经常会被用作划分微服务边界的依据。首先微服务和限界上下文本质上都是物理边界,其次微服务和限界上下文通常由同一个团队负责,因此我们可以说微服务就是限界上下文,但是限界上下文是不是就等同于微服务?咱们潜在在介绍限界上下文的概念时,反复强调过限界上下文是统一语言和业务模型的边界,在同一个限界上下文中,统一语言没有歧义,模型没有冲突。咱们来举例说明,在在线广告平台中,业务对象Lead(销售线索)在Promotions和Sales上下文中是有区别的,因此我们就可以通过Lead来划分为如下所示的两个限界上下文:

image.png

如果咱们进一步假设上图中两个限界上下文内部没有其他业务模型的冲突,那么基于我们最终形成的限界上下文就是营销和销售。如果我们继续分析这两个限界上下文,就会发现每个限界上下文中会划分出来多个子域,比如客户,活动,登录页等,并且这些子域可以在两个限界上下文中移动,因此如果我们随机组合的话,会有很多种不同的限界上下文切法。但是无论是如何切,最终形成的限界上下文包含的业务功能都非常庞大,从微服务架构边界设计的角度,上图中显示的两个限界上下文,都不合适用来作为微服务划分的边界。

   因此我们可以得出本文的第一个结论:限界上下文和微服务并不是对称的,微服务是限界上下文,但是限界上下文并不一定是微服务。限界上下文要解决的问题是切分“有效”服务的最大边界(也可以称作单体应用),这种类型的应用在某些特定场合下,依然是最有效的系统拆分边界。

注:大家要区分单体应用和巨型单体应用的区别,单体应用本身是有价值的,特别是单体应用在边界内保障了应用的业务模型一致性,以及统一语言的唯一性。巨型单体应用特指应用内部模块划分不合理,并且模块之间相互依赖,无论是变更,还是部署,成本极高。

   基于上边的讨论我们来总结一下,微服务的边界的上限是限界上下文,我们在设计系统的时候,系统边界在限界上下文和微服务边界之间是最合理的,超过限界上下文,或者小于微服务的粒度,结果就是巨型单体或者分布式单体应用,如下图所示:

image.png

聚合(Aggreates)

   限界上下文一般会由多个聚合组成,因此如果微服务和限界上下文不是一一对应,那么微服务是否和聚合一一对应呢?结合咱们在前边几篇文章中的介绍,聚合是系统设计的最小单位,我们将一组业务实体(实体对象)归类,这组对象的生命周期一致,并且对这组对象的访问,都可以通过聚合根(对象)来实现。因此聚合也不太适合作为微服务的边界,并且聚合是微服务边界的下限,如果我们的服务拆分粒度小于聚合,例如咱们在(上)这篇文章中的极限推导,小于这个边界的结果就是分布式单体,系统的全局复杂度会快速升高。

   具体来说,聚合包含独立的业务功能,聚合封装了业务功能的复杂度,业务流程,业务规则等。但是聚合需要和其他聚合服务的协助来实现服务能力,因此微服务在大多数情况下,会包含多个聚合,这些聚合需要协作,或者说聚合需要依赖于其他的聚合来对外提供可复用的业务能力。按照咱们在上文讨论中,一个服务对外部的依赖越多,这个服务就越“浅”,因此我们在大多数场景下,微服务的边界都大于聚合,聚合是微服务边界的下限。这也同时意味着,在有些极端场景下,聚合作为微服务的边界也合理。

子域(Sub Domain)

   接下来我们继续讨论子域和微服务边界的关系,微服务的边界和子域对齐是一种被多位业务大神极力推荐的设计思路。如咱们在前边几篇文章所述,子域封装的是粒度适中的业务能力,子域中包含的业务能力是企业核心业务正常运转不可分割的一部分。从业务架构设计的角度看,子域强调的是业务能力,即子域能够提供哪些业务能力,而不是如何实现这些能力。从技术的角度,子域中包含相互关联的业务用例,这些用例使用一套相同的业务模型,并且使用相同的数据模型。最为重要的是,我们如果对某个业务用例进行了修改,有极大的可能性会影响到与之相关的其他业务用例。

   由于子域聚焦于模块能够对外提供什么样的能力,而不是如何提供这种能力,这就让子域天然的成为“深”模型,子域对外提供的能力对外封装了实现的复杂度,如果我们将子域中包含的这些相关的业务用例拆分到多个微服务中,那么就增加了协作的复杂度,让这些拆分后的服务相比子域的模型变得更“浅”。

   结合上边的讨论,引出了笔者这篇文章的第二个结论:微服务划分的边界应该和子域对齐,特别是子域天然就是一种“深”模型,在大多数场景下,微服务按照子域的边界来划分都可以产出符合功能和非功能需求的设计。当然事无绝对,微服务边界和子域对齐不是在所有场景都适用,我们还需要考虑非功能需求的约束,组织结构的约束,业务策略等因素。笔者的建议是在项目初期基于子域来对定义微服务的边界,随着项目的推进,不断的优化迭代,让微服务的设计适配企业的业务发展和其他非功能性,组织性的诉求。

   确定微服务的边界只是微服务设计的一部分,我们还需要让服务更“深”,咱们接下来讨论一下大家应该都熟知的公共服务(Open-Host Service)和防腐层(Anticorruption Layer)模式是如何帮助我们设计更“深”的微服务接口,或者叫共享服务能力。

公共服务模式(Open-Host Service)

   公共服务模式为了降低模块之间的耦合,因此将用来进行系统集成的领域模型和实现业务领域能力的模型进行了隔离,如下图所示:

image.png 如上图所示,由于通过Open-Host Service暴露的接口使用的业务对象模型和领域服务对象模型进行了隔离,因此我们对领域模型的任何优化,迭代,变更都不会影响到对外暴露的接口,换句话说,我们通过公共服务模式降低了系统的全局复杂度。另外由于对外暴露的接口具备稳定性,并且面向最终的消费端,因此基于相同的领域模型和逻辑,暴露不同的,受约束的公共服务,让服务更具有深度的同时,也让微服务的灵活性得到极大的提升。

防腐层模式(Anticorruption Layer)

   防腐层模式和公共服务模式刚好相反,在有些集成场景中,我们因为各种原因无力改变我们需要集成的上游系统,但是我们又不想被强依赖于上游系统的具体实现,因为为了保护我们将要实现的业务领域,我们需要采用防腐层模式来保护业务领域不受上游系统的侵蚀,如下图所示:

image.png


  如上图所示,我们通过防腐层(ACL)来将同时降低本地复杂度和全局复杂度,消费者端只需要关系自己领域内的复杂度,集成复杂度被抽取到ACL这一层;并且由于下游系统(消费者端)集成的是面向集成数据模型(ACL),因此消费者服务的对外接口得到了简化,服务的变得更深。


相关文章
|
3天前
|
存储 Linux KVM
Proxmox VE (PVE) 主要架构和重要服务介绍
Proxmox VE (PVE) 是一款开源的虚拟化平台,它基于 KVM (Kernel-based Virtual Machine) 和 LXC (Linux Containers) 技术,支持虚拟机和容器的运行。PVE 还提供高可用集群管理、软件定义存储、备份和恢复以及网络管理等企业级功能。
45 7
|
3月前
|
安全 前端开发 JavaScript
逆向海淘代购集运系统:sugargoo的技术架构与创新服务解读
逆向海淘代购集运系统整合中国电商资源,为海外用户提供便捷购物及物流服务,降低购物成本。sugargoo系统搭建攻略包括: - **需求分析与规划**: 深入了解目标市场需求,明确服务特色。 - **平台开发**: 选用合适技术栈,开发关键功能模块,集成电商数据。 - **物流合作**: 建立物流合作关系,集成物流API提升自动化。 - **支付解决方案**: 支持多种支付方式,保障支付安全。 - **客户服务**: 提供多语言支持,建设专业客服团队。 - **营销与推广**: 优化SEO,利用社交媒体扩大品牌影响。
|
7天前
|
消息中间件 Kafka 数据库
微服务架构中,如何确保服务之间的数据一致性
微服务架构中,如何确保服务之间的数据一致性
|
20天前
|
存储 搜索推荐 数据库
MarkLogic在微服务架构中的应用:提供服务间通信和数据共享的机制
随着微服务架构的发展,服务间通信和数据共享成为关键挑战。本文介绍MarkLogic数据库在微服务架构中的应用,阐述其多模型支持、索引搜索、事务处理及高可用性等优势,以及如何利用MarkLogic实现数据共享、服务间通信、事件驱动架构和数据分析,提升系统的可伸缩性和可靠性。
22 5
|
25天前
|
XML Java 数据库
在微服务架构中,请求常跨越多个服务,涉及多组件交互,问题定位因此变得复杂
【9月更文挑战第8天】在微服务架构中,请求常跨越多个服务,涉及多组件交互,问题定位因此变得复杂。日志作为系统行为的第一手资料,传统记录方式因缺乏全局视角而难以满足跨服务追踪需求。本文通过一个电商系统的案例,介绍如何在Spring Boot应用中手动实现日志链路追踪,提升调试效率。我们生成并传递唯一追踪ID,确保日志记录包含该ID,即使日志分散也能串联。示例代码展示了使用过滤器设置追踪ID,并在日志记录及配置中自动包含该ID。这种方法不仅简化了问题定位,还具有良好的扩展性,适用于各种基于Spring Boot的微服务架构。
31 3
|
8天前
|
编解码 Linux 开发工具
Linux平台x86_64|aarch64架构RTMP推送|轻量级RTSP服务模块集成说明
支持x64_64架构、aarch64架构(需要glibc-2.21及以上版本的Linux系统, 需要libX11.so.6, 需要GLib–2.0, 需安装 libstdc++.so.6.0.21、GLIBCXX_3.4.21、 CXXABI_1.3.9)。
|
2月前
|
负载均衡 应用服务中间件 网络安全
Django后端架构开发:Nginx服务优化实践
Django后端架构开发:Nginx服务优化实践
40 2
|
2月前
|
消息中间件 Java API
解密微服务架构:如何在Java中实现高效的服务通信
微服务架构作为一种现代软件开发模式,通过将应用拆分成多个独立的服务,提升了系统的灵活性和扩展性。然而,实现微服务之间的高效通信仍然是许多开发者面临的挑战。本文将探讨在Java环境中实现微服务架构时,如何使用不同的通信机制来优化服务之间的交互,包括同步和异步通信的方法,以及相关的最佳实践。
|
2月前
|
Kubernetes 安全 Serverless
Kubernetes 的架构问题之Serverless Container中提供对外服务如何解决
Kubernetes 的架构问题之Serverless Container中提供对外服务如何解决
66 5
|
2月前
|
编解码 Linux 数据安全/隐私保护
Linux平台x86_64|aarch64架构如何实现轻量级RTSP服务
为满足在Linux平台(x86_64与aarch64架构)上实现轻量级RTSP服务的需求,我们开发了一套解决方案。该方案通过调用`start_rtsp_server()`函数启动RTSP服务,并设置端口号及认证信息。支持AAC音频和H.264视频编码,可推送纯音频、纯视频或音视频流。此外,还支持X11屏幕采集、部分V4L2摄像头采集、帧率/GOP/码率调整、摄像头设备选择与预览等功能。对于音频采集,支持alsa-lib和libpulse接口。整体设计旨在提供150-400ms的低延迟体验,适用于多种应用场景。
下一篇
无影云桌面