当前位置: 首页 > 科技观察

微服务与领域驱动设计,架构实践总结

时间:2023-03-18 14:55:05 科技观察

什么样的架构才值得快速变革?一、软件复杂度1、复杂度产生的原因如果软件系统存在一个不断迭代的循环,那么业务、技术、架构的复杂度都会线性增加,相应的开发难度也会增加。根本原因可以用一句话概括:唯一不变的是变化;业务变化:复杂的根源,多端、多版本适配过程中代码的快速膨胀;数据变化:数据随着业务的变化和发展不断积累,需要做水平垂直管理;技术升级:技术组件可能会因漏洞或更好的解决问题而不断升级;人员变动:模块的开发人员一旦流动,更换就会给代码带来风格差异;心态起伏:不断处理复杂的问题,但难以保持稳定的心态,这也是人员流动的一个因素;应对复杂变化一直是软件工程的核心难点,如何用小的架构变化来应对大的业务变化,也就是设计中常说的:高内聚低耦合;还需要补充一个很重要的一点:仅仅从技术层面是不可能持续解决复杂问题的,还需要从管理的角度定义流程标准,将各种解决方案标准化。这是整个部门必须持续面对的事情。2、应对复杂性无论是设计模式、原则、面向对象,还是架构中常用的集群、微服务、领域驱动等,都在寻找更合理的方案来应对业务变化;但没有一劳永逸的解决方法,既要做出一定的前瞻性设计来预见业务,又要避免过多的设计影响业务进度;分析理解,在技术层面不断优化解决方案;例如,微服务的思想是通过拆分的方式实现业务块之间的低耦合,领域驱动设计是实现各个业务逻辑的高内聚;下面对两种方法的实践进行详细分析。二、微服务架构1、架构设计系统的架构设计是一件极其复杂的事情。在过去几年的工作中,大致经历了以下几个阶段:单服务、多服务集群、微服务、持续集成;2年内比较稳定的选型是微服务+自动化集成的模式:思考其本质的变化逻辑,即应对更复杂的业务系统;无论是业务拆分还是模型设计,都在不断实现高内聚、低耦合的原则;减少业务之间关联的影响,分离业务与技术的高耦合。2.业务场景先来看一个经典的业务场景:电子商务交易;在一个基于微服务架构的电商交易场景中,通常至少涉及以下核心服务:交易、账户、订单、商品、仓储、物流;从业务角度,模块化拆分和管理,结合持续的集成组件,通常可以轻松应对各种复杂的业务场景,但没有真正的一劳永逸的手段,业务变化引起的各种问题,总会无脑去推动开发寻找更合理的解决方案;在一个完整的电商交易场景中,实际涉及的微服务远不止图中的几个,多个其他服务交织在Trade服务中,在MVC的分层管理下,不会有很大的风险初期,但一旦业务经过多次版本升级,仍然存在版本兼容需求,会给人一种极度混乱和不可靠的感觉;如果团队成员的综合能力高,版本有足够的时间进行设计和优化。这种问题是可以妥善解决的。如果时间紧,任务重,伴随而来的压力就是继续开发和测试。在时间之间来回跳跃;解决过相关业务场景的研发都知道,重构加上持续集成能力,结合严密的测试,可以应对业务的不断变化;但是在版本兼容的过程中,还是会导致项目中的代码膨胀,飞起来,尤其是有中场换人的时候,接手的人在被埋没的过程中会有剧烈的心理挣扎离开。3、问题分析在MVC架构模型中,项目通常进行分层管理:控制层、服务层、持久层、存储层;服务层会在具体复杂的场景下进行细分,比如第一个常用中间件的三方对接和二次封装:对于在复杂业务线上竞争的玩家,对Mvc分层模型的缺陷有深刻的理解.Service层专注于大量复杂的逻辑,通常在核心业务块中总会有几千行以上代码的实现逻辑。不管用什么思路和模型来拆分封装,都很难解决这一层不断扩容带来的扩容问题。4.面向过程在MVC分层中,过程化代码极其明显,通常是基于数据库表和关系,映射和构建相关的实体对象,这些实体对象没有具体的行为和逻辑,只是作为实现的载体数据和结构:来自面向对象类的定义:属性和行为;在MVC模型中,大部分实体只是对数据输入输出的结构定义,可以理解为数据容器,在MVC各层之间不断地处理和处理。3.领域驱动设计与MVC的分层设计相比,领域驱动设计(简称DDD)为复杂业务系统的实现提出了更合理的解决方案。DDD模型涉及到大量的专业术语和抽象概念,可以参考EricEvans的相关书籍。本文仅描述实践中的核心概念。1.分离模式DDD模型在分层设计上分为四个核心层:接入层、应用层、领域层和基础设施层;注意,这只是从服务端的常规架构来看,分离明显MVC模型中服务实现层的逻辑:领域层是关键,用于封装复杂的业务,提供对业务管理的核心支持到应用层;整个模型也更加垂直化的思考,有效的缓解了单层复杂度高的现象;单从模型设计的角度来看,在项目中基于这一层来管理代码包,也可以让各层的设计更加清晰和独立。2.设计思维领域驱动设计并不是简单的层级管理模型,它涉及到很多抽象逻辑和专业术语,比如:领域、限界上下文、实体、聚合、值对象等;待解决问题的集合受范围和边界的限制;域可以拆分为多个子域,通常描述为:核心域、支撑域、通用域:子域的划分也是对业务属性的一种参照,核心域可以理解为最关键的业务场景,并需要资源倾斜以配合其持续发展;支撑域可以理解为相对稳定的业务;通用域偏向于系统架构层面的公共能力;业务的划分是通过领域的拆分来实现的,这与拆分微服务的思路是一致的,两种模式从业务的角度来说是比较统一的;2.2BoundedContextDDD中最晦涩的抽象概念,具体模型的有界应用,不过可以借用原文的比喻来认识一下:cells之所以能够存在,是因为细胞膜定义了里面的东西细胞和细胞外是什么,决定了哪些物质可以通过细胞膜:有界上下文的定义涉及到粒度的思想,即每个粒度必须是独立的;如上图存储业务,可以将服务部署与存储子域和存储上下文一一对应,或者在存储子域中定义两个上下文:仓库和货架;这里有很大的灵活性,并没有真正意义上的标准可以参考。2.3映射关系做好边界上下文的划分,理清各个上下文之间的关系,明确业务场景中的依赖顺序,可以更好的促进开发过程的执行;context之间关系的描述远不止图中的,还有共享内核,合作等:服务调用者为D,服务提供者为U;防腐层(Anticorruption-Layer,简称ACL):上下文交互时的一层封装,提供动作的验证、适配、转换等;开放主机服务,发布语言(Open-Host-Service缩写为OHS,Published-Language缩写为PL):定义访问协议;在上下文交互时,防腐层可以保持上下文的隔离性和独立性,保证调用者不直接依赖服务提供者,从而实现不同上下文之间依赖关系的解耦;同时,这也会带来大量的对象转换动作;2.4建模设计子域和边界线完成业务的拆分和切片,从而分而治之;基于防腐层降低各边界上下文的耦合度;聚合思想保证了业务问题解决方案的内聚性;严格的分层模型实现支撑能力的服务去中心化;Anticorruption-Layer:上下文交互时封装的层;Domain-Layer:负责分层架构中领域逻辑的设计和实现;Domain-Service:当行为无法识别其所属实体时,被封装为领域服务;聚合:相关对象的集合,描述核心领域,通常以聚合作为数据修改的单位;实体:通过标识定义的对象,而不是基于属性,如Uid来标识用户实体;值对象(Value-Object):描述特性或属性但不标识对象;工厂(Factory):封装了对象复杂的创建逻辑和类型;Repository:存储、缓存、搜索等资源封装机制,对应领域模型;领域模型的核心追求目标:高内聚低耦合;更抽象、更复杂的设计思想也意味着更难实现,但不可否认的是,作为复杂业务的解决方案,领域模型在逻辑上更合理。3.工程实践领域模型在代码工程实践中,可以将不同的子域集成到各自的服务中,或者在一个服务中,可以使用多个模块(Module)进行隔离和维护,即一个模块对应一个oneboundaryContext:通过分模块、分层、分包的方式来隔离业务问题是代码工程的基本方法。这里只是描述组织方法。实际开发中,类库解包管理要按照依赖的先后顺序进行;在程序执行过程中,并不是所有的交互命令都需要经过领域层。事实上,业务中的大部分查询命令都超过了增删改查命令。因此,在纯读取数据请求中,应用层可以绕过开放域层直接访问基础设施层,减少一层数据处理逻辑。4.实践总结最后,我们来谈谈一些架构实践经验。随着技术的不断发展和升级,无论是单一服务中的各种成熟组件,还是分布式服务,都为解决业务问题提供了极大的便利。微服务系统,或者专注于业务管理的领域模型;每种架构选择都有其适用的场景,不同的选择意味着不同的实施成本;事实上,在做架构选择的时候,成熟有经验的Leader是极其善于妥协的,也就是常说的退一步;通常需要考虑团队的综合水平、业务需求和产品设计。当然,在实际协作过程中,多方需要做出相对让步,但对质量的要求和核心业务逻辑的实现是不能打折扣的。5.参考源码编程文档:https://gitee.com/cicadasmile/butte-java-note应用仓库:https://gitee.com/cicadasmile/butte-flyer-parent