DDD终结篇
总结一:微服务设计和拆分的原则
微服务的演进策略:
1、绞杀者策略:绞杀者策略是一种逐步剥离业务能力,用微服务逐步代替原有单体系统的策略,它对单体系统进行领域建模,根据领域边界,在单体系统之外,将新功能和部分业务能力独立出来,建设独立的微服务。新微服务与单体系统保持松耦合关系。随着时间的推移,大部分单体系统的功能将被独立为微服务,这样就慢慢绞杀掉了原来的单体系统。绞杀者策略类似建筑拆迁,完成部分新建筑物后,然后拆除部分旧建筑物。
2、修缮者策略:修缮者策略是一种维持原有系统整体能力不变,逐步优化系统整体能力的策略。它是在现有系统的基础上,剥离影响整体业务的部分功能,独立为微服务,比如高性能要求的功能,代码质量不高或者版本发布频率不一致的功能等。通过这些功能的不离,我们就可以兼顾整体和局部,解决系统整体不协调的问题。修缮者策略类似古建筑修复,将存在问题的部分功能重构或者修复后,重新加入到原有的建筑中,保持建筑原貌和功能不变。一般人从外表感觉不到这个变化,但是建筑质量却得到很大的提升。
3、另起炉灶:就是将原有的系统推到重做。建设期间,原有单体系统照常运行,一般会停止开发新需求,而新系统则会组织新的项目团队,按照原有系统的功能域,重新做领域建模,开发新的功能,在完成数据迁移后,进行新旧系统切换。对于大型系统一般不建议使用这种策略,因为系统重构后的不稳定性、大量未知的潜在技术风险和新的开发模式下项目团队磨合等不确定因素,会导致项目实施难度大大增加。
不同场景下的领域建模策略:
1、新建系统:简单领域建模:简单的业务领域,一个领域就是一个小的子域。在这个小的问题域内,领域建模过程相对简单,直接采用事件风暴的方法构建领域模型即可。
复杂领域建模:对于复杂的业务领域,领域可能需要多级拆分后才能开始领域建模。领域拆分为子域,甚至子域还需要进一步拆分。对于复杂领域,可以分三步来完成领域建模和微服务设计:
第一步:拆分子域建立领域模型:根据业务领域的特点,参考流程节点边界或功能聚合模块等边界因素,结合领域专家和项目团队的讨论,将领域逐级分解为大小合适的子域,
针对子域采用事件风暴,划分聚合和限界上下文,初步确定子域内的领域模型。
第二步:领域模型微调:梳理领域内所有子域的领域模型,对各子域模型进行微调。微调的过程重点考虑不同领域模型中聚合的重组。同步考虑领域模型和聚合的边界,服务以及事件之间的依赖关系,
确定最终的领域模型。
第三步:微服务的设计和拆分:根据领域模型和微服务拆分原则,完成微服务的拆分和设计。
2、单体遗留系统:面对一个单体遗留系统,只需要将部分功能独立为微服务,而其余仍为单体,整体保持不变,比如将面临性能瓶颈的模块拆分为微服务,我们只需要将这一特定功能,理解为一个简单的子领域,参考简单领域建模的方式就可以了。在微服务设计中,我们还要考虑新老系统之间服务和业务的兼容,必要时可引入防腐层。
DDD使用误区:
1、所有领域都用DDD:在资源有限的情况下,应聚焦核心域,建议你先从富领域模型的核心域开始,而不必以下就在全业务域推开。
2、全部采用DDD战术设计方法:不同的设计方法有它的使用环境,我们应该选择它最擅长的场景。比如聚合根和值对象。聚合根利用仓储管理聚合内实体数据之间的一致性,这种方法对于管理新建和修改数据非常有效,比如在修改订单数据时,它可以保证订单总金额与所有商品明细金额的一致,但它并不擅长较大数据量的查询处理,甚至有延迟加载进而影响效率的问题。
3、重战术设计而轻战略设计:战略设计时构建的领域模型,是为服务设计和开发的输入,它确定了微服务的边界、聚合、代码对象以及服务等关键领域对象。领域模型边界划分得清不清晰,领域对象定义得明不明确,会决定微服务的设计和开发质量。没有领域模型的输入,基于DDD的微服务的设计和开发将无从谈起。因此不仅要重视战术设计,更要重视战略设计。
4、DDD只适用于微服务:在DDD沉默的二十多年里,它一直也被应用在单体应用的设计中。具体项目实施时,要吸取DDD的核心设计思想和理念,结合具体的业务场景和团队特点,多种方法组合,灵活运用,用正确的方式解决实际问题。
微服务设计原则:
1、要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。
微服务设计首先应建立领域模型,确定逻辑和物理边界以及领域对象后,然后才开始微服务的拆分和设计。
而不是先定义数据模型和数据库表结构,也不是前端界面需要什么,就去调整核心领域逻辑代码。
在设计时应该将外部需求从外到内逐级消化,尽量降低对核心领域层逻辑的影响。
2、要边界清晰的微服务,而不是泥球小单体。
微服务上线后其功能和代码也不是一层不变的,伴随着需求或设计变化,领域模型会迭代,微服务的代码也会分分合合。
边界清晰的微服务,可快速实现微服务代码的重组。微服务内聚合之间的领域服务和数据库实体原则上应杜绝相互依赖。
可通过应用服务编排或者事件驱动,实现聚合之间的解耦,以便微服务的架构演进。
3、要职能清晰的分层,而不是什么都放的大箩筐。
分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,也就是说职能从外层调用内层服务,
内层通过封装、组合或编排对外层逐层暴露,服务粒度也由细到粗。应用层负责服务的组合和编排,
不应有太多的核心业务逻辑,领域层负责核心领域业务逻辑的实现。各层应各司其职,职责边界不要混乱。
在服务演进时,应尽量将可复用的能力向下层沉淀。
4、要做自己能hold注的微服务,而不是过度拆分的微服务。
微服务过度拆分必然会带来软件维护成本的上升,比如:集成成本、运维成本、监控和定位问题的成本。
企业在微服务转型过程中还需要有云计算、DevOps、自动化监控能力,而一般企业很难在短时间内提升这些能力,
如果项目团队没有这些能力,将很难hold住这些微服务。如果项目团队没有这些能力,这很难hold住这些微服务。
如果在微服务设计之初按照DDD的战略设计方法,定义好微服务内的逻辑边界,做好架构的分层,其实我们不必要拆分太多的微服务,
即使是单体也未尝不接。随着技术积累和能力提升,当我们有了这些能力后,由于应用内有清晰的逻辑边界,
我们可以随时轻松地重组出新的微服务,而这个过程不会花费太多的事件和精力。
微服务拆分需要考虑的因素:
1、基于领域模型:基于领域模型进行拆分,围绕业务领域按职责单一性、功能完整性拆分。
2、基于业务需求变化频率:识别领域模型中的业务需求变动频繁的功能,考虑业务变更频率与相关度,将业务需求变动较高
和功能相对稳定的业务进行分离。这是因为需求的经常性变动必然会导致代码的频繁修改和版本发布,
这种分离可以有效降低频繁变动的敏态业务对稳态业务的影响。
3、基于应用性能:识别领域模型中性能压力较大的功能。因为性能要求高的功能可能会拖累其他功能,在资源要求上也会有区别,
为了避免对整体性能和资源的影响,我们可以把在性能方面有较高要求的功能拆分出去。
4、基于组织架构和团队规模:除非有意识地优化组织架构,否则微服务的拆分应尽量避免团队和组织架构的调整,
避免由于功能的重新划分,而增加大量且不必要的团队之间的沟通成本。
5、基于安全边界:有特殊安全要求的功能,应从领域模型中拆分独立,避免相互影响。
6、基于技术异构等因素:领域模型中有些功能虽然在同一业务域内,但在技术实现时可能会存在较大的差异,
也就会说领域模型内部不同的功能存在技术异构的问题。由于业务场景或者技术条件的限制,有的可能用
.NET,有的则是Java,有的甚至是大数据架构。对于这些存在技术异构的功能,可以考虑按照技术边界进行拆分。
总结二:分布式架构关键设计10问。
1、选择什么样的分布式数据库:分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。目前主要有三种不同的分布式数据库解决方案,它们的主要差异是数据多副本的处理方式和数据库中间件。
一体化分布式数据库方案:它支持数据多副本、高可用。多采用Paxos协议,一次写入多数据副本,多数据副本写入成功即算成功,代表产品是OceanBase和高斯数据库。一体化分布式数据库主要由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。
集中式数据库+数据库中间件方案:它是集中式数据库与数据库中间件结合的方案,通过数据库中间件实现数据路由和全局数据管理。数据库中间件和数据库独立部署,采用数据库自身的同步机制实现主副本数据的一致性。集中式数据库主要有MySQL和PostgreSQL数据库,基于这两种数据库衍生出了很多的解决方案,比如开源数据库中间件MyCat+MySQL方案,TBase(基于PostgreSQL,但做了比较大的封装和改动)等方案。集中式数据库+数据库中间件方案,实施成本和技术能力要求始终,可满足中大型企业业务要求。
集中式数据库+分库类库方案:它是一种轻量级的数据库中间件方案,分库实际上是一个基础JAR包,与应用软件部署在一起,实现数据路由和数据归集。它适合比较简单的读写交易场景,在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有ShardingSphere。集中式数据库+分库类库方案可处理简单的业务场景,成本和技能要求相对较低,在选择数据库的时候,我们要考虑自身能力、成本以及业务需要,从而选择合适的方案。
2、如何设计数据库分库主键:与客户接触的关键业务,建议以客户ID作为分库主键,这样可以却白同一个客户的数据分布在同一个数据单元内,避免出现跨数据单元的频繁数据访问,跨数据中心的频繁服务调用或跨数据单元的查询,会对系统性能造成致命的影响。将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务,而对起一页涞水,以客户为中心的业务能力,首先就要做到数据上的“以客户为中心”。当然,也可以根据业务需要用其他业务属性作为分库主键,比如机构、用户等。
3、数据库的数据同步和复制:在微服务架构中,数据被进一步分割。为了实现数据的整合,数据库之间批量数据同步与赋值是必不可少的。数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制、以及不同主题数据的整合等。
传统的数据传输方式有ETL(ETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程)工具和定时提数程序,但数据在时效性方面存在短板,分布式架构一般采用基于数据逻辑日志增量数据捕获(CDCChange Data Capture(改变数据捕获)是Oracle在数据库级别实现的增量抽取解决方案之一,能够帮助你识别从上次提取之后发生变化的数据)技术,他可以实现准实时的数据复制和传输,实现数据处理与应用逻辑解耦,使用起来更加简单便捷。现在主流的PostgreSQL和MySQL数据库外围,有很多数据库日志捕获技术组件,CDC也可以用在领域事件驱动设计中,作为领域事件增量数据的获取技术。
4、跨库关联查询如何处理:跨库关联查询是分布式数据库的一个短板,会影响查询性能。在领域建模时,很多实体会分散到不同的微服务中,但很多时候会因为业务需求,他们之间需要关联查询。
关联查询的业务场景包括两类:
第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,这种查询会跨多个业务线的微服务。
解决方案:由于数据分散在不同的微服务里,我们无法跨多个微服务来统计这些数据。可以建立面向主体的分布式数据库,它的数据来源于不同业务的微服务。采用数据库日志捕获技术,从各业务端微服务将数据准实时汇集到主体数据库。在数据汇集时,提前做好数据关联(如将多表数据合并为宽表)或者建立数据模型。面向主体数据库建设查询微服务,这样一次查询就可以获取客户所有维度的业务数据了,还可以根据主题或场景设计适合的分库主键,提高查询效率。
第二类是表与表之间的关联查询,比如机构表与业务表的连表查询,但机构表和业务表分散在不同的微服务。
解决方案:对于不在同一个数据库的表与表之间的关联查询场景,可以采用小表广播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还可以提高数据的查询效率。
5、如何处理高频热点数据:常见的做法就是将这些高频热点数据(例如商品、机构等)从数据库加载到如Redis等缓存中,公国缓存提供数据访问服务。这样既可以降低数据库的压力,还可以提高数据的访问性能。对于需要模糊查询的高频数据,可以选用ElasticSearch等搜索引擎。缓存就想调味料一样,投入小、见效快、用户体验提升快。
6、前后序业务数据的处理:一般来说,前后序的数据都跟领域事件有关,可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。可以将前序数据设计为实体或者值对象,并被当前实体引用,再设计时需要关注:如果前序数据在当前为服务只可整体修改,并且不会对它做查询和统计分析,可以将它设计为值对象。当前数据是多条,并且需要做查询和统计分析,可以将它设计为实体。如果前序数据被设计为实体,可以将前序数据作为查询条件,在本地微服务完成多维度的综合数据查询,只有必要时才从前序微服务,获取前序实体的明细数据。这样,既可以保证数据的完整性,还可以降低微服务的依赖,减少跨微服务调用,提升系统性能。
7、数据中台与企业级数据集成:分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分而形成很多数据孤岛,增加数据集成和企业级数据使用的难度,可以通过数据中台是西安数据融合,解决分布式架构下的数据应用和集成问题。
可以分三步建立数据中台:
1、按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。
2、建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道试图等。
3、建立业务需求驱动的数据体系,支持业务和商业模式创新。
注:数据中台不仅限于分析场景,也适用于交易型场景,可以建立在数据仓库和数据平台上,将数据平台化之后提供给前台业务使用,为交易场景提供支持。
8、BFF于企业级业务编排和协同:企业级业务流程往往是多个微服务一起协作完成的,每个单一职责的微服务就像积木块,他们只完成自己特定的功能。
如何组织这些微服务,完成企业级业务编排和协同?
可以在微服务和前端应用之间,增加一层BFF微服务(Backend for Frontends),BFF主要职责是处理微服务之间的服务组合和编排。微服务内的应用服务也是处理服务的组合和编排,两者的差异主要体现在:BFF位于中台微服务之上,主要职责是微服务之间的服务协调,应用服务主要处理微服务内的服务组合和编排。在设计时,我们尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用。
BFF像齿轮一样,来适配前端应用与微服务之间的步调,它通过Facade服务适配不同的前端,通过服务组合和编排,组织和协调微服务。BFF微服务可根据需求和流程变化,与前端应用版本协同发布,避免中台微服务为适配前端需求的变化,而频繁地修改和发布版本,从而保证微服务核心领域逻辑的稳定。如果BFF做得足够强大,他就是一个继承了不同中台微服务能力、面向多渠道应用的业务能力平台。
9、分布式事务还是事件驱动机制?
分布式架构下,原来单体的内部调用,会变成分布式调用。如果一个操作涉及多个微服务的数据修改,就会产生数据一致性的问题。数据一致性有强一致性和最终一致性,它们实施方案不一样,实施代价也不一样。对于实时性要求较高的强一致性业务场景,可以采用分布式事务,但分布式事务有性能代价,尽量避免分布式事务的产生。领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时场景的数据最终一致性问题,基于消息中间件的领域事件发布和订阅,可以很好的解耦微服务。通过削峰填谷,可以减轻数据库实时访问雅鹿,提高业务吞吐量和处理能力,还可以通过事件驱动实现读写分离,提高数据库访问性能,对最终一致性的场景,建议采用领域事件驱动的涉及方法。
10、多中心多活的设计:分布式架构的高可用主要通过多活设计来实现,多中心多活是一个非常复杂的工程,设计的关键如下:
1、选择合适的分布式数据库,数据库应该支持多数据中心部署,满足数据u都副本以及数据底层赋值和同步技术要求,以及数据恢复的时效性要求。
2、单元架构设计。将若干个应用组成的业务丹云作为部署的基本单位,实现同城和异地多活部署,以及跨中心弹性扩容。各单元业务功能自包含,
所有业务流程都可在本单元完成,任意单元的数据在多个数据中心有副本,不会因故障而造成数据丢失,任何单元故障不影响其他同类单元的正常运行。
单元化设计时,我们要尽量避免跨数据中心和单元的调用。
3、访问路由。访问路由包括接入层、应用层和数据层的路由,确保前端访问能够按照路由准确到达数据中心和业务单元,准确写入或获取业务数据所在的数据库。
4、全局配置数据管理,实现各数据中心全局配置数据的统一管理,每个数据中心全局配置数据实时同步,保证数据的一致性。
DDD书籍推荐
《领域驱动设计:软件核心复杂性应对之道》
《实现领域去顶设计》
《微服务架构设计模式》
结语:深刻理解DDD的设计思想和内涵,把握好边界和分层这个大原则,结合企业文化和技术特点,灵活运用战术设计方法,选择最合适的技术和方法解决实际问题,切勿为了DDD而做DDD。