2020-01-09 09:47:35 wei_wei10 阅读数 7

大家好,欢迎来到小蒋的技术圈。上次跟大家聊了IaaS、PaaS、SaaS都是什么,估计大家还有印象。Iaas、Paas、SaaS实际上也是软件行业进化的不同阶段的产物。在这个进化的过程中,有一种设计思想在2004年的时候就被提出来了,也被作者写成书出版。到现在为止经过了18年了,但依然炙手可热。而且逐渐变成了行业内微服务设计的标准实践。他就是“领域驱动设计”,今天小蒋就准备和大家一起聊聊看。下面我们开始吧。

 

领域驱动设计并不是一个什么新鲜玩意,它早在2004年的时候就被作者埃里克·埃文斯发表了。书名为Domain-Driven Design – Tacking Complexity in the Heart of Software。中文译名为领域驱动设计,清华出版社在2006年3月发布译本。大家也亲切的称呼它为DDD,也就是它的英文单词首字母。

 

我们来看一下Wikipedia中对领域驱动设计的定义:

领域驱动设计(英语:Domain-driven design,缩写DDD)是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法。紧接着Wikipedia中写到,领域驱动设计的前提是:

  • 把项目的主要重点放在核心领域(core domain)和域逻辑
  • 把复杂的设计放在有界域(bounded context)的模型上
  • 发起一个创造性的合作之间的技术和领域专家以迭代地完善的概念模式,解决特定领域的问题

 

这就是Wiki中对DDD的定义,想详细了解的伙伴,可以买这本书看看。

 

既然我们知道了DDD是一种软件开发方法,或者说是软件设计方法。那小蒋插一句说下我对设计的理解,欢迎大家拍砖。设计是把双刃剑,没有最好的,也没有更好的,而是条条大路通罗马。不设计和过度设计都是有问题的,恰到好处的设计才是我们要追求的。不过要做到恰到好处非常困难。

 

所以埃里克·埃文斯就推出了他的著作DDD。有一段时间DDD非常火爆,好多软件公司都在搞。不过我要先说明DDD只是一种方法,谈不上压倒性的优势,更不是完美无缺。切忌不可盲目崇拜,每种设计方法都有它特有的局限性。Microsoft就曾经建议仅将它用于复杂领域中。虽然领域驱动设计提供了许多技术优势,如可维护性。但领域驱动设计的系统可能会花费较高的成本。对于小型的系统,使用领域驱动设计可能会得不偿失。

 

基础

在“领域驱动设计”书中作者阐述了一些高层级的概念和实践,比如通用语言,这意味着领域模型应该形成领域专家为描述系统需求而提供的共同语言。我先给大家说一下,大家先有个概念就好。后面我再给家大家具体的分享一些真实的项目。

 

Entity-实体

一个不由自身属性定义而是由标识线和它身份定义的对象。

 

Value Object-值对象

只包含元素属性的不可变对象。

 

Service-领域服务

强调与其他对象的关系,只定义了可以为客户做什么,不应该替代Entity和Value Object的所有行为。

 

Module-模块

一种表达机制,划分代码和概念。

 

Factory-工厂

对于那些需要创建特定域对象的方法应该委派给工厂对象,这样可以更容易的替换实现。

 

Repository-资源库

对于检索特定域对象的方法应该委派给Repository对象,因为这样可以很容易地互换替代存储的实现

 

Aggregate-聚合

由Root Entity 绑定在一起的对象的集合,也成为聚合根。聚合根通过禁止外部对象保持其成员的引用来保证在聚合内进行的更改的 一致性。

 

Domain Event-领域事件

一个域对象定义了一个事件。域事件是域专家所关心的事件。

 

 

 

问题

 

说了一大堆虚的概念,到底DDD是个什么玩意,究竟人们用DDD解决什么具体问题?能不能更直观一点?

 

我给大家找了一个“美团点评业务系统”的开发案例,他们在DDD实践过程中的一个真实案例。那我们来看一下他们究竟要用DDD解决什么样的真实问题:

 

过度耦合

据美团工程师文彬和子维介绍,业务初期,我们的功能大都非常简单,普通的CRUD就能满足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得越来越复杂,我们的系统也越来越冗余。模块彼此关联,谁都很难说清楚模块具体功能意图是啥。修改一个功能时,往往光回溯该功能需要的修改点就需要很长时间,更别提修改带来的不可预知的负影响面。

 

 

 

订单服务接口中提供了查询、创建订单相关的接口。也提供了订单评价、支付、保险的接口。同时我们的表也是一个订单大表,包含了非常多字段。在我们维护代码时,牵一发而动全身,很可能只是想改一下评价相关的功能,却影响到了创建订单的核心路径。虽然我们可以通过测试保证功能完备性,但当我们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各种问题。

 

后来我们总结,上述问题,归根到底在于系统架构不清晰,划分出来的模块内聚度低、高耦合。

 

我们尝试过一种解决方案,就是按照演进式设计的理论,让系统的设计随着系统实现的增长而增长。我们不需要提前做设计,就让系统伴随业务成长而演进,这其实是一种敏捷实践。后来发现效果很差,当然也可能是我们内部的问题。我们承认敏捷实践中的重构、测试驱动设计及持续集成可以对付各种混乱问题。重构,保持行为不变的代码改善清除了不协调的局部设计。测试驱动设计,确保对系统的更改不会导致系统丢失或破坏现有的功能。持续集成,则为团队提供了同一个代码库。

 

在这三种实践中,重构是克服演进式设计中大杂烩问题的主力,通过在单独的类及方法级别上做一系列小步重构来完成。我们可以很容易重构出一个独立的类来放某些通用的逻辑,但是你会发现你很难给它一个业务上的含义,只能给予一个技术维度的含义。这会带来什么问题呢?新同学并不总是知道对通用逻辑的改动或获取来之该类。显然,制定项目规范并不是好的idea。我们又闻到了代码即将腐败的味道。

 

事实上,你可能意识到问题之所在。在解决现实问题时,我们会将问题映射到脑海中的概念模型,在模型中解决问题,再将解决方案转为实际的代码。上述问题在于我们解决了设计到代码之间的重构,但提炼出来的设计模型,并不具有实际的业务含义,这就导致在开发新需求时,其他同学并不能很自然地将业务问题映射到该设计模型。设计似乎变成了重构者的自娱自乐,代码继续腐败,重构重构……无休止的循环。

 

我们要解决的实际问题就是代码的设计模型与业务模型不匹配这个问题,导致的重构工作无休止的循环。后来我们选择了DDD。

 

为什么选择DDD

解决复杂的大规模软件的武器可以被粗略地归为三类:分治、抽象和知识。

 

分治,把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题需要足够小,以便一个人单枪匹马就能解决他们;其次,必须考虑如何将分割的各个部分装配为整体。分割得越合理越容易理解,在装配成整体时,所需跟踪的细节也就越少。即更容易设计各个部分的协作方式。评判什么是分治得好,即高内聚低耦合。

 

抽象,使用抽象能够精简问题空间,而且问题越小越容易理解。举个例子,从北京到上海出差,可以先理解为使用交通工具前往,但不需要一开始就想清楚到底是高铁还是飞机,以及乘坐他们需要注意什么。

 

DDD提供了这样的知识手段,让我们知道如何抽象出限界上下文以及如何去分治。DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。

 

这就是“美团点评业务系统”他们用DDD来解决的真实问题。

 

总结

所以说,DDD实际上就是一整套的设计方法论,和复杂场景下如何开展软件开发的一种工具。为的就是帮助我们建立一整套的逻辑思维方式,指导我们解决复杂场景下的的软件建设。

 

小蒋想跟大家分享的是我们是否关注设计本身,不管是什么流派的设计,有设计就是好的。经历了这么多年的开发,领悟到设计真的很重要啊,不设计的代码今天不死也是拖到明天去死。不管我们在团队里待多久,不能给未来的兄弟挖坑啊!

 

好的,以上就是今天的分享。下次我们来探讨一下DDD中提到的领域模型具体是个什么东西。好,我们下次见。

 

 

音频地址:https://download.csdn.net/download/wei_wei10/12091662

(审核通过,就能下载了)

 

2018-08-06 00:35:10 qq_16681169 阅读数 6090

一. 什么是DDD

2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD。Evans DDD是一套综合软件系统分析和设计的面向对象建模方法。
领域建模时思考问题的角度:
我们设计领域模型时不能以用户为中心作为出发点去思考问题,不能老是想着用户会对系统做什么。而应该从一个客观的角度,根据用户需求挖掘出领域内的相关事物,思考这些事物的本质关联及其变化规律作为出发点去思考问题。
领域模型是排除了人之外的客观世界模型,但是领域模型包含人所扮演的参与者角色,但是一般情况下不要让参与者角色在领域模型中占据主要位置,如果以人所扮演的参与者角色在领域模型中占据主要位置,那么各个系统的领域模型将变得没有差别,因为软件系统就是一个人机交互的系统,都是以人为主的活动记录或跟踪;比如以Eric Evans(DDD之父)在他的书中的例子中:
如果是以人为中心的话,就变成了:托运人托运货物,收货人收货物,付款人付款,等等。
如果是领域驱动设计的思想,在经过一些用户需求讨论之后,在用户需求相对明朗之后,Eric这样描述领域模型:一个Cargo(货物)涉及多个Customer(客户,如托运人、收货人、付款人),每个Customer承担不同的角色。Cargo的运送目标已指定,即Cargo有一个运送目标。由一系列满足Specification(规格)的Carrier Movement(运输动作)来完成运输目标。

二. 为什么选择DDD

1.软件系统复杂性应对
解决复杂和大规模软件的武器可以被粗略地归为三类:抽象、分治和知识。
分治 把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题需要足够小,以便一个人单枪匹马就能够解决他们;其次,必须考虑如何将分割后的各个部分装配为整体。分割得越合理越易于理解,在装配成整体时,所需跟踪的细节也就越少。即更容易设计各部分的协作方式。评判什么是分治得好,即高内聚低耦合。
抽象 使用抽象能够精简问题空间,而且问题越小越容易理解。举个例子,从北京到上海出差,可以先理解为使用交通工具前往,但不需要一开始就想清楚到底是高铁还是飞机,以及乘坐他们需要注意什么。
知识 顾名思义,DDD可以认为是知识的一种。
DDD提供了这样的知识手段,让我们知道如何抽象出限界上下文以及如何去分治。

2.与微服务架构相得益彰
微服务架构众所周知,此处不做赘述。我们创建微服务时,需要创建一个高内聚、低耦合的微服务。而DDD中的限界上下文则完美匹配微服务要求,可以将该限界上下文理解为一个微服务进程。
上述是从更直观的角度来描述两者的相似处。
在系统复杂之后,我们都需要用分治来拆解问题。一般有两种方式,技术维度和业务维度。技术维度是类似MVC这样,业务维度则是指按业务领域来划分系统。
微服务架构更强调从业务维度去做分治来应对系统复杂度,而DDD也是同样的着重业务视角。
DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致,在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。

三. 如何实践DDD

设计领域模型的一般步骤如下:
1.根据需求划分出初步的领域和限界上下文,以及上下文之间的关系。
2.进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象。
3.对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根。
4.为聚合根设计仓储,并思考实体或值对象的创建方式。
5.在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

四. 战略建模

战略和战术设计是站在DDD的角度进行划分。战略设计侧重于高层次、宏观上去划分和集成限界上下文,而战术设计则关注更具体使用建模工具来细化上下文。

领域
现实世界中,领域包含了问题域和解系统。一般认为软件是对现实世界的部分模拟。在DDD中,解系统可以映射为一个个限界上下文,限界上下文就是软件对于问题域的一个特定的、有限的解决方案。

限界上下文
一个由显示边界限定的特定职责。领域模型便存在于这个边界之内。在边界内,每一个模型概念,包括它的属性和操作,都具有特殊的含义。
一个给定的业务领域会包含多个限界上下文,想与一个限界上下文沟通,则需要通过显示边界进行通信。系统通过确定的限界上下文来进行解耦,而每一个上下文内部紧密组织,职责明确,具有较高的内聚性。
一个很形象的隐喻:细胞质所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。

上下文映射图
在进行上下文划分之后,我们还需要进一步梳理上下文之间的关系。
康威定律告诉我们,系统结构应尽量的与组织结构保持一致。这里,我们认为团队结构(无论是内部组织还是团队间组织)就是组织结构,限界上下文就是系统的业务结构。因此,团队结构应该和限界上下文保持一致。
梳理清楚上下文之间的关系,从团队内部的关系来看,有如下好处:
a. 任务更好拆分,一个开发人员可以全身心的投入到相关的一个单独的上下文中。
b. 沟通更加顺畅,一个上下文可以明确自己对其他上下文的依赖关系,从而使得团队内开发直接更好的对接。
从团队间的关系来看,明确的上下文关系能够带来如下帮助:
每个团队在它的上下文中能够更加明确自己领域内的概念,因为上下文是领域的解系统;
对于限界上下文之间发生交互,团队与上下文的一致性,能够保证我们明确对接的团队和依赖的上下游。

限界上下文之间的映射关系
合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。
开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。
发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。
大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
另谋他路(SeparateWay):两个完全没有任何联系的上下文。
通过上下文映射关系,我们明确的限制了限界上下文的耦合性,无论是上下文内部交互(合作关系)还是与外部上下文交互(防腐层),耦合度都限定在数据耦合(Data Coupling)的层级。

五. 战术建模——细化上下文

梳理清楚上下文之间的关系后,我们需要从战术层面上剖析上下文内部的组织关系。首先看下DDD中的一些定义。

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

值对象
当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。
例:比如颜色信息,我们只需要知道{“name”:”黑色”,”css”:”#000000”}这样的值信息就能够满足要求了,这避免了我们对标识追踪带来的系统复杂性。
值对象很重要,在习惯了使用数据库的数据建模后,很容易将所有对象看作实体。使用值对象,可以更好地做系统优化、精简设计。
它具有不变性、相等性和可替换性。
在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性。在不同上下文集成时,会出现模型概念的公用,如商品模型会存在于电商的各个上下文中。在订单上下文中如果你只关注下单时商品信息快照,那么将商品对象视为值对象是很好的选择。

谨慎使用值对象: 在实践中,我们发现虽然一些领域对象符合值对象的概念,但是随着业务的变动,很多原有的定义会发生变更,值对象可能需要在业务意义具有唯一标识,而对这类值对象的重构往往需要较高成本。因此在特定的情况下,我们也要根据实际情况来权衡领域对象的选型。

聚合根
Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。聚合是一个非常重要的概念,核心领域往往都需要用聚合来表达。其次,聚合在技术上有非常高的价值,可以指导详细设计。聚合由根实体,值对象和实体组成。
聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。
聚合有以下特点:
1. 每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体;
2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持 对它的引用的唯一元素;
3. 聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是从属于这个聚合的;
4. 聚合根负责与外部其他对象打交道并维护自己内部的业务规则;
5. 基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,也就是说我们不能直接查询聚合内部的某个非根的对象;
6. 聚合内部的对象可以保持对其他聚合根的引用;
7. 删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念;
8. 如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,那么我们可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。

如何创建好的聚合?
1. 边界内的内容具有一致性:在一个事务中只修改一个聚合实例。如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
2. 设计小聚合:大部分的聚合都可以只包含根实体,而无需包含其他实体。即使一定要包含,可以考虑将其创建为值对象。
3. 通过唯一标识来引用其他聚合或实体:当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象。如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象。
4. 如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。
聚合内部多个组成对象的关系可以用来指导数据库创建,但不可避免存在一定的抗阻。如聚合中存在List<值对象>,那么在数据库中建立1:N的关联需要将值对象单独建表,此时是有ID的,建议不要将该ID暴露到资源库外部,对外隐蔽。
下图为项目中的聚合根架构设计:
这里写图片描述

六. DDD的实现

领域服务
一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。
当我们采用了微服务架构风格,一切领域逻辑的对外暴露均需要通过领域服务来进行。如原本由聚合根暴露的业务逻辑也需要依托于领域服务。
我们将领域行为封装到领域对象中,将资源管理行为封装到资源库中,将外部上下文的交互行为封装到防腐层中。我们再回过头来看领域服务时,能够发现领域服务本身所承载的职责也就更加清晰了,即就是通过串联领域对象、资源库和防腐层等一系列领域内的对象的行为,对其他上下文提供交互的接口。
当然,你也可以把服务理解为一个对象,但这和一般意义上的对象有些区别。因为一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要强调的是领域服务是无状态的,它存在的意义就是协调领域对象共完成某个操作,所有的状态还是都保存在相应的领域对象中。我觉得模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。这也符合了现实中出现的各种现象,有动有静,有独立有协作。

领域对象
与以往的仅有getter、setter的业务对象不同,领域对象具有了行为,对象更加丰满。同时,比起将这些逻辑写在服务内(例如**Service),领域功能的内聚性更强,职责更加明确。

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

资源库
领域对象需要资源存储,存储的手段可以是多样化的,常见的无非是数据库,分布式缓存,本地缓存等。资源库(Repository)的作用,就是对领域的存储和访问进行统一管理的对象。
资源库对外的整体访问由Repository提供,它聚合了各个资源库的数据信息,同时也承担了资源存储的逻辑(例如缓存更新机制等)。比起以往将资源管理放在服务中的做法,由资源库对资源进行管理,职责更加明确,代码的可读性和可维护性也更强。
仓储里面存放的对象一定是聚合,原因是之前提到的领域模型中是以聚合的概念去划分边界的;聚合是我们更新对象的一个边界,事实上我们把整个聚合看成是一个整体概念,要么一起被取出来,要么一起被删除。我们永远不会单独对某个聚合内的子对象进行单独查询或做更新操作。因此,我们只对聚合设计仓储。
仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,在领域模型中我们定义仓储的接口,而在基础设施层实现具体的仓储。这样做的原因是:由于仓储背后的实现都是在和数据库打交道,但是我们又不希望客户(如应用层)把重点放在如何从数据库获取数据的问题上。所以我们需要提供一个简单明了的接口,供客户使用,确保客户能以最简单的方式获取领域对象,从而可以让它专心的不会被什么数据访问代码打扰的情况下协调领域对象完成业务逻辑。这种通过接口来隔离封装变化的做法其实很常见。
尽管仓储可以像集合一样在内存中管理对象,但是仓储一般不负责事务处理。一般事务处理会交给一个叫“工作单元(Unit Of Work)”的东西。

工厂(Factory)
DDD中的工厂也是一种体现封装思想的模式。DDD中引入工厂模式的原因是:有时创建一个领域对象是一件比较复杂的事情,不仅仅是简单的new操作。正如对象封装了内部实现一样(我们无需知道对象的内部实现就可以使用对象的行为),工厂则是用来封装创建一个复杂对象尤其是聚合时所需的知识,工厂的作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。领域模型中其他元素都不适合做这个事情,所以需要引入这个新的模式,工厂。
工厂在创建一个复杂的领域对象时,通常会知道该满足什么业务规则(它知道先怎样实例化一个对象,然后在对这个对象做哪些初始化操作,这些知识就是创建对象的细节),如果传递进来的参数符合创建对象的业务规则,则可以顺利创建相应的对象;但是如果由于参数无效等原因不能创建出期望的对象时,应该抛出一个异常,以确保不会创建出一个错误的对象。当然我们也并不总是需要通过工厂来创建对象,事实上大部分情况下领域对象的创建都不会太复杂,所以我们只需要简单的使用构造函数创建对象就可以了。隐藏创建对象的好处是显而易见的,这样可以不会让领域层的业务逻辑泄露到应用层,同时也减轻了应用层的负担,它只需要简单的调用领域工厂创建出期望的对象即可。

防腐层
亦称适配层。在一个上下文中,有时需要对外部上下文进行访问,通常会引入防腐层的概念来对外部上下文的访问进行一次转义。
有以下几种情况会考虑引入防腐层:
1. 需要将外部上下文中的模型翻译成本上下文理解的模型。
2. 不同上下文之间的团队协作关系,如果是供奉者关系,建议引入防腐层,避免外部上下文变化对本上下文的侵蚀。
3. 该访问本上下文使用广泛,为了避免改动影响范围过大。
如果内部多个上下文对外部上下文需要访问,那么可以考虑将其放到通用上下文中。

数据流转
首先领域的开放服务通过信息传输对象(DTO)来完成与外界的数据交互;在领域内部,我们通过领域对象(DO)作为领域内部的数据和行为载体;在资源库内部,我们沿袭了原有的数据库持久化对象(PO)进行数据库资源的交互。同时,DTO与DO的转换发生在领域服务内,DO与PO的转换发生在资源库内。
与以往的业务服务相比,当前的编码规范可能多造成了一次数据转换,但每种数据对象职责明确,数据流转更加清晰。
如同为项目中用到的数据流转换图:
这里写图片描述

上下文集成
通常集成上下文的手段有多种,常见的手段包括开放领域服务接口、开放HTTP服务以及消息发布-订阅机制。同时,如果在一个上下文对另一个上下文进行集成时,若需要一定的隔离和适配,可以引入防腐层的概念。

分离领域
典型的企业应用架构由下面四个概念上的层组成:
这里写图片描述
1. 用户界面(表现层) 负责给用户展示信息,并解释用户命令。
2. 应用层 该层协调应用程序的活动。不包括任何业务逻辑,不保存业务对象的状态,但能保存应用程序任务过程的状态。
3. 领域层 这一层包括业务领域的信息。业务对象的状态在这里保存。业务对象的持久化和它们的状态可能会委托给基础设施层。
4. 基础设施层 对其它层来说,这一层是一个支持性的库。它提供层之间的信息传递,实现业务对象的持久化,包含对用户界面层的支持性库等。

让我们更详细地看一下应用层和领域层:
应用层
• 负责应用中UI屏幕之间的导航,以及与其它系统应用层之间的交互。
• 还能对用户输入的数据进行基本(非业务相关)的验证,然后再把数据传到应用的其它层(更底层)。
• 不包含任何业务、领域相关的逻辑、或数据访问逻辑。
• 没有任何反映商业用例的状态,但却能处理用户会话或任务进展的状态。
• 应用服务虽然没有领域逻辑,但涉及到了对多个领域服务的编排。当业务规模庞大到一定程度,编排本身就富含了业务逻辑(除此之外,应用服务在稳定性、性能上所做的措施也希望统一起来,而非散落各处),那么此时应用服务对于外部来说是一个领域服务,整体看起来则是一个独立的限界上下文。此时应用服务对内还属于应用服务,对外已是领域服务的概念,需要将其暴露为微服务。
领域层
• 负责业务领域的概念,业务用例和业务规则的相关信息。领域对象封装了业务实体的状态和行为。
• 如果用例跨越多个用户请求(比如贷款登记过程包含多个步骤:用户输入贷款详细信息,系统基于贷款特性返回产品和利率,用户选择特定的产品/利率组合,最后系统会用这个利率锁定贷款),还可以管理业务用例的状态(会话)。
• 包含服务对象,这些服务对象只包含一个定义好的、不属于任何领域对象的可操作行为。服务封装了业务领域的状态,而业务领域并不适用于领域对象本身。
• 是商业应用的核心,应该与应用的其它层隔离开来。而且,它不应该依赖于其它层使用的应用框架(JSP/JSF、Struts、EJB、Hibernate、XMLBeans等)。

在分层架构中其他层如何与领域层交互
从经典的领域驱动设计分层架构中可以看出,领域层的上层是应用层,下层是基础设施层。那么领域层是如何与其它层交互的呢?对于会影响领域层中领域对象状态的应用层功能,一般应用层会先启动一个工作单元,然后:
1. 对于修改领域对象的情况,通过仓储获取领域对象,调用领域对象的相关业务方法以完成业务逻辑处理;
2. 对于新增领域对象的情况,通过构造函数或工厂创建出领域对象,如果需要还可以继续对该新创建的领域对象做一些操作,然后把该新创建的领域对象添加到仓储中;
3. 对于删除领域对象的情况,可以先把领域对象从仓储中取出来,然后将其从仓储中删除,也可以直接传递一个要删除的领域对象的唯一标识给仓储通知其移除该唯一标识对应领域对象;
如果一个业务逻辑涉及到多个领域对象,则调用领域层中的相关领域服务完成操作。注意,以上所说的所有领域对象都是只聚合根,另外在应用层需要获取仓储接口以及领域服务接口时,都可以通过IOC容器获取。最后通知工作单元提交事务从而将所有相关的领域对象的状态以事务的方式持久化到数据库。

下图为领域设计的架构:
这里写图片描述

工作单元
工作单元的几种实现方法:
1. 基于快照的实现,即领域对象被取出来后,会先保存一个备份的对象,然后当在做持久化操作时,将最新的对象的状态和备份的对象的状态进行比较,如果不相同,则认为有做过修改,然后进行持久化;这种设计的好处是对象不用告诉工作单元自己的状态修改了,而缺点也是显而易见的,那就是性能可能会低,备份对象以及比较对象的状态是否有修改的过程在当对象本身很复杂的时候,往往是一个比较耗时的步骤,而且要真正实现对象的深拷贝以及判断属性是否修改还是比较困难的。
2. 不基于快照,而是仓储的相关更新或新增或删除接口被调用时,仓储通知工作单元某个对象被新增了或更新了或删除了。这样工作单元在做数据持久化时也同样可以知道需要持久化哪些对象了;这种方法理论上不需要ORM框架的支持,对领域模型也没有任何倾入性,同时也很好的支持了工作单元的模式。对于不想用高级ORM框架的朋友来说,这种方法挺好。
3. 不基于快照,也不用仓储告诉工作单元数据更改了。而是采用AOP的思想,采用透明代理的方式进行一个拦截。在Hibernate中,我们的属性通常要被声明为virtual的,一个原因就是Hibernate会生成一个透明代理,用于拦截对象的属性被修改时,自动通知工作单元对象的状态被更新了。这样工作单元也同样知道需要持久化哪些对象了。这种方法对领域模型的倾入性不大,并且能很好的支持工作单元模式,如果用Hibernate作为ORM,这种方法用的比较多。
4. 对于不会影响领域层中领域对象状态的查询功能:
可以直接通过仓储查询出所需要的数据。但一般领域层中的仓储提供的查询功能也许不能满足界面显示的需要,则可能需要多次调用不同的仓储才能获取所需要显示的数据;其实针对这种查询的情况,可以直接通过CQRS的架构来实现。即对于查询,我们可以在应用层不调用领域层的任何东西,而是直接通过某个其他的用另外的技术架构实现的查询引擎来完成查询,比如直接通过构造参数化SQL的方式从数据库一个表或多个表中查询出任何想要显示的数据。这样不仅性能高,也可以减轻领域层的负担。领域模型不太适合为应用层提供各种查询服务,因为往往界面上要显示的数据是很多对象的组合信息,是一种非对象概念的信息,就像报表。

2015-11-11 17:37:41 u010870518 阅读数 17061

背景

使用DDD开发大概也有五个月的时间了,由于当时公司导师的推荐,第一次接触DDD领域驱动到现在彻底迷恋这种开发的模式,为其思想的奥妙所折服,一直以来,总想花一点时间来总结一下,正直光棍节(天猫狂欢购物节)当天,“静下心来”(PS:没有人民币)总结一下。

说起DDD不得不说一篇文章:http://www.cnblogs.com/netfocus/archive/2011/10/10/2204949.html

第一次接触,就是这篇文章,当时看起来晦涩难懂,后来慢慢的读起来,每一次都有精神的提升,网上也有很多关于DDD的介绍,这里将不会在介绍概念什么的,下边主要是自己工作过程中的一些总结。

如何快速入门

第一次接触DDD的时候,概念高深莫测,奥秘深不可见,大有不知所云的趋势,后来导师的引导,让直接从项目中直接入手,遂逐步揭开其一层层雾纱,如图1:

这里写图片描述

DDD一般的分层结构和调用顺序如上所述,infrastructure是基础设施层,domain是领域层,application是应用层,facade和facade-impl是门面层(前者是门面接口层,后者是门面实现层),webapp是用户接口层(采用web形式)。

下边是一种项目的分层图,采用的Maven管理代码。图2:

这里写图片描述

分层的介绍

1、web:首先包含网站前端,如果使用SpringMVC的话,还需要其Controler,

这里写图片描述

该Controler层主要是用于接收HTTP请求和返回客户端,一般不进行逻辑上的判断,其代表了用户可以进行的操作,一般不涉及领域驱动的思想,示例代码:

@ResponseBody
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public String create(AirlineWhiteListDTO airlineWhiteListDTO) {
        MDC.put(ConstString.TRACE_ID, LogUtil.getTraceId(ConstAirlineWhiteList.CREATE));
        Response response = airlineWhiteListFacade.create(airlineWhiteListDTO);
        return super.handleResponse(response);
    }

第一行使用MDC用于记录(打log)http请求的顺序和调用的方法,第二行调用“门面层”facede,获得Response返回对象,第三行用于判断是返回界面还是返回data等操作,即时SpringMVC的ModalAndView;

2、facade和facade-impl一个是门面层接口一个是门面层接口的实现类,示例代码:

@Autowired
IAirlineWhiteListApplication airlineWhiteListApplication;

public Response create(AirlineWhiteListDTO airlineWhiteListDTO) {
        Response response = new Response();
        try {
            AirlineWhiteList airlineWhiteList = AirlineWhiteListAssembler.toEntity(airlineWhiteListDTO);
            airlineWhiteListApplication.create(airlineWhiteList);
        } catch (Exception e) {
            response = new Response(e);
        }
        return response;
    }

可以看出这是在web的controller中访问的方法,主要进行数据的组装(实体对象和数据传输对象的转换),以及返回对象Response的转换。

之所以成为门面层,我们暂可认为是我们用户可以看到的整个系统的东西,例如上述中的Response就是返回给用户看的东西。

3、Application层,上述的facade调用到了application中的方法,

@Inject
private AirlineWhiteList airlineWhiteList;

public boolean create(AirlineWhiteList airlineWhiteList) {
        return this.airlineWhiteList.add(airlineWhiteList);
    }

这一层主要是操作实体对象的,是于数据更近一层的操作,主要定义了用户所拥有的方法和属性,操作实体对象层,将实体对象所有的方法展示出来供用户使用;

4、domain层,数据实体层,相比MVC中的modal简单的只是数据库的映射,这种“毫无灵魂”的对象,领域模型中不但有一个实体对象的属性还有其方法,(我们可以在实际使用的时候使用继承DTO的方式),在这一层中定义了实体对象操作数据库的方法;

这里我们的实体对象不仅是拥有属性还具有方法的,就像一个人一样,我们不但拥有做人的基本特征(两手、两脚等),我们还有属于自己的技能(方法),这样的话才是一个拥有灵魂的东西;

5、infra这一层包含了访问数据库的方法、数据仓储(sql、nosql、api)和一些工具类,service等;

由于是domain层调用该层的,实体对象的操作固然包含CRUD,既是我们需要进行对数据库的操作,当我们只有一个数据源的时候,很简单,但是后期项目中数据源可能会增加,可能会添加缓存等,这样的话使用原来的MVC模式的话,我们可能需要修改很多,如下图:

这里写图片描述

但是使用DDD的话,由于我们的Domain调用的是infra 数据仓库Repository接口,Repository中定义了访问数据源的方法,这样的话,当我门新增数据源的时候,我们的Domain层以上都无需修改,只需进行infra层的修改即可。
这里写图片描述

上图中有2个数据源,一个是sql一个是nosql,在方框中的Repository即调用的是nosql和sql中的方法,这样的话Domain直接调用Repository即可。

领域驱动的示例:

如果想快速的领回DDD的奥妙,这里有一个案例,一个很不错的开源系统,使用的正是DDD思想,可以把代码下载下来仔细研究,其中的思想是很不错、很不错的,地址如下:http://www.openkoala.org/

总结:

类比MVC“哑铃式”的分层结构中,Model和View代码少,Controller代码臃肿的布局格式,DDD拥有更多的分层,各层之间各司其职,协调工作,一步步调用,井然有序,对于后期的维护,只要是熟悉DDD的开发人员都可以很好的维护。

2018-07-01 12:36:37 weixin_42378461 阅读数 1243

http:// 

详细内容讲解:https://study.163.com/course/introduction/1005643030.htm?share=1&shareId=1142344671

内容:一步一步搭建一个实用的基于领域驱动设计(DDD)开发模式的一个项目开发框架。通过结合实际的代码应用让大家对领域驱动开发有一个更好的理解。

源码:https://github.com/lidingbin/MicBeach.Framework


2012-01-31 16:25:47 haha_mingg 阅读数 2471
1.介绍DDD概念
    Eric Evans的“Domain-Driven Design领域驱动设计”简称 DDD,它是一套综合软件系统分析和设计的面向对象建模方法,或者可称为MDD模型驱动方法的一种,区别于MDA模型驱动架构。它是一种分析设计建模方法,它倡导统一语言,提出了实体和值对象以及聚合根等概念,借助DDD我们能够在结构理清需求中领域模型。 

  过去系统分析和系统设计都是分离的,正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

  DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

  DDD是解决复杂大型软件的一套行之有效方式,在国外已经成为主流。DDD认为很多原因造成软件的复杂性,我们不可能避免这些复杂性, 能做的是对复杂的问题进行控制。而一个好的领域模型是控制复杂问题的关键。领域模型的价值在于提供一种通用的语言,使得领域专家和软件技术人员联系在一起,沟通无歧义。
2.DDD的技术关键点,如下所示:

  2.1面向对象建模与数据库建模两种分析设计方法的比较
  数据库驱动设计与对象建模是决定软件不同命运的两大派别,谁可以让软件更具有生命,维护拓展更方便?伸缩性更强?

  2.2对象和关系数据库的天然阻抗
  软件是讲究方法的,要谈方法,这个世界只有两种:一是将复杂问题简单化的方法;另一是将简单问题复杂化的方法。对于软件这个领域,你只能选择前者。

  2.3 面向对象与领域建模
  据调查,目前有70%左右程序员是在使用OO语言编写传统过程化软件,缺乏完整的面向对象思维方法的教育和培训是基本根源,本文对软件开发中几个常见问题提出了独立的见解及尖锐的观点

  2.4 Evans DDD 领域建模
  如何提炼模型,而不是数据表,进而精化模型对象,使其能够反映领域概念基本本质是一个复杂过程,Evans DDD是2004年提出的具备革命性影响的软件思想。

   2.5 DDD(Evans DDD  Domain-Driven Design领域驱动设计),领域模型驱动设计(Evans DDD)之模型提炼 
领域建模是一种艺术的技术,不是数学的技术,它是用来解决复杂软件快速应付变化的解决之道。如何从职 责和协作中发现丰富对象?给出了DDD具体实践中一些具体细节,是和DDD配合一起进行面向对象分析设计的好方法。

  2.6 DCI架构是什么?
  DCI架构:DCI: 对象的Data数据, 对象使用的Context场景, 对象的Interaction交互行为

  2.7 Domain Events异步应用
  领域驱动设计和异步架构完美实战解决之道。

  2.8 DDD DCI和领域事件
  将DDD DCI Event sourcing结合在一个案例中,展示OOA和OOD实现过程,直至可运行的源代码。

  2.9 DSM:Domain-Specific Modeling
  DSM是超越UML/MDA一种新的建模方法,它成倍提高软件开发效率。

  2.10 四色原型
  我们在一个软件革命的开始,它象90年代我们看到的面向对象编程从传统过程语言中抽象出来一样。 如果说GOF设计模式开辟了OO对象设计新时代,那么原型模式和MDA将开辟后十年的软件新时代。

  2.11 Feature-Driven Development特征驱动开发
  使用JdonFramework等现代Model/Service框架是在什么项目工程背景下进行的?不是以前的XP(Extreme Programming )或RUP,而是FDD。

  2.12 UML和Java的阻抗
  如果Java和UML这种发展概念不匹配下去,我们真的要问UML过时了吗?

  2.13 状态对象:数据库的替代者
  这是一个实战中非常重要但是容易被忽视的概念,说它重要,是因为它比数据库重要;说它容易被忽视也是同样的原因,它经常被数据库概念替代。


    3.其他的一些概念

     DCI: Data数据模型, Context上下文或场景, Interactions交互行为是一种新的编程范式,由MVC发明人Trygve Reenskaug提出。
    E文: DCI : Data Context interactions in context, the interactions of Role will be assigned to data model;
     DCI的关键是:
     1. 要让核心模型非常瘦.
     2. 逻辑或行为应该放在角色这个类中。

DDD领域驱动概述

阅读数 3242

没有更多推荐了,返回首页