由于企业发展历程以及企业技术和文化的不同,DDD和微服务的实施策略也会有差异。那么面对这种差异,我们应该如何落地DDD和微服务呢?今天我们就来聊聊微服务的设计原则和演进策略。
最常见的单体遗留系统
如果我们面对的是一个单体遗留系统,只需要将部分功能独立为微服务,而其余仍为单体,整体保持不变,比如将面临性能瓶颈的模块拆分为微服务。我们只需要将这一特定功能,理解为一个简单子领域,参考简单领域建模的方式就可以了。在微服务设计中,我们还要考虑新老系统之间服务和业务的兼容,必要时可引入防腐层(为了屏蔽系统之间的相互影响,保证领域模型的纯洁性,在微服务增加一些代码进行业务适配,比如新老系统的协议转换等。)
业务专家或领域专家的问题
传统企业中业务人员是需求的主要提出者,但由于部门墙,他们很少会参与到软件设计和开发过程中。如果研发模式不调整,你不要奢望业务人员会主动加入到项目团队中,一起来完成领域建模。没有业务人员的参与,是不是就会觉得没有领域专家,不能领域建模了呢?其实并不是这样的。对于成熟业务的领域建模,我们可以从团队需求人员或者经验丰富的设计或开发人员中,挑选出能够深刻理解业务内涵和业务管理要求的人员,担任领域专家完成领域建模。对于同时熟悉业务和面向对象设计的项目人员,这种设计经验尤其重要,他们可以利用面向对象的设计经验,更深刻地理解和识别出领域模型的领域对象和业务行为,有助于推进领域模型的设计。而对于新的创业企业,他们面对的是从来没人做过的全新的业务和领域,没有任何可借鉴的经验,更不要提什么领域专家。对于这种情况,就需要团队一起经过更多次更细致的事件风暴,才能建立领域模型。当然建模过程离不开产品愿景分析,这个过程是确定和统一系统建设目标以及项目的核心竞争力在哪里。这种初创业务的领域模型往往需要经过多次迭代才能成型,不要奢望一次就可以建立一个完美的领域模型。
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,有的甚至大数据架构。对于这些存在技术异构的功能,可以考虑按照技术边界进行拆分。
总结
相信你在微服务落地的时候会有很多的收获和感悟。对于DDD和微服务,我想总结的就是:深刻理解DDD的设计思想和内涵,把握好边界和分层这个大原则,结合企业文化和技术特点,灵活运用战术设计方法,选择最适合的技术和方法解决实际问题,切勿为了DDD而做DDD!
推荐的书籍
微服务架构设计模式
实现领域驱动设计
领域驱动设计:软件核心复杂性应对之道