目录
0.1传统企业数字化转型的困难
0.1.1 传统企业数字化建设回顾
0.2 AKF可扩展能力立方体模型
0.3 企业数字化转型的重要关注点
1.1平台是中台吗
1.2中台到底是什么
1.3传统企业中台的建设策略
1.4如何实现前中后台的协同
第2章 企业中台能力框架。
2.1中台能力总体架构
2.2业务中台
2.3数据中台
2.4技术中台
第3章 微服务设计为什么要选择DDD。
3.1 软件架构演进史
3.3 为什么DDD适合微服务
第7章 实体和值对象:领域模型的基础单元
7.1实体
7.2值对象
第8章 聚合和聚合根:怎样设计聚合
8.1聚合
第9章 领域事件:解耦微服务的关键
第10章 DDD分层架构
10.1什么是DDD分层架构
14.2微服务代码模型
14.2.1 1级代码目录
15.2从领域模型到微服务落地
第17章服务和数据在微服务各层的协作。
17.1服务视图
总结
第19章基于DDD的微服务代码详解。
19.2聚合中的对象
19.3领域事件
19.4仓储模式
19.6服务的组合与编排
总结
总结
第23章微服务拆分和设计原则。
23.4微服务设计原则
第24章分布式架构的关键设计。
24.3数据库的数据同步和复制
24.4跨库关联查询如何处理
24.5如何处理高频热点数据
24.6前后序业务数据的处理
24.7数据中台与企业级数据集成
24.8 BFF与企业级业务编排和协同
24.9分布式事务还是事件驱动机制
24.10多中心多活设计
第一阶段:以产品外购方式为主,存在一个系统包打天下,产品耦合度高,扩展能力弱等问题
第二阶段:自主研发为辅,外包为主。实现IT建设的局部可控
第三阶段:自主研发为主,外包为辅。实现IT建设自主可控
第四阶段:集团统一运营模式。对于大型跨业经营集团,不同分子公司由于早期缺少统一规划,技术栈和应用系统建设上存在较大差异,导致集团内部出现大量IT重复投入和重复建设,难以在集团内实现交叉销售和商业模式创新
集团统一运营是统一规划、统筹管理、统一运营、重构中台业务模型、统一技术标准、统一云环境和优化研发运营体系等,实现集团内各个子公司业务流程的共享和联通、技术体系标准统一和资源共享的信息系统建设模式。这种模式改变了子公司“山头林立、各自为政”的IT建设方式,可在整个集团实现企业级业务能力复用和集团统一运营
1.X轴:容量扩展能力
X轴关注无差别的服务和数据复制,解决应用和数据库水平扩容的问题。
在分布式架构下,X轴的典型实践案例主要体现在应用和数据库实例的水平扩展能力上。如Nginx负载均衡,应用或数据库的多实例,应用的弹性伸缩,数据库多副本和读写分离等场景。
2.Y轴:业务扩展能力
Y轴关注应用的业务职责划分,如根据数据类型、交易类型或根据两者组合来划分业务和应用边界,在划分过程中会遵循单一职责原则。Y轴主要用于划分业务和应用边界,解决业务能力复用的问题。
DDD ( Domain Driven Design,领域驱动设计)方法就是一种行之有效的划分业务领域边界的方法,以帮助完成应用的拆分和微服务的设计。它会按照流程或功能边界分解业务领域,根据业务上下文边界,构建领域模型,并将其作为微服务设计的输人。如将电商业务按照商品、订单、支付和客户等业务上下文边界进行Y轴拆分,构建基于不同业务边界的领域模型,最后完成微服务的设计和开发。
3.Z轴:数据扩展能力
Z轴关注数据的扩展能力,它按照业务类型或数据属性进行数据分片。根据数据分片策略将数据集划分为不同的数据子集,提升数据的扩展能力。如按照地域、机构或按照客户ID哈希进行数据分片。
Z轴的典型实践案例有:数据库水平切分和单元化架构。
数据库水平切分是通过数据分片规则将一个大的数据集切分为多个数据子集,并分布到不同的数据库中,按照分片规则可以路由到具体数据库完成数据查询等操作。单元化架构是按照业务特点或用户需求进行数据分片,将一个数据集水平切分为多个数据子集,然后根据数据分片分别部署业务应用单元。业务应用单元内包含若干依赖紧密的应用,应用在单元内可以不依赖单元外的服务独立完成单元内的业务全流程,以形成业务场景闭环。业务单元之间相互独立、天然隔离。当业务需要扩容时,只需增加和部署新的业务单元与数据子集就可以很容易实现扩容,从而提高业务的承载能力。单元化架构在很多多数据中心的多中心多活方案中都有实践
1.提升技术能力,完成集中式架构向分布式架构的转型
2.降低应用建设复杂度,完成从单体到微服务的转型
3.提高业务复用能力,从IT建设到中台战略
4.提升移动运营能力,从传统PC端向移动线上化转型
5.提升且组织能力,建设与中台相适应的组织架构和方法体系
业务中台前身是共享平台,是单体应用对公共能力和核心能力按照业务分开建设,解决公共模块重复投入问题,将核心业务链路整体当做一个平台产品来做。
下面我们来分析一下传统企业大平台战略和阿里中台战略的主要差异。
传统企业的很多平台只是将部分通用的公共能力独立为共享平台。这类平台虽然可以通过API或者以数据服务的形式对外提供共享服务,解决系统重复建设的问题,但它们并没有与企业内的其他平台或应用实现从前端到后端的页面、业务流程和数据的全面融合,没有将企业核心业务服务链路作为企业级解决方案来考虑。各平台仍然是分离且独立的,本质上仍然是烟囱式建设模式。
可见,项目级的平台虽然解决了公共能力复用的问题,但与企业级中台的建设目标显然还有一定差距!
我们可以提炼出几个关于中台的关键词:共享、联通、融合和创新。联通是前台以及中台之间各业务板块的联通,融合是前台企业级业务流程和数据的融合,并以共享的方式支持前台一线业务的发展和创新。
我认为,中台首先体现的是一种企业级的能力,它提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、业务联通和融合的问题,支持业务和商业模式创新。通过平台联通、业务和数据融合,为前台用户提供--致体验,更敏捷地支撑前台一线业务。
中台来源于平台,但与平台相比,中台更多是一种理念的转变,它主要体现在这三个关键能力上。
1)对前台业务的快速响应能力。
2)企业级的复用能力。
3)从前台、中台到后台的设计、研发、页面操作、流程、服务和数据的无缝联通、融合能力。
其中最关键的是业务快速响应能力和企业级无缝联通及融合能力,尤其是对于跨业经营的超大型企业来说,这种能力至关重要。
在中台设计和规划时,我们需要整体考虑企业内前台、中台以及后台应用的协同,实现不同渠道应用的前端页面、流程和服务的共享,还有核心业务链路的联通以及前台流程和数据的融合、共享,以支持前台一线业务和商业模式创新。
1.4.1 前台
传统企业的早期系统有不少是基于业务领域或企业组织架构建设,每个系统都有自己的前端和后端业务逻辑,不同的系统之间相互独立,用户操作是竖井式,有时一笔业务需要登录多个系统才能完成。
完成中台建设后,进行前台建设时,需要一套企业级整体解决方案,以实现各种不同中台的前端操作、流程和界面的组合、联通和融合。不管后端有多少个中台,前端用户感受到的始终只有一个前台
1.4.2 中台
传统企业的核心业务大多是基于集中式架构开发的。这种集中式单体系统,一般都存在扩展能力弱、弹性伸缩能力差的问题,无法适应突发高频访问的互联网业务场景。同时,传统企业数据类应用大多通过ETL工具抽取数据以实现数据建模、统计和报表分析功能。这种传统的数据仓库处理模式往往会存在数据时效性问题,再加上传统数据类应用主要面向企业管理和决策分析,并不是为前台而生的,因此难以快速响应前台一线业务的数据服务要求。
所以,在企业数字化转型时,需要同时解决传统的业务和数据应用建设的问题,采用双中台模式同步建设业务中台和数据中台。
1.业务中台
业务中台的建设可采用DDD方法,通过领域建模,将可复用的公共能力从各个单体中剥离、沉淀并组合。采用微服务架构,建设成为可共享的通用能力中台。通用能力中台更强调标准化和抽象能力,面向企业所有业务领域实现能力复用。同样地,我们也可以通过微服务架构将核心能力建设成可以面向不同渠道和场景的可复用的核心能力中台。
核心能力中台设计时,需充分释放出极强的快速适应不同业务场景和渠道的企业核心能力,从而在面向不同渠道和客户时,能够快速灵活地持续发挥出企业的核心竞争力优势。而通用能力则可通过抽象和标准化设计,让其具有更强的业务融合和企业级组合与支撑能力,通过企业主应用联通各个不同业务板块,发挥企业业务、数据和流程的黏合剂作用。
业务中台落地后的微服务可以向前端、第三方和其他中台提供API服务,实现通用能力和核心能力复用,如图1-4所示。
2.数据中台
为了打通数据孤岛,通过数据智能化
实现业务和数据融合以及商业模式创新,支持在线数据服务,支持业务中台和前台的精细化数字化运营,企业需要同步建设数据中台。数据中台的主要目标如下。
一是完成企业全域数据的采集与存储,实现不同业务类别中台数据的集中管理。
二是按照标准的数据规范或数据模型,基于不同主题域或场景对数据进行加工和处理,形成面向不同主题和场景的数据应用,比如客户视图、代理人视图、渠道视图、机构视图等不同的数据服务体系。
三是建立数据驱动的运营体系,基于各个维度的数据,萃取数据价值,组合企业各种能力,支持业务智能化和商业模式的创新,实现精细的数字化运营。
相应地,数据中台的建设就可分为三步。
第一步,实现各中台业务数据的汇集,解决数据孤岛和初级数据共享问题。第二步,实现企业级实时或非实时全维度数据的深度融合、加工和共享。第三步,萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。
数据中台可以建立在数据仓库或数据平台之上,将数据服务化之后提供给中台或者前台应用。与数据平台相比,数据中台不仅服务于分析型场景,还更多服务于交易型业务场景,为前台业务提供数据智能服务。基于数据库日志捕获的技术,使得数据获取的时效性大大提升,这样就可以为数据中台的交易型场景提供很好的支撑。
综上,数据中台主要完成数据的融合和加工,通过数据智能化,实现智能化的业务和流程创新;通过萃取数据业务价值,提供数据服务,最终实现数字化运营。
1.4.3 后台
后台主要面向企业内部运营和后台管理人员。对于后台,为了实现内部的管理要求,很多人总会习惯将一些管理流程嵌入核心业务链路中。而这类内控管理类的需求对权限、管控规则和流程等要求一般都比较严格,但是大部分管理人员只是参与了某个局部业务环节的审核。这些复杂的管理需求,会凭空增加不同渠道应用的前台界面与核心流程的融合难度以及软件开发的复杂度。
在设计流程审核和管理类功能的时候,其实我们可以考虑按角色或岗位进行功能聚合,将一些复杂的管理需求从通用的核心业务链路中剥离,通过特定程序人口嵌人前台App或应用中,专门供后台管理人员使用。而对于中台与后台的数据交互则可以采用事件驱动的异步化的数据最终一致性模式实现数据复制,减轻中台业务压力。
当管理需求从前台核心业务链路剥离后,前台应用将会具有更好的通用性,可以更容易地实现各渠道前台界面和流程的融合。前台应用或App就可以无差别地同时面向外部客户和内部销售以及其他业务人员,从而促进传统渠道与互联网渠道业务模型的统一和前台应用的融合。
企业级的综合能力,一般包含以下四种:业务能力、数据能力、技术能力和组织能力,如图2-1所示。
业务能力主要体现为对中台领域模型的构建能力,对领域模型的持续演进能力,企业级业务能力的复用、融合和产品化运营能力,以及快速响应市场的商业模式创新能力。
数据能力主要体现为企业级的数据融合能力、数据服务能力以及对商业模式创新和企业数字化运营的支撑能力。
技术能力主要体现为对设备、网络等基础资源的自动化运维和管理能力,对微服务等分布式技术架构体系化的设计、开发和架构演进能力。
组织能力主要体现为一体化的研发运营能力和敏捷的中台产品化运营能力,还体现为快速建设自适应的组织架构和中台建设方法体系等方面的能力。
企业所有能力建设都是服务于前台一线业务的。从这个角度来讲,所有中台应该都可以称为业务中台。但我们所说的业务中台一般是指支持企业线上核心业务的中台。
业务中台承载了企业核心关键业务,是企业的核心业务能力,也是企业数字化转型的重点。业务中台的建设目标是:“将可复用的业务能力沉淀到业务中台,实现企业级业务能力复用和各业务板块之间的联通和协同,确保关键业务链路的稳定高效.提升业务创新效能。"
图2-2是一个业务中台示例。在业务中台设计时,我们可以将用户管理、订单管理、商品管理和支付等这些通用的能力,通过业务领域边界划分和领域建模,沉淀到用户中心、
订单中心、商品中心和支付中心等业务中台,然后基于分布式微服务技术体系完成微服务建设,形成企业级解决方案,面向前台应用提供可复用的业务能力。
在技术实现上,中台的系统落地可以采用微服务架构。微服务是目前公认的业务中台技术最佳实现,可以有效提升业务扩展能力,实现业务能力复用。
在业务建模上,中台领域建模可以采用领域驱动设计(DDD)方法,通过划分业务限界上下文边界,构建中台领域模型,根据领域模型完成微服务拆分和设计。
业务中台可以面向前台应用提供基于API接口级的业务服务能力,也可以将领域模型所在的微服务和微前端组合为业务单元,以组件的形式面向前台应用,提供基于微前端的页面级服务能力。
业务中台建设完成后,前台应用就可以联通和组装各个不同中台业务板块,既提供企业级一体化业务能力支撑,又可以提供灵活的场景化销售能力支撑。
数据中台与业务中台相辅相成,共同支持前台一线业务。数据中台除了拥有传统数据平台的统计分析和决策支持功能外,会更多聚焦于为前台一线交易类业务提供智能化的数据服务,支持企业流程智能化、运营智能化和商业模式创新,实现“业务数据化和数据业务化”。
最近几年,数据应用领域出现了很多新的趋势。数据中台建设模式也随着这些趋势在发生变化,主要体现在以下几点。
第一,数据应用技术发展迅猛。近几年涌现出了大量新的数据应用技术,如 NoSQL、NewSQL和分布式数据库等,以及与数据采集、数据存储、数据建模和数据挖掘等大数据相关的技术。这些技术解决业务问题的能力越来越强,但同时也增加了技术实现的复杂度。
第二,数据架构更加灵活。在从单体向微服务架构转型后,企业业务和数据形态也发
生了很大的变化,数据架构已经从集中式架构向分布式架构转变。
第三,数据来源更加多元化,数据格式更加多样化。随着车联网、物联网、LBS 和社交媒体等数据的引入,数据来源已从单一的业务数据向复杂的多源数据转变,数据格式也已经从以结构化为主向结构化与非结构化多种模式混合的方向转变。
第四,数据智能化应用将会越来越广泛。在数字新基建的大背景下,未来企业将汇集多种模式下的数据,借助深度学习和人工智能等智能技术,优化业务流程,实现业务流程的智能化,通过用户行为分析提升用户体验,实现精准营销、反欺诈和风险管控,实现数字化和智能化的产品运营以及AIOps 等,提升企业数字智能化水平。
数据中台的大部分数据来源于业务中台,经过数据建模和数据分析等操作后,将加工后的数据,返回业务中台为前台应用提供数据服务,或直接以数据类应用的方式面向前台应用提供API数据服务。
数据中台一般包括数据采集、数据集成、数据治理、数据应用和数据资产管理,另外还有诸如数据标准和指标建设,以及数据仓库或大数据等技术应用。图2-3是2017年阿里云栖大会上的一个数据中台示例。
保障系统高可用,海量业务访问。
一般来说,技术中台会有以下几类关键技术领域的组件,如 API网关、前端开发框架、微服务开发框架、微服务治理组件、分布式数据库以及分布式架构下诸如复制、同步等数据处理相关的关键技术组件,如图2-4所示。
我们知道,在单机和集中式架构时代,大多采用瀑布开发模式。系统分析、设计和开发往往是独立、分阶段割裂进行的。比如,在系统建设过程中,我们经常会看到这样的情形:A负责提出需求,B负责需求分析,C负责系统设计,D负责代码实现,这样的流程很长,经手的人也很多,很容易导致信息丢失。最后,就很容易导致需求、设计与代码实现的不一致,往往到了软件上线后我们才发现很多功能并不是自己想要的,或者做出来的功能跟自己提出的需求偏差太大。软件无法快速响应需求和业务的迅速变化,最终企业错失发展良机。
DDD是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。
DDD不是架构,它是一种架构设计方法论,它通过业务边界划分将复杂业务领域简单化,帮我们划分出清晰的业务领域和应用边界,从而可以很容易地实现微服务的架构演进。DDD的出现使得原来微服务拆分和设计过程中的问题不再是难题。
DDD包括战略设计和战术设计两部分,它们分别从不同的视角出发,完成领域建模和微服务的拆分和设计。
战略设计是从业务视角出发,划分业务的领域边界,建立基于通用语言和业务上下文语义边界的限界上下文,构建领域模型。而限界上下文就可以作为微服务拆分和设计的边界。
战术设计则是从技术视角出发,侧重于对领域模型的技术实现,按照领域模型完成微服务的开发和落地。在战术设计中会有聚合、聚合根、实体、值对象、领域服务、领域事件、应用服务和仓储等领域对象,这些领域对象会以代码的形式映射到微服务中,完成设计和系统落地。
首先,针对业务领域,通过用例分析、场景分析和用户旅程分析等方法,尽可能全面地、不遗漏地梳理业务领域,发现这些业务领域中的命令、领域事件、领域对象以及它们的业务行为,并梳理这些领域对象之间的关系,这是一个发散的过程。
然后,我们将事件风暴过程中提取的实体、值对象和聚合根等领域对象,从不同的维度进行聚类,形成如聚合和限界上下文等边界,并在限界上下文边界内建立领域模型,这是一个收敛的过程,收敛输出的结果就是领域模型,如图3-2所示。
我们可以分三步来构建领域模型和划分微服务的边界。
第一步,在事件风暴中根据场景分析,梳理业务过程中的用户操作、领域事件以及与外部的依赖关系等,找出哪些业务对象产生了这些业务操作或行为,并根据这些业务对象梳理出实体等领域对象。
第二步,根据领域实体之间的业务关联性,找出聚合根,将业务紧密相关的、相互依赖的实体组合形成聚合,确定聚合中的聚合根、值对象和实体。在图3-2中,同一个限界上下文内,聚合之间的边界是微服务内部的第一层边界,这些聚合在同一个微服务实例内运行。这个边界是一个逻辑边界,所以用虚线表示。
第三步,根据业务语义环境及上下文边界等因素,将一个或者多个聚合划定在一个限界上下文内,构建领域模型。在图3-2中,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界。不同限界上下文内的领域模型的业务逻辑,被隔离在不同的微服务实例中运行,它们在物理上是相互隔离的,所以这-层边界是物理边界,边界之间用实线来表示。
DDD战术设计中有两个重要的概念:实体(Entity)和值对象( Value Object)。二者是领域模型中非常重要的基础领域对象(Domain Object,DO)。
在DDD的领域模型中有这样一类对象,它们拥有唯一标识符,并且它们的标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是属性,而是其延续性和标识,这种对象的延续性和标识会跨越甚至超出软件的生命周期。我们把领域模型中这样的领域对象称为实体。
在领域模型映射到数据模型时,一个实体可能对应0个、1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中,有些实体只是暂驻内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。
在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的、提供查询服务的数据类微服务,比如数据字典。
在构建领域模型时,我们会根据用户旅程或场景分析中的一些业务操作和行为找出产生这些行为的实体或值对象,从这些实体对象中找出聚合根,进而将这些业务关联紧密的聚合根、实体和值对象组合在一起,构成聚合,再根据业务语义边界将多个聚合划定到同一个限界上下文中,在限界上下文内构建领域模型。
在DDD中,实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有相对丰富的业务属性和业务行为。而值对象主要是属性集合,主要完成对实体的状态和特征描述。
聚合在 DDD分层架构里属于领域层,同一个微服务的领域层可以有多个聚合,每个聚合内有一个聚合根,多个实体、值对象和领域服务等领域对象。同一个限界上下文内的多个聚合,通过应用层组合在一起共同实现了领域模型的核心领域逻辑。
我们为每一个聚合设计一个仓储完成聚合数据的持久化操作。为了避免聚合数据频繁地提交,建议你尽可能将聚合内变更的数据,封装在一次交易中提交仓储完成持久化。
聚合在领域模型里是一个逻辑边界,它本身没有业务逻辑实现相关的代码。聚合的业务逻辑是由聚合内的聚合根、实体、值对象和领域服务等来实现的。聚合内的实体以充血模型实现自身的业务逻辑。跨多个实体的领域逻辑通过领域服务来实现。比如,有的业务场景需要同-个聚合的A和B两个实体来共同完成,我们就可以将这段业务逻辑用领域服务组合A和B两个实体来完成。
跨多个聚合的业务逻辑的组合和编排,是通过应用服务来实现的。比如,有的业务逻辑需要聚合C和聚合D中的两个领域服务来共同完成,为了避免聚合之间的领域服务直接调用,实现微服务内聚合解耦,此时你可以将这段业务逻辑上升到应用层,通过应用服务组合两个聚合的领域服务来实现。
领域事件采用事件驱动架构(Event-Driven Architecture,EDA)设计,可以切断领域模型之间的强依赖关系,在领域事件发布后,事件发布方不必关心订阅方的事件处理是否成功。这样就可以实现领域模型的解耦,维护领域模型的独立性。当领域模型映射到微服务时,领域事件就可以解耦微服务,这时微服务之间的数据就可以不再要求强一致性,而是基于最终一致性。
微服务架构模型有好多种,例如洋葱架构、CQRS和六边形架构等。虽然这些架构模式提出的时代和背景不同,但其核心理念都是为了设计出“高内聚,低耦合”的微服务
10.1.1用户接口层(前端界面)
在很多描述DDD 用户接口层的文章中,对用户接口层的解释通常都是这样的:“用户接口层负责向用户显示信息和解释用户指令,这里的用户可能是用户、程序、自动化测试和批处理脚本等。”
但随着微服务架构的盛行,大多数应用都采用了前后端分离的设计模式。为了连接前端应用和后端微服务,于是又出现了API 网关。
用户接口层在前后端分离设计时,主要完成后端微服务与前端不同用户的接口和数据适配。这里的用户仍然可以是用户、程序、自动化测试和批处理脚本等。而作为需要复用的中台微服务的用户接口层,它还可以面向不同的商业生态,适配不同业务接入方的集成技术体系和要求,提供灵活的服务接入和适配能力。
10.1.2应用层(微服务入口)
应用层连接用户接口层和领域层,它是很薄的一层,主要职能是协调领域层多个聚合完成服务的组合和编排。
应用层之下是领域层,领域层是由多个业务职责单一的聚合构成,实现核心的领域逻辑。应用层负责协调领域层多个聚合的领域服务,面向用例和业务流程完成服务的组合和编排。所以理论上应用层不应该实现领域模型的核心领域逻辑。这也是应用层为什么会很薄的原因。
应用层之上是用户接口层,在应用层完成领域层服务组合和编排后,应用服务被用户接口层Facade服务封装,完成接口和数据适配后,以粗粒度的服务通过API网关面向前端应用发布。
此外,应用层也是微服务之间服务调用的通道,微服务在应用层可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排。
在应用层主要有应用服务、事件订阅和发布等相关代码逻辑。其中,应用服务主要负责服务的组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等。
10.1.3领域层
领域层位于应用层之下,是领域模型的核心,主要实现领域模型的核心业务逻辑,体现领域模型的业务能力。领域层用于表达业务概念、业务状态和业务规则,可以通过各种业务规则校验手段保证业务的正确性。
10.1.4基础层
基础层贯穿了DDD所有层,它的主要职能就是为其他各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。常见的功能是完成实体的数据库持久化。
基础层主要有仓储服务代码逻辑。仓储采用依赖倒置设计,封装基础资源逻辑的服务实现,实现应用层、领域层与基础层的解耦,降低外部资源变化对领域逻辑的影响。
并分别为它们建立了interfaces、application、domain和infrastructure四个一级代码目录,如图14-2所示。
这些代码目录的职能和代码形态如下。
interfaces(用户接口层):它主要存放用户接口层与
前端应用交互、数据转换和交互相关的代码。前端应用通过这一层的接口,从应用服务获取前端展现所需的数据。处理前端用户发送的REStful请求,
解析用户输人的配置文件,并将数据传递给application层。数据的组装、数据传输格式转换以及facade接口封装等代码都会放在这一层目录里。
application(应用层):它主要存放与应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务,完成服务的组合和编排,向上为用户接口层提供各种应用数据支持服务。应用服务和事件等代码会放在这一层目录里。
domain(领域层):它主要存放与领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合内的聚合根以及实体、方法、值对象、领域服务和事件等相关代码会放在这一层目录里。
infrastructure(基础层):它主要存放与基础资源服务相关的代码。为其他各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
14.2.2 各层代码目录
下面我们一起来看一下用户接口层、应用层、领域层以及基础层各自的二级代码目录结构。
1.用户接口层
interfaces目录下的代码目录结构有assembler、dto和facade三类,如图14-3所示。
assembler :实现DTO与DO领域对象之间的相互转换和数据交换。一般来说,assembler 与 dto 总是同时出现。
dto :它是前端应用数据传输的载体,不实现任何业务逻辑。我们可以面向前端应用将应用层或领域层的DO对象转换为前端需要的DTO对象,从而隐藏领域模型内部领域对象DO;也可以将前端传入的 DTO对象转换为应用服务或领域服务所需要的DO对象。
facade:封装应用服务,提供较粗粒度的调用接口,或者将用户请求委派给一个或多个应用服务进行处理。
2.应用层
application的代码目录结构有event和l service,如图14-4所示。
event(事件):这层目录主要存放事件相关的代码°它包括两个子目录: publish和 subscribe。前者主要存放事件发布 相关代码,后者主要存放事件订阅相关代码。事件处理相关的核心业务逻辑在领域层实现。
应用层和领域层都可以进行事件发布。为了实现事件订阅的统一管理,建议你将微服务内所有事件订阅的相关代码都统一放到应用层。事件处理相关的核心业务逻辑实现可以放在领域层。通过应用层调用领域层服务,来实现完整的事件订阅处理流程。
service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或其他微服务的应用服务进行封装、编排和组合,对外提供粗粒度的服务。你可以为每个聚合的应用服务设计一个应用服务类。
另外,在进行跨微服务调用时,部分DO对象需要转换成DTO,所以应用层可能也会有用户接口层的assembler 和 dto对象。这时,你可以根据需要增加 assembler和 dto 代码目录结构。
3.领域层
domain下的目录结构是由一个或多个独立的聚合目录构成,每一个聚合是一个独立的业务功能单元,多个聚合共同实现领域模型的核心业务逻辑。
聚合内的代码模型是标准且统一的,它一般包括entity .event、repository和 service 四个子目录,如图14-5所示。
aggregate(聚合):它是聚合目录的根目录,你可以根据实际项目的聚合名称来命名,比如将聚合命名为“Person”。
聚合内实现高内聚的核心领域逻辑,聚合可以独立拆分为微服务,也可以根据领域模型的演变,在不同的微服务之间进行聚合代码重组。
将聚合所有的代码放在一个目录里的主要目的,不仅是为了业务的高内聚,也是为了未来微服务之间聚合代码重组的便利性。有了清晰的聚合代码边界,你就可以轻松地实现以聚合为单位的微服务拆分和重组。
聚合之间的松耦合设计和清晰的代码边界,在微服务架构演进中具有非常重要的价值。聚合内可以定义聚合根、实体和值对象以及领域服务等领域对象,一般包括以下目录结构。
entity(实体):它存放聚合根、实体和值对象等相关代码。实体类中除了业务属性,还有业务行为,也就是实体类中的方法。如果聚合内部实体或值对象比较多,你还可以再增加一级子目录加以区分。
event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码。
service(领域服务):它存放领域服务、.T厂服务等相关代码。一个领域服务是由多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中。如果有些领域服务的业务逻辑相对复杂,你也可以将一个领域服务设计为一个领域服务类,避免将所有领域服务代码都放在一个领域服务类中而出现代码臃肿的问题。领域服务可以封装多个实体或方法供上层应用服务调用。
repository(仓储):它存放仓储服务相关的代码。仓储模式通常包括仓储接口和仓储实现服务。它们一起完成聚合内DO领域对象的持久化,或基于聚合根ID查询,完成聚合内实体和值对象等DO领域对象的数据初始化。另外,仓储目录还会有持久化对象PO,以及持久化实现逻辑相关代码,如DAO等。在仓储设计时有一个重要原则,就是一个聚合只能有一个仓储。
4.基础层
infrastructure的代码目录结构有config 和 util两个子目录,如图14-6所示。config:主要存放配置相关代码。
util :主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库和通用算法等基础代码。
你可以为不同的资源类别建立不同的子目录。
|42.3微服务总目录结构
完成一级和二级代码目录结构模型设计后,你就可以看到微服务代码模型目录结构的全貌了,如图14-7所示。这些代码虽然根据不同的职能分散到了不同的层和目录,但它们是在同一个微服务的工程内,作为一个微服务部署包进行整体发布和部署。
总结:区别于controller、service、Dao三层结构,领域模型的结构更加细致,重点改变是将领域层抽象出来,这样业务代码更清晰、业务代码重构更方便、可扩展性更好
数据流动过程:前端(界面)->接口层(前端界面、脚本、测试用例等)->应用层(restful接口)->领域层(清晰的核心业务逻辑处理层)->基础设施层(工具类、中间件调用等)
看着好像和目前的三层架构区别不是很大,主要是引入了领域层的概念,更加关注核心业务的抽象,采用mq等消息中间件来驱动业务操作相关事件。解耦各个事件,并按类型划分提取公共成分。有利于提高架构性能。小公司和小规模传统企业感觉并不适用,因为领域建模会增加系统复杂性,问题会更多,而且不好排查,领域事件引入mq会提高开发工作量和运维成本(不过优势是解耦了基础业务、提高核心业务性能)。
在构建领域模型时,我们往往是站在业务视角,重点关注业务场景和问题,不会过多考虑技术实现方案,有些领域对象还带着业务语言。所以我们还需要将领域模型作为微服务设计的输人,完成领域对象的设计和转换,让领域对象与代码对象建立映射关系,从而完成微服务的概要设计。
换句话说,从领域模型到微服务落地,我们还需要进一步的设计和分析。
领域建模时提取的领域对象,还需要经过进一步的用户故事或领域故事分析,完成微服务设计后,才能用于微服务开发。这个过程会比领域建模的过程更深入、更细致。
分析过程中我们主要关注以下内容:
分析微服务内有哪些服务?
服务所在的分层?
应用服务由哪些服务组合和编排完成?领域服务包括哪些实体的业务逻辑?采用充血模型的实体有哪些属性和方法?有哪些值对象?
哪个实体是聚合根等?
↑5.2‖领域层的领域对象
事件风暴结束时,领域模型的聚合内一般会有聚合根、实体、值对象、命令和领域事件等领域对象。完成领域故事分析和微服务设计后,微服务的聚合内一般会有聚合根、实体、值对象、领域事件、领域服务、工厂和仓储、持久化对象等领域对象。这里的领域对象是一个广义的概念,它包括领域模型中的所有对象,而不仅仅是指实体等领域对象。
下面我们就来看一下这些领域对象是怎么分析得来的。
1.设计聚合根
聚合根来源于领域模型,我们需要找出领域模型内与聚合根关联的所有实体和值对象。在个人客户聚合里,个人客户实体是聚合根,它可以关联并负责管理聚合内的地址、联系电话以及银行账号等实体的生命周期。
聚合根是一种特殊的实体,我们需要设计它的属性和方法。客户聚合根类有自己的实现方法,比如生成客户编码,新增和修改客户信息等方法。同时它也可以管理聚合内实体和值对象等领域对象的生命周期。聚合根可以引用聚合内的所有实体,也可以实现聚合之间的基于聚合根ID的引用。
聚合根类放在领域层聚合的entity目录结构下。
2.设计实体
在DDD分层架构里,实体类采用充血模型,在实体类内实现实体的全部业务逻辑。这些实体有自己的业务属性、方法和业务行为。
我们需要分析并设计出这些实体的属性、关联的实体和值对象以及业务行为对应的方法。比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。
另外,实体还需要完成持久化操作,所以我们还可以建立实体与持久化对象的关系。大多数情况下,领域模型的实体对象与数据库持久化对象是一一对应的。但领域模型的某些实体在微服务设计时,可能会被设计为一个或多个数据持久化实体,或者实体的某些属性会被设计为值对象。
还有些领域对象在领域建模时不太容易被我们发现,所以在微服务设计时,我们需要根据更详细的需求将其识别和设计出来。
实体类代码对象放在领域层聚合的entity目录结构下。
3.设计值对象
一般,在用事件风暴构建领域模型时,我们不需要严格区分DO对象是实体还是值对象。但是在从领域模型映射到代码模型以完成微服务设计时,我们需要根据具体的业务场景将它们区分为实体和值对象,将某些属性或属性集设计为值对象。
4.设计领域事件
如果领域模型中领域事件会触发下一步业务操作,那么我们就需要设计领域事件了。首先确定领域事件是发生在微服务内还是微服务之间,判断是否需要引入事件总线或消息中间件。
然后设计事件实体对象、事件的发布和订阅机制,以及事件的处理机制。
在个人客户聚合中有客户已创建的领域事件,因此就有客户已创建事件这个实体。领域事件实体类放在领域层聚合的event目录结构下。领域事件的订阅建议放在应用层的event目录结构下。领域事件发布相关代码放在领域层或者应用层都是可以的。
5.设计领域服务
如果领域模型里面的一个业务动作或行为需要多个实体协同完成,我们就需要设计领域服务。
领域服务通过对多个实体和实体方法进行组合和编排,完成多个实体组合的核心业务逻辑。你也可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。所以如果实体方法需要被前端应用调用,我们需要将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根创建个人客户信息的方法,会被封装为创建个人客户信息领域服务然后再被封装为创建个人客户信息应用服务,最后会被封装成facade接口发布到API网关向前端应用暴露。
跨多实体的业务逻辑在聚合根方法和领域服务中都可以实现。建议你将这类业务逻辑尽量放在领域服务中实现,避免聚合根内的业务逻辑过于庞杂。
一个聚合可以建立一个领域服务类,你可以将聚合中所有的领域服务都在这个领域服务类中实现。
领域服务类放在领域层聚合的service目录结构下。
6.设计工厂和仓储
一个聚合只有一个仓储。仓储包括仓储接口和仓储实现,通过依赖倒置原则实现应用业务逻辑与数据库资源逻辑的解耦。
个人客户聚合可以通过工厂和仓储模式两者组合,完成聚合内实体和值对象等DO对象的构建、数据初始化和持久化。
工厂类( factory)放在领域层聚合的service目录结构下。仓储相关代码放在领域层聚合的repository目录结构下。
7.设计持久化对象
持久化对象PO主要完成DO对象的数据库持久化操作,PO一般与数据库表是一对一的关系。持久化对象设计过程的本质就是完成从领域模型到数据模型的设计过程。
↑5.22应用层的领域对象
应用层主要有应用服务和领域事件的发布和订阅。
在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它紧邻的下一层服务。服务从下到上依次为:实体方法、领域服务、应用服务和 facade接口。
如果需要实现服务的跨层调用,应该怎么处理?建议采用服务逐层封装的方式。我们看一下图15-1,服务封装主要有以下几种方式。
1.实体方法的封装
实体的方法是最底层的实体的原子业务逻辑,它体现的是实体的业务行为。
在采用严格分层架构时,如果实体方法需要被应用服务调用,你可以将它封装成领域服务。这样领域服务就可以被应用服务组合和编排了。如果它还需要被用户接口层调用,你还需要将这个领域服务封装成应用服务。
经过逐层服务的封装,实体方法就可以暴露给上面不同的层,实现跨层调用了。
2.领域服务的组合和封装
领域服务主要完成对多个实体和实体方法的组合和编排,供应用服务调用。
DDD分层架构确定了微服务的总体架构。在微服务代码模型里,我们根据领域模型里领域对象的属性和依赖关系,将领域对象进行了分层,定义了与之对应的代码对象和代码目录结构。微服务内的主要对象有服务和实体等,它们共同协作完成业务逻辑。
那在运行过程中,这些服务和实体在微服务各层具体是如何协作的呢?下面我们就来解剖一下基于DDD分层架构的微服务,看看它的内部到底是怎样运作的?
在微服务内有很多不同类型的服务,它们的实现方式不同,承担的职能也不同。它们连接微服务内不同的层,实现微服务之间的服务访问和协作。下面我们来分析一下这些服务的调用、组合和封装关系以及它们之间的依赖关系。
17.1.1服务的类型
我们先来回顾一下DDD分层架构中的服务。按照分层架构设计出来的微服务,其内部主要有facade接口服务、应用服务、领域服务和基础服务。各层服务的主要功能和职责如下。
facade接口服务:位于用户接口层,包括接口和实现两部分。用于处理用户发送的RESTful请求和解析用户输入的配置文件等,并将数据传递给应用层。完成应用服务封装,将DO组装成DTO,并将数据传递给前端应用。
应用服务:位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,
负责处理业务用例的执行顺序和结果拼装,对外提供粗粒度的服务。
领域服务:位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域逻辑。它对多个实体或实体方法的业务逻辑进行组合或编排。或者在严格分层架构中对实体的方法进行封装,以领域服务的方式供应用层调用。
基础服务:位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置原则提供基础资源服务。领域服务和应用服务都可以调用仓储接口服务,通过仓储实现服务实现数据持久化。
17.1.2服务的调用
微服务的服务调用包括三类主要应用场景:微服务内跨层服务调用、微服务之间服务调用和领域事件驱动,如图17-1所示。
1.微服务内跨层服务调用
微服务架构往往采用前后端分离的设计模式。前端应用实现前端页面逻辑,后端微服务实现核心领域逻辑,前后端应用分别独立部署。前端应用调用发布在APIl网关上的facade接口服务,facade接口服务定向到应用服务。
在微服务内,应用服务作为服务的组织者和编排者,它的服务调用有两种路径。
第一种是应用服务调用并组装领域服务。此时领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过工厂服务和仓储接口,访问仓储实现获取持久化数据对象,完成实体构建和数据初始化。
第二种是应用服务直接调用仓储服务。这种方式主要针对类似缓存或文件等类型的基础层数据访问,或者涉及多表关联的复杂数据查询操作。这些数据查询类操作,由于没有太多需要进行业务规则控制的领域逻辑,所以不需要经过领域层。
2.微服务之间的服务调用
对于实时性要求高的场景,微服务中应用服务可以通过API网关,访问其他微服务的应用服务,采用同步方式实现数据强一致性。
注意,在涉及跨微服务的数据新增和修改操作时,你需要关注分布式事务,保证数据的强一致性。但是这样微服务之间的依赖和耦合度就比较高了,也会影响应用的性能,所以一般优先选择领域事件驱动的数据最终一致性机制。
3.领域事件驱动
领域事件驱动是一种特殊的、异步化的调用方式,它包括微服务内和微服务之间的领域事件。微服务内的领域事件通过事件总线完成聚合之间的异步处理?。微服务之间的领域事件通过消息中间件完成。
如果发生领域事件,当业务逻辑处理完成后,可调用事件发布服务,完成事件发布。事件订阅服务接收到订阅的主题数据时,会调用事件处理领域服务,完成进一步的业务操作。
对于实时性要求不高的场景,建议优先采用领域事件驱动设计方式,通过异步方式实现数据最终一致性。
17.1.3服务的封装与组合
微服务的服务是从领域层逐级向上封装、组合和暴露的,如图17-2所示。
1.基础层
基础层的服务形态主要是仓储服务。仓储服务包括仓储接口和仓储实现两部分。仓储接口服务可以供应用层或者领域层服务或方法调用。仓储实现服务完成领域对象的持久化或提供数据初始化所需要的PO数据。
2.领域层
领域层实现核心业务逻辑,负责表达领域模型业务概念、业务状态和业务规则。领域层主要服务的形态有实体方法和领域服务。
实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,具体实现形式是实体类中的方法。实体是微服务内的原子业务对象,在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力,这是一种面向对象的编程方法。实体方法不会过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。
DDD提倡富领域模型,尽量将业务逻辑归属到实体对象上,实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排,实现跨多个实体的复杂核心业务逻辑。你也可以认为领域服务是介于实体和应用服务之间的薄薄的一层。它的主要职能是实现领域层复杂核心领域逻辑的组合和封装。
采用严格分层架构时,实体方法如果需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务。
3.应用层
应用层主要面向前端应用和用户,根据前端用例和流程要求,通过服务组合和编排实现粗粒度的业务行为。应用层主要服务形态有:应用服务和事件订阅服务。
应用服务负责服务的组合、编排和转发,负责处理业务用例的执行顺序和结果的拼装,负责不同聚合之间的服务和数据协调。通过应用服务对外暴露微服务的内部核心领域功能,可以隐藏领域层核心业务逻辑的复杂性和内部的实现机制。
应用服务用于组合和编排的服务,主要来源于领域服务,也可以来源于外部微服务的应用服务。除了完成服务的组合和编排外,应用服务内还可以完成安全认证、权限校验、初步的数据校验和分布式事务控制等功能。
4.用户接口层
用户接口层是前端应用和微服务之间服务访问和数据交换的桥梁。用户接口层的主要服务形态是facade接口服务。
在LeaveApplicationService类内实现与leave聚合相关的应用服务。在 LoginApplicationService类内封装外部权限微服务的登录认证应用服务在PersonApplicationService类内实现与person聚合相关的应用服务。
,应用层类似于controler层,接受外部服务请求,转换请求实例调用下层领域层完成核心业务处理
聚合根
实体
值对象
领域服务
在创建请假单和请假审批过程中会产生“请假单已创建”和“审批已通过”的领域事
件。为了方便管理,我们将聚合内与领域事件相关的代码放在leave聚合的event目录中。
领域事件实体在聚合仓储内完成持久化,但是事件实体的生命周期不受聚合根管理。19.3.1领域事件基类
你可以建立一个统一的领域事件基类DomainEvent,如代码清单19-8所示。基类的基本属性至少要包括事件ID、时间戳、事件源以及事件相关的业务数据。
19.4.1DO与PO对象的转换
Leave聚合根除了自身的属性外,还会根据领域模型设计并引用多个值对象,如Applicant和 Approver等。这两个值对象包含多个属性,如: personld、personName和personType等。
在设计持久化对象PO时,你可以将这些值对象属性嵌人PO属性中,或设计一个组合属性字段,以JSON串的方式存储在PO 中。
为了减少数据库表数量以及表与表的复杂关联关系,我们将Leave实体和多个值对象的数据放在一个LeavePO中。如果以属性嵌入的方式,Applicant值对象在LeavePO中会展开为: applicantld、applicantName和 applicantType三个属性。(不建议这么干,虽然这么做减少了数据库表,但是对于后期如果出现的多对多改造会非常棘手甚至要重构表结构这是非常痛苦的,而且对于子表的数据变更也是非常麻烦,比如名称改了,这个数据就不会变更,不过如果是冗余设计可以仔细设计后确认可行性)
总结:仓储模式其实就是Dao层上增加一层代理入口,这样如果做了数据库切换(ES)改造会比较方便,但是增加了工作量需要考虑研发投入,而且目前主流的持久化框架都是支持多种类型的数据库,比如mybaties-plus
应用层的应用服务主要完成领域服务的组合与编排。
一个聚合可以建立一个应用服务类,管理聚合所有对外封装好的应用服务。比如 leave聚合有LeaveApplicationService类,person聚合有PersonApplicationService类。
在请假微服务中,有三个聚合: leave、person和 rule。下面我们来看一下应用服务是如何组织这三个聚合的领域服务来完成服务组合和编排的。
以创建请假单createLeaveInfo应用服务为例,它可以分为这样三个关键步骤。
第一步,根据请假单定义的人员类型、请假类型和请假时长等参数,从rule聚合中获取请假审批规则。这一步是通过approvalRuleDomainService类的getLeaderMaxLevel领域服务来实现。
第二步,根据rule聚合中获取的请假审批规则,从person聚合中获取上级请假审批人。这一步是通过PersonDomainService类的 findFirstApprover领域服务来实现。
第三步,根据请假单数据和从person聚合获取审批人等数据,创建请假单。这一步是通过LeaveDomainService类的createLeave领域服务来实现。
由于领域模型的核心逻辑已经很好地沉淀到领域层中,这些核心领域逻辑可以高度复用。应用服务只需要灵活地组合和编排这些不同聚合的领域服务,就可以很容易地适配前端业务用例和流程的变化。因此,应用层不会积累太多业务逻辑代码,代码维护起来也会容易得多。
:和三层架构的区别:
1.聚合根关联的实体方式, 成员变量是值对象而不是简单放关联的id
2.聚合根将一些简单无需数据库交互等业务行为放在了聚合根中(可以减少代码)
3.基于领域模型,不同领域高内聚耦合低,这样极大的减少了业务服务拆分的工作,代码结构清晰规范
4、代码分层更加清晰,应用层下沉到领域内,也可以方便服务拆分
5.如果将聚合中的一部分拆分,则通过修改领域层改为openFeign接口请求拆分后的系统
2.全部采用DDD战术设计方法
不同的设计方法有不同的适用场景和环境,我们应该将它用在它最擅长的业务场景中。DDD有很多概念和战术设计方法,比如聚合根和值对象等。聚合根利用仓储管理聚合内实体数据之间的一致性,这种方法对于管理新建和修改数据非常有效,比如在修改订单数据时,它可以保证订单总金额与所有商品明细金额的一致。
但聚合根并不擅长复杂的联表查询场景,对于量较大的数据查询处理,甚至有延迟加载进而影响效率的问题。而传统的设计方法,可能用一条简单的SQL语句就可以很快解决。
在很多贫领域模型的业务场景,比如数据统计和分析,DDD的很多方法可能都用不上,或用得并不顺手,而采用传统的方法很容易就解决了。
因此,在遵守领域边界和微服务分层等大原则下,在进行战术层面设计时,我们应该更灵活地考虑选择多种方法,不只采用DDD设计方法,传统设计方法也应该在选项内。具体要结合企业情况,以快速、高效解决实际问题为目标,不要为做DDD而做DDD。
微服务设计原则中,如高内聚低耦合、复用、单一职责等这些常见的设计原则在此就不再赘述了。这里主要强调下面几条。
第一条,要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。
微服务设计应先建立领域模型,在确定了逻辑和物理边界,提取了领域对象,并建立了领域对象之间的依赖关系后,才开始微服务的拆分和设计。
不是先定义数据模型和库表结构,也不是前端界面需要什么,就去调整核心领域逻辑代码。在设计时,应将外部需求变化从用户接口层到应用层和领域层逐级消化,尽量降低前端需求对领域层核心领域逻辑的影响。
第二条,要边界清晰的微服务,而不是分布式小单体。
微服务上线后,其功能和代码也不是一成不变的。随着需求或设计变化,领域模型会迭代演进,微服务的代码也会分分合合。边界清晰的微服务,可快速实现微服务代码的重组。
微服务内聚合之间的领域服务和数据库实体原则上应杜绝相互依赖。你可将聚合之间的调用上升到应用层,通过应用服务对领域服务进行编排或者采用领域事件驱动,实现聚合之间的解耦,以便微服务的架构演进。
第三条,微服务分层要职能清晰,而不是依赖混乱的“小泥球”。
分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,即只能从外层调用内层服务,内层服务通过封装、组合或编排对外逐层暴露,服务粒度也会由细到粗。
应用层负责服务的组合和编排,不应有太多核心业务逻辑。其中,领域层负责核心领域业务逻辑的实现。各层应各司其职,职责边界不要混乱。
在服务演进时,应尽量将可复用的能力向下层沉淀。
第四条,要做自己能掌控的微服务,而不是过度拆分的微服务。
微服务过度拆分必然会带来软件维护成本的上升,比如集成成本、运维成本、监控和定位问题的成本。企业在微服务转型过程中还需要有云计算、DevOps、自动化监控等能力,而一般企业很难在短时间内提升这些能力,如果项目团队没有这些能力,将很难掌控这些微服务。
如果在微服务设计之初按照DDD的战略设计方法,定义好了微服务内的逻辑边界,做好了架构的分层,其实我们不必拆分太多的微服务,即使是单体也未尝不可。
随着技术积累和能力提升,当我们有了这些能力后,由于应用内有清晰的逻辑边界,我们可以随时轻松地重组出新的微服务,而这个过程不会花费太多的时间和精力。
23.5微服务拆分要考虑哪些因素
理论上,一个限界上下文内的领域模型可以被设计为微服务,但是由于领域建模主要从业务视角出发,没有考虑非业务的因素,比如需求变更频率、高性能、安全、团队以及技术异构等因素。这些非业务因素对于领域模型的系统落地也会起到决定性作用,因此在微服务拆分时也需要重点考虑它们。下面列出几个主要因素,供你参考。
1.基于领域模型
基于领域模型,也就是按照限界上下文边界进行拆分,围绕业务领域边界按职责单一性、功能完整性进行微服务拆分。
2.基于业务需求变化频率的不同
你需要识别领域模型中业务需求变动频繁的功能,考虑业务变更频率与相关度,将业务需求变动较高和功能相对稳定的业务进行分离。
这是因为需求的经常性变动必然会导致代码的频繁修改和版本发布。这种分离可以有效降低频繁发布版本的业务对不需要经常发布版本的业务的影响。
3.基于应用性能的要求不同
你需要识别领域模型中性能压力较大的业务。因为对性能指标要求高的业务在资源需求上要求会比其他业务高,这样可能会拖累其他业务,也会造成资源无谓的浪费。为了降低对应用整体性能和资源要求的影响,我们可以将对性能方面有较高要求的业务与对性能要求不高的业务进行拆分。
4.基于组织架构和团队规模
除非有意识地优化组织架构,否则微服务的拆分应尽量避免对团队和组织架构的调整,避免由于功能的重新划分,而增加大量且不必要的团队之间的沟通成本。
拆分后的微服务项目团队规模保持在10~12人为宜。在进行微服务拆分和组建项目团队时,应尽量将沟通边界控制在小团队内。
5.基于安全边界的不同
对于有特殊安全要求的业务,应从领域模型中拆分、独立出来。避免因为不同的安全要求,而带来不必要的成本,或带来泄密的风险。
6.基于技术异构等因素
领域模型中有些功能虽然在同一个业务领域内,但由于各种条件的限制,在技术实现时可能会存在较大的差异,也就是说领域模型内部不同的功能存在技术异构的问题。
由于业务场景或者技术条件的限制,有的可能采用.NET语言,有的则采用Java语言,甚至有的采用大数据技术架构。
对于这些存在技术异构的功能,你可以考虑按照技术栈的边界进行拆分。
综上,建立领域模型后,我们还需要考虑以上影响微服务拆分的非业务因素。说了这么多,需要注意一点:这些拆分都是以领域模型的聚合为单位拆分的。
在DDD里,聚合是可以拆分为微服务的最小单位,所以我们在领域建模时一定要把握好聚合这个逻辑边界,它随时可能会发挥出让你意想不到的威力。
企业数字化转型不仅要关注商业模式、业务边界、领域建模以及前中台的融合设计,同时由于中台大多采用分布式微服务架构,我们还需要关注分布式架构下的关键技术实现细节。诸如分布式架构下的数据库选择?数据如何同步和复制?高频热点数据的处理方式等问题。
本章结合我多年的实施经验和思考,一起来聊聊分布式架构下的几个关键设计问题,给出一点点意见和建议。
目前有三类主要的分布式数据库解决方案,这些方案的差异主要在于数据多副本的处理方式和采用的数据库中间件的类型。
1)原生分布式数据库方案。它支持数据多副本、高可用。多采用Paxos协议,一次写入多数据副本,多数副本写入成功即算成功。
2)集中式数据库+数据库中间件方案。它是集中式数据库与数据库中间件相结合的方案,通过数据库中间件实现数据路由和全局数据管理。数据库中间件和数据库分别独立部署,采用数据库自身的同步机制实现主副本数据的一致性。
开源的集中式数据库主要有MySQL 和 PostgreSQL。基于这两种数据库衍生出了很多
解决方案,比如开源数据库中间件MyCat+MySQL方案等。
3)集中式数据库+分库类库方案。它是一种轻量级的数据库中间件方案,分库类库实际上是一个基础JAR包,与应用软件部署在一起,实现数据路由和数据归集。它适合比较简单的读写交易场景,在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有ShardingSphere。
综上,这三种方案实施成本不一样,业务支持能力差异也比较大。
原生分布式数据库大多由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。集中式数据库+数据库中间件方案,实施成本和技术能力要求适中,可基本满足中大型企业业务要求。分库类库的方案可处理简单的业务场景,成本和技能要求相对较低。
在选择数据库的时候,需要考虑自身技术能力、业务量、成本以及业务场景需要,以选择合适的数据库方案。
在分布式微服务架构中,数据会根据数据扩展能力要求被进一步垂直或水平分割。为了实现数据的整合,数据库之间的批量数据同步与复制是必不可少的。
数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制,以及不同主题数据的整合等。
传统的数据同步方式有ETL工具和定时提数程序,但在数据时效性方面存在短板。分布式架构一般采用数据库日志捕获技术(CDC),根据数据库增量日志提取数据库增量数据,实现准实时的数据复制和传输。这种设计方式可以实现业务处理逻辑及数据复制和同步处理逻辑的独立与解耦,使用起来会更加简单、便捷。
现在主流的PostgreSQL和 MySQL等数据库外围,有很多数据库日志捕获技术组件。
CDC技术也可以应用于领域事件驱动设计中,作为领域事件增量数据的捕获工具。
跨库关联查询是分布式数据库的一个短板,会影响查询性能。
在领域建模时,很多原来在一个数据库的实体会被分散到不同的微服务中,但很多时候因为业务需求,它们之间需要关联查询。关联查询的业务场景一般包括两类。
第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,由于客户的业务数据会随着不同业务条线的操作而被分散在不同业务领域中,这类数据查询会跨多个微服务。
第二类是表与表之间的关联查询,比如机构表与业务表的联表查询。但机构表和业务表却分散在不同的微服务。
如何解决这两类业务场景下的查询呢?
对于第一类场景,由于数据分散在不同微服务里,我们无法跨多个微服务来统计这些数据。你可以建立面向不同业务主题的分布式数据库,它的数据来源于不同业务领域的微服务。我们可以采用数据库日志捕获技术和领域事件驱动机制,从各业务端微服务将数据准实时汇集到主题数据库。
在数据汇集时,需要提前做好数据关联处理(如将多表数据合并为一个宽表)或者建立数据模型。然后建立面向主题数据查询的微服务,这样你就可以相对容易地一次获取客户所有维度的业务数据了。你还可以根据主题或场景设计分布式数据库的分库主键,以提高大数据量条件下的数据库查询效率。
对于第二类场景,即不在同一个数据库的表之间的关联查询场景,你可以采用小表广播的设计模式。
比如,如果需要基于机构代码进行关联查询,我们可以在需要进行关联查询的业务库中增加一张冗余的机构代码表,这张表的数据只用于关联查询。机构代码表的数据在主数据微服务中进行生命周期管理,当主数据微服务中的机构代码数据发生变化时,你可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有订阅了机构代码表数据的冗余表中的数据。通过异步消息广播和数据冗余的方式,既可以解决表与表的关联查询,还可以提高数据的查询效率。
对于高频热点数据,比如商品、机构等代码类数据,它们同时面向多个应用,需要有很高的并发响应能力。这样会给数据库带来巨大的访问压力,影响系统的性能。
常见的做法是将这些高频热点数据从数据库加载到如Redis等缓存中,通过缓存提供高频数据访问服务。这样既可以降低数据库的压力,还可以提高数据的访问性能。
另外,对需要模糊查询的高频数据,你也可以选用ElasticSearch等搜索引擎。缓存就像调味料一样,投入小、见效快,用户体验提升快。
在微服务设计时你会经常发现,某些数据需要关联前序微服务的数据。比如:在保险业务中,投保微服务生成投保单后,保单会关联前序业务的投保单数据等。在电商业务中,货物运输单会关联前序业务的订单数据。由于这些需要关联的数据分散在业务流程的前序微服务中,你无法通过不同微服务的数据库来给它们建立数据关联。
那如何解决这种前后序的实体关联呢?
一般来说,前后序的数据都跟领域事件有关。你可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。
你可以将前序数据设计为实体或者值对象,供当前实体引用。在设计时你需要关注以下内容:如果前序数据在当前微服务只可整体修改,并且不会对它做查询和统计分析,那么你可以将它设计为值对象。当前序数据是多条,并且需要做查询和统计分析,你可以将它设计为实体。
这样,当货物运输的前端应用需要查看订单数据时,你可以在货物运输微服务中一次同时获取前序订单的清单数据和货物运输单数据,并将所有数据一次反馈给前端应用,这样就降低了跨微服务之间的调用。
如果前序数据被设计为实体,你还可以将存储在本地业务库的前序数据作为查询条件,在本地微服务完成多维度的综合数据查询。只有在必要时才从前序微服务获取前序业务实体的明细数据。通过数据冗余设计,即使前序微服务出现故障不能提供服务,也不会影响当前微服务与前序数据相关的业务和服务。
这样,既能保证数据的完整性,还能降低微服务的依赖,减少跨微服务的频繁调用,提升系统性能。
分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分而形成很多数据孤岛,增加数据集成和企业级数据使用的难度。
你可以通过数据中台来实现数据融合,解决分布式架构下的数据应用和集成问题。可以分三步来建设数据中台。
第一,按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。
第二,建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道视图等。
第三,建立业务需求驱动的数据体系,支持业务和商业模式创新。
数据中台不只适用于分析场景,也适用于交易型场景。你可以将其建立在数据仓库和数据平台上,将数据平台化之后提供给前台业务使用,为交易场景提供支持。
当然,数据中台的建设投入高、见效慢、收益高,你需要整体权衡。
企业级业务流程往往是由多个微服务协作完成的,每个单一职责的微服务就像积木块,它们只完成自己特定的功能。但是企业级的业务功能往往是由多个中台微服务的功能组成的,那如何组织这些微服务,完成企业级业务编排和协同呢?
你可以在微服务和前端应用之间,增加一层BFF ( Backend for Frontends)微服务。BFF的主要职责是处理微服务之间的服务组合和编排。
前面提到了,微服务内的应用服务也可以处理服务的组合和编排。那么你可能会问,这两者之间有什么差异呢?
BFF是位于中台微服务之上,它的主要职责是负责微服务之间的服务协调和编排。而应用服务主要处理微服务内的服务组合和编排,它可以组合和编排领域服务。在小型项目里,应用服务也可以编排其他微服务的应用服务,我们就没必要增加一层BFF的逻辑了。
在设计时我们应尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用,带来不必要的开销。
BFF像齿轮一样,适配着前端应用与微服务之间的步调。通过BFF微服务中的facade接口服务,向上适配不同的前端应用。通过协调不同微服务,向下实现企业级业务能力的组合、编排和协同。
BFF微服务可根据需求和流程变化,与前端应用版本协同发布,避免中台微服务为适配不同前端需求的变化,而频繁地修改和发布版本,从而保证中台微服务版本和核心领域逻辑的稳定。
如果你的BFF做得足够强大,它可以成为一个集成了不同中台微服务能力、面向多渠道应用的业务能力聚合平台。
分布式架构下,原来单体的内部调用会变成跨微服务的分布式调用。如果一笔交易同时新增和修改了多个微服务的数据,就容易产生数据一致性的问题。数据一致性有强一致性和最终一致性两种实现方案,它们的实现方式不同,代价也不同。
对实时性要求高的强一致性业务场景,你可以采用分布式事务,但分布式事务有性能代价,在设计时我们需平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。
领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时性场景下的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好地解耦微服务。通过削峰填谷,可以减轻数据库实时访问压力,提高业务吞吐量和业务处理能力。
你还可以通过领域事件驱动机制实现读写分离,提高数据库访问性能。对数据实时性要求不高的最终一致性的场景,一般优先采用异步化的领域事件驱动设计方式。
分布式架构的高可用主要通过多中心多活设计来实现。多中心多活是一个非常复杂的话题,涉及的技术很多,我主要列出以下几个关键的设计点。
1)选择合适的分布式数据库。数据库应该支持多数据中心部署,满足数据多副本以及数据底层复制和同步的技术要求,满足数据复制和恢复的时效性要求。
2)单元化架构设计。将若干个应用组成的业务单元集合作为部署的基本单位,实现同城和异地多活部署,以及跨中心的弹性扩容。各单元集合内业务功能自包含,所有业务流程都可在本单元集合内完成。任意单元的数据在多个数据中心有副本,不会因故障而造成数据丢失。任何单元故障不影响其他同类单元的正常运行。单元化设计时,我们要尽量避免跨数据中心和跨单元的调用。单元化架构可以很好地支持全链路压测和灰度发布,支持数据中心和业务的快速扩容。
3)访问路由。访问路由包括接入层、应用层和数据层的路由,需要确保前端请求能够按照正确的路由到达数据中心和业务单元,准确地写人或获取业务数据所在数据库。
4)全局配置数据管理。统一管理各数据中心全局配置数据,所有数据中心全局配置数据可实现实时同步。保证所有数据中心配置数据的一致性,在出现灾难时,可以一-键切换。
注:文章内容大多来自《中台架构与实现》及网络,仅供学习,如有侵权请联系删除 [email protected] 。