前言php
领域驱动设计是一个开放的设计方法体系,目的是对软件所涉及到的领域进行建模,以应对系统规模过大时引发的软件复杂性的问题,本文将介绍领域驱动的相关概念。前端
一.软件复杂度的根源mysql
软件的需求决定了系统的规模。当需求呈现线性增加的趋势时,为了实现这些功能,软件规模也会以近似的速度增加。因为需求不可能作到彻底独立,致使出现相互影响相互依赖的关系,修改一处就会牵一发而动全身。就好似城市的一条道路由于施工须要临时关闭,此路不通,通行的车辆只能改道绕行,这又致使了其余本来已经饱和的道路,由于涌入更多车辆,超出道路的负载从而变得更加拥堵,这种拥堵现象又会顺势向这些道路的其余分叉道路蔓延,造成一种辐射效应的拥堵现象。程序员
2.技术复杂度(软件的结构)web
结构之因此变得复杂,在多数状况下仍是由于系统的质量属性决定的。例如,咱们须要知足高性能、高并发的需求,就须要考虑在系统中引入缓存、并行处理、CDN、异步消息以及支持分区的可伸缩结构。假若咱们须要支持对海量数据的高效分析,就得考虑这些海量数据该如何分布存储,并如何有效地利用各个节点的内存与 CPU 资源执行运算。sql
从系统结构的视角看,单体架构必定比微服务架构更简单。thinkphp
3.人为的因素数据库
不存在一致性、不存在风格、也没有统一的概念可以将不一样的部分组织在一块儿。缺乏必要的注释,没有字段说明和数据字典, 意大利面条式的代码,缺少统一编码风格,致使错综复杂和不可维护的程序。设计模式
4.需求引发的软件复杂度api
需求分为业务需求与质量属性需求,于是需求引发的复杂度能够分为两个方面:技术复杂度与业务复杂度。
技术复杂度来自需求的质量属性,诸如安全、高性能、高并发、高可用性等需求,为软件设计带来了极大的挑战,让人痛苦的是这些因素彼此之间可能又互相矛盾、互相影响。例如,系统安全性要求对访问进行控制,不管是增长防火墙,仍是对传递的消息进行加密,又或者对访问请求进行认证和受权等,都须要为整个系统架构添加额外的间接层,这不可避免会对访问的低延迟产生影响,拖慢了系统的总体性能。又例如,为了知足系统的高并发访问,咱们须要对应用服务进行物理分解,经过横向增长更多的机器来分散访问负载;同时,还能够将一个同步的访问请求拆分为多级步骤的异步请求,再经过引入消息中间件对这些请求进行整合和分散处理。这种分离一方面增长了系统架构的复杂性,另外一方面也由于引入了更多的资源,使得系统的高可用面临挑战,并增长了维护数据一致性的难度。
业务复杂度对应了客户的业务需求,于是这种复杂度每每会随着需求规模的增大而增长。因为需求不可能作到彻底独立,一旦规模扩大到必定程度,不只产生了功能数量的增长,还会由于功能互相之间的依赖与影响使得这种复杂度产生叠加,进而影响到整个系统的质量属性,好比系统的可维护性与可扩展性。在考虑系统的业务需求时,还会由于沟通不顺畅、客户需求不清晰等多种局外因素而带来的需求变动和修改。若是不能很好地控制这种变动,则可能会由于屡次修改而致使业务逻辑纠缠不清,系统可能开始慢慢腐烂而变得不可维护,最终造成一种如 Brian Foote 和 Joseph Yoder 所说的“大泥球”系统。
以电商系统的促销规则为例。针对不一样类型的顾客与产品,商家会提供不一样的促销力度;促销的形式多种多样,包括赠送积分、红包、优惠券、礼品;促销的周期须要支持定制,既能够是特定的日期,如双十一促销,也能够是节假日的固定促销模式。若是咱们在设计时没有充分考虑促销规则的复杂度,并处理好促销规则与商品、顾客、卖家与支付乃至于物流、仓储之间的关系,开发过程则会变得踉踉跄跄、举步维艰。
技术复杂度与业务复杂度并不是彻底独立,两者混合在一块儿产生的化合做用更让系统的复杂度变得不可预期,难以掌控。
二.控制软件复杂度的原则
1.保持结构的清晰与一致.
2.分而治之、控制规模
3.拥抱变化(变化对软件系统带来的影响能够说是无解)
除了在开发过程当中,咱们应尽量作到敏捷与快速迭代,以此来抵消变化带来的影响;在架构设计层面,咱们还能够分析哪些架构质量属性与变化有关,这些质量属性包括:
三.传统开发设计的模式
1.传统的设计分层结构:
Model层:
包含数据对象,是service操纵的对象,model层中的对象被建模成业务对象,这些对象是对DB中表的映射,一个表对应一个model,表中的字段就对应成model对象的属性,而后在加上get() / set()方法,可是并无包含这个对象的业务上的行为,不知道它会作什么,这样就是一个很典型的贫血模式。
Dao层(数据访问层,DTO对象:数据传输对象):
Dao层主要是和数据库打交道,作数据持久化的工做,也包括一些数据过滤,为model层服务的,好比php里面的mysqli和pdo。
Service层:
公开一些接口给外部服务调用的,放置全部的服务类,它会调用Dao层去处理数据(获取设置数据)。
展示层(UI):
前端的一些业务逻辑展示,使用各类UI框架,如Layzui,smarty,twing等模版js框架去渲染页面。
Controller层:
层负责具体的业务模块流程的控制,在这层调用能够调用service层的接口来控制业务流程,也能够访问model层获取数据。
如今主流的php框架都是按照这样的分层去设计和开发,thinkphp,laveral,ci。
2.传统的设计方式和开发框架及其问题
1.因为设计或者编码的不当,核心业务逻辑容易散布在各处。
因为业务逻辑混散在各处,带来的麻烦维护很困难,有可能在model层 service层作一些业务方面的东西,或者在action里面写一些业务相关的代码,好比有些业务写在展示层,那么就要去改展示层里面的代码,写在service层就要去改service的逻辑。当想要了解这里业务逻辑的时候得看下上下文,翻阅不少类,须要追代码各个文件去看才能大概的明白。
2.过分耦合
业务初期,咱们的功能大都很是简单,普通的CRUD就能知足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得愈来愈复杂,咱们的系统也愈来愈冗杂。模块彼此关联,谁都很难说清模块的具体功能意图是啥。修改一个功能时,每每光回溯该功能须要的修改点就须要很长时间,更别提修改带来的不可预知的影响面。
下图是一个常见的系统耦合病例。
订单服务接口中提供了查询、建立订单相关的接口,也提供了订单评价、支付、保险的接口。同时咱们的表也是一个订单大表,包含了很是多字段。在咱们维护代码时,牵一发而动全身,极可能只是想改下评价相关的功能,却影响到了创单核心路径。虽然咱们能够经过测试保证功能完备性,但当咱们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各类问题。
上述问题,归根到底在于系统架构不清晰,划分出来的模块内聚度低、高耦合。
问题:既然架构不清晰重构是否能够解决这些问题?
能够,可是并不能解决根本问题。通常重构都是经过在单独的类及方法级别上作一系列小步重构来完成,封装一些经常使用的操做,提炼出通用的代码片断。因此咱们能够很容易重构出一个独立的类来放某些通用的逻辑,可是你会发现你很难给它一个业务上的含义,只能给予一个技术维度描绘的含义。这会带来什么问题呢?新来的同事并不老是知道对通用逻辑的改动或获取来自该类。显然,制定项目规范并非好的方法,随着业务的变化在不久的未来重构还会一直继续下去。
3.贫血模式,基于数据表的设计,数据驱动(Data-Driven),全部的开发都是围绕数据表来进行的。
四.贫血模型
贫血领域对象(Anemic Domain Object)是指仅用做数据的载体,而没有行为和动做的领域对象,只有get和set方法,或者包含少许的CRUD方法,全部的业务逻辑都不包含在内。
贫血模型实际上是违背了oop模式,对象有什么反应的就是属性,对象会作什么,反应在类里面对应的就是方法,传统开发中很明显看不到能作什么,不会有业务行为,get / set方式只是外部获取属性值的载体而已。
数据库有什么,模型才会反应什么,这样迫使咱们先去设计数据库,这就是传统的开发思路,这就是基于数据库表的设计,致使的问题就是表中会有一些重复的多余的字段,在设计的时候也会依赖于的特定的数据库(由于是先去设计数据库),因此好的系统是不该该依赖于特定的数据库,更不该该依赖于特定数据库的存储过程,存储函数,触发器等,作到数据库无关性,当作到数据迁移等时候很方便很平滑。因此应该先去设计业务对象,就是对对象建模而后再去反向设计数据库。
缺点:
1.沟通困难,开发人员和业务人员交流语言不统一,开发用技术语言和业务沟通,而业务人员不了解技术,就是交流障碍。
2.业务逻辑不能重用,由于业务散在各个层,业务各个方法互相调用,你不知道调用哪一个方法,业务后期的查找维护也比较困难,业务逻辑也会和应用逻辑的混合,业务逻辑反应的是需求,应用逻辑是和系统相关,好比业务要查询什么数据,查询的过程是业务,而如何展示在ui上是应用。
3.传统的开发是和特色的技术耦合的,若是想把业务脱离出来,去更换某种技术就很困难。
4.适应将来的变化就颇有问题。
优势:
这种贫血模型的传统开发也是当前咱们最经常使用的方法,开发速度快,开发人员容易掌握。
// 贫血模型下的实现 public class User{ private $id; private $name; ... // 省略get/set方法 } public class UserManagerService{ public function save(User user){ // 持久化操做.... } } // 保存用户的操做多是这样 $userManagerService::getInstance()->save(user);
个人业务逻辑都是写在userManagerService中的,User只是个数据载体,没有任何行为。简单的业务系统采用这种贫血模型和过程化设计是没有问题的,但在业务逻辑复杂了,业务逻辑、状态会散落到在大量方法中,本来的代码意图会渐渐不明确,咱们将这种状况称为贫血模型或者是由贫血症引发的失忆症。
// 充血模型下的实现 public class User{ private $id; private $name; ... // 省略get/set方法
//用户信息保存 public function save(User user){ // 持久化操做.... } } // 保存用户的操做多是这样 $User::getInstance()->save(user);
更好的是采用领域模型的开发方式,将数据和行为封装在一块儿,并与现实世界中的业务对象相映射。各种具有明确的职责划分,将领域逻辑分散到领域对象中。继续举咱们上述的例子,用户保存信息就应当放到User类中。
五.为何选择DDD
既然上述传统开发和贫血模式有这些问题,那么有没有什么方法来解决这些问题?
解决思路
1.思想理论:基于领域驱动设计(DDD能应对复杂性与快速变化)。
2.技术实现:
1).从技术维度实现分层:遵循分层架构模式,可以在每层关注本身的事情,好比领域层关注业务逻辑的事情,仓储关注持久化数据的事情,应用服务层关注用例的事情,接口层关注暴露给前端的事情。经过‘开发主机服务’(REST服务是其中的一种)、消息模式、事件驱动 等架构风格实现.
2).业务维度:经过将大系统划分层多个上下文,关注点放在domain上,将业务领域限定在同一上下文中,可让不一样团队和不一样人只关注当前上下文的开发。下降上下文之间的依赖,业务核心与特定的技术隔离开来,不依赖任何一个技术框架。
六.理解DDD概念
DDD的全称为Domain-driven Design,即领域驱动设计。是一种思惟方式和概念,能够应用在处理复杂业务的软件项目中,加快项目的交付速度。下面我从领域、问题域、领域模型、设计、驱动这几个词语的含义和联系的角度去阐述DDD是如何融入到咱们平时的软件开发初期阶段的。要理解什么是领域驱动设计,首先要理解什么是领域,什么是设计,还有驱动是什么意思,什么驱动什么。
领域表明的是某个范围,假如如今要作一个系统,这个系统有一些要实现的功能。那么这个系统确定属于某个特定的领域,好比论坛是一个领域,只要你想作一个论坛,那这个论坛的核心业务是肯定的,好比都有用户发帖、回帖等核心基本功能。好比电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易,物流等核心环节,或者一个支付平台等。因此,同一个领域的系统都具备相同的核心业务,由于他们要解决的问题的本质是相似的。
所以,咱们能够推断出,一个领域本质上能够理解为就是一个问题域,只要是同一个领域,那问题域就相同。因此,只要咱们肯定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本肯定了。一般咱们说,要成为一个领域的专家,必需要在这个领域深刻研究不少年才行。由于只有你研究了不少年,你才会遇到很是多的该领域的问题,同时你解决这个领域中的问题的经验也很是丰富。领域专家的重要性对于设计良好的领域驱动设计是很重要的。在开发前理解领域知识是基础,也很重要,由于一个系统要作成什么样,里面包含哪些业务规则,核心业务关注点是什么,就要求对这个领域内的一切业务相关的知识都很是了解,若是开发一个陌生的系统,好比航空管理软件,让一个只会开发电商的程序员去写,是彻底不知道从哪开始下手。
在平常开发中,咱们一般会将一个大型的软件系统拆分红若干个子系统。这种划分有多是基于架构方面的考虑,也有多是基于基础设施的。可是在DDD中,咱们对系统的划分是基于领域的,也便是基于业务的,领域的划分,一个大的领域能够划分红多个小的领域,也就是子域。
领域及子域的划分是如何进行的,如何去限定的,这个就得须要限界上下文和上下文映射图,下面待会说。
2.什么是设计
DDD中的设计主要指领域模型的设计。为何是领域模型的设计而不是架构设计或其余的什么设计呢?由于DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每个领域,都有一个对应的领域模型,领域模型可以很好的帮咱们解决复杂的业务问题。
从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就必定是解决了领域中的核心问题的。由于:1)领域驱动领域模型设计;2)领域模型驱动代码实现。咱们只要保证领域模型的设计是正确的,就能肯定领域模型能够解决领域中的核心问题;同理,咱们只要保证代码实现是严格按照领域模型的意图来落地的,那就能保证最后出来的代码可以解决领域的核心问题的。这个思路,和传统的分析、设计、编码这几个阶段被割裂(而且每一个阶段的产物也不一样)的软件开发方法学造成鲜明的对比。
3.什么是驱动
上面其实已经提到了,就是:1)领域驱动领域模型设计;2)领域模型驱动代码实现。这个就和咱们传统的数据库驱动开发的思路造成对比了。DDD中,咱们老是以领域为边界,分析领域中的核心问题(核心关注点),而后设计对应的领域模型,再经过领域模型驱动代码实现。而像数据库设计、持久化技术等这些都不是DDD的核心,而是外围的东西。
领域驱动设计第一步最关键就是应该尽可能先把领域模型想清楚,而后再开始动手编码,这样的系统后期才会很好维护。可是,不少项目(尤为是互联网项目,为了赶工)都是一开始模型没想清楚,一上来就开始建表写代码,代码写的很是冗余,彻底是过程是的思考方式,最后致使系统很是难以维护。并且更糟糕的是,前期的领域模型设计的很差,不够抽象,若是你的系统会长期须要维护和适应业务变化,那后面你必定会遇到各类问题维护上的困难,好比数据结构设计不合理,代码处处冗余,改BUG处处引入新的BUG,新人对这种代码上手困难等。而那时若是你再想重构模型,那要付出的代价会比一开始从新开发还要大,由于你还要考虑兼容历史的数据,数据迁移,如何平滑发布等各类头疼的问题。
因此经过创建领域模型来解决领域中的核心问题,这就是模型驱动的思想。
七.DDD核心组件
造成统一的领域术语,尤为是基于模型的语言概念,是沟通可以达成一致的前提。尤为是开发人员与领域专家之间,他们掌握的知识存在巨大的差别,尤为是专业性很强的业务,好比金融系统,医疗系统,它们的术语都很专业。而善于技术的开发人员关注于数据库、通讯机制、集成方式与架构体系,而精通业务的领域专家对这些却一窍不通,但他们在讲解业务知识时,很是天然,这些对于开发人员来讲,却成了天书,这种交流就好似使用两种不一样语言的外国人在交谈。
使用统一语言能够帮助咱们将参与讨论的客户、领域专家与开发团队拉到同一个维度空间进行讨论,若没有达成这种一致性,那就是鸡同鸭讲,毫无沟通效率,相反还可能形成误解。所以,在沟通需求时,团队中的每一个人都应使用统一语言进行交流。
一旦肯定了统一语言,不管是与领域专家的讨论,仍是最终的实现代码,均可以经过使用相同的术语,清晰准确地定义领域知识。重要的是,当咱们创建了符合整个团队皆认同的一套统一语言后,就能够在此基础上寻找正确的领域概念,为创建领域模型提供重要参考。
2.界限上下文(Bounded Contexts):
界限上下文是DDD中的一个核心模式,这种模式是帮助咱们剥离开复杂的应用程序,将他们隔离开,造成不一样的上下文边界。不一样的模块有着不一样的上下文,且能独立调用,而各自的模块能够有本身的持久化的,互不干扰。
在大型的应用程序中,不一样的人对不一样的的东西可能取相同的名字,这跟咱们程序的类同样,为什么咱们要在外面放一个namespace在外面,其实也是造成一个边界。程序内部也是如此。例如,售楼部内的员工把商品房认为是产品(Product);可是在工程部,可能他们把刷灰和修理管道的服务叫作产品,为了消除这些歧义,能够定义一个边界,分离开两种状况,以避免在系统内产生混淆。每一个界限上下文根据特色,具体实现方式又不一样,好比有些界限上下文基本没有业务逻辑,就是增删改查,则可使用CRUD最简单的模式;有些界限上线文有必定的业务逻辑,但对高并发、高性能没要求,则可使用经典DDD模式。有些界限上下文有必定的业务逻辑,并且有高性能要求,则可使CQRS模式(命令查询职责分离(Command Query Responsibility Segregation,简称CQRS))。
上面这张图展现了界限上下文
3.实体:
有业务生命周期,采用业务标识符进行跟踪。好比一个订单就是实体,订单有生命周期的,并且有一个订单号惟一的标识它本身,若是两个订单全部属性值所有相同,但订单号不一样,也是不一样的实体。
实体之间的关系:
1)关系越多,耦合越大。
2)找出整个业务期间的都依赖的关系,某些关系只是在对象建立的时候有意义,好比建立订单的时候会查询一下商品价格信息。
3)尽量的简化关系,避免双向依赖关系。
4.值对象:
无业务生命周期,无业务标识符,一般用于描述实体。好比订单的收货地址、订单支付的金额等就是值对象。
根据上下文的不一样,一个值对象在一个界限上下文中上值对象,到了另外一个界限上下文环境中会是实体,就是在不一样的领域里属性是不同的,具体还得根据上下文来看。
值对象是不可变(只读),这样线程安全,能够处处传递。
值对象最大的好处在于增长了代码复用。
5.领域服务:
无状态,有行为,服务自己也是对象,但它却没有属性(只有行为),所以说是无状态,一般负责协调多个领域对象的操做来完成一些功能。好比尝试如何将信息转化为领域模型,但并不是全部的点咱们都能用Model来涵盖。对象应当有属性,状态和行为,但有时领域中有一些行为是没法映射到具体的对象中的,咱们也不能强行将其放入在某一个模型对象中,而将其单独做为一个方法又没有地方,此时就须要服务。协调聚合之间的业务逻辑,而且完成用例,表示某种能力。
6.聚合:
聚合是一组相关的对象,它经过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的造成,咱们把聚合看做是一个修改数据的单元,目的将这些对象做为一个单元(是业务的一个最小单元,持久化最小单元),每一个聚合都有一个边界和一个根,边界定义了聚合里应该包含什么,根是聚合中惟一能够被外部饮用的元素,好比说不能直接绕过订单实体去访问订单项,但在聚合边界内部,能够互相引用。聚合根具备全局惟一标识,聚合根由仓储负责持久化其生命周期,而实体只有在聚合内部有惟一局部标识,由聚合根负责其生命周期持久化。
一般将多个实体和值对象组合到一个聚合中来表达一个完整的概念,好比订单实体、订单明细实体、订单金额值对象就表明一个完整的订单概念,并且生命周期是相同的,而且须要统一持久化到数据库中。
7.聚合根:
将聚合中表达总概念的实体作成聚合根,好比订单实体就是聚合根,对聚合中全部实体的状态变动必须通过聚合根,由于聚合根协调了整个聚合的逻辑,保证一致性。固然其余实体能够被外部直接临时查询调用。
8.仓储:
用于对聚合进行持久化,一般为每一个聚合根配备一个仓储便可。仓储可以很好的解耦领域逻辑与数据库。
9.工厂:
用于建立复杂的领域对象,可以将领域对象复杂的建立过程保护起来,能够建立实体,值对象。在大型系统中,实体和聚合一般是很复杂的,这就致使了很难去经过构造器来建立对象,工厂就决解了这个问题,其实就是一种封装,隐藏了复杂的建立细节。
10.上下文映射图
若是咱们将限界上下文理解为是对工做边界的控制,则上下文之间的协做实则就是团队之间的协做,高效的团队协做应遵循“各司其职、权责分明”的原则。从组织层面看,须要预防一个团队的“权力膨胀”,致使团队的“势力范围”扩大到整个组织。从团队层面,又须要避免本身的权力遭遇压缩,致使本身的话语权愈来愈小,这中间就存在一个平衡问题。映射到领域驱动设计的术语,就是要在知足合理分配职责的前提下,谨慎地确保每一个限界上下文的粒度。职责的合理分配,能够更好地知足团队的自组织或者说自治,但不可能作到“万事不求人”,全靠本身来作。若是什么事情都由这一个团队完成,这个团队也就成为无所不能的“上帝”团队了。上下文映射展示了一种组织动态能力(Organizational Dynamic),它能够帮助咱们识别出有碍项目进展的一些管理问题。”这也是我为什么要在识别上下文的过程当中引入项目经理这个角色的缘由所在,由于在团队协做层面,限界上下文与项目管理息息相关。
领域驱动设计根据团队协做的方式与紧密程度,定义了五种团队协做模式:
1)合做关系(Partnership):两个上下文紧密合做的关系,互相联系紧密。
2)共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
3)客户方-供应方开发(Customer-Supplier Development):正常状况下,这是团队合做中最为常见的合做模式,体现的是上游(供应方)与下游(客户方)的合做关系。这种合做须要两个团队共同协商。
4)遵奉者(Conformist):下游限界上下文对上游限界上下文模型的追随,作出遵奉模型决策的前提是须要明确这两个上下文的统一语言是否存在一致性,由于限界上下文的边界自己就是为了维护这种一致性而存在的。
5)分离方式(Separate Ways):在典型的电商网站中,支付上下文与商品上下文之间就没有任何关系,两者是“分离方式”的体现。
八.DDD系统的分层架构
分层就是将具备不一样职责的组件分离开来,组成一套层内部高聚合,层与层之间低耦合的软件系统,领域驱动设计的讨论一样也是创建在层模式的基础上的,但与传统的分层架构相比,它更注重领域架构和技术架构的分离。
领域驱动设计将软件系统分为四层:基础结构层、领域层、应用层和表现层。与上述的三层相比,数据访问层已经不在了,它被移到基础设施层了,这些是属于外围的,不是核心。
从上图还能够看到,表现层与应用层之间是经过数据传输对象(DTO)进行交互的,数据传输对象是没有行为的POCO对象,它的目的只是为了对领域对象进行数据封装,实现层与层之间的数据传递。为什么不能直接将领域对象用于数据传递?由于领域对象更注重领域,而DTO更注重数据。不只如此,因为“富领域模型”的特色,这样作会直接将领域对象的行为暴露给表现层。
领域层是业务的核心,全部的业务都是在领域层。应用层是在领域层之上,为ui服务,它是响应ui的请求去领域层调用相应的服务,把结果返回给ui,这里面包括一些事务,分页等和业务无关的,因此这些和业务无关都会放在应用层。基础设施层是服务领域层的。
架构风格
针对DDD的架构设计,《实现领域驱动设计》书中提到了几种架构风格:六边形架构、REST架构、CQRS、事件驱动等。在实际使用中,落地的架构并不是是纯粹其中的一种,而颇有可能户将上述几种架构风格结合起来实现。
所谓的六边形架构,实际上是分层架构的扩展,原来的分层架构一般是上下分层的,好比常见的MVC模式,上层是对外的服务接口,下层是对接存储层或者是集成第三方服务,中层是业务逻辑层。咱们跳出分层的概念,会发现上面层和下面层其实都是端口+适配器的实现,上面层开放http/tcp端口,采用rest/soap/mq协议等对外提供服务,同时提供对应协议的适配器;下层也是端口+适配器,只不过应用程序这时候变成了调用者,第三方服务或者存储层提供端口和服务,应用程序自己实现适配功能。
领域驱动设计(Domain Driven Design)有一个官方的sample工程,名为DDDSample,官网:http://dddsample.sourceforge.net/,该工程给出了一种实践领域驱动设计的参考架构。下图就是它的代码结构。
各个目录含义:Infrastructure(基础实施层),Domain(领域层),Application(应用层),Interfaces(表示层,也叫用户界面层或是接口层),config(各类配置)
九.领域驱动总结
DDD与数据库设计不一样:
1.领域驱动设计是一种面向对象的设计,先建模再去设计数据库。
2.领域驱动设计主要是基于现实业务中的模型,更加贴近真实业务,不只仅是一种技术的实现。
3.领域驱动设计出来的产品---领域对象(Domain Object),是一个充血模型,不但包含业务对象的属性,也包含业务对象的方法和行为,更加符合oo原则。
4.领域驱动设计并不包含数据库具体设计,而是和领域专家一块儿,采用统一的语言分析领域对象的属性,业务方法,以及领域之间的关系,并为之建模。
5.领域驱动设计能减小沟通的成本。
DDD的特色:
1.统一语言,业务 产品 技术交流都不会设计到具体的技术方面,主要是对核心业务的建模,不会先考虑数据表的设计,先考虑建模。
2.专有的领域层,领域层除了业务以外不设计软件架构,等底层技术。
3.领域层代码就是业务文档,看到领域层代码就能看到业务 的核心,就是从对象中不只仅看到属性还能能够看到业务。
DDD的一些问题
1.为何DDD能够应对复杂性?
答:就是分而自治思想,好比说一个系统几百张表,不可能一会儿弄清楚,可是能够按业务,模块去划分,DDD里面叫作界限上下文,和模块(可是提出了更多概念,好比聚合,一个模块有可能仍是很大。)相似,划分红一个个领域,而领域模型有清晰的边界,同时DDD重构了设计模式 架构模式 它里面也引入了ioc,工厂模式 策略模式 只是在更高层次上的应用。
2.为何能够快速应对变化?
当问题空间出现变化的时候,咱们能够快速的找到领域模型。领域层是能够很容易将业务模块拿出来重用的。
什么时候考虑使用领域驱动设计?
1.若是系统只是简单的curd,没有很复杂的业务逻辑,不须要领域驱动设计,反而回增长复杂性。
2.若是你的应用多于用例场景,你的系统可能会逐渐成为一个大泥球(混杂在一块儿的上下文关系,边界不清晰,代码混乱)。若是你肯定你的系统将会更复杂,你应该使用领域驱动设计来处理这个复杂性。
使用领域驱动的难点
应用领域驱动设计并没那么简单容易,这须要花费时间和精力去了解业务领域、术语、调查、和领域专家一块儿合做去划分如何去划分领域,划分好边界并建模,去业务进行抽象,这也是DDD的最重要的地方。
十.一些相关的扩展阅读
核心思想是将应用程序的查询部分和命令部分彻底分离,这两部分能够用彻底不一样的模型和技术去实现。好比命令部分能够经过领域驱动设计来实现;查询部分能够直接用最快的非面向对象的方式去实现,好比用SQL。这样的思想有不少好处:
1) 实现命令部分的领域模型不用常常为了领域对象可能会被如何查询而作一些折中处理;
2) 因为命令和查询是彻底分离的,因此这两部分能够用不一样的技术架构实现,包括数据库设计均可以分开设计,每一部分能够充分发挥其长处;
3) 高性能,命令端由于没有返回值,能够像消息队列同样接受命令,放在队列中,慢慢处理;处理完后,能够经过异步的方式通知查询端,这样查询端能够作数据同步的处理。
基于DDD的设计,对于聚合,不保存聚合的当前状态,而是保存对象上所发生的每一个事件。当要重建一个聚合对象时,能够经过回溯这些事件(即让这些事件从新发生)来让对象恢复到某个特定的状态;由于有时一个聚合可能会发生不少事件,因此若是每次要在重建对象时都从头回溯事件,会致使性能低下,因此咱们会在必定时候为聚合建立一个快照。这样,咱们就能够基于某个快照开始建立聚合对象了。
DCI架构强调,软件应该真实的模拟现实生活中对象的交互方式,代码应该准确朴实的反映用户的心智模型。在DCI中有:数据模型、角色模型、以及上下文这三个概念。数据模型表示程序的结构,目前咱们所理解的DDD中的领域模型能够很好的表示数据模型;角色模型表示数据如何交互,一个角色定义了某个“身份”所具备的交互行为;上下文对应业务场景,用于实现业务用例,注意是业务用例而不是系统用例,业务用例只与业务相关;软件运行时,根据用户的操做,系统建立相应的场景,并把相关的数据对象做为场景参与者传递给场景,而后场景知道该为每一个对象赋予什么角色,当对象被赋予某个角色后就真正成为有交互能力的对象,而后与其余对象进行交互;这个过程与现实生活中咱们所理解的对象是一致的;
DCI的这种思想与DDD中的领域服务所作的事情是同样的,但实现的角度有些不一样。DDD中的领域服务被建立的出发点是当一些职责不太适合放在任何一个领域对象上时,这个职责每每对应领域中的某个活动或转换过程,此时咱们应该考虑将其放在一个服务中。好比资金转账的例子,咱们应该提供一个资金转账的服务,用来对应领域中的资金转账这个领域概念。可是领域服务内部作的事情是协调多个领域对象完成一件事情。所以,在DDD中的领域服务在协调领域对象作事情时,领域对象每每是处于一个被动的地位,领域服务通知每一个对象要求其作本身能作的事情,这样就好了。这个过程当中咱们彷佛看不到对象之间交互的意思,由于整个过程都是由领域服务以面向过程的思惟去实现了。而DCI则通用引入角色,赋予角色以交互能力,而后让角色之间进行交互,从而可让咱们看到对象与对象之间交互的过程。但前提是,对象之间确实是在交互。由于现实生活中并非全部的对象在作交互,好比有A、B、C三个对象,A通知B作事情,A通知C作事情,此时能够认为A和B,A和C之间是在交互,可是B和C之间没有交互。因此咱们须要分清这种状况。资金转账的例子,A至关于转账服务,B至关于账号1,C至关于账号2。所以,资金转账这个业务场景,用领域服务比较天然。有人认为DCI能够替换DDD中的领域服务,我持怀疑态度。
1) 时刻-时间段原型(Moment-Interval Archetype)
表示在某个时刻或某一段时间内发生的某个活动。使用粉红色表示,简写为MI。
2) 参与方-地点-物品原型(Part-Place-Thing Archetype)
表示参与某个活动的人或物,地点则是活动的发生地。使用绿色表示。简写为PPT。
3) 描述原型(Description Archetype)
表示对PPT的本质描述。它不是PPT的分类!Description是从PPT抽象出来的不变的共性的属性的集合。使用蓝色表示,简写为DESC。
举个例子,有一我的叫张三,若是某个外星人问你张三是什么?你会怎么说?可能会说,张三是我的,可是外星人不知道“人”是什么。而后你会怎么办?你就会说:张三是个由一个头、两只手、两只脚,以及一个身体组成的客观存在。虽然这时外星人仍然不知道人是什么,但我已经能够借用这个例子向你们说明什么是“Description”了。在这个例子中,张三就是一个PPT,而“由一个头、两只手、两只脚,以及一个身体组成的客观存在”就是对张三的Description,头、手、脚、身体则是人的本质的不变的共性的属性的集合。但咱们人类比较聪明,很会抽象总结和命名,已经把这个Description用一个字来代替了,那就是“人”。因此就有所谓的张三是人的说法。
4) 角色原型(Role Archetype)
角色就是咱们平时所理解的“身份”。使用黄色表示,简写为Role。为何会有角色这个概念?由于有些活动,只容许具备特定角色(身份)的PPT(参与者)才能参与该活动。好比一我的只有具备教师的角色才能上课(一种活动);一我的只有是一个合法公民才能参与选举和被选举;可是有些活动也是不须要角色的,好比一我的不须要具有任何角色就能够睡觉(一种活动)。固然,其实说人不须要角色就能睡觉也是错误的,错在哪里?由于咱们能够这样理解:一个客观存在只要具备“人”的角色就能睡觉,其实这时候,咱们已经把DESC看成角色来看待了。因此,其实角色这个概念是很是广的,不能用咱们平时所理解的狭义的“身份”来理解,由于“教师”、“合法公民”、“人”均可以被做为角色来看待。所以,应该这样说:任何一个活动,都须要具备必定角色的参与者才能参与。
用一句话来归纳四色原型就是:一个什么什么样的人或组织或物品以某种角色在某个时刻或某段时间内参与某个活动。 其中“什么什么样的”就是DESC,“人或组织或物品”就是PPT,“角色”就是Role,而”某个时刻或某段时间内的某个活动"就是MI。
以上这些东西若是在学习了DDD以后再去学习会对DDD有更深刻的了解,但我以为DDD相对比较基础,若是咱们在已经了解了DDD的基础之上再去学习这些东西会更加有效和容易掌握。
十一 .其余的软件开发模式
TDD:测试驱动开发(Test-Driven Development)
BDD:行为驱动开发(Behavior Driven Development)
ATDD:验收测试驱动开发(Acceptance Test Driven Development)