DDD 在京东 DevOps 项目协作领域的落地实战
DDD 即领域驱动设计(Domain-Driven Design)。它来源于著名建模专家 Eric Evans 发表的非常具有影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文:领域驱动设计—软件核心复杂性应对之道)。其最早广泛应用于传统软件架构设计中。
今年4月,InfoQ 发布了软件架构与设计的趋势报告。在报告中可以看出,微服务、领域驱动设计等已成为目前软件开发行业的主流趋势,笔者也了解到,DDD 已成为大部分企业微服务落地、中台建设的指导思想。
(图片来源:infoQ)
隔离变化和沉淀复用
在互联网时代,业务系统构建初期,场景通常简单清晰,基于数据库表设计+CRUD 就能支撑业务的敏捷迭代。随着时间的推移,场景和业务逻辑变得复杂、定制,模块之间彼此耦合,系统变得越来越臃肿,往往牵一发而动全身。即使加一个字段这样看似简单的逻辑,光梳理对其他功能点和模块的影响,就至少需要一个对业务和代码都很熟悉的团队老成员才能稳妥敲定。相信一个实现1年以上的业务系统,其架构和代码就有可能正在经历着以上这些,开始慢慢腐化。
一个最经典的例子——电商系统里面诸如以下 product 类的设计(现实中字段数可能几百个),它是不符合高内聚低耦合这个架构设计原则的:
- product 类包罗万象,什么领域的知识都知道;其职责不清晰,在沉淀复用上,团队成员的期望都不一致,实现姿势可能千奇百怪;
- 采购、仓储、配送业务领域的逻辑变动可能都要修改 product ,一个高内聚的系统设计,是需要尽可能隔离变化,使之产生的影响最小且控制在一定边界内。
DDD 通过划分限界上下文,拆分子领域,实现解耦和复用:
以下图片来自软件开发教父:Martin Fowler。诚然,为应付越来越复杂的业务逻辑,以自顶向下设计的领域聚合模型替代数据表模型,才能真正实现以业务实体为核心的灵活拓展:
- 面向过程的脚本,不能很好地被扩展;
- 面向数据表的设计,过度关注数据忽略行为,不能很好地被复用。
以上,让经历互联网泡沫存活下来的系统弄潮儿逐渐重燃对 DDD 的热情,希望借助 DDD 能重拾建造更合理软件架构的信心。但在落地上,荆棘环生,困境各异。下面我们来介绍一下 DDD 在京东 DevOps 项目协作领域的落地实践。
将 DDD 应用到组件化
在京东内部,项目协作领域有两个用户群体覆盖特别广(总用户数 8W+)的系统:PMP 和行云。
PMP是一个更面向项目经理的项目管理软件,包含项目流程管理、需求管理、里程碑管理、任务管理、工时管理、成本管理、OKR 管理、ROI 验证和满意度评分等。在架构设计上,PMP 是一个非常庞大的单体应用,其前后端不分离,所有业务逻辑糅杂在一起,项目表单字段数达到了 100+。经过时间和组织的变迁,在可扩展性和可维护性上,简直是场灾难。
行云起初的定位是面向研发的敏捷协同软件,包含跨部门需求管理、敏捷迭代和任务管理等。随着行云的广泛使用,将 PMP 功能融合到行云的呼声越来越高。
同时,行云对公司外部提供 ToB 服务。内外部存在着大量差异化的场景和业务,单纯靠以往的不断添加 if else 揉泥球的方式,已经完全行不通。新的定位和背景下,要求我们必须将业务逻辑拆分为一个个灵活的组件,做到复用与逻辑沉淀;另一方面,差异化的逻辑可以灵活扩展。这其中有着巨大的挑战:
上图中间圆弧描述的是我们在对组件拆分时参考的原则(业务可独立运营、高内聚低耦合、数据完整性、渐进性),以此为基础,在应对业务和技术视角上的挑战时,我们采取的相关 DDD 战略和战术。
业务的挑战
PMP 和行云中都有需求管理、任务管理。这其中需求、任务是不是一个概念?项目、需求、迭代、任务等之间是什么关系?如何对内和对外沉淀一套清晰可复用的业务组件?
DDD 中概念比较多,包含限界上下文、聚合、聚合根、实体、值对象等,本文先不关注这些细节,直接聚焦以下问题:
1、 划分边界,识别领域对象或者领域类,保证其职责清晰纯粹;
2、 明确领域类之间的关联、依赖,界定调用关系和方式。
通过领域分析建模,形成统一语言,最终将项目协作管理这个整体,拆分为一个个功能完整、业务逻辑明确的业务域,如项目域、任务域、工时域等等。每一个业务域对外提供明确的领域服务,一个业务领域可以管理多个业务实体。
x_lazy=1&wx_co=1)
技术的挑战
在划分完领域后,理想的情况是各领域高度自治,减少依赖,甚至各领域可以由不同的团队来实现,各团队聚焦沉淀该领域核心能力。
最终,我们形成了一个组件化方案:根据划分出来的领域,将系统拆分成业务组件(核心子领域)和基础组件(通用和支撑子领域),各组件前后端分离。即会有以下前端应用:需求管理、项目管理、缺陷管理、用例管理等;后端应用相应提供不同的领域服务,领域服务采用分层架构(interface, application, domain, infrastructure)。权限管理、网关服务、消息服务等基础组件下沉,实现最大化组件复用。
按照领域拆分成业务组件,对系统来说,最终提供给终端用户还需是一个整体。但组件化意味着分离,那对于业务组件的前后端应用又面临怎样的协同挑战呢?**
业务组件前端应用的挑战
为了实现各业务组件能并行、独立演进,理想情况下,前端子应用需满足独立编译、打包、部署,并最终集成到一个平台上呈现统一的 UI 风格和一致的交互体验。
以上业界将此归结为微前端架构:实现一种架构风格,可以将众多独立交付的前端应用组合成一个大型整体,对客户表现为一件单一完整的产品。
最终,我们行云团队落地了一个 Jmodule 微前端组件框架。
前端应用通过微前端架构和前端组件框架注册到平台,系统层实现组件元数据管理、运行时热加载静态资源和组件间通信,对用户侧提供完全可插拔可配置的组件。具体原理和实现等将在后续另一篇章节中展开。
业务组件后端应用的挑战
行云在设计之初,后端服务采用的是微服务的架构(利用spring cloud gateway、Eureka 等实现服务注册、网关调用等),这和 DDD 相得益彰。在核心业务组件内,领域服务按照经典分层架构(interface, application, domain, infrastructure)来组织。
interface-用户界面层:Controller、restful接口调用,或者web端UI界面、移动端UI界面、第三方服务等;
apllication-应用层:对外为展现层提供各种应用功能,对内调用领域层(领域服务),应用层更像是实现某个特定场景的策略,或者流程上的编排等。其协同多个领域的服务,实现场景和业务的隔离;
domain-领域层:负责表达业务概念,业务行为,业务状态以及业务规则,领域模型处于这一层,是业务软件的核心。其提供的是一系列原子服务,在这一层提供丰富的OPEN API,是实现组件化至关重要的一步;
infrastructure-基础层:实现业务和技术的隔离层。一般包含:网络通讯、数据库持久化、异步消息服务、南向网关服务等。这一层在落地的时候,可以实现多种适配器adaptor 来兼容对内、对外、多云异构中间件环境。
除了界定组件内部分层的边界,还需要明确组件间的调用和依赖关系,进一步明确组件的职责。在 DDD 中相应地定义了限界上下文之间的映射关系:
合作关系:两个上下文紧密合作的关系;
客户方-供应方:上下文之间有组织的上下游依赖;
遵奉者:下游上下文只能盲目依赖上游上下文;
分离方式:两个完全没有任何联系的上下文;
共享内核:两个上下文依赖部分共享的模型;
防腐层:一个上下文通过一些适配和转换与另一个上下文交互;
开放主机服务:定义一种协议来让其他上下文来对本上下文进行访问;
事件发布订阅:通常用于定义开放主机的协议。
在组件的调用和依赖关系上,我们可以借鉴以上映射关系的理念来落地。
举个例子,我们已经沉淀了项目组件和任务组件。假设需要实现的业务场景:当删除某个项目时,需要检查项目中是否已有进行的任务,无则删之,有则提示不能删。根据 DDD 的映射关系,任务被项目依赖,任务处在上游的位置,理想情况下它不需要知道项目业务领域删除的具体逻辑规则,同时尽量减少项目业务规则的变化所带来的影响。
于是,我们在落地的时候,淘汰了下图左侧的实现方式——合作关系(项目和任务网状式相互调用,我中有你,你中有我),而是在下游项目领域加了一层适配层,来隔离业务的变化对上游的影响。同时,也确保任务这层逻辑干净、清晰,以供更多其他组件复用。
总结
总结一下,本文大体给出了 DDD 思潮在以下方面落地上的指导意义:
- 如何沉淀清晰可复用的业务组件?
- 如何隔离组件间的变化对其他组件的影响?
敬请期待
之后会在后续章节中分享 DDD 在插件落地的实践,解决如下问题:
如何快速响应五花八门的定制化需求,同时保持自身不腐化?
以上是 DDD 在京东 DevOps 项目协作领域的落地实战 的全部内容, 来源链接: utcz.com/a/44031.html