领域驱动设计(DDD)是一种基于模型驱动的软件设计方式。它以领域为核心,分析领域中的问题,经过创建一个领域模型来有效的解决领域中的核心的复杂问题。领域驱动设计提出了一套核心构造块(如聚合、实体、值对象、领域服务、领域工厂、仓储、领域事件,等),这些构造块是对面向对象领域建模的一些核心最佳实践的浓缩。这些构造块可使得咱们的设计更加标准、有序。html
什么是领域:领域本质上能够理解为就是一个问题域,只要是同一个领域,那问题域就相同。任何一个系统都会属于某个特定的领域,好比论坛是一个领域,只要你想作一个论坛,那这个论坛的核心业务是肯定的,好比都有用户发帖、回帖等核心基本功能。好比电商平台、普通电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易等核心环节。因此,同一个领域的系统都具备相同的核心业务,由于他们要解决的问题的本质是相似的git
什么是驱动:领域驱动领域模型设计,领域模型驱动代码实现。这个就和咱们传统的数据库驱动开发的思路造成对比了。DDD中,咱们老是以领域为边界,分析领域中的核心问题(核心关注点),而后设计对应的领域模型,再经过领域模型驱动代码实现。而像数据库设计、持久化技术等这些都不是DDD的核心,而是外围的东西。github
什么是设计:DDD中的设计主要指领域模型的设计。DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每个领域,都有一个对应的领域模型,领域模型可以很好的帮咱们解决复杂的业务问题。数据库
理解领域:假设你如今打算作一个电商平台,可是你对这个领域没什么了解,那你必定得先去了解下该领域内主流的电商平台,好比淘宝、天猫、京东、亚马逊等。这个了解的过程就是你沉淀领域知识的过程。虽然咱们明确了要作一个什么样的系统,该系统主要解决什么问题,可是就这样咱们还没法开始进行实际的需求分析和模型设计,咱们还必须将咱们的问题进行拆分,需求进行细化。要知道一个系统到底该作成什么样子,到底哪些是核心业务关注点,只能靠沉淀领域内的各类知识,别无他法。。api
拆分领域:有时一个领域每每太复杂,涉及到的领域概念、业务规则、交互流程太多,致使咱们没办法直接针对这个大的领域进行领域建模。因此,咱们须要将领域进行拆分,本质上就是把大问题拆分为小问题,而后各个击破的思路。而后既然把一个大的领域划分为了多个小的领域(子域),那最关键的就是要理清每一个子域的边界;而后要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撑子域;而后,还要思考子域之间的联系是什么。拿经典的电商系统来分析,一般一个电商系统都会包含好几个大块,好比:跨域
上面这些中心看起来很天然,由于你们对电子商务的这个领域都已经很是熟悉了,因此都没什么疑问,好像很天然的样子。之因此咱们以为子域划分很简单,是由于咱们对整个大领域很是了解了。若是咱们遇到一个冷门的领域,就没办法这么容易的去划分子域了。这就须要咱们先去努力理解领域内的知识。子域划分没有什么技巧,这个工做没有任何诀窍可使用。当咱们对整个领域有必定的熟悉了,了解了领域内的相关业务的本质和关系,咱们就天然而然的能划分出合理的子域了。不过并非全部的系统都须要划分子域的,有些系统只是解决一个小问题,这个问题不复杂,可能只有一两个核心概念。因此,这种系统彻底不须要再划分子域。缓存
细化子域:了领域里的知识,也对领域进行了子域划分。但这样还不够,凭这些咱们还没法进行后续的领域模型设。还必须再进一步细化每一个子域,进一步明确每一个子域的核心关注点,即需求细化,们须要细化的方面有如下几点:安全
从上面这4个方面,咱们从领域概念、业务规则、交互场景、业务流程等维度梳理了咱们到底要什么,整理了整个系统应该具有的功能。这个工做是一个很是具备创造性和有难度的工做。咱们一方面会主观的定义咱们想要什么;另外一方面,咱们还会思考咱们要的东西的合理性。架构
关于领域概念的梳理,我以为能够采用四色原型分析法,这个分析法经过系统的方法,将概念划分为不一样的种类,为不一样种类的概念标注不一样的颜色。而后将这些概念有机的组合起来,从而让咱们能够清晰的分析出概念和概念之间的关系。有兴趣的同窗能够在网上搜索下四色原型。框架
领域:用户会把软件程序应用于某个主体区域,这个区域就是软件的领域。简单来讲,就认为是公司的某块业务好了。若是领域比较大,能够将其拆分为多个子域,子域包含核心域和支撑子域,核心域顾名思义,是最重要的子域,咱们应该把关注点集中在它上面;其他的子域都是支撑子域。支撑子域里有一类特殊的用于解决通用问题的子域,称为通用子域,例如用户和权限等。不过这些都是相对而言的,对于消费方来讲,他的支撑子域有可能就是你的核心域。个别子域可能会有交集,称为共享内核,目的是减小重复,可是仍保持两个独立的上下文。因为不一样子域的开发团队可能会同时修改共享内核,因此须要当心并注意沟通。
限界上下文: 通用语言里,同一个名词在不一样的场景里不必定有相同的意思。好比用户,在推荐好友(可能关注年龄、性别、地域)或是浏览商品(可能关注喜爱、历史购买记录)的时候有着不一样的含义。所谓的不一样的场景,其实就是不一样的限界上下文。不一样的限界上下文之间,经过上下文映射图来进行交互。上下文映射图其实就是一个简单的框图,表示限界上下文之间的的映射关系,这张图就是一个简单的例子 U表示上游被依赖方,D表示下游依赖方。因为上下游的限界上下文模型不一样,实现时,能够用RPC、Restful、消息机制等集成方式。下游须要防腐层来将上游的返回内容翻译为下游的领域模型。
关于领域、领域模型、限界上下文的关系:
关于领域、子领域、核心子域、通用子域,以及共享内核的理解:
实体:实体是有标识的,两个拥有相同属性的实体不是相等的,除非它们的标识相等;而不一样实体的标识不能相等。实体有生命周期,实体从被建立后可能会被持久化到数据库,而后某个时候又会被取出来
例如:某人下了两个相同的订单,里面都购买了相同的商品。这两个订单就是有标识(订单号)的两个实体,虽然内容相同,但它们是两个不一样的实体。实体做为领域模型的主体,须要拥有本身的方法,方法名来自于通用语言。经过这些方法来保证本身始终是一致的状态,而非被调用者set来set去。例如:people.runTo(x, y)
,而非people.setX(x);people.setY(y);
值对象:值对象只用于描述或度量一个东西。值对象没有任何标识,只要两个值对象的属性相等,那么它们就是相等的。值对象是不可变的,若是要改变值对象的内容,那就从新建立一个值对象。值对象没有生命周期,由于它只是值而已。例如:金额(含数值和货币单位),颜色(含rgb值)等
聚合及聚合根:聚合表示一组领域对象(包括实体和值对象),用来表述一个完整的领域概念,而每一个聚合都有一个根实体,这个根实体又叫作聚合根。举个简单的例子,一个电脑包含硬盘、CPU、内存条等,这一个组合就是一个聚合,而电脑就是这个组合的聚合根。。聚合根是聚合所表述的领域概念的主体,外部对象须要访问聚合内的实体时,只能经过聚合根进行访问,而不能直接访问。关于聚合的划分学问仍是挺大的,须要在实践中慢慢积累。同一个实体,在不一样的聚合中,它多是聚合根,也可能不是,须要根据实际的业务决定。
聚合根,实体,值对象区别和联系:
聚合有如下一些特色:
如何识别聚合?
这个须要从业务的角度深刻分析哪些对象它们的关系是内聚的,即咱们会把他们当作是一个总体来考虑的;而后这些对象咱们就能够把它们放在一个聚合内。所谓关系是内聚的,是指这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。当咱们在修改一个聚合时,咱们必须在事务级别确保整个聚合内的全部对象知足这个固定规则。做为一条建议,聚合尽可能不要太大,不然即使可以作到在事务级别保持聚合的业务规则完整性,也可能会带来必定的性能问题。有分析报告显示,一般在大部分领域模型中,有70%的聚合一般只有一个实体,即聚合根,该实体内部没有包含其余实体,只包含一些值对象;另外30%的聚合中,基本上也只包含两到三个实体。这意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。
聚合设计的原则:
如何识别聚合根?
若是一个聚合只有一个实体,那么这个实体就是聚合根;若是有多个实体,那么咱们能够思考聚合内哪一个对象有独立存在的意义而且能够和外部直接进行交互。
聚合根、实体、值对象对象之间如何创建关联?
聚合根到聚合根:经过ID关联;聚合根到其内部的实体,直接对象引用;聚合根到值对象,直接对象引用;
实体对其余对象的引用规则:1)能引用其所属聚合内的聚合根、实体、值对象;2)能引用外部聚合根,但推荐以ID的方式关联,另外也能够关联某个外部聚合内的实体,但必须是ID关联,不然就出现同一个实体的引用被两个聚合根持有,这是不容许的,一个实体的引用只能被其所属的聚合根持有;
值对象对其余对象的引用规则:只需确保值对象是只读的便可,推荐值对象的全部属性都尽可能是值对象;
例子分析:帖子与回复的模型
不 变性分析:帖子和回复之间有不变性规则吗?彷佛咱们只知道一点是确定的,那就是帖子和回复之间的关系,1:N的关系;除了这个以外,咱们看不到任何其余的 不变性规则。那么这个1:N的对象关系是一种不变性规则吗?不是!首先,一个帖子能够没有任何回复,帖子也不对它的回复有任何规则约束,它甚至都不知道自 己有多少个回复;再次,发表了一个回复和帖子也没有任何关系;其次,发表回复对帖子没有任何改变;从业务场景的角度去分析,咱们有发表帖子的场景,有发表 回复的场景。当在发表回复的时候,是以回复为主体的,帖子只是这个回复里所包含的必要信息,用于说明这个回复是对哪一个帖子的回复。这些都说明帖子和回复之 间找不出任何不变性约束的规则;由于帖子和回复都有各自独立的业务场景的须要,因此能够很容易理解它们都是独立的聚合根;那也很容易知道该如何创建他们之 间的关联了,可是咱们要尽可能减小关联,因此只保留回复对帖子的关联便可;帖子没有任何须要去保存一个回复的ID的列表;那么你可能会说,当我删除一个帖子 后,回复应该是没有存在的意义的呀?不对,不是没有存在的意义,而是删除了帖子后致使了回复对帖子的关联信息的缺失,致使数据不一致。这是由于帖子和回复 之间有一种必然的联系(1:N),回复必定会有一个对应的帖子;可是回复有其本身的生命周期,不该该随着帖子的删除而级联删除。这种状况下,若是你删除了 帖子,就致使回复也成为了一条无效的数据;因此,咱们绝对不容许删除任何聚合根,由于一旦你删除了聚合根,那就意味着与该聚合根相关的其余任何聚合根都会 有外键引用缺失的问题,会致使整个领域模型数据的不一致;因此,永远都不要删除聚合根;
领域服务:领域模型主张富领域模式,也就是说把领域逻辑尽可能写在领域实体里面,也就是常说的“充血模式”,而对于业务逻辑,最好是以服务的形式提供。至于领域逻辑和业务逻辑的界定,这个要根据实际状况来定。若是通用语言里面出现了名词,那通常就是实体或值对象;若是里面出现了动词,那一般就意味着领域服务。例如:支付,这是一个比较明显的业务操做。另外,若是有什么操做会让实体变得臃肿,也可使用领域服务来解决。可是不能把全部的东西都堆到领域服务里,过分使用领域服务会致使贫血对象的产生。良好的领域服务具备如下三个特征:
工厂:工厂是生命周期的开始阶段,它能够用来建立复杂的对象或是一整个聚合。复杂对象的建立是领域层的职责,但它并不属于被建立的对象自身的职责。实体和值对象的工厂不太同样,由于值对象是不可变的,因此须要工厂一次性建立一个完整的值对象出来。而实体工厂则能够选择建立以后再补充一些细节。工厂的做用是将建立对象的细节隐藏起来。客户传递给工厂一些简单的参数,而后工厂能够在内部建立出一个复杂的领域对象而后返回给客户。领域模型中其余元素都不适合作这个事情,因此须要引入这个新的模式,工厂。工厂在建立一个复杂的领域对象时,一般会知道该知足什么业务规则(它知道先怎样实例化一个对象,而后在对这个对象作哪些初始化操做,这些知识就是建立对象的细节),若是传递进来的参数符合建立对象的业务规则,则能够顺利建立相应的对象;可是若是因为参数无效等缘由不能建立出指望的对象时,应该抛出一个异常,以确保不会建立出一个错误的对象。固然咱们也并不老是须要经过工厂来建立对象,事实上大部分状况下领域对象的建立都不会太复杂,因此咱们只须要简单的使用构造函数建立对象就能够了。隐藏建立对象的好处是显而易见的,这样能够不会让领域层的业务逻辑泄露到应用层,同时也减轻了应用层的负担,它只须要简单的调用领域工厂建立出指望的对象便可
资源库:资源库是生命周期的结束,它封装了基础设施以提供查询和持久化聚合的操做。这样可以让咱们始终聚焦于模型,而把对象的存储和访问都委托给资源库来完成。以订单和订单明细的聚合为例,由于必定是经过订单这个聚合根来获取订单明细,因此能够有订单的资源库,可是不能有订单明细的资源库。也就是说,只有聚合才拥有资源库。须要注意的是,资源库并非数据库的封装,而是领域层与基础设施之间的桥梁。DDD关心的是领域内的模型,而并不是是数据库的操做。理想的资源库对客户(而非开发者)隐藏了内部的工做细节,委托基础设施层来干那些脏活,到关系型数据库、NOSQL、甚至内存里读取和存储数据。
领域驱动设计的经典分层架构:
用户界面/展示层:负责向用户展示信息以及解释用户命令。更细的方面来说就是:
应用层:很薄的一层,定义软件要完成的全部任务。对外为展示层提供各类应用功能(包括查询或命令),对内调用领域层(领域对象或领域服务)完成各类业务逻辑,应用层不包含业务逻辑。
领域层:负责表达业务概念,业务状态信息以及业务规则,领域模型处于这一层,是业务软件的核心
基础设施层:本层为其余层提供通用的技术能力;提供了层间的通讯;为领域层实现持久化机制;总之,基础设施层能够经过架构和框架来支持其余层的技术需求;
业务初期,咱们的功能大都很是简单,普通的CRUD就能知足,此时系统是清晰的,使用三层结构开发方式,对象只是数据的载体,没有行为。以数据为中心,以数据库ER设计做驱动。三层架构在这种开发模式下,能够理解为是对数据移动、处理和实现的过程。
但在业务逻辑复杂了,业务逻辑、状态会散落到在大量方法中,本来的代码意图会渐渐不明确,而DDD将数据和行为封装在一块儿,并与现实世界中的业务对象相映射。各种具有明确的职责划分,将领域逻辑分散到领域对象中,解决了代码高耦合低聚合的问题
DDD试图解决的是软件的复杂性问题,若是软件比较复杂,或者是预期会很复杂,或者是你不知道,那么均可以开始考虑DDD。不然,因为维系领域模型须要实现大量的封装和隔离,DDD会带来较大的成本。可是,DDD并非一个笨重的开发过程,它可以和敏捷开发很好地结合起来,另外,DDD也倾向于“测试先行,逐步改进”。
咱们建立微服务时,须要建立一个高内聚、低耦合的微服务。而DDD中的限界上下文则完美匹配微服务要求,能够将该限界上下文理解为一个微服务进程。他们的具体关系以下图:
需求:举办一个比赛,有两个队参加,比赛在某个时间开始,只能开始一次,比赛结束后,统计积分
做为用户,但愿看到:参加比赛的队伍名称,比赛开始时间,比赛结束时间,比赛结束后的分数。
这是贫血失血模型,对象只有属性,没有本身的行为方法,有的只有setter/getter方法而已。
本文只是粗浅介绍了下DDD,demo来源于http://www.jdon.com/44815
文章大部分摘自汤雪华的连载博客共23篇,涵盖了DDD的方方面面,如想深刻研究,请参考:http://www.cnblogs.com/netfocus/category/361987.html
其他参考以下:https://tech.meituan.com/DDD%20in%20practice.html