DDD学习笔记 - 总结篇

19 | 总结(一):微服务设计和拆分要坚持哪些原则

课程链接:https://time.geekbang.org/column/article/171185

由于企业发展历程以及企业技术和文化的不同,DDD 和微服务的实施策略也会有差异。那么面对这种差异,应该如何落地 DDD 和微服务呢?

 

微服务的演进策略

在从单体向微服务演进时,演进策略大体分为两种:绞杀者策略和修缮者策略。

1. 绞杀者策略

绞杀者策略是一种逐步剥离业务能力,用微服务逐步替代原有单体系统的策略。它对单体系统进行领域建模,根据领域边界,在单体系统之外,将新功能和部分业务能力独立出来,建设独立的微服务。新微服务与单体系统保持松耦合关系。

随着时间的推移,大部分单体系统的功能将被独立为微服务,这样就慢慢绞杀掉了原来的单体系统。绞杀者策略类似建筑拆迁,完成部分新建筑物后,然后拆除部分旧建筑物。

2. 修缮者策略

修缮者策略是一种维持原有系统整体能力不变,逐步优化系统整体能力的策略。它是在现有系统的基础上,剥离影响整体业务的部分功能,独立为微服务,比如高性能要求的功能,代码质量不高或者版本发布频率不一致的功能等。

通过这些功能的剥离,就可以兼顾整体和局部,解决系统整体不协调的问题。修缮者策略类似古建筑修复,将存在问题的部分功能重建或者修复后,重新加入到原有的建筑中,保持建筑原貌和功能不变。一般人从外表感觉不到这个变化,但是建筑物质量却得到了很大的提升。

其实还有第三种策略,就是另起炉灶,顾名思义就是将原有的系统推倒重做。建设期间,原有单体系统照常运行,一般会停止开发新需求。而新系统则会组织新的项目团队,按照原有系统的功能域,重新做领域建模,开发新的微服务。在完成数据迁移后,进行新旧系统切换。

对于大型核心系统一般不建议采用这种策略,这是因为系统重构后的不稳定性、大量未知的潜在技术风险和新的开发模式下项目团队磨合等不确定性因素,会导致项目实施难度大大增加。

 

不同场景下的领域建模策略

由于企业内情况千差万别,发展历程也不一样,有遗留单体系统的微服务改造,也有全新未知领域的业务建模和系统设计,还有遗留系统局部优化的情况。不同场景下,领域建模的策略也会有差异。下面就分几类场景来看看如何进行领域建模。

1. 新建系统

新建系统又分为简单和复杂领域建模两种场景。

简单领域建模:简单的业务领域,一个领域就是一个小的子域。在这个小的问题域内,领域建模过程相对简单,直接采用事件风暴的方法构建领域模型就可以了。

复杂领域建模:对于复杂的业务领域,领域可能需要多级拆分后才能开始领域建模。领域拆分为子域,甚至子域还需要进一步拆分。比如:保险它需要拆分为承保、理赔、收付费和再保等子域,承保子域再拆分为投保、保单管理等子子域。复杂领域如果不做进一步细分,由于问题域太大,领域建模的工程量会非常浩大。不太容易通过事件风暴,完成一个很大的领域建模,即使勉强完成,效果也不一定好。

对于复杂领域,可以分三步来完成领域建模和微服务设计。

第一步,拆分子域建立领域模型。根据业务领域的特点,参考流程节点边界或功能聚合模块等边界因素。结合领域专家和项目团队的讨论,将领域逐级分解为大小合适的子域,针对子域采用事件风暴,划分聚合和限界上下文,初步确定子域内的领域模型。

第二步,领域模型微调。梳理领域内所有子域的领域模型,对各子域领域模型进行微调。微调的过程重点考虑不同领域模型中聚合的重组。同步考虑领域模型和聚合的边界,服务以及事件之间的依赖关系,确定最终的领域模型。

第三步,微服务的设计和拆分。根据领域模型和微服务拆分原则,完成微服务的拆分和设计。

2. 单体遗留系统

如果面对的是一个单体遗留系统,只需要将部分功能独立为微服务,而其余仍为单体,整体保持不变,比如将面临性能瓶颈的模块拆分为微服务。只需要将这一特定功能,理解为一个简单子领域,参考简单领域建模的方式就可以了。在微服务设计中,还要考虑新老系统之间服务和业务的兼容,必要时可引入防腐层。

 

DDD 使用的误区

很多人在接触微服务后,但凡是系统,一概都想设计成微服务架构。其实有些业务场景,单体架构的开发成本会更低,开发效率更高,采用单体架构也不失为好的选择。同样,虽然 DDD 很好,但有些传统设计方法在微服务设计时依然有它的用武之地。下面就来聊聊 DDD 使用的几个误区。

1. 所有的领域都用 DDD

很多人在学会 DDD 后,可能会将其用在所有业务域,即全部使用 DDD 来设计。DDD 从战略设计到战术设计,是一个相对复杂的过程,首先企业内要培养 DDD 的文化,其次对团队成员的设计和技术能力要求相对比较高。在资源有限的情况下,应聚焦核心域,建议先从富领域模型的核心域开始,而不必一下就在全业务域推开。

2. 全部采用 DDD 战术设计方法

不同的设计方法有它的适用环境,应选择它最擅长的场景。DDD 有很多的概念和战术设计方法,比如聚合根和值对象等。聚合根利用仓储管理聚合内实体数据之间的一致性,这种方法对于管理新建和修改数据非常有效,比如在修改订单数据时,它可以保证订单总金额与所有商品明细金额的一致,但它并不擅长较大数据量的查询处理,甚至有延迟加载进而影响效率的问题。

而传统的设计方法,可能一条简单的 SQL 语句就可以很快地解决问题。而很多贫领域模型的业务,比如数据统计和分析,DDD 很多方法可能都用不上,或用得并不顺手,而传统的方法很容易就解决了。

因此,在遵守领域边界和微服务分层等大原则下,在进行战术层面设计时,应该选择最适合的方法,不只是 DDD 设计方法,当然还应该包括传统的设计方法。这里要以快速、高效解决实际问题为最佳,不要为做 DDD 而做 DDD。

3. 重战术设计而轻战略设计

很多 DDD 初学者,学习 DDD 的主要目的,可能是为了开发微服务,因此更看重 DDD 的战术设计实现。殊不知 DDD 是一种从领域建模到微服务落地的全方位的解决方案。

战略设计时构建的领域模型,是微服务设计和开发的输入,它确定了微服务的边界、聚合、代码对象以及服务等关键领域对象。领域模型边界划分得清不清晰,领域对象定义得明不明确,会决定微服务的设计和开发质量。没有领域模型的输入,基于 DDD 的微服务的设计和开发将无从谈起。因此不仅要重视战术设计,更要重视战略设计。

4. DDD 只适用于微服务

DDD 是在微服务出现后才真正火爆起来的,很多人会认为 DDD 只适用于微服务。在 DDD 沉默的二十多年里,其实它一直也被应用在单体应用的设计中。

具体项目实施时,要吸取 DDD 的核心设计思想和理念,结合具体的业务场景和团队技术特点,多种方法组合,灵活运用,用正确的方式解决实际问题。

 

微服务设计原则

微服务设计原则中,如高内聚低耦合、复用、单一职责等这些常见的设计原则在此就不赘述了,主要强调下面这几条:

第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。

微服务设计首先应建立领域模型,确定逻辑和物理边界以及领域对象后,然后才开始微服务的拆分和设计。而不是先定义数据模型和库表结构,也不是前端界面需要什么,就去调整核心领域逻辑代码。在设计时应该将外部需求从外到内逐级消化,尽量降低对核心领域层逻辑的影响。

第二条:要边界清晰的微服务,而不是泥球小单体。

微服务上线后其功能和代码也不是一成不变的。随着需求或设计变化,领域模型会迭代,微服务的代码也会分分合合。边界清晰的微服务,可快速实现微服务代码的重组。微服务内聚合之间的领域服务和数据库实体原则上应杜绝相互依赖。可通过应用服务编排或者事件驱动,实现聚合之间的解耦,以便微服务的架构演进。

第三条:要职能清晰的分层,而不是什么都放的大箩筐。

分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,也就是说只能从外层调用内层服务,内层通过封装、组合或编排对外逐层暴露,服务粒度也由细到粗。应用层负责服务的组合和编排,不应有太多的核心业务逻辑,领域层负责核心领域业务逻辑的实现。各层应各司其职,职责边界不要混乱。在服务演进时,应尽量将可复用的能力向下层沉淀。

第四条:要做自己能 hold 住的微服务,而不是过度拆分的微服务。

微服务过度拆分必然会带来软件维护成本的上升,比如:集成成本、运维成本、监控和定位问题的成本。企业在微服务转型过程中还需要有云计算、DevOps、自动化监控等能力,而一般企业很难在短时间内提升这些能力,如果项目团队没有这些能力,将很难 hold 住这些微服务。

如果在微服务设计之初按照 DDD 的战略设计方法,定义好了微服务内的逻辑边界,做好了架构的分层,其实不必拆分太多的微服务,即使是单体也未尝不可。随着技术积累和能力提升,当有了这些能力后,由于应用内有清晰的逻辑边界,可以随时轻松地重组出新的微服务,而这个过程不会花费太多的时间和精力。

 

微服务拆分需要考虑哪些因素?

理论上一个限界上下文内的领域模型可以被设计为微服务,但是由于领域建模主要从业务视角出发,没有考虑非业务因素,比如需求变更频率、高性能、安全、团队以及技术异构等因素,而这些非业务因素对于领域模型的系统落地也会起到决定性作用,因此在微服务拆分时需要重点考虑它们。列出了以下主要因素供参考。

1. 基于领域模型:基于领域模型进行拆分,围绕业务领域按职责单一性、功能完整性拆分。

2. 基于业务需求变化频率:识别领域模型中的业务需求变动频繁的功能,考虑业务变更频率与相关度,将业务需求变动较高和功能相对稳定的业务进行分离。这是因为需求的经常性变动必然会导致代码的频繁修改和版本发布,这种分离可以有效降低频繁变动的敏态业务对稳态业务的影响。

3. 基于应用性能:识别领域模型中性能压力较大的功能。因为性能要求高的功能可能会拖累其它功能,在资源要求上也会有区别,为了避免对整体性能和资源的影响,可以把在性能方面有较高要求的功能拆分出去。

4. 基于组织架构和团队规:模除非有意识地优化组织架构,否则微服务的拆分应尽量避免带来团队和组织架构的调整,避免由于功能的重新划分,而增加大量且不必要的团队之间的沟通成本。拆分后的微服务项目团队规模保持在 10~12 人左右为宜。

5. 基于安全边界:有特殊安全要求的功能,应从领域模型中拆分独立,避免相互影响。

6. 基于技术异构等因素:领域模型中有些功能虽然在同一个业务域内,但在技术实现时可能会存在较大的差异,也就是说领域模型内部不同的功能存在技术异构的问题。由于业务场景或者技术条件的限制,有的可能用.NET,有的则是 Java,有的甚至大数据架构。对于这些存在技术异构的功能,可以考虑按照技术边界进行拆分。

 

======================================================================================================

 

 

20 | 总结(二):分布式架构关键设计10问

课程链接:https://time.geekbang.org/column/article/171185

一、选择什么样的分布式数据库?

分布式架构下的数据应用场景远比集中式架构复杂,会产生很多数据相关的问题。谈到数据,首先就是要选择合适的分布式数据库。

分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。目前主要有三种不同的分布式数据库解决方案。它们的主要差异是数据多副本的处理方式和数据库中间件。

1. 一体化分布式数据库方案

它支持数据多副本、高可用。多采用 Paxos 协议,一次写入多数据副本,多数副本写入成功即算成功。代表产品是 OceanBase 和高斯数据库。

2. 集中式数据库 + 数据库中间件方案

它是集中式数据库与数据库中间件结合的方案,通过数据库中间件实现数据路由和全局数据管理。数据库中间件和数据库独立部署,采用数据库自身的同步机制实现主副本数据的一致性。集中式数据库主要有 MySQL 和 PostgreSQL 数据库,基于这两种数据库衍生出了很多的解决方案,比如开源数据库中间件 MyCat+MySQL 方案,TBase(基于 PostgreSQL,但做了比较大的封装和改动)等方案。

3. 集中式数据库 + 分库类库方案

它是一种轻量级的数据库中间件方案,分库类库实际上是一个基础 JAR 包,与应用软件部署在一起,实现数据路由和数据归集。它适合比较简单的读写交易场景,在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有 ShardingSphere。

小结:这三种方案实施成本不一样,业务支持能力差异也比较大。一体化分布式数据库主要由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。集中式数据库 + 数据库中间件方案,实施成本和技术能力要求适中,可满足中大型企业业务要求。第三种分库类库的方案可处理简单的业务场景,成本和技能要求相对较低。在选择数据库的时候,要考虑自身能力、成本以及业务需要,从而选择合适的方案。

 

二、如何设计数据库分库主键?

选择了分布式数据库,第二步就要考虑数据分库,这时分库主键的设计就很关键了。

与客户接触的关键业务,建议以客户 ID 作为分库主键。这样可以确保同一个客户的数据分布在同一个数据单元内,避免出现跨数据单元的频繁数据访问。跨数据中心的频繁服务调用或跨数据单元的查询,会对系统性能造成致命的影响。

将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务。而对企业来说,“以客户为中心”的业务能力,首先就要做到数据上的“以客户为中心”。

当然,也可以根据业务需要用其它的业务属性作为分库主键,比如机构、用户等。

 

三、数据库的数据同步和复制

在微服务架构中,数据被进一步分割。为了实现数据的整合,数据库之间批量数据同步与复制是必不可少的。数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制、以及不同主题数据的整合等。

传统的数据传输方式有 ETL 工具和定时提数程序,但数据在时效性方面存在短板。分布式架构一般采用基于数据库逻辑日志增量数据捕获(CDC)技术,它可以实现准实时的数据复制和传输,实现数据处理与应用逻辑解耦,使用起来更加简单便捷。

现在主流的 PostgreSQL 和 MySQL 数据库外围,有很多数据库日志捕获技术组件。CDC 也可以用在领域事件驱动设计中,作为领域事件增量数据的获取技术。

 

四、跨库关联查询如何处理?

跨库关联查询是分布式数据库的一个短板,会影响查询性能。在领域建模时,很多实体会分散到不同的微服务中,但很多时候会因为业务需求,它们之间需要关联查询。

关联查询的业务场景包括两类:第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,这种查询会跨多个业务线的微服务;第二类是表与表之间的关联查询,比如机构表与业务表的联表查询,但机构表和业务表分散在不同的微服务。

如何解决这两类关联查询呢?对于第一类场景,由于数据分散在不同微服务里,无法跨多个微服务来统计这些数据。可以建立面向主题的分布式数据库,它的数据来源于不同业务的微服务。采用数据库日志捕获技术,从各业务端微服务将数据准实时汇集到主题数据库。在数据汇集时,提前做好数据关联(如将多表数据合并为一个宽表)或者建立数据模型。面向主题数据库建设查询微服务。这样一次查询就可以获取客户所有维度的业务数据了。还可以根据主题或场景设计合适的分库主键,提高查询效率。

对于第二类场景,对于不在同一个数据库的表与表之间的关联查询场景,可以采用小表广播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还可以提高数据的查询效率。

 

五、如何处理高频热点数据?

对于高频热点数据,比如商品、机构等代码类数据,它们同时面向多个应用,要有很高的并发响应能力。它们会给数据库带来巨大的访问压力,影响系统的性能。

常见的做法是将这些高频热点数据,从数据库加载到如 Redis 等缓存中,通过缓存提供数据访问服务。这样既可以降低数据库的压力,还可以提高数据的访问性能。

另外,对需要模糊查询的高频数据,也可以选用 ElasticSearch 等搜索引擎。

缓存就像调味料一样,投入小、见效快,用户体验提升快。

 

六、前后序业务数据的处理

在微服务设计时会经常发现,某些数据需要关联前序微服务的数据。比如:在保险业务中,投保微服务生成投保单后,保单会关联前序投保单数据等。在电商业务中,货物运输单会关联前序订单数据。由于关联的数据分散在业务的前序微服务中,无法通过不同微服务的数据库来给它们建立数据关联。

如何解决这种前后序的实体关联呢?

一般来说,前后序的数据都跟领域事件有关。可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。

可以将前序数据设计为实体或者值对象,并被当前实体引用。在设计时需要关注以下内容:如果前序数据在当前微服务只可整体修改,并且不会对它做查询和统计分析,可以将它设计为值对象;当前序数据是多条,并且需要做查询和统计分析,可以将它设计为实体。

这样,可以在货物运输微服务,一次获取前序订单的清单数据和货物运输单数据,将所有数据一次反馈给前端应用,降低跨微服务的调用。如果前序数据被设计为实体,还可以将前序数据作为查询条件,在本地微服务完成多维度的综合数据查询。只有必要时才从前序微服务,获取前序实体的明细数据。这样,既可以保证数据的完整性,还可以降低微服务的依赖,减少跨微服务调用,提升系统性能。

 

七、数据中台与企业级数据集成

分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分而形成很多数据孤岛,增加数据集成和企业级数据使用的难度。可以通过数据中台来实现数据融合,解决分布式架构下的数据应用和集成问题。

可以分三步来建设数据中台。

第一,按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。

第二,建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道视图等。

第三,建立业务需求驱动的数据体系,支持业务和商业模式创新。数据中台不仅限于分析场景,也适用于交易型场景。可以建立在数据仓库和数据平台上,将数据平台化之后提供给前台业务使用,为交易场景提供支持。

 

八、BFF 与企业级业务编排和协同

企业级业务流程往往是多个微服务一起协作完成的,每个单一职责的微服务就像积木块,它们只完成自己特定的功能。那如何组织这些微服务,完成企业级业务编排和协同呢?

可以在微服务和前端应用之间,增加一层 BFF 微服务(Backend for Frontends)。BFF 主要职责是处理微服务之间的服务组合和编排,微服务内的应用服务也是处理服务的组合和编排,那这二者有什么差异呢?

BFF 位于中台微服务之上,主要职责是微服务之间的服务协调;应用服务主要处理微服务内的服务组合和编排。在设计时应尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用。

BFF 像齿轮一样,来适配前端应用与微服务之间的步调。它通过 Façade 服务适配不同的前端,通过服务组合和编排,组织和协调微服务。BFF 微服务可根据需求和流程变化,与前端应用版本协同发布,避免中台微服务为适配前端需求的变化,而频繁地修改和发布版本,从而保证微服务核心领域逻辑的稳定。

如果 BFF 做得足够强大,它就是一个集成了不同中台微服务能力、面向多渠道应用的业务能力平台。

 

九、分布式事务还是事件驱动机制?

分布式架构下,原来单体的内部调用,会变成分布式调用。如果一个操作涉及多个微服务的数据修改,就会产生数据一致性的问题。数据一致性有强一致性和最终一致性两种,它们实现方案不一样,实施代价也不一样。

对于实时性要求高的强一致性业务场景,可以采用分布式事务,但分布式事务有性能代价,在设计时需平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。

领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时场景的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好地解耦微服务。通过削峰填谷,可以减轻数据库实时访问压力,提高业务吞吐量和处理能力。还可以通过事件驱动实现读写分离,提高数据库访问性能。对最终一致性的场景,建议采用领域事件驱动的设计方法。

 

十、多中心多活的设计

分布式架构的高可用主要通过多活设计来实现,多中心多活是一个非常复杂的工程,下面主要列出以下几个关键的设计。

1. 选择合适的分布式数据库。数据库应该支持多数据中心部署,满足数据多副本以及数据底层复制和同步技术要求,以及数据恢复的时效性要求。

2. 单元化架构设计。将若干个应用组成的业务单元作为部署的基本单位,实现同城和异地多活部署,以及跨中心弹性扩容。各单元业务功能自包含,所有业务流程都可在本单元完成;任意单元的数据在多个数据中心有副本,不会因故障而造成数据丢失;任何单元故障不影响其它同类单元的正常运行。单元化设计时要尽量避免跨数据中心和单元的调用。

3. 访问路由。访问路由包括接入层、应用层和数据层的路由,确保前端访问能够按照路由准确到达数据中心和业务单元,准确写入或获取业务数据所在的数据库。

4. 全局配置数据管理。实现各数据中心全局配置数据的统一管理,每个数据中心全局配置数据实时同步,保证数据的一致性。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章