当前位置:网站首页>DDD驱动领域设计学习笔记

DDD驱动领域设计学习笔记

2022-06-09 22:49:00 找了一圈尾巴

DDD 驱动领域设计

定义

领域驱动设计(简称 DDD) 概念来源于2004 年著名建模学家 eric evans 发表的他最具影响力的书籍 《领域驱动设计–软件核心复杂性应对之道》一书。

领域驱动设计一般分为两个阶段:

  1. 以一种领域专家,设计人员都能理解的"通用语言"作为相互交流的工具,在不断的交流过程中发现和挖出一些主要的领域概念,然后将这些概念设计成一个领域模型。
  2. 由领域模型驱动,用代码来表现该领域模型。领域需求的最初细节,在功能层面通过领域专家的讨论得出。

特点

领域模型具有以下特点:

  1. 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分
  2. 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,地址等,还能反映领域中的一些过程概念,如资金转账,考勤等。
  3. 领域模型确保了我们的软件业务逻辑都在一个模型中,都在一个地方;这样对提高软件的业务及方面都有很好的帮助
  4. 领域模型能够帮助我们相对平滑的将领域知识转化为软件构造
  5. 领域模型贯穿软件分析,设计及开发的整个过程;领域专家,设计人员,通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,让做出来的软件真正满足需求。
  6. 要建立正确的领域模型并不简单,需要领域专家,设计,积极沟通,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型
  7. 为了让领域模型看得见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的方式,代码或文字描述也能表达领域模型。
  8. 领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合的领域模型能够更快的响应需求变化

基本概念

  1. 实体(entity)

一个由它的标识定义的对象叫做实体,通常实体具备唯一id,能够被持久化,具有业务逻辑,对应现实世界业务对象;
实体一般和主要的业务/领域对象有一个直接的关系。一个实体的基本概念是一个持续抽象的生命,可以变化不同的形态和情形,但总有相同的标识

  1. 值对象 (value object)
    值对象的定义是: 描述事物的对象;一个没有概念上标识描述一个领域方面的对象。这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性标识但同时表达了领域中某类含义的概念

实体与值对象的区别:

  • 实体具有唯一标识,而值对象没有唯一标识,这是实体和值对象最大的不同
  • 实体就是领域中需要唯一标识的领域概念。有两个实体,如果唯一标识不一样,那么即便实体的其它所有属性都一样,也认为是两个不同的实体;一个实体的概念是一个持续抽象的生命,可以变化不同的形态和情形,但总有相同的标识
  • 不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上
  • 值对象在判断是否是同一个对象时是通过它们的所有属性是否相同,如果相同则认为是一个值对象;而实体是否是同一个实体,只是看实体的唯一标识是否相同,而不管实体的属性是否相同
  • 值对象另外一个特点是不可变,所有属性都是只读的。因为属性是只读的,所以可以被安全的共享。当共享值对象时,一般有复制和共享两种做法,具体采用哪个还得根据实际情况而定。
  • 值对象的设计应尽量简单,不要让它引用很多其他的对象,因为本质上讲值对象只是代表一个值。
  1. 聚合及聚合根 (aggregate,aggregate root)
  • 聚合是用来定义领域对象所有权和边界的领域模式。聚合的作用是帮助简化模型对象的关系。聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。

  • 划分aggregation 是对领域模型的进一步深化,aggregation 能阐释领域模型内部对象之间的深层关联,对aggregation 的划分会直接映射到程序结构上

  • 一个聚合是一组相关的被视为整体的对象,每个聚合都有一个根对象(聚合根实体) ,从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体

  • 只有聚合根才能使用仓储直接查询,其他的只能通过相关的聚合访问。如果根实体被删除,聚合内部的其它对象也被删除

  • 通常,我们把聚合组织放到一个文件夹或一个包中,每个聚集对应一个包,并且每个聚集成员包括实体,值对象,domain事件,仓储接口和其它工厂对象。

聚合的特点

  1. 每一个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内部的某个实体

  2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝不能绕过聚合根直接访问聚合对象,也就是说聚合根是外部可以保持对它的引用的唯一元素

  3. 聚合内根除外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是属于这个聚合的

  4. 聚合根负责与外部其它对象打交道并维护自己内部的

  5. 基于聚合以上的概念,我们可以推论出查询时的单元也是以聚合为一个单元。也就是我们不能直接查询聚合内部的某个非根的对象

  6. 聚合内部的对象可以保持对其他聚合根的引用

  7. 删除一个聚合根时必须同时删除该聚合根内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念

  8. 工厂(factories)

工厂用来封装创建一个复杂对象尤其是聚合时所需的知识,作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。当创建实体和值对象复杂时建议使用工厂模式。

  1. 仓储(repositories)

仓储是用来管理实体的集合
仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念划分边界的;聚合作为一个整体的概念,要么一起被取出来,要么一起被删除。外部访问不会单独对某个聚合内的对象进行单独操作。因此,我们只对聚合设计仓储。
仓储还有一个重要的特征是分为仓储定义部分和仓储实现部分,我们在领域模型中定义仓储的接口,而在基础设置层实现具体的仓储。也符合按照接口分离模式在领域层定义仓储库接口的原则。注意: repositories 本身是一种领域组件,但repositories 的实现却不是领域层中的。

  1. 服务(services)

服务这个词在服务模式中是这么定义的:服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系

  • 所有的service 只负责协调并委派业务逻辑给领域对象做处理,其本身并非真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了。
  • service可与多种组件进行交互,这些组件包括:其他的service,领域对象和repository 或 dao
  • 通常应用中一般包括:domain 模型服务和应用层服务
  • 当一个领域操作被视为一个重要的领域概念,一般就应该作为领域服务。服务应该是无状态的。
  • 设计实现领域服务来协调业务逻辑,只在领域服务中实现领域逻辑的调用
  • 通常服务对象名称中都应包含一个动词。service 接口的传入传出参数也应该是DTO,可能包含的工作有领域对象和dto 的相互转换以及事务。

服务的三个特征:

  1. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象
  2. 被执行的操作涉及到领域中其他的对象
  3. 操作是无状态的

领域服务与domain 的区别

一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要强调的是领域服务是无状态的,它存在的意义是协调领域对象共同完成某个操作,所有的状态还是都保存在相应的领域对象中。

  1. domain 事件
  • 企业级事件大致可以分为三类:系统事件,应用事件和领域事件。领域事件的出发点在领域模型(domain model ) 中。它的作用是将领域对象从对repository或service 的依赖中解脱出来。避免让领域对象对这些基础设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并作出处理。

  • 通过使用领域事件,我们可以实现领域模型对象状态的异步更新,外部系统接口的委托调用,以及通过事件派发机制实现系统集成。另外,领域事件本身具有自描述性。它不仅能够表述系统发生了什么事情,而且还能够描述发生事件的动机。

  • domain 事件也用表进行存储

  1. DTO 数据传输对象

dto 在设计之初的主要考量是以粗粒度的减少并简化调用接口

驱动领域架构

驱动领域架构

  • user Interface
    该层包含与其它系统/客户进行交互的接口与通信设施;在多数应用中,该层可能包括web Service ,rmi,rest 等在内的一种或多种通信接口。该层主要由facade,dto 和 assembler 三类组件构成,三类组件是典型的j2ee 模式

    • dto :以粗粒度的减少并简化调用接口。在领域驱动设计中,采用dto模型,可以起到隐藏领域细节,帮助实现独立封闭的领域模型的作用
    • assembler: dto 与领域对象之间的相互转换多由 assembler承担
    • facade: 为远程客户端提供粗粒度的调用接口。facade 本身不处理任何的业务逻辑,它的工作是将一个用户请求委派到一个或多个service 进行处理。同时借助 assembler 将领域对象转换为dto 对象进行传输
  • application

application 层主要组件就是service 。

  • transaction script (事务脚本) 的核心是面向过程,通过过程的调用来组织业务逻辑,业务逻辑在服务(service) 层进行处理。transaction script 的特点是简单容易理解,面向过程设计。缺点在于,对于复杂的业务逻辑难以保持良好的设计,事务之间的冗余代码不断增多。应用架构容易出现"胖服务层”和“贫血的领域模型”

  • 领域模型具备自己的属性行为和状态,领域对象元素之间通过聚合配合解决实际业务应用。可复用,可维护,易拓展,可以采用合适的设计模型进行详细设计。

  • transactionscript 风格业务逻辑主要在service 中实现,而在领域驱动设计的架构里,service 只负责协调并委派业务逻辑给领域对象进行处理。

  • domain

domain 层是整个系统的核心层,该层维护一个使用技术实现的领域模型,几乎全部的业务逻辑都会在该层实现。domain层包含 entity(实体) ,valueObject(值对象) ,domain event(领域事件) 和 repository (仓储) 等多种重要的领域组件

  • infrastructure

基础设施层为interfaces,application,domain 三层提供支持。所有与具体平台,框架相关的实现会在infrastructure 中提供。避免三层参杂进这些实现,从而污染领域模型。infrastructure中常见的一类设施是对象持久化的具体实现。

  • n 层
    层( layers) 被视为构成应用或服务的水平堆叠的一组逻辑上的组件。它们帮助我们区分不同任务的组件,提供一个最大化复用的设计。简言之,是关于在架构方面应用关注点分离的原则。

设计领域模型的一般步骤

  1. 根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联,关联可以暂时没有方向但必须有(1:1,1:n,m:n) 这些关系;可以用文字精确的没有歧义的描述出每个领域概念的含义以及包含的主要信息
  2. 分析主要的软件功能,识别出主要的应用层的类;这样有助于及早发现那些是应用层的职责,哪些是领域层的职责
  3. 进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;
  4. 分析关联,通过对业务的更深入分析以及各种原则及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联
  5. 找出聚合边界以及聚合根,这是一件很有难度的事情;因为你在分析的过程中往往会碰到很多摸棱两可难以清晰判断的选择问题;
  6. 为聚合根配备仓储,一般情况下是为一个聚合根分配一个仓储,此时只需要设计好仓储的接口即可
  7. 走查场景,确定我们设计的领域模型能够有效的解决
  8. 考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数
  9. 停下来重构模型,寻找模型中觉得有可疑或者是蹩脚的地方

领域模型的特征

领域建模是一个不断重构,持续完善模型的过程,大家会在讨论中将变化反馈到模型中,从而使模型不断细化朝着正确方向走。

  • 应该是一个以pojo 为基础的架构
  • 应该支持使用ddd 概念的业务领域模型的设计和实现
  • 应该支持像依赖注入(di) 和面向切面编程 (aop) 这些概念的开箱即用
  • 与框架整合
  • 与其他java/java ee 框架进行良好的集成,比如jpa,hibernate,toplink 等。

一些反模式

  • 贫血的领域对象
  • 重复的dao
  • 肥服务层;服务类在这里最终会包含所有的业务逻辑
  • 依恋情节: 函数对某个类的兴趣高过对自己所处类的兴趣。

六层架构

  1. user Interface 用户接口层
  2. Scheduler 层 调度层 ;负责多进程管理及调度,多线程管理及调度,多协程调度和维护业务实例的状态模型。当调度层收到用户接口层的请求后,委托Transaction 层对本次操作相关的事务进行处理
  3. Transaction 事务层: 对应一个业务流程,在大多数场景下都有选择结构。万一事务失败,则立即执行回滚。当事务层收到调度层的请求后,委托Context 层的Action 进行处理,常常还伴随使用Context层的Specification 进行 Action 的选择
  4. Context 是环境层,以Action 为单位,处理一条同步消息或异步消息,将Domain 层的领域对象cast 成合适的role,让role 交互起来完成业务逻辑。环境层通常也包含Specification 的实现,即通过Domain层的知识去完成一个条件判断。
  5. Domian 领域层,不仅包括领域对象之间关系的建模,还包括对象的角色role的显式建模
  6. infrastructure 层是基础设施层 为其他层提供通用的技术能力。

六边形架构

端口与适配器

通过依赖倒置原则(Dependency inversion Principle,DIP) 它通过改变不同层之间的依赖关系达到改进系统的目的。

有新的客户对接时,添加一个新的适配器将客户的输入转化成能被系统API 所理解的参数就行。对于每种特定的输出,都有一个新建的适配器负责完成相应的转化功能。

在这里插入图片描述

原网站

版权声明
本文为[找了一圈尾巴]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_41645817/article/details/124931442