我们为什么要了解ddd?作为开发人员,我们肯定接过别人的项目。我想你一定有这样的体会:面对一个复杂的系统,模块之间相互关联,没有人能把每一个细节都描述清楚,没有文档,即使有文档也匹配不上系统。当一个新的需求需要修改一个功能时,往往需要很长时间来回顾这个功能所涉及的流程,更不用说修改带来的不可预知的影响了。于是RD加了一个开关,小心翼翼的切断线上的流量,一有问题就关掉开关。面对这样的场景,要么逃跑,要么重构。重构是克服进化设计中大杂烩问题的主要力量。通过在各个类和方法层面做一系列的小步重构,我们可以很容易的重构一个独立的类,放一些通用的逻辑,但是你会发现你很难赋予它业务意义,而且你只能给它一个技术维度。你在重构的时候是在为后代挖洞。作为架构师,如何降低软件开发中的系统复杂度是一个永恒的挑战,虽然可以通过一系列的设计模式或范式来降低一些常见的复杂度。但问题是,这些概念通过技术手段解决技术问题,却没有从根本上解决业务问题。如果你也有这种苦恼,那么ddd的思想或许能给你一些启发。DDD与传统的数据驱动DDD的区别在于Domain-DrivenDesign,中文称为领域驱动设计。它是一套用于复杂软件系统分析和设计的面向对象的建模方法学。什么是数据驱动在传统的数据驱动开发模型中,View、Service、Dao的三层分层模型,开发者自然会写程序化的代码。这种开发方式中的对象只是数据载体,没有行为。,是贫血对象模型。以数据为中心,以数据库ER图为驱动,分层架构可以看作是这种开发模式下数据处理和实现的过程。什么是领域驱动?之前的系统分析和设计是分开的,导致需求和成品有偏差。两人相对独立,造成沟通困难。DDD打破了这种差距,提出了领域模型的概念,将分析和设计编程统一起来,使软件更加灵活,能够快速跟进需求的变化。DDD的宏观概念其实并不难理解,但是和REST一样,DDD只是一种设计思想,缺乏一套完整的规范,这让DDD新手很难实施。由于DDD不是一套框架,而是一种架构思想,在代码层面缺乏足够的约束,导致DDD在实际应用中的门槛很高。甚至可以说大部分人对DDD的理解是有偏见的。例如(贫血领域模型)在实际应用中层出不穷,而一些依然火爆的ORM工具如Hibernate、EntityFramework实际上助长了贫血模型的泛滥。同样,传统的基于数据库技术和MVC的四层应用架构(UI、业务、数据访问、数据库)在一定程度上混淆了DDD的一些概念,导致大多数人在实际应用中只使用DDD。建模的想法,但是它的整个架构体系的想法是无法实现的。从单机时代来看,面向服务的架构还仅限于单机+LB使用MVC提供Rest接口对外调用。在万物皆可称为“服务”(XAAS)的时代,人们在踩着拆分服务的重重陷阱后(拆分太细导致服务爆炸,拆分不合理导致频分重构等),死锁的原因开始。DDD的思想让我们静下心来思考哪些东西可以通过服务来拆分,哪些逻辑需要聚合起来带来最小的维护成本,而不是单纯的追求开发效率。以DDD为指导,以微服务的事件为导向,是完美的架构。DDD与微服务之间的关系系统越来越复杂是必然趋势。原因可能来自自身业务的进化,也可能是技术创新。然而,一个人和一个团队对复杂性的理解是有限度的。就像服务器的性能极限一样,唯一的解决办法就是分而治之,把大问题拆解成小问题,最终突破这个极限。微服务在这方面给出了理论指导和最佳实践,如注册中心、断路器、限流等解决方案,但微服务对于“处理复杂业务场景”的问题并没有给出合理的解决方案。因为微服务的重点是治理,而不是分工。我们都知道,在构建系统时,我们应该考虑以下几个方面:功能维度质量维度(包括性能和可用性)工程维度微服务在第二维度做得很好,但是第一维度和第三维度做得不够。这给了DDD一个“利用的机会”。DDD给出的缺陷是微服务在功能划分上没有给出很好的指导。所以在面对复杂问题和构建系统时,它们是一种互补的关系。DDD和微服务如何合作仅仅知道DDD和微服务是不够的,我们还需要知道它们是如何合作的。一个系统(或一个公司)的业务范围和在这个范围内进行的活动称为域。域是现实生活中面临的问题域,与软件系统无关。域可以分为子域,比如电子商户域可以分为商品子域、订单子域、发票子域、库存子域等,在不同的子域中,不同的概念有不同的含义,所以一定要有一个明确的界限在建模的时候,这个边界在DDD中叫做boundedcontext,是系统架构内部的一个边界,《整洁之道》本书中提到:系统架构是由系统内部的架构边界决定的,边界之间的依赖关系定义的,没有任何关系处理系统中组件之间的调用方法。所谓服务本身只是一种比函数调用成本略高的切分应用行为的形式,与系统架构无关。因此,复杂系统划分的第一个要素是划分系统内部的架构边界,即划分上下文,并明确它们之间的关系。这对应前面提到的第一个维度(功能维度),这就是DDD派上用场的地方。其次,我们考虑如何基于非功能维度进行划分,这也是微服务发挥优势的地方。如果我们把服务分成ABC三种上下文:我们可以在一个进程中部署单个应用,或者通过远程调用完成功能调用。这是目前微服务的方式,更多的时候我们使用两种方式混合,比如A和B在一个部署单元,C单独部署。这是因为C很重要,或者并发量比较大,或者需求变化频繁。此时C具有独立部署的几个优势:C资源独立部署:资源倾斜更合理,自主扩缩容。弹性服务:重试、熔断、降级等都实现了故障隔离。独立技术栈:C可以用其他语言编写,更适合个性化的团队技术栈。团队独立性:不同的团队可以负责。架构是可以进化的,所以拆分需要考虑架构所处的阶段。前期更多关注业务逻辑边界,后期需要考虑更多方面,比如数据量、复杂度等,但即使有这个政策,也往往众说纷纭。一下子就正确地界定了界限,其实这里根本就没有明确的对错。即使边界定义不合适,聚合根也可以确保我们可以演化出更合适的上下文。在上下文中,实体和值对象用于对领域概念建模。一组实体和值对象属于聚合根。根据DDD的约束要求:首先,聚合根保证了内部实体规则的正确性和数据的一致性;第二,外部对象只能通过id引用聚合根,不能引用聚合根内部的实体;第三,聚合根数据库事务不能在它们之间共享,它们之间的数据一致性需要通过最终一致性来保证。有了聚合根,并且基于这些约束,将聚合根升级为上下文甚至将来根据需要将其拆分为微服务都相对容易。其实DDD的核心诉求就是将业务架构映射到系统架构上。当业务架构因业务变化而调整时,系统架构也随之变化。微服务在业务层面追求复用,设计的系统架构与业务保持一致;在技??术架构上,系统模块完全解耦,可以自由选择合适的技术架构,去中心化管理技术和数据。大家可以参考下图更好的理解双方的协作关系:每个概念找一个Spring模式开发的映射概念,简单易懂,但只是用来理解,不要太依赖。另外,可能还需要将理解与下面的代码反复结合,才能融入到实际工作中。域映射概念:分段服务。字段是一个范围。范围的点是边界。领域的核心思想是将问题逐级细分,降低业务和系统的复杂度,这也是DDD讨论的核心。子域映射概念:子服务。域可以进一步划分为子域,或子域。这是一种处理高度复杂领域的设计思想,试图将技术实现的复杂性分离。这种分裂存在于许多架构中,例如C4。核心域映射概念:核心服务。在域划分的过程中,子域会不断划分,子域会根据重要性分为三类:核心域、通用域和支撑域。通用域映射概念:中间件服务或第三方服务。支持域映射概念:企业公共服务。统一语言映射概念:统一概念。定义上下文的含义。它的价值在于可以解决沟通障碍,不管你是RD、PM、QA等什么角色,让每个团队都使用统一的语言(概念)进行沟通,代码的可读性更高。通用语言包含归属和用例场景,可以直接体现在代码中。事件风暴(会议)可以统一语言,甚至可以映射中英文、业务和代码模型等,可以用一个表格来记录。有界上下文映射概念:服务责任划分的边界。定义上下文的边界。领域模型存在于边界之内。对于同一个概念,不同的语境会有不同的理解。例如,商品在销售阶段称为商品,在运输阶段称为商品。理论上,限界上下文的边界就是微服务的边界,所以理解限界上下文在设计中非常重要。聚合映射概念:包。聚合的概念类似于你理解的包的概念。每个包包含一类实体或行为,有助于分散系统的复杂性。它也是一种高级抽象,简化了对领域模型的理解。拆分后的实体不能全部放在一个服务中,这就涉及到拆分,所以有拆分就有聚合。聚合是为了保证领域内对象之间的一致性。在定义聚合时,应遵守不变约束规则:聚合边界内必须包含哪些信息,否则不能称为有效聚合;聚合中某些对象的状态必须满足一定的业务规则:一个聚合只有一个聚合根,可以独立存在,聚合中的其他实体或值对象依赖于聚合根。外部只能访问聚合根,聚合根维护聚合内部一致性。聚合根映射概念:包。一个上下文可能包含多个聚合,每个聚合都有一个称为聚合根的根实体,一个聚合只有一个聚合根。实体映射概念:域或实体。《领域驱动设计模式、原理与实践》一书中提到实体是一个具有同一性和连续性的领域概念。可见实体其实是一个特殊的域。这里需要注意两点:唯一标签(identity)和连续性。两者缺一不可。可以想象,文章可以是实体,作者也可以,因为它们有id作为唯一标识符。值对象映射概念:域或实体。为了更好地展示领域模型之间的关系,制定的对象本质上是一个实体,但与实体相比,它没有状态和身份。它的存在是为了表示一个值,通常使用value对象来以表示的形式来传达数量。比如钱,让它有id显然是不合理的,你不可能通过id去查询一个钱。值对象的定义取决于具体场景的区分。甚至可以把Article中的Author看成一个值对象,但是必须明确Author独立存在时是一个实体,或者如果要用Author做复杂的业务逻辑,那么Author也会升级为一个聚合根。四大领域模型失血模型,贫血模型,充血模型,血肿模型四模型示例失血模型DomainObject是一个纯数据类,只有属性getter/setter方法,所有业务逻辑完全由业务对象完成。publicclassArticleimplementsSerializable{privateIntegerid;privateStringtitle;privateIntegerclassId;privateIntegerauthorId;privateStringauthorName;privateStringcontent;privateDatepubDate;//getter/setter/toString}publicinterfaceArticleDao{publicArticlegetArticleById(Integerid);publicArticlefindAll();publicvoidupdateArticle(Articlearticle);}贫血模型简单来说,就是DomainObject包含不依赖持久化的领域逻辑,那些依赖持久化的领域逻辑分离到Service层。publicclassArticleimplementsSerializable{privateIntegerid;privateStringtitle;privateIntegerclassId;privateIntegerauthorId;privateStringauthorName;privateStringcontent;privateDatepubDate;//getter/setter/toString//判断是否为热门分类(假设等于57或102的文章为热门分类文章)publicbooleanisHoticle(Art){returnStream.of(57,102).anyMatch(classId->classId.equals(article.getClassId()));}//更新分类,但不持久化。你不能依赖Dao来操作实体.changeClass(article,ac);articleDao.update}ate(注意这个模式在Domain层不依赖DAO,持久化的工作也需要在DAO或者Service中完成。这种方式的优缺点优点:各层单向依赖,结构清晰。缺点:将DomainObject中与持久化DomainLogic密切相关的部分分离到Service层,不够。OOService层太厚。拥塞模型类似于第二种模型。区别在于业务逻辑的划分。在Domain中,Service是一个很薄的层,封装了少量业务逻辑,不与DAO打交道:Service(事务封装)—>DomainObject<—>DAOpublicclassArticleimplementsSerializable{@ResourceprivatestaticArticleDaoarticleDao;privateIntegerid;privateStringtitle;privateIntegerclassId;privateIntegerauthorId;privateStringauthorNameprivateStringcontent;privateDatepubDate;//getter/setter/toString//使用articleDao进行持久化交互publicList
