DDD-快速hold住业务的利器

分享于2021年05月10日
以前在小厂,由于业务场景与组织架构很小, 生产关系与链路都比较简单,业务的复杂度也相对较小。但在所谓的大厂(生活所迫),业务场景较多且要支持业务的快速扩展,组织架构庞大,上下游的生产关系与链路巨复杂,所以研发在很大程度上都是在解决业务的复杂性的问题,一个如同“大泥团”般的项目,会给业务项目/团队协作/技术架构都会带来极高的复杂性,同时也带来了各种各样的问题,这些都让一个业务新人面对要接手的业务或服务一下子懵逼的(恭喜你,已经成功入坑了!),所以要想让一个新人快速上手业务并有产出,需要一个强大的指导工具与方法论的指引。· 战略:运用资源实现目标的全局性长久性的纲领规划;· 战术:运用资源实现目标的局部性短期性规划和实现方法;· 战略和战术的区别是博弈的整体与局部、长与短、抽象与具体的区别.      一个资深老手肯定是同时具有好的战略规划能力与战术执行能力,说白了就是既要能看得远分得清,也要能脚踏实地认真干。对于这样的能力要求,DDD就成为了一门必修课程。1. DDD定义领域驱动设计(DDD)是一种软件设计方法, 提供了战略和战术的上的建模工具,战略部分用于理解、梳理业务,找到核心业务,更好地划分领域/系统/服务; 战术部分用于落地到代码上,用代码来清晰地表示业务,代码如何分层、如何设计都有一套成熟的指导方案.· 战略设计: 战略设计工具可帮助团队作出最有竞争力的软件设计选择和业务整合决策,并让团队在这些具有核心竞争力的软件模型中获取最大的利益。使用限界上下文的战略设计模式来分离领域模型,并在其中发展出一套领域模型的通用语言,如领域术语。· 战术设计: 战术实施工具可帮助团队设计出实用的软件,对业务的运作方式进行精准的建模。将若干实体和值对象以恰当的大小聚集在一起,也叫做的聚合模式。2.DDD作用      DDD可以帮助提高设计的有效性,并用来实现高价值的软件,也用来解决软件设计上的复杂度/扩展/开发成本等问题,在目前日益竞争的市场环境下持续交付出最有效的软件设计与实现。采用DDD带来的可能改变:· 提高项目的成功率;· 提升业务的竞争力,正确地对业务需求建模;· 能比较轻松地对软件架构进行迭代演进并扩展.3. 限界上下文限界上下文是语义和语境上的边界,意味着边界内的每个软件模型的组件都有特定的含义并处理特定的事务,限界上下文中的这些组件有特定但上下文语境和语义理据。其中可以理解为限界上下文是一个问题空间以及这个问题的解决方案空间。· 问题空间: 给定项目的约束条件下进行高级战略分析与设计的各个步骤的地方。可以使用简单的图表来展示讨论中高级的项目驱动因素,并记录关键目标与风险。· 解决方案空间: 真正实施解决方案的地方,这些解决方案在问题空间讨论中被识别为核心域,当限界上下文被当作组织的关键战略举措进行开发时,即被称为核心域。主要通过源代码和测试代码来实现限界上下文中的解决方案,也会在解决方案空间中编写代码,来支撑与其他限界上下文之间的集成。· 通用语言:  团队在限界上下文中发展了一种语言用于表达其边界内的软件模型,这一语言在限界上下文中开发软件模型的每个成员使用。该语言就是软件模型团队交流时使用的语言,而软件模型的源代码就是这种语言的书面表达方式,强烈建议定义领域术语,比如某个英文单词就特定代表某个模型并写上该单词的备注解释,让不同地域、不同团队成员都可以准确理解专用语言的含义,该语言可用在数据库、系统设计、设计文档等等方面。核心域的识别是一个持续的精炼过程,把一堆混在在一起的组件分离,以某种形式提炼出最重要的内容,这种形式也将核心域更具价值,可以让产品研发的资源更聚焦到最小化可行产品上,不断获取用户反馈,并在这最小化可行产品上持续快速迭代,从而获得一个稳定的核心产品。一个团队应该在一个限界上下文中工作,每个限界上下文应该有一个独立的源代码仓库,一个团队可能工作在多个限界上下文中,但是多个团队不应该在同一个限界上下文中共事,应该采用和分离通用语言同样的方式,把不同的限界上下文的源代码与数据库模式隔离开,并且,将同一个限界上下文中的验收测试、单元测试和主要源代码存放在一起。4.架构在限界上下文中不仅是一个领域模型,里面还包括了合理的架构用来组织领域模型,输出相关的领域能力。在限界上下文的很常见: 输入适配器,例如用户界面控制器、REST终端节点和消息监听器;编排用例和管理事务的应用服务;输出适配器,如持久化管理和消息发送器。目前经常可以看到的几种架构风格:· 事件驱动架构: 是一种以事件为媒介,实现调用接口者和接口实现者之间的解耦,事件驱动则是调用者和被调用者互相不知道对方,两者只和中间消息队列耦合。· 命令和查询职责分离(CQRS): 将读取和写入操作分成不同的模型,使用命令更新数据,并使用查询来读取数据,从不修改数据。命令应基于任务,而不以数据为中心。命令可以放置在队列上进行异步处理,而不是同步处理。· 响应式架构和Actor模型:  是事件驱动的一种实现方式,特别擅长处理多个客户端并发的向服务端请求服务的场景。如Reactor模式会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如: netty、nio等,还有一种如Akka的actor模型。· 面向服务的的架构: 面向服务的体系结构,是一个组件模型,它将应用程序的不同功能单元通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互,目前比较常见的是RPC架构,包括了服务注册中心,服务提供者、服务消费者三大组件。· 具象状态传输(REST):  描述了一个架构样式的互联系统。REST约束条件作为一个整体应用时,将生成一个简单、可扩展、有效、安全、可靠的架构。由于它简便、轻量级以及通过HTTP直接传输数据的特性。用于web服务和动态Web应用程序的多层架构可以实现可重用性、简单性、可扩展性和组件可响应性的清晰分离。5.子域        DDD项目中总会碰到很多限界上下文,这些限界上下文一定会有一个成为核心域,而其他的限界上下文之中会存在许多不同的子域名。子域是整个业务领域的一部分,子域代表的实施一个单一的、有逻辑的模型,大多数的业务领域过于庞大和复杂,难以作为整体来分析,因此我们只关心那些必须在单个项目中涉及的子域,子域可以用来有逻辑地拆分整个业务领域,通过DDD来创建子域,它将会被实现成一个清晰的限界上下文。项目中有三种类型的子域:· 核心域:  它是组织内一个唯一的、定义明确的领域模型,要对他进行较大的战略投资,并在一个明确的限界上下文中投入大量的资源去构建通用语言,它是组织中最重要的项目,是业务的核心竞争力所在。· 支撑子域: 这类场景下提倡“定制开发”,能最大限度支撑核心域的发展为第一要义,但对它的投入无论如何也达不到与和核心域相同的程度,也许会考虑使用外包的方式来定义限界上下文。· 通用子域: 通用子域可以使用外包开发、采购或者内部团队提供,但不会为其分配与和核心域一样的研发资源。业务领域中的某些系统边界可能是遗留系统,也许是由你的团队构建的,也许是通过购买软件许可的方式获得的,此时子域就是目前的关注点。在整个遗留系统中可能充满了多个错综复杂的模型,能准确地识别出每个子域以及子域的边界,用子域来思考和讨论此类系统有助于我们应对错综复杂模型的显示。当使用这类工具时,我们可以明确那些对业务有价值、对项目更重要的子域,而降低其他子域可以降低到次要位置。当使用DDD时,限界上下文应该与子域一一对应(1:1),如果存在一个限界上下文,那么目标就是归类出一个对应的子域模型。6. 上下文映射上下文映射的种类合作关系存在与两个或者多个团队之间,每个团队负责一个限界上下文。两个团队通过互相依赖的一套目标联合起来形成合作关系,经常会面对同步日程和相互关联的工作,还使用持续集成对保持集成工作协调一致的。保持长期的合作关系很有挑战性,因此许多进入合作关系的团队可能尽最大努力为这种关系设置一个期限,只能在彼此发挥优势时才维持合作关系。· 共享内核: 两个团队之间共享着一个小规模但却通用的模型,团队必须要共享的模型元素达成一致,有可能它们当中只有一个小团队会维护、构建以及测试共享模型的代码。· 客户-供应商:  供应商位于上游,客户位于下游,支配这种关系的是供应商,因为它必须提供客户需要的东西。客户需要与供应商共同制定规划来满足这种预期,但最终却由供应商来决定客户获得的是什么,什么时候获得。· 跟随者: 关系存在于上游团队和下游团队之间,上游团队没有任何动机满足下游团队的具体需求。由于各种原因,下游团队的也无法投入资源去翻译上游模型的通用语言来适应自己的特定需求,因此只能顺应上游的模型。防腐层就是下游团队好用的工具之一,是最具有防御性的上下文映射关系,下游团队在其通用模型和位于它上游的通用语言(模型)之间创建了一个翻译层次,隔离了下游模型和上游模型,并完成两者之间的翻译。· 开放主机服务: 定义了一套协议或接口,让限界上下文被当作一组服务访问,该协议是开放的,所有需要与限界上下文进行集成的客户端都可以轻松使用它,通过API提供出来服务并且都有的详细的说明文档。· 已发布语言:  是一种有着丰富文档的信息交换语言,可以被许多消费方的限界上下文简单地使用和翻译。需要读写信息的消费者们可以把共享语言翻译成自己的语言,最好的例子就是SDK。上下文映射集成方式· 基于SOAP的RPC: 目前主流的Dubbo/HSF的框架都是典型的例子,基于SOAP的RPC的设计思路就是让调用另一个系统的服务如同调用同一个本地过程或方法那样简单,SOAP的请求需通过网络传输才能抵达相关系统,成功执行后并返回结果。但这种集成方式缺乏健壮性,这就是为什么RPC框架中有很多容错设计的原因。· RESTful HTTP: 注意力集中在上下文之间交换的资源,有POST/GET/PUT/DELETE等操作,支持REST接口的服务端限界上下文应该提供开放主机服务和说明文档。同样这种方式也有可能因网络或服务提供商故障,网络延时等,错误或异常的全链路跟踪与日志也是很困难的。使用REST设计错误是直接把模型中的聚合暴露出来。· 消息机制: 在使用消息机制进行集成时,很多工作都是通过客户端限界上下文订阅由它自己或另一个限界上下文发布的领域事件来完成,使用消息机制可以消除阻塞调用,一个领域消息可被一个或多个订阅方消费,常用的消息中间件一般可用于消峰/解耦,但也可能造成领域事件过多,消费过慢等情况发生。7.子域中的聚合设计目前还需要对具体的子域进行具体的详细设计(战术层面)。子域中有实体、值对象、聚合、聚合根等建模工具,使用了聚合设计的思路或工具来进行设计,其中一个聚合中是由一个或者多个实体组成,一个实体成为聚合根,聚合的组成还包括了值对象。· 实体: 一个实体模型就是一个独立的事物,每个实体模型都拥有一个唯一的标识符,可以将它的个体和所有其他类型相同或者不同的实体区分开,并且每个实体都有一个生命周期,将实体与其他建模工具分开的主要因素是它的唯一性;· 值对象: 值对象是对一个不变的概念整体所建立的模型。在这个模型中,它没有唯一标识符,而是由类型封装的属性对比来决定相等性,一个值对象不是事物,而是被常常用来描述、量化或者测量一个实体;· 聚合根: 每个聚合的根实体控制着所有聚集在其他的元素,根实体的名称是聚合根概念上的名称,每个聚合都会形成保证事务一致性的边界。意味着在一个单独的聚合中,在控制被提交给数据库事务事,它的所有组成部分根据业务规则保持一致。一个聚合也要建立概念上的完整模型,事务一致性。· 事务:如何隔离对聚合的修改,以及如何保证业务不变性(即软件必须遵守的原则)在每一次业务操作中都保持一致。无论是通过原子级的数据库事务还是其他方法来控制需求,聚合的状态或者它通过事件溯源方法表现出的形式,必须安全和正确地进行转移和维护;聚合设计的一条普遍原则:只能在一次事务中修改一个聚合实例并提交,聚合的经验法则:· 在聚合边界内保护业务规则不变性; · 聚合要设计得小巧;· 只能通过标识符引用到其他聚合;· 使用最终一致性更新其他聚合;8.敏捷项目中管理DDDDDD不仅在软件设计与开发中,也可以作用在敏捷项目管理中,为了更好地提升业务与产品价值,在版本研发周期中的几个节点上加入DDD的考虑因素,可以在产品版本迭代周期内不断提升业务价值。SWOT分析法swot分析方法是根据项目/产品所拥有的资源,进一步分析项目/产品的优势与劣势以及企业外部环境的机会与威胁,进而选择适当的战略。SWOT分析是一种综合考虑内外部条件的各种因素,进行系统评价,从而选择最佳经营战略的方法。在每个版本的迭代周期中,在做需求分析/规划/评审的时间点,利用SWOT分析法工具可以让业务或产品的发展方向更加聚焦,将研发资源投放到真正该投放的方向。· 优势: 领先于对手的业务或项目特征;· 劣势: 落后于对手的业务或项目特征;· 机会: 可以发挥项目优势的要素;· 威胁: 存在于环境中并可能给业务或项目带来问题的因素.任务识别与工作量估算         任务识别和工作量估算是做敏捷迭代很重要的一个步骤,时间代表了人力,代表了金钱,估算的准确度直接决定了版本发布的时间偏差。这里在平常的敏捷迭代的评估是基于用户故事下的,粒度较粗,估算稍不准确,这里使用了领域事件/命令/聚合+简单复杂程度等领域驱动设计的概念作为估算度量指标,使其更加准确,更加符合实际。- END -往期回顾◆高可用架构设计之无状态服务◆Twitter 广告平台实时计费系统的架构增强之道◆使用契约测试提高分布式系统的质量