admin管理员组

文章数量:1650782

文章目录

  • 领域建模
    • 事件风暴
    • 四色建模法
  • DDD名称解析
    • 领域
    • 子域
    • 核心域
    • 通用域
    • 支撑域
    • 限界上下文
    • 战术设计
    • 实体
    • 值对象
    • 聚合和聚合根
    • 工厂
    • 资源库
    • 领域服务
    • 领域事件
  • DDD代码的分层
  • 名词解析
    • 实体
    • 值对象
    • 聚合根
    • 领域服务
    • 领域事件
  • 建模
    • 为什么要建模
    • 怎么建模才合理
    • 什么是“领域”模型
    • 模型的设计和实现
    • 领域模型的特点
    • 如何画领域模型
  • VO&DTO&DO&PO
  • 博客


领域建模

领域驱动设计的核心在于领域建模,架构师的水平高低在很大程度上也体现在领域建模水平上。

领域建模的主要目的是捕捉业务知识,形成统一语言,沉淀领域模型。好的领域建模就意味着对业务要有深刻的理解,能够洞察问题本质。

建模就是将一个大的需求或者系统,经过一系列方法拆分细化,变成一些小而专的小需求,小服务。

领域驱动的战略设计应该如何分析呢?我们不需要独自去摸索,总结前辈的一些经验即可:

  • 通用语言:它的作用是定义上下文含义,以便限界上下文定义领域边界

  • 事件风暴:由项目团队、领域专家等多人参与,采用头脑风暴的方式进行用户故事分析,找出并建立领域对象

  • 四色建模:按时间发展先后顺序,识别“追溯单据”作用的“时标”概念,直达业务核心数据;它强调可追溯性与执行效率

  • 限界纸笔建模:回到一百年前,在一个我们没有计算机的年代,我们要做业务设计会用什么方法呢?我们可以用纸和笔画表格并写实例,管理核心领域“恰好够用”的数据,增强数据完整性,避免过度设计

  • DCI建模:可能DCI我们听得比较少,其实DCI架构与MVC架构的提出者是同一个人。DCI建模通过角色扮演模型使得领域模型易于理解,通过小类大对象的手法避免上帝类的问题;同时它也能解决贫血模型和充血模型之争,使模块更加高内聚、低耦合;当然,DCI建模也可以与四色建模融合使用

事件风暴

事件风暴就是一个建模的过程,梳理出系统或需求中有哪些事件。

在事件风暴中,主要关注以下几个方面:

  • 事件:某个动作的结果。

  • 属性:事件的输入和输出。

  • 命令:描述某个动作。

  • 实体:执行命令的触发者。

简单理解,即谁(实体)使用什么(输入)做了什么(命令、动作),产生了什么(输出),并影响了什么(事件)。

首先邀请正确的人来一起参加:有问题的人和有答案的人。

第一步:梳理事件(橙色贴纸)

从一个个事件开始(事件是已发生且重要的事情,事件必须是既成事实,且业务关注的事情。),梳理清楚该事件发生前有哪些事件?事件一定会发生吗?事件发生后下一个事件是什么?

假设第一个事件是:专栏已订阅,那么“专栏已订阅”事件发生前前须有“订单已支付”事件。
订单一定可以创建成功吗?不是,贴上“订单创建失败”事件。

经过一系列讨论,得出“专栏已订阅”事件会发生以下事件:

当大家发现新事件的速度接近停滞的时候,就应进入梳理业务规则的阶段了。

第二步:业务规则(粉色贴纸)

业务规则或者业务逻辑,是业务中最重要的部分,主持人会提出以下问题:

事件是否一定成功?如果不是,那么成功的前提条件是什么?该事件是否会导致其他事件的发生?

例如“订单已创建”事件的业务逻辑:

  • 订单已创建的前提条件是专栏可订阅,同时用户未订阅过该专栏。
  • 订单创建后,会导致发起支付。

第三步:行动者(浅黄色贴纸),命令(蓝色贴纸)、阅读模型(绿色贴纸)和系统(紫色贴纸)

主持人通过问题引导:

  • 是什么触发了事件,是命令还是规则

  • 是谁执行了动作,是人还是系统

  • 做出动作前,用户需要获取到哪些信息

通过类似上面的问题,逐步引导大家找到Actor, Command, Read Model

第四步:热点问题(红色贴纸

业务痛点、瓶颈、模糊点可以用红色贴纸记录,这些问题需要业务带回去讨论确定清楚,不要在事件风暴中尝试解决所有的hotspot。

第五步:故事串讲

邀请一名现场参与成员,按事件发生的时间顺序串讲业务,过程中,听众注意到不一致的地方,提出问题;大家一起讨论,调整相关的事件、逻辑来达成一致。


第六步:产出架构
通过事件风暴,业务流程和处理逻辑应该已经很清楚了,接下来就由架构师产出对应的架构。可以依据事件风暴产出领域模型、用例图、状态图、活动图、序列图等关键架构交付物。

四色建模法

待学习。。。

DDD名称解析

在DDD中可以分为战略设计战术设计,各自包含的内容如下图所示:

战略设计指的是对整个领域进行分析和规划(宏观),确定领域中的概念、业务规则和领域边界等基础性问题。在战略设计中,需要对领域进行全面的了解和分析,探究业务的规则和本质,并且需要考虑到领域的未来发展趋势和可能的变化。领域、子域和限界上下文属于战略设计的范畴。

领域

领域(Domain)其实就是一个组织所要做的整个事情,以及这个事情下所包含的一切内容,指在特定的范围或边界内要解决的业务问题域,它的核心思想是将业务问题域逐级细为子域/核心域/通用域/支撑域,降低业务理解和系统实现的复杂度。

这是一个范围概念,而且是面向业务的(注意这里不是面向技术的,更不是面向数据库的持久化的),每个组织都有自己的人员、规则和流程,当你为该组织开发软件的时候,你面对的就是这个组织的领域。

例如,在“智学公司”的“智慧课堂”中,领域就是知识付费领域。

子域

子域是指在一个大的领域中,可以进一步划分出来的独立的业务子领域,它们有着自己的业务概念、规则和流程等。

为了区分重要性的不同,我们又会将子域划分成核心域、通用域以及支撑域。

例如,在“智慧课堂”中,子域可以有订阅域、金融域、专栏域等。

核心域

决定公司和产品核心竞争力的子域就是核心域,它是业务成功的主要因素。核心域直接对业务产生价值。

例如,在“智慧课堂”中,订阅域就是核心域。

通用域

没有太多个性化的诉求,同时被多个子域使用的、具有通用功能的子域就是通用域。通用域间接对业务产生价值。

例如,在“智慧课堂”中,权限域、登录域就是通用域。

支撑域

支撑其他领域业务,具有企业特性,但不具有通用性。支撑域间接对业务产生价值。

例如,在“智慧课堂”中,专栏域、评论域就是支撑域。

限界上下文

限界上下文就是业务边界的划分,这个边界可以是一个子域或者多个子域的集合。如何进行划分,一个行之有效的方法是一个界限上下文必须支持一个完整的业务流程,保证这个业务流程所涉及的领域都在一个限界上下文中。限界上下文是微服务拆分的依据,即每个限界上下文对应一个微服务。

例如,在“智慧课堂”中,可以有一个限界上下文叫“专栏订阅上下文”,它就包含了订单域和订阅域。

战术设计

战术设计则是在战略设计的基础上,对领域中的具体问题进行具体的解决方案设计。战术设计关注的是领域中的具体情境和场景,需要针对具体的问题进行具体的分析和设计,以满足业务需求。实体、值对象、聚合、工厂、资源库、领域服务和领域事件就属于战术设计的范畴。

实体

实体是拥有唯一标识和状态,且具有生命周期的业务对象。实体通常代表着现实世界中的某个概念,实体与领域模型密切相关,它是领域模型中多个属性、操作或者行为的载体。

例如:在“智慧课堂”中,专栏、课程文章、订阅都是实体。

实体的代码形态一般有四种形态:

  • 失血模型:模型仅仅包含数据的定义和getter/setter方法,业务逻辑和应用逻辑都放到服务层中。这种类在Java中叫POJO。
  • 贫血模型:贫血模型中包含了一些业务逻辑,但不包含依赖持久层的业务逻辑。这部分依赖于持久层的业务逻辑将会放到服务层中。
  • 充血模型:充血模型中包含了所有的业务逻辑,包括依赖于持久层的业务逻辑。
  • 胀血模型:胀血模型就是把和业务逻辑不想关的其他应用逻辑(如授权、事务等)都放到领域模型中。

值对象

通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。值对象没有唯一标识,没有生命周期,不可修改,当值对象发生改变时只能替换。

值对象的业务形态:大多数情况下实体具有很多属性,这些属性一般都是平铺,但有的属性进行归类和聚合后能够表达一个业务含义,就将这些属性封装到一起形成值对象,从而方便沟通而无需关注细节,因此可以说值对象就是用来描述实体的特征。当然实体的单一属性也是值对象。

值对象的代码形态:值对象有两种:单一属性的值对象,例如字符串、整型、枚举等;多个属性的值对象,这时候设计成class,包含多个属性,但是没有ID,值对象中可以嵌套值对象。

一本书对象中,书本的颜色,尺寸等描述就是值对象。

聚合和聚合根

聚合是一种更大范围的封装,把一组有相同生命周期、在业务上不可分隔的实体和值对象放在一起考虑,只有根实体可以对外暴露引用,这个根实体就是聚合根,聚合也是一种内聚性的表现。

领域、子域、限界上下文、聚合都是用来表示一个业务范围,那他们的关系是怎样的呢?
领域、子域、限界上下文属于战略设计,而聚合属于战术设计,聚合的范围是小于前三者的,范围大小图如下:

值得注意的是,一个微服务最小不要小于一个聚合,避免引入分布式事务的复杂度。

一个用户对象,一个地址对象,用户对象内包含地址对象,那么用户对象就是聚合根,关于用户及地址对象内的描述信息就是值对象。

工厂

工厂是一种重要的设计模式;

在DDD中,工厂负责将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。对于聚合来说,我们应该一次性地创建整个聚合,并且确保它的不变条件得到满足。

资源库

资源库(Repository)是一种模式,用于封装数据访问逻辑,提供对数据的持久化和查询。它旨在将数据访问细节与领域模型分离,使领域模型更加独立和可测试。资源库提供了一种统一的接口,使得领域模型可以与不同的数据存储方式(如关系数据库、文档数据库、内存数据库等)进行交互,同时也提供了一些查询操作,以便在领域层中进行数据查询。如果我们使用MyBatis的话,Mapper就是对资源库的一种实现。

例如持久化到数据库,可以实现一个MyBatis的持久化类,存储到磁盘,就可以实现一个刷盘类等等。

领域服务

有些领域中的动作看上去并不属于任何对象。它们代表了领域中的一个重要的行为,不能忽略它们或者简单地把它们合并到某个实体或者值对象中。当这样的行为从领域中被识别出来时,推荐的实践方式是将它声明成一个服务,这个服务就是领域服务。

例如,在“智慧课堂”中,订阅(Subscribe)行为是一个非常重要的领域概念,它涉及到订单创建、支付、增加订阅记录等和多个实体相关联的操作,将该行为放到任何一个实体中都不合适,在这种情况下,将“订阅”识别为领域服务是比较合适的。

领域事件

领域事件是发生在领域中且值得注意的事件。而领域事件通常意味着领域对象状态的改变。领域事件在系统中起到了传递消息、触发其他动作的作用,是解耦领域模型的重要手段之一。我们往往利用消息队列来传递领域事件。

例如,在“智慧课堂”中,当用户订阅了一个专栏后,会产生一个“专栏订阅成功”的领域事件,用户成长域会根据这个领域事件决定增加用户积分。


在DDD中,领域模型是核心。领域模型是一个抽象的概念,它代表着业务领域中的实体、值对象、聚合根、领域服务等。在建模领域模型时,需要从业务需求出发,将领域模型与业务模型相对应,并将它们映射到代码实现中。

领域驱动设计则是将二者有机地结合起来,以统一的领域通用语言(不一定是UML)进行系统建模,进行系统的分析和设计工作,即该领域专业的业务人员(领域专家) 和 软件开发人员通过领域模型进行需求沟通,彼此共享领域知识,确立符合真实业务需求的领域模型。
其本质上就是对面向对象分析过程的一个扩展和延伸。

建模及名词原文-知乎

DDD代码的分层

  • Interfaces(用户接口层):它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。

  • Application(应用层):它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。

  • Domain(领域层):它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。

  • Infrastructure(基础层):它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

箭头表示该层次可以调用的其他层次,领域层不能调用其他领域层,必须通过应用层来调用。

四层架构的作用

  • 用户交互层:web请求,rpc请求,mq消息等外部输入均被视为外部输入的请求,可能修改到内部的业务数据。
  • 业务应用层:与MVC中的service不同的不是,service中存储着大量业务逻辑。但在应用服务的实现中(以功能点为维度),它负责编排、转发、校验等。(在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的服务就会演化为传统的三层架构,业务逻辑会变得混乱。)
  • 领域层:或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
  • 基础设施层:主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。
分层英文描述
表现层User Interface用户界面层,或者表现层,负责向用户显示解释用户命令
应用层Application Layer定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。
领域层Domain Layer或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手
基础设施层Infrastructure Layer主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现;

战术设计下的分层:

类型英文描述
值对象value object无唯一标识的简单对象
实体entity充血的领域模型, 有唯一标识
聚合(聚合根)aggregate实体的聚合,拥有聚合根,可为某一个实体
领域服务service无法归类到某个具体领域模型的行为
领域事件event不常用
仓储repository持久化相关,与基础设施层关联
工厂factory负责复杂对象创建
模块module子模块引入,可以理解为子域划分

代码结构描述
后端Java代码工程为例:

├─company.microservice
├─company.microservice
│    │ 
│    ├─apis   API接口层/表现层 
│    │    └─controller       控制器,对外提供(Restful)接口
│    │ 
│    ├─application           应用层
│    │    ├─model            数据传输对象模型及其装配器(含校验)
│    │    │    ├─assembler   装配器,,实现模型转换eg. apiModel<=> domainModel
│    │    │    └─dto         模型定义(含校验规则)      
│    │    ├─service          应用服务,非核心服务,跨领域的协作、复杂分页查询等
│    │    ├─task             任务定义,协调领域模型
│    │    ├─listener         事件监听定义
│    │    └─***              others
│    │ 
│    ├─domain   领域层
│    │    ├─common           模块0-公共代码抽取,限于领域层有效  
│    │    ├─module-xxx       模块1-xxx,领域划分的模块,可理解为子域划分     
│    │    ├─module-user      模块2-用户子域(领域划分的模块,可理解为子域划分)
│    │    │    ├─action      行为定义
│    │    │    │    ├─UserDomainService.java        领域服务,用户领域服务
│    │    │    │    ├─UserPermissionChecker.java    其他行为,用户权限检查器
│    │    │    │    ├─WhenUserCreatedEventPublisher.java     领域事件,当用户创建完成时的事件 
│    │    │    ├─model       领域聚合内模型 
│    │    │    │    ├─UserEntity.java                领域实体,有唯一标识的充血模型,如本身的CRUD操作在此处
│    │    │    │    ├─UserDictVObj.java              领域值对象,用户字典kv定义       
│    │    │    |    ├─UserDPO.java                   领域负载对象    
│    │    │    ├─repostiory  领域仓储接口
│    │    │    │    ├─UserRepository.java
│    │    │    ├─reference   领域适配接口
│    │    │    │    ├─UserEmailSenderFacade.java
│    │    │    └─factory     领域工厂  
│    │ 
│    ├─infrastructure  基础设施层
│    │    ├─persistence      持久化机制
│    │    │    ├─converter   持久化模型转换器
│    │    │    ├─po          持久化对象定义 
│    │    │    └─repository.impl  仓储类,持久化接口&实现,可与ORM映射框架结合
│    │    ├─general          通用技术支持,向其他层输出通用服务
│    │    │    ├─config      配置类
│    │    │    ├─toolkit     工具类  
│    │    │    ├─extension   扩展定义  
│    │    │    └─common      基础公共模块等 
│    │    ├─reference        引用层,包装外部接口用,防止穿插到Domain层腐化领域模型等
│    │    │    ├─dto         传输模型定义
│    │    │    ├─converter   传输模型转换器       
│    │    │    └─facade.impl 适配器具体实现,此处的RPC、Http等调用
│    │ 
│    └─resources  
│        ├─statics  静态资源
│        ├─template 系统页面 
│        └─application.yml   全局配置文件

DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。

名词解析

层次概念

代码层次

实体

当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。
例:最简单的,公安系统的身份信息录入,对于人的模拟,即认为是实体,因为每个人是独一无二的,且其具有唯一标识(如公安系统分发的身份证号码)。

在实践上建议将属性的验证放到实体中。

值对象

当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。

例:比如颜色信息,我们只需要知道 {“name”:“黑色”,”css”:“#000000”} 这样的值信息就能够满足要求了,这避免了我们对标识追踪带来的系统复杂性。它具有不变性、相等性和可替换性。

在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性。在不同上下文集成时,会出现模型概念的公用,如商品模型会存在于电商的各个上下文中。在订单上下文中如果你只关注下单时商品信息快照,那么将商品对象视为值对象是很好的选择。

值对象很重要,在习惯了使用数据库的数据建模后,很容易将所有对象看作实体。使用值对象,可以更好地做系统优化、精简设计。

聚合根

Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。

聚合由根实体,值对象和实体组成。

领域服务

一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。

当我们采用了微服务架构风格,一切领域逻辑的对外暴露均需要通过领域服务来进行。如原本由聚合根暴露的业务逻辑也需要依托于领域服务。

领域事件

领域事件是对领域内发生的活动进行的建模。

建模

我们从以下三个问题了解一下什么是领域模型:为什么要建模;怎么建模才合理;“领域”模型具体指什么。

为什么要建模

客户在专卖店买了个手机,留下了自己的名字和电话,店员做了记录。客人来时,只要店员能在记录里查到客人名字和电话的订单,就说明客人曾经买过手机。

什么人需要查看订单呢?店员A 需要查看,店员B也需要查看。客人来咨询的时候,应该能随时调取。老板也需要查看,用来汇总销售情况。大家都要看,格式就必须统一,要不然有的只记了电话,有的只记了名字,有的什么都没记,就乱套了。

大家商量之后决定:订单必须包括客户名字、电话和购买的商品。那么就有“订单=名字+电话+商品信息"。这是店员和老板的心智模型(mental model)。

要用一个数字系统来支持订单的管理,必须形成对应的数据模型(data model),称之为数据建模(data modeling),中文简称建模。电脑采用数字化的精确储存,所以数据的格式必须提前明确,比如名字是2-4个中文字符,电话是11位数字等等。

建模本质上是一种抽象。抽象就是归类,其目的是减轻认知的负担,避免重复的思考和工作,提升人的计算能力。所以,“通用”是建模的第一步,接下来我们还需要“复用”建好的模型。

假设手机卖出之后,客户需要维修服务。客户来到店里,询问店员,店员查询确认了订单,然后把客人引到门店旁边的维修中心。维修中心的工程师拿到订单,发现手机已经过了保修期,所以他写了一个维修单,把客户的名字、电话、手机信息、维修费用写到上面。客户交了费,拿到修好的手机,走了。

这引出一个问题: 维修中心需要的客户信息,其实在店员那边有,没有必要自己再抄一遍,否则很容易出错,还会遇到信息同步的问题。那么,我们就需要再做一次建模,把客户的名字和电话从订单模型中拿出来,单独做一个客户模型。订单和维修单都复用这个客户模型。

这样,我们就得到了如下三个模型。

  • 订单=客户+商品信息
  • 维修单=客户+商品信息+维修信息
  • 客户=名字+电话

这里,维修单模型里面似乎包含了一个完整的订单(客户+商品信息),为什么不直接复用订单呢?也许是因为维修部门也负责别的地方购买的商品。另外,客户的名字和电话更新之后,是不是要直接修改已经完成的订单和维修单呢?值得商椎,已经完成的信息不应该有意料之外的变化。还有,如果客户是一个单独的模型,那么背后的团队会是怎么样呢?需要仔细考虑。

那么,为什么要建模?

  • 第一,要把心智模型提取出来,显性化,让不同的人对业务的理解达成一致;
  • 第二,要归类复用,避免重复的工作,让人可以关注更高层面的事务。

如前面所示,即便是建立简单的模型,我们也需要诸多考虑。把这些需要考虑的点体系化,就引出了下一个问题:怎么建模才合理?

怎么建模才合理

判断模型好坏的重要依据是它的使用效率(扩展度、灵活度、与组织的对应度),所以建模的合理性也围绕这个来展开。

为了形成高扩展度、高灵活度并且顺应康威定律的模型,Eric Evans 汇总并命名了DDD这种方法论。它的独特之处,是它认可了“人”这种生物在做抽象过程中的一些必然缺憾,并且提出了一些解决方案。

首先,它明确指出了“自然语言”这个工具的不精确性。其次,它明确指出了人在设计和实现上的矛盾性,即:人类的预测能力很差,所以我们拥抱变化;但同时,我们不能纯寄希望于误打误撞地演化出一个好结果,应该提前深思熟虑地做好设计,再辅之以小的改进。

语言的不精确性,可以用“普通话”(Ubiquitous Language)来解决,即大家在讨论任何东西之前先规范语言,统一词汇的定义。兼顾稳固和灵活的设计,则由良好的分层来做到。

DDD把模型分成四层:

  • Ul层,负责界面展示。
  • 应用层(Application Layer),负责业务流程。
  • 领域层(Domain Layer),负责领域逻辑。
  • 基建层(Infrastructure Layer),负责提供基建。

分类的依据是:越往上,预期变动越频察;越往下,预期变动越少。
对于程序员来说,UI和基建应该很容易分清,一个只管展示,一个只管提供持续储存、网络传输等等基础设施,都没有“业务”的参与。容易混淆的是应用层和领域层,在这两层中存在的,就是应用模型和领域模型。这就引出了下一个问题:到底什么是领域模型?

什么是“领域”模型

按DDD的定义,领域模型应该捕捉“业务规则”或者“领域逻辑"(business rules/domain logic),而应用模型则捕捉“应用逻辑”(application logic)。

模型属于哪一层,有个粗略的判断方式:

  • 如果是一个实体(entity)和针对实体的增删改查,就属于领域层;
  • 如果是一个场景,比如出现在UI菜单上的选项,就属于应用层。

比如:账单,用户,编辑商品,编辑库存,这些是领域层;“购买商品”,则是应用层。前者是针对实体的操作。每一个实体都只有增删改查这样的操作。与之相反的是,要完整实现“购买商品”这个场景,也许需要检查库存、创建订单、创建交易等多个操作。

这样看来,领域模型就像是数据库的表。不过,除了字段定义之外,领域模型还需要有领域逻辑。关系型数据库通常可以部分实现领域逻辑,比如使用外键、自增ID等等,但是更为复杂的领域逻辑则需要用代码来实现。比如,在创建订单的时候,需要扣除相应数量的库存;当订单失效,则需要恢复库存。

我们可以从两个方面理解领域逻辑。

  • 第一,领域逻辑就是显性的专业知识,是相对容易理解和学习的部分。买了东西要给钱,出了货要扣库存,飞机来了要腾跑道,炮弹来了要拦截,这些都是专业知识中符合逻辑,可以很容易地推导和学习。与之相对的是隐性的专业知识,或者Martin Fowler 所说的“业务非逻辑"(business illogic),指的是逻辑很难推论的软知识,与人相关的部分,与不可抗拒的、稀奇古怪的意外情况相关的部分。

  • 第二,领域逻辑是提纯、通用的规则。不管是买手机、买房、买汽车,都会有创建订单、创建交易这样的零售领域模型,它是高度提纯、通用的。如果我们需要去修改领域模型,说明我们已经进入了另一个领域。此外,规则的存在是为了维护某个事物,这个事物,就是领域模型的正确、完整性。对领域模型的正常操作,总会给我们一个“合规”的领域。

不管我们怎么去玩弄订单、交易、商品模型,增删改查,乱搞一通,最后出来的结果都应该符合规则。创建订单的时候,订单模型会尝试减库存,成功,则创建,失败,则不允许创建,如此云云。

最后出来的结果不会有逻辑问题,比如订单上的商品不存在,或者交易对应的订单不存在。
当然,领域模型只管“合规”,但不管“合理”。大型超市里,一位收银员决定把所有货品下架,这是否合理?这个不属于领域模型关心的范围,领域模型只知道,要把货物库存的位置从货架转移到仓库,目标仓库必须有能存放这个货物的空位,转移完毕时从货架的可用空间中减去货物的大小。至于谁做,为什么做,能不能做,这么做合不合理,(通常)不在领域层的关心范围,而是放到应用层去做。

在前面的例子中,如果一个客户在黑名单中,不允许购买,那么这个检查,通常是在应用层去检查。这样,我们就可以很容易地复用领域模型或者调整应用规则,而不至于把易变的应用规则混到稳定的业务规则里去。

模型的设计和实现

那么,DDD的模型属于业务描述还是代码、数据库定义?
作者想表达的意思很明确,二者都是。好的业务描述应该能非常好地对应到代码,代码也应该以最清晰地方式来呈现业务描述。当然,转换和适配肯定不可少,毕竟有先例摆在那。

设计和实现的最佳契合点,其实就在“界面”之中。“界面”这个名字本身已经体现了这层意思。所以,建好的模型,和暴露出来的界面、接口,应该有相当高(或者完全一致)的对应关系。这个界面,可能是一个类的公开成员函数,也可能是一个微服务上暴露出来的RESTful接口,或者是一个公开的普通WebAPl,都没有差,只要和设计符合就行。

最后,总结起来,就是:

  • DDD把业务分成UI、应用、领域、基建四层,其核心是高度提纯、通用、少变化的领域层,是谓“领域驱动";
  • 领域层中包含领域模型,捕捉领域逻辑,暴露出接口用于操作领域模型,这些接口提供的操作可以确保领域是自治的;
  • 领域模型既是业务描述,又是代码实现的结构设计,二者的结合点在于公开出来的界面、接口。

领域模型的特点

总结一下,领域模型具有如下的特点:

  • 领域模型是业务概念的可视化描述,是需求分析的产物
  • 领域模型用于指导程序设计,但领域模型与实现方式无关,领域建模时不应该考虑如何实现
  • 领域模型需要同项目所有成员(客户、项目经理、开发、测试…)达成共识

如何画领域模型

  1. 找出用例模型中的名词;
  2. 然后识别这些名词本身的相关信息;
  3. 以及名词之间的相互关联关系;
  4. 用UML画出领域模型。

第一步 找出用例模型中的名词
原有用例如下,用蓝色加黑标出名词(重复的就不标了):

  • 1)顾客携带商品到收银台;
  • 2)收银员扫描商品条形码;
  • 3)系统根据条形码获取并显示商品信息;
  • 4)收银员重复2~3步,直到所有商品扫描完毕;
  • 5)系统计算商品总额;
  • 。。。。。。。。。。。。。。。。。。。。
  • n)系统打出商品清单,完成交易。

这个用例中的名词有“顾客”、“商品”、“收银台”、“收银员”、“商品条形码”、“系统”、“商品信息”、“商品总额”、“商品清单”、“交易”。稍加整理:

  • “顾客”、“收银员”是系统的外部对象,不需要我们进行设计,但这些对象要和系统进行交互;
  • “商品”、“商品条形码”、“商品信息”、“商品总额”、“商品清单”、“交易”是领域对象,但“商品条形码”、“商品信息”可以算作“商品”的属性、“商品总额”可以算作“交易”的属性,最后从这个用例总结出来的领域对象有“商品”、“商品清单”、“交易”三个。

第二步 识别这些名词本身的相关信息
一个对象的属性可能分布在多个用例中,因此可以通过迭代不断的完善一个对象的属性,大家可以看到,我们在第一步中的样例就已经分析了一部分了:“商品条形码”、“商品信息”可以算作“商品”的属性。

对象除了属性外,还有一些约束或者限制,这些在用例中可能有,也可能没有,这就需要分析人员来发现了。比如说交易金额必须大于0.1元小于99999元这种约束,用例中不一定会体现,可能需要分析人员向客户咨询。

第三步: 识别对象间的关系
面向对象设计就是依靠对象间的互相协作来配合完成相应的功能,因此识别出对象和对象本身的属性外,还要识别对象间的关系,例如1对多、1对1、依赖等,详细的各种关系可以参考UML的标准定义。

我们以第一步识别的三个对象为例:“商品清单”包含多个“商品”、一次“交易”对应一个“商品清单”、一个“商品”只能属于一个“交易”等。

第四步: 画出领域模型UML

原文

VO&DTO&DO&PO

每一层都有自己特定的数据,可以做如下区分:

  • VO(View Object):视图对象,主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
  • DTO(Data Transfer Object):数据传输对象,主要用于远程调用等需要大量传输对象的地方。比如我们一张表有100个字段,那么对应的PO就有100个属性。但是我们界面上只要显示10个字段,客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。在这里,我泛指用于展示层与服务层之间的数据传输对象。
  • DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
  • PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。最形象的理解就是一个PO就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。

PO是数据库表结构的一一映射,DO是数据库结构在领域服务层的处理对象。多个DO可以组成一个PO,一个DO不一定包含PO所有值。

前端发送请求(VO) -> 系统接收请求 (VO->DTO) -> 系统各个模块处理请求(DTO->DO) -> 持久化到库 -> (DO->PO)

博客

微信公众号博客

爪哇缪斯

是下雨天啊

VO、DTO、DO、PO的概念

如何设计实体和值对象,进行领域建模

DDD领域驱动设计实战

代码建模示例

足球赛事建模示例

本文标签: 架构DDD