作者:人月神话,新浪博客同名
简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践
今天谈下领域驱动设计方面的内容,其中部分内容来源于《领域驱动设计:软件核心复杂性应对之道》书籍的读书笔记整理。
我前面谈了很多关于中台,SOA和微服务的文章,实际上你可以看到中台层对外和对前台提供的服务更多就应该是粗粒度的领域服务能力,如果中台最终只提供书籍对象的CRUD类API接口服务,那么根本就谈不上中台的共性业务服务能力下沉。
领域驱动设计概述
什么是领域驱动设计(DDD)
2004年著名建模专家Eric Evans发表了他最具影响力的书籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计—软件核心复杂性应对之道),书中提出了“领域驱动设计(简称 DDD)”的概念。
领域驱动设计事实上是针对OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术架构进行了分层规划,同时对每个类进行了策略和类型的划分。
领域模型是领域驱动的核心。采用DDD的设计思想,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射。
领域模型就是由这样许多的细粒度的类组成。基于领域驱动的设计,保证了系统的可维护性、扩展性和复用性,在处理复杂业务逻辑方面有着先天的优势。
领域驱动设计的特点
领域驱动的核心应用场景就是解决复杂业务的设计问题,其特点与这一核心主题息息相关:
01-分层架构与职责划分:领域驱动设计很好的遵循了关注点分离的原则,提出了成熟、清晰的分层架构。同时对领域对象进行了明确的策略和职责划分,让领域对象和现实世界中的业务形成良好的映射关系,为领域专家与开发人员搭建了沟通的桥梁。
02-复用:在领域驱动设计中,领域对象是核心,每个领域对象都是一个相对完整的内聚的业务对象描述,所以可以形成直接的复用。同时设计过程是基于领域对象而不是基于数据库的Schema,所以整个设计也是可以复用的。
领域驱动设计的分层架构
下面我们简单介绍一下领域驱动设计的分层架构和构成要素,这部分内容在Eric Evans的书中有非常详尽的描述,想要详细了解的,最好去读原版书籍。
下面这张图是该书中著名的分层架构图,如下:
整个架构分为四层,其核心就是领域层(Domain),具体描述如下:
- 用户界面/展现层:负责向用户展现信息以及解释用户命令。
- 应用层:很薄的一层,用来协调应用的活动。它不包含业务逻辑。它不保留业务对象的状态,但它保有应用任务的进度状态。
- 领域层:本层包含关于领域的信息。这是业务软件的核心所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层。
- 基础设施层:本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用。
对于领域在这里我更愿意将其理解为业务领域,领域的表现是一系列存在相关关联和依赖关系的业务实体对象表现出现的业务行为的一个合集。
传统的用例驱动往往我们会先分析会有哪些业务行为和业务操作,再来分析这些行为操作的承载对象,如RUP中的实体类控制类等。领域驱动的方法则可能是尽快先找出核心的业务实体对象,再通过交互分析来分析对象应该展现出来的行为。
在解耦的层面我们看到两个层面的解耦:
第一个层面是业务操作和业务实体的解耦,这也是传统的SOA的一个思想,在领域建模里面可以看到分解为业务服务,实体对象和值对象等。
第二个层面则是应用功能,服务能力,基础设施三层的解耦,在领域驱动设计的分层架构中则将其分解为了应用层,领域层,基础设施层。基础设施层提供资源层和持久化的能力,领域层提供业务服务能力,而应用层仅仅处理能力的协同,状态保存,服务的编排问题等。
领域驱动设计中领域层的构建可以是整个系统构建的核心,通过领域层的构建再拓展到持久化层和界面展现层。领域模型完全基于面向对象思路构建,因此完全可以兼顾底层是关系型数据库还是NoSQL数据库来持久化。而且领域模型的持久化好像专门就是为适合NoSQL数据库而设计。对于界面展现层相同的道理,领域层只是提供和暴露业务服务,具体业务服务如何交互,协同和展现并不是关键。
一个完整的领域层应该包括了实体对象,值对象,主域模型类,业务服务类几个关键的类。
实体对象有唯一的标识符,需要持久化,对应现实中的业务对象,而值对象无唯一标识符,仅仅关注它拥有的一组属性。实体对象本身会表现出对应到行为,而值对象仅仅是属性的结合。在多个实体对象上层可能还要构建主域层对象,程度跨多个实体对象的操作组合。而对于业务服务仅仅是能力通过接口方式的暴露。一方面是实现应用层到领域层的协同,一方面是实现多个domain主域间的协同。
对于领域层,在大型系统构建过程中可以是首先进行全局的领域层建模再划分为多个domain域,形成业务组件或模块。也可以首先进行业务主体域的划分,然后再分解下去后识别每个业务域里面的实体对象和表现行为。组件划分思路需要引入到大型领域层的构建。即领域层也会组件化和模块化。业务服务即需要考虑模块内,也需要考虑模块间协同。在这里可以看到是领域层本身进行模块化后引入的,对业务服务的能力进行了扩展。
领域驱动设计让我们在分析和构建一个应用系统的时候,真正的转正核心矛盾,即领域对象和行为表现,而不是太多的关注基础设施和持久化机制,不去关心前台的展现层技术等。因为只有领域层这块在各种技术架构中是完全可以复用的,也是业务系统的核心。我们谈业务架构驱动应用架构,而业务架构核心内容就应该体现到应用架构的领域层模型中。
领域模型关键对象和UML彩色建模
领域驱动设计实质是业务场景和实体驱动的设计,有关注业务场景和流程,分析,识别和抽象业务对象,进一步分析对象间的创建,关联,逻辑和服务。注意领域模型和多层架构和技术关联很小,更多的是关注概念模型和业务逻辑,因此抛开了多层架构和框架本身,先研究领域模型再由内而外过渡到数据层和界面层,以及多层的集成。按照Eric的表述,将领域中的组成角色分为五种:
- 实体(Entity):拥有唯一标识的对象。
- 值对象(Value Object):没有唯一标识的对象。
- 工厂(Factory):定义创建实体的方法。
- 资源库(Repository):管理实体的集合并封装其持久化过程。
- 服务(Service):实现不能指派或封装在一个单一对象上的操作。
对于实体很容易理解,也就是业务对象,通过业务场景分析和识别的业务对象。而值对象更多的是属性集合,值对象是没有行为的,类似于结构体。这个是实体和值对象最主要的区别。书里面举了一个例子,Customer是一个实体,有具体的行为;而Address地址信息包括了街道,省份,区域等各种信息且都属于地址内容,可以抽象为一个值对象,而地址本身是没有任何行为的。
对于实体,在UML彩色建模里面分了四种不同的类型:
1. Party, Place, Thing
Party: 事件的参与方,例如某人人、某组织等
Place: 事件的发生地,例如仓库、零售店铺
应当是指事件中具体的物品,比如客户的购买事件中,thing可能不是指订单(order),而是订单中具体的物品,例如图书、衣服等。
2. Role
角色,对party, place, thing的参与行为、方式的抽象。
3. Moment-Interval
指业务的活动、对象等,因业务需要和法律原因等需要操作、记录、追踪的东西,例如销售、订单、预定、航班、会议、行程等。
4. Description
对某一类型事物的额外描述数据,例如产品有各种基本属性,但各种类型的产品差异比较大,比如做电子商务系统时,图书、服装、眼镜等不同类型的商品,在前台购买流程、后端订单处理流程、财务记账处理、库存管理方式、售后服务原则等各方面都会存在比较大的差异,这些差异按照产品类型设计成产品的额外描述属性,这些被称为description。
可以看到UML彩色建模本身就是对领域驱动设计思想的一个实践,特别是对于实体进行了细分,将事物和业务互动实体,操作者都进行了分离。这个分离本身有一个巨大的好处,就是通过Color为不同的颜色,通过颜色的识别让静态的领域模型增加了动态性的信息。
资源库(Repository)的作用是管理实体的集合,并封装其持久化过程,这里面说明了资源库管理实体对象的全生命周期。注意对实体对象生命周期管理的很多行为和操作已经不在实体类中,而是在资源库中,资源库转变为一个操作类和类实例化后持久化的一个结合。而这里让我感到疑惑的点仍然在在领域建模的初期可以关注类之间的关系和聚合,但是不能过早过渡到资源库分析,即从静态领域建模过渡到动态行为分析。
服务简单点讲就是接口,在做领域分析的时候会发现有些领域很难映射到对象,拥有属性和暴露行为,这个时候就应该识别为服务。服务一个重要作用仍然是屏蔽领域逻辑内部复杂性,体现高内聚,松耦合的思想。
注意SOA方法论里面很重要的一点仍然是业务和流程驱动IT和架构,业务架构和模型在先,而技术架构和模型在后,SOA从业务入手的重点正式流程和业务实体两个重要内容,而我们识别,分析流程的目的仍然在于通过流程分析识别业务实体和交互关系,所以将领域驱动设计的一些思路融入到SOA的系统分析和设计中可以更好的和SOA业务建模阶段结合。业务分析师关注业务和流程,技术架构师关注实体识别和涉及事务,接口等。
领域模型-实体对象和值对象
对于实体Entity和值对象Value Object是领域驱动设计里面两个重要的模型对象。所以有必要对两者的关系和区别进行理解。以下部分内容直接引用自《领域驱动设计》一书相关内容。
首先对于实体Entity,实体核心是用唯一的标识符来定义,而不是通过属性来定义。即即使属性完全相同也可能是两个不同的对象。同时实体本身有状态的,实体又演进的生命周期,实体本身会体现出相关的业务行为,业务行为会实体属性或状态造成影响和改变。
真正的现实世界,每个事物都一定会有唯一的标识,关键点是我们实际的业务场景和需求是否需要管理到唯一标识。
书里面举了一个例子,当我们发放的门票上有座位号的时候,座位需要作为独立的实体,座位号是唯一的标识。而当先到先座模式下,我们只关心剩余座位数,那么座位号并不是唯一标识。这跟我们的业务需求有关。
一个对象不由属性来定义,那么看人这个对象,身份证号是属性,其实也是对于人的唯一标识。不考虑本身身份证号的位数升级,一个身份证号会跟随你一辈子。但是对于人我们一般仍然会作为实体Entity来看待,因为人有状态,有对象演进的生命周期,会主动产生各种行为。
对于企业内信息系统,很多时候我们把员工工卡号作为唯一标识来使用,但是要意识到工卡号只是人员的一个属性。虽然工卡号本身不会出现两个重复的,但是该属性仍然可能演变,如果将工卡号作为唯一标识和ID,那么在该属性变化时候所有其余关联对象都将受到影响。从这个层面来看,一个唯一的内码ID才是可信的唯一标识。
而对于值对象Value Object,它用于描述领域的某个方面本身没有概念标识的对象,值对象被实例化后只是提供值或叫设计元素,我们只关心这些设计元素是什么?而不关心这些设计元素是谁。书里面谈到颜色,数字是常见的值对象。这种对象无状态,本身不产生行为,不存在生命周期演进。
是否为值对象跟实际的业务场景仍然关系密切。
书里面又举了地址的例子,当地址是值对象的时候,地址本身无状态,可以被多个实际有状态的实体使用,地址不存在太多的生命周期演进场景下地址为值对象。而对于本身行政区域管理软件中,地址本身存在状态,存在根据行政区域规划变化而演进的过程,因此地址为实体。
如果从值对象本身无状态,不可变,并且不分配具体的标识层面来看。那么值对象可以仅仅理解为实际的Entity对象的一个属性集合而已。该值对象附属在一个实际的实体对象上面。值对象本身不存在一个独立的生命周期,也一般不会产生独立的行为。
值对象往往可能是多个属性的聚合,本身无唯一标识,多个属性最终形成的一个结果值,而这个结果值往往又依附在一个实际的实体Entity上面。那么如果从这个概念来说,值对象往往不会单独进行持久化,或形成数据库设计的一张数据表。另外一种情况,对于简单的数据字典类对象,是否考虑作为值对象,这种对象需要持久化,如纳税属性,物料类型,它们设计到数据字典中取值,这个数据字典无状态,无自己的生命周期,是可以作为值对象来处理的。
领域模型-聚合和聚合根
聚合和聚合根是领域模型里面很重要的一个概念,其实我们在从真实世界对业务对象进行识别和概念建模的时候,关注的就是聚合根,这才是我们真正要管理的业务对象。
一个对象可能有多个层次,也可能有多个子实体,但是这些子实体都不可能孤立存在,它们必须依附于一个聚合根存在,它们和根节点具有同样的生命周期。
如果一个客户消亡,客户联系方式,客户的多张银行账户信息将不再有任何意义。如果一张采购订单头消失,那么采购订单明细没有任何存在的意义。客户,采购订单,发票这些从真实业务中转化过来的业务对象才是真正的领域核心对象。
这些对象可能在领域建模的时候会分解到多个Entity或Value Object,但是一定要意识到实际的聚合在哪里?我们真正关注的业务对象实体究竟有哪些?
为什么如此强调领域模型,强调聚合根的概念,因此我们在关注领域模型的时候将有助于我们打破原有的关系型数据库的思维模式,转化为对象和领域的思维模式。
可以看到领域建模和聚合根的思路正是既适合于关系型数据库,也适合NoSql数据库的建模思路。因为在NoSQL持久化的时候,我们看到采购订单就是一个对象,其它明细和关联信息都是这个对象下的子实体信息,采购订单应该作为一个对象整体进行查询和存储,我们并不关心NoSQL会如何去存储这个对象。让我们正在关注领域对象,而不是去关心如何持久化。
聚合Aggregate就是一组相关对象的集合,我们把它作为数据修改和访问的单元。每个聚合都会有一个聚合根和聚合的边界Boundary,边界定义了在一个聚合里面内部应该有哪些实体,哪些子实体对象。定义边界的原因是我们期望对一个聚合的访问是通过聚合根点进行的,聚合里面的子实体对外界是完全封闭的。对于外部对象不应该去访问到一个聚合边界里面的子实体。
在一些场景下,对于一个聚合的访问,我们往往只需要查询到头信息,而不关心具体的子实体信息,这个有点类似于传统O/R Mapping里面的惰性加载。在这里也必须要考虑到。在实现和设计聚合的时候,需要考虑到这种场景,即根据需要来加载一个完整聚合中的实体和子实体,以满足性能的需要。如何对应关系型数据库,对一个聚合实际的新增变更处理则可能涉及到多个数据表的多次操作,而这已经是仓储接口和仓储实现需要考虑的问题。现在对一个聚合的一次操作一定应该在一个完整的事务里面,以保障实际的事务完整性要求。
按实际对象分析思路,在领域模型中的领域对象分析应该按照从顶向下的思路进行展开,如果这样的话首先识别到的就是聚合根对象,然后再考虑对聚合根对象进行展开,在聚合根对象的展开过程中进一步细化子实体之间的关联和依赖关系。
领域模型-规格模式
对于Specification模式是在第九章讲解将隐式概念转变为显式概念的时候讲到的,首先为何会单独剥离规格类,书里面的最重要解释就是对于业务规则通常不适合作为entity或value object本身的职责,而且规则的变化和组合也会掩盖领域对象的基本含义。但是如果将规则移出领域层,那么结果会更加糟糕,因为领域代码本身就不再很好的表达业务模型了。
对于Specification模式的主要应用场景书里面谈到三点,即:
- 验证对象,检验对象本身是否满足某些业务要求
- 从集合中选择符合特定业务规则的对象或对象子集
- 指定在创建新对象的时候必须要满足某种业务要求
如果从以上几点来看的话,对于规格模式可以更多的看做是对业务实现过程中业务规则的单独剥离,放到独立的规格类来实现,主要就是处理业务规则。在谈到这里的时候我们再回顾下领域模型中的几个特定对象:
- 实体(Entity):拥有唯一标识的对象。
- 值对象(Value Object):没有唯一标识的对象。
- 工厂(Factory):定义创建实体的方法。
- 资源库(Repository):管理实体的集合并封装其持久化过程。
- 服务(Service):实现不能指派或封装在一个单一对象上的操作。
如果基于这些核心对象来看,需要增加一些对整个领域模型和模式使用的一些分析。
对于最简单的业务对象的业务操作,比如就一个单表用户信息的维护,这种场景下Repository对象下的实体CRUD方法已经够用,但是还是要通过Service对象进一步封装再暴露为服务接口。只有对于复杂对象的时候才启用聚合和工厂模式,这从减轻架构的复杂性上是可以的。
对于Service对象中的每一个方法最好都是对应明确的业务方法,这些业务方法往往是对应到业务系统前台具体的业务功能或业务操作的。如一个转账操作可以是service层的一个方法,但是在转账操作的实现过程中需要判断用户账户是否有效,用户是否有欠款,那么这些就是业务规则。
对于业务规则不需要暴露为Service对象中的具体方法,在不考虑Specification模式的时候可以将具体的业务规则直接写到Service方法里面,但是可以看到会导致Service对象变重,而对于Service对象更多应该只是下层对象的方法调用和方法组合。因此才会出现将业务规则单独抽取为独立的方法,同时新增加一个规则类类存储这些规则和方法。
在这样处理后,整个逻辑和思路和常见的SOA架构方法论就能够更好的对应和映射,即:
- 实体和仓储类:更多的是承载对象的CRUD数据操作,不承载过多的业务规则。
- 规格类:承载业务规则,是在实体和仓储类外的业务规则和逻辑校验实现等。
- Service类:对上面两类对象中方法的调用和组合,本身并没有太多的业务和规则实现。
如果按照这种方法来实现,那么Service中的方法更多都可以转化为后期的BPEL服务编排方式来实现。另外对于规则类是否可以直接访问DAO层,在书里面是可以的,即这部分规则实现是不走实体和仓储类的。
领域模型-工厂和仓储
首先为什么需要工厂Factory,因为有了聚合的概念,很多时候我们需要创建Aggregate整个聚合,创建过程很复杂,如果我们把这个创建职责分配给聚合里面的任何一个Entity来说都是不合适的,一个是暴露了聚合内部的结构破坏了边界,一个是聚合内的实体承担了本身不应该自己承担的职责。
因此应该将创建复杂对象的实例和聚合的职责转移给一个单独的对象,这个对象本身在领域模型中可能没有职责,但是它仍然是领域模型的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口应该不需要客户引用要被实例化的对象的具体类。在创建Aggregate的时候要把它作为一个整体,并确保它满足固定规则。
而设计模式里面的工厂模式,则强调的是工厂提供一个公共的接口,可以根据我们的需求灵活的返回不同的实现该接口的对象实例。对象的创建和实例化由工厂来完成,而不是简单的由对象的构造函数完成。而实际领域模型里面的Factory往往并没有这么复杂,仅仅是接管复杂聚合对象的创建和实例化而已。
对于简单的Entity,或者本身不存在聚合的时候是不需要再加上Factory类的。
对于仓储Repository需要说明两个方面的内容。一个是解决持久化的问题,一个是对数据层做屏蔽,避免应用或展现层直接跳过领域层对数据库进行操作而使领域模型最终无用。在有了Repository后,我们不再关心对象的存储和访问操作,而将重心真正转移到领域模型本身。或者叫使应用程序和领域设计与持久化技术解耦。
对于工厂和仓储的关系,工厂负责对象生命周期的开始,而仓储负责对象生命周期的中间或结束。当对象驻留在内存或对象数据库的时候很好理解。但是至少有一部分数据会持久化存在到类似关系型数据库或文件中,这样检索出来的数据就必须重建为对象形式。
对于工厂和仓储的协同,有些理解和书上有些不一致。个人理解工厂不仅仅应该关注复杂对象的创建,同时也应该关注复杂对象的保存。工厂不负责对象的持久化,工厂将持久化职责委托到仓储来完成。仓储不应该直接和应用层打交道,对于整个领域层来说。和应用层打交道的是Service接口,而和持久化层打交道的是Repository接口而已。和书里面理解最大的差异就是Repository没有保留给Client,也不是Repository委托Factory来重建对象。
举一个场景来说,根据订单号获取一个聚合复杂对象采购订单,对采购订单进行修改后再进行保存。这个时候和持久化层存在两次交互,第一次是数据的读取,第二次是修改后数据的存入。
对于数据读取,到领域层则是Factory需要实例化一个聚合对象并返回应用层。而Factory将该工作分解到聚合里美的每一个子实体,子实体通过Repository接口获取到ResultSet并进行OR转换后返回,Factory将拿到的所有实例化对象进行聚合返回一个完整的聚合对象实例。对于数据存储,仍然应该是Factory接管该操作,然后对数据进行分解后分别调用聚合中的每一个实体的仓储接口本身的保存方法,对数据进行持久化,在Factory层进行完整的事务控制并返回结果。
领域服务层的构建
在当前的领域分层架构中没有服务层的概念,在领域层和应用层都有服务,可以将服务层进一步抽取出来,服务层即向应用层提供所有的服务能力。这个服务能力不仅仅是原子服务能力,也包括了组合服务能力。对于服务层提供的服务能力,由服务层控制事务。对于进一步的服务编排,则在应用层进行完成,如果完全基于SOA参考架构,则在上面还会有BPM或BPEL层,重点是对原子服务进行组装和编排。
在领域建模中的整体思路中,我们做两个层面的理解:
- 其一是领域模型层重点是隔离传统的数据表并抽象为领域对象;
- 其二将应用层和领域模型层解耦,模型层提供的能力是以领域服务的方式暴露。
对于最贫血的领域服务层,就是一个DAL层封装的服务化,即只提供数据库表CRUD能力的服务化,在这种情况下基本满足所有的业务处理和应用需求,但是领域服务层没有任何领域逻辑,也没有领域对象的转换。
而我们这种需要的领域服务主要包括三个方面的内容:
- 其一是领域对象识别,然后将领域对象的类似CRUD操作暴露为服务;
- 其二是对于核心的业务规则的识别,将业务规则识别为业务服务;
- 其三是组合服务,根据业务场景需要将几个原子服务组合为一个更大的服务。
领域对象我前面已经谈到过,领域对象是具有完整相同的生命周期的对象,领域对象中的各个子对象不能脱离主对象独立存在。
如我们经常说的订单,合同,供应商都是领域对象;但是这些领域对象在后台往往存在多张一对多的数据表,如订单至少包括了订单头和订购明细信息等。
在领域对象识别中必须要首先识别核心的实体对象,再根据实体对象的属性需求来识别值对象,领域对象识别清楚了再根据业务场景的需求考虑领域对象的能力暴露。
领域对象中有一个核心就是将数据库表对象转化为领域对象,并将领域对象的能力暴露为粗粒度的服务,即数据库的CRUD能力转换为了对象的生命周期操作能力。但是在这种分析模式下容易遗漏业务规则转换为业务服务部分的能力需求,这类业务服务往往需要在对象关联依赖和真实业务场景和用例活动中才能够识别。
举例来说供应商领域对象开始只识别了供应商的增删改查的对象处理能力;但是在我们做采购订单和合同的时候,有一个业务规则是需要校验供应商是否有效?在这种场景下我们需要将这种独立可复用的业务规则转化为业务服务,这种业务服务相当多,也可复用,属于我们经常说的粗粒度服务范畴。
还有一类是组合服务,跟业务流程中的子流程或活动相挂钩,如银行转账,资产调拨,供应商合并,单据提交(单据保存+流程启动)等,都是典型的组合服务,往往需要调用多个原子服务或原子API操作才能够完成。这种组合服务能力可以进一步在领域服务层进行封装。
在引入了领域对象层和领域服务层后,需要对传统的分层架构进行调整。
首先是引入领域对象,对原有的数据库表对象进行第一层抽象,数据接口转换为对象接口;其次是对原有的展现层和逻辑层进行解耦,引入领域服务层,在领域服务层需要承载业务逻辑,但是又不是完全承载;初步思考的是原有的业务逻辑层的内容有1/3左右划分到应用层;而2/3左右能够下沉到领域服务层。
领域服务层贫血很多时候可以追溯到原有分层架构中本身的业务逻辑层就贫血,业务逻辑层逻辑都放到应用层和数据库存储过程中,自然转换到领域建模中也存在贫血的问题。
领域对象服务中的领域对象本身就是多个数据表的汇聚,所有对象服务可以解决对象级的事务问题,如订单保存中头和明细的操作严格控制在一个事务里面。但是对于领域服务层的组合服务仍可能存在分布式事务的问题,在这里还是建议基于BASE模式思路进行操作。还有一个方式就是对于组合服务而言,不用直接对原子服务进行组合,而是对原子服务下层的对象级操作API进行组合,这样即方便启用数据库层或逻辑层的事务进行事务控制。
基于领域驱动思路下业务系统构建
在前面已经谈到,对于贫血的领域层,主要体现在两个方面,一个是没有领域业务对象的概念(领域模型中的聚合根),一个是没有明确的粗粒度的业务规则逻辑处理层。在这种情况下,原有的业务逻辑层变化为仅仅是DAL层的一个简单封装或通道,实际的业务处理全部转化到action层或dal层进行了处理,导致无法真正提炼一个业务模块真正应该具备的领域服务能力。
粗粒度的领域服务提供包括两个方面,一个是完整的领域业务对象提供的数据服务,一个是处理业务规则和逻辑用的业务服务。在领域驱动设计中,前者部分在仓储模型中完成,后者在service中完成。这里要注意DDD里面的service,和我们基于SOA分析和设计中的服务层仍然还是有差别,所有的共性的,应该提供给应用层访问的粗粒度的能力都应该抽象为服务层的服务。
在当前的领域分层架构中没有服务层的概念,在领域层和应用层都有服务,可以将服务层进一步抽取出来,服务层即向应用层提供所有的服务能力。这个服务能力不仅仅是原子服务能力,也包括了组合服务能力。对于服务层提供的服务能力,由服务层控制事务。对于进一步的服务编排,则在应用层进行完成,如果完全基于SOA参考架构,则在上面还会有BPM或BPEL层,重点是对原子服务进行组装和编排。
实体要考虑两个层面,一个是完全的data entity,一个含操作的entity,为了考虑在SOA架构中的数据和操作分离,建议还是采用完全的data entity实体,这个时候这种实体可以进一步作为跨层传输的DTO对象。而对于原有的DAO层,也不仅仅是实现数据的持久化,很多OR-Mapping操作也会在该层完成。数据层关注的是数据对象即和数据库一一映射的表,而领域层关注的是领域对象(可能涉及多张强聚合的表);数据层最终关心的是数据对象的持久化,而领域层关心的是业务对象全生命周期的管理。
在考虑了数据和操作分离后,对于聚合根的业务对象应该有一个专门的业务对象类,来处理所有和业务对象属性,状态变化相关的操作,即控制业务对象完整的数据和生命周期。而service类本身不应该对数据进行任何cud操作,service类的所有操作都应该转入到业务对象类中进行处理。在这种模式下service类目的仅仅是处理业务规则和逻辑,最终处理结果的落地仍然通过业务对象类来完成。
业务系统的构建,不是简单的横向分层,还包括了纵向的分业务模块和业务组件。在领域驱动设计里面没有太多的对这两块结合的描述。在这里要注意两个方面的内容,一个是每一个业务组件都会向上层提供领域服务能力,第二每一个业务组件也会向其它业务组件提供领域服务能力。