上一篇:《IDDD 实现领域驱动设计-架构之经典分层》
阅读目录:
DDD 的一大好处就是并不需要使用特定的架构,经典分层架构只是一种,由于核心域位于限界上下文中,我们可以使用多种风格的架构,既然如此,我们应该把眼界看的更宽广些,有意思的东西多着呢。
SOA 和 REST 这两个货,我们都比较熟悉,他俩并不是由 DDD 引入,但却可以适用于 DDD。我个人觉得,要想把他俩发挥好,最好结合六边形架构(也可以称之为端口和适配器),至于原因,请接着往下看。
很重要的一张图,摘自:《实现领域驱动设计 P111》
1. SOA-面向服务架构
SOA(Service-Oriented Architecture),我没用过这货,下面说一下自己对于它的认识,可能不是很准确。
Service 意为服务,面向服务,也就是在 SOA 架构中,服务是核心。业务系统中分布着大量的业务模块,这些业务模块进一步提炼就是服务,然后通过规定的服务契约发布出来,这些服务集合起来就是 SOA 的核心,服务调用者可以是多样的,Web 端、移动端、桌面端都可以,也就是说它是分布式的架构,这些服务调用者调用的时候,要遵守发布者规定的一些契约和协议,而在服务本身或者之间也需要一定的契约和协议用来约束,要不然整个服务集合就会乱套,比如服务发布协议要有统一的规范,不能说一个服务是一个规范,服务之间也需要进行抽象抽离,尽可能的做到重用性等等。
因为我没用过 SOA,所以有些东西表达不出来,我用另一种概念去试着理解它,那就是 OWIN,他们可能不是一个领域的概念,但我觉得有些东西都是共通的,我们都知道 OWIN 体系结构中,有四大模块:Host、Server、Middleware 和 Application,他们之间最重要的一点就是组件化,并且任何一种模块都不依赖于彼此的任何一方,这就是自治,一种模块也构建不成 OWIN 的概念,这就是模块组合,一种模块的任意实现,都可以组合成整个 OWIN,原因是什么?因为他们彼此都遵守 OWIN 请求处理管道的协议。
回到 SOA 上面,你会发现,他们的概念其实是共通的,夏天到了,出去游玩,走着走着口渴了,我们就在路边买一个巨大的椰子,然后蜂拥而至一大堆基友,他们人手拿着一个吸管,而且种类和颜色各不相同,然后你的椰子上插满了各种习惯,看起来就像一个刺猬一样,你最后的下场可能就只有用刀划开个口子喝了,不管是用吸管,还是用到划开个口子,我们最后的目的都是喝椰子汁,只是使用的工具不同,一个椰子可能里面的汁更好喝,外面的更难喝。在这个日常生活示例中,一个椰子可以看作是一个 SOA 架构,内部就是各种小的服务(可以看作是一块一块的椰子肉,里面的更好吃),各种各样的吸管和刀子可以看作是服务调用者,而椰子皮也可以看作是服务协议,因为椰子汁需要椰子皮的包裹才不会洒出来。
SOA 架构原则:
- 服务封装
- 服务松耦合(Loosely coupled)- 服务之间的关系最小化,只是互相知道。
- 服务契约 - 服务按照服务描述文档所定义的服务契约行事。
- 服务抽象 - 除了服务契约中所描述的内容,服务将对外部隐藏逻辑。
- 服务的重用性 - 将逻辑分布在不同的服务中,以提高服务的重用性。
- 服务的可组合性 - 一组服务可以协调工作并组合起来形成一个组合服务。
- 服务自治 – 服务对所封装的逻辑具有控制权
- 服务无状态 – 服务将一个活动所需保存的资讯最小化。
- 服务的可被发现性 – 服务需要对外部提供描述资讯,这样可以通过现有的发现机制发现并访问这些服务。
SOA 相关资料:
2. REST 与 RESTful
RESTful 架构概念,是 Fielding 提出的,Fielding 这号人物就是 HTTP 协议的主要设计者之一。我们先看下 RESTful 这个词,ful 是跟在名词之后,表示程度,什么什么的,例如 helpful 乐于助人的,因此我们可以看出符合 REST 的架构就可以称为 RESTful,接着我们看下 REST,全称为“Representational State Transfer”,意为“表现层状态转化”。
在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。 -Fielding
这是 Fielding 在论文中所提到的,对于 REST 虽说是架构,但如果深入一点,就像是 HTTP 协议一样,可以看成一种规则或是协议。我们从一个地点到另一个地点,可以坐汽车、高铁、飞机等,对于 REST 就像是其中的一种交通方式,但 REST 的根本是 HTTP 协议,也就是说 REST 是基于 HTTP 协议的,这点就像坐汽车必须要有公路,坐高铁必须要有铁路是一样的道理,有时候为什么选用 REST,就像我们从南京到徐州,选择坐高铁而不选择坐飞机一样。
“Representational State Transfer”我们分解下:
- Representational 表现层:表现层表现什么,应该呈现资源(Resources),一个图片、一段文字、一个文件都成为资源,每个资源都用一个 URI(统一资源定位符)指向它,表现层就是调用 URI 把资源呈现出来,而且只是呈现,不做其他操作。举个例子:有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而 URI 应该只代表"资源"的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。
- State Transfer 状态转化:访问一个网站,就表示客户端和服务器发生一次交互行为,在这个过程中,就不发生数据和状态的转化,上面说到 HTTP 协议具有无状态性,如果客户端操作服务器,必须要状态转化,这个体现在表现层上,所以叫“表现层状态转化”。
通过上面的理解,可以总结下什么是 RESTful 架构:
- 每一个 URI 代表一种资源。
- 客户端和服务器之间,传递这种资源的某种表现层。
- 客户端通过四个 HTTP 动词(PUT、GET、POST 和 DELETE),对服务器端资源进行操作,实现"表现层状态转化"。
上面 REST 和 RESTful 的概念,摘自很久之前的一篇博文:初试ASP.NET Web API/MVC API(附Demo),并做了部分修改。
我再来说一下自己现在的理解,首先,REST 是一种架构风格,而不是一种架构,一种架构风格可以用多种架构进行实现,一个架构中也可能包含多种架构风格,这两者的关系,你可以理解为抽象和实现的区别,另外,REST 严格来说,应该属于 Web 架构的一种架构风格,因为它离不开 HTTP 协议。
REST 架构风格的两个关键:
2.1 资源(Resources)
Web 资源的表述是 URI,一个规范的 URI 就是开放出来的一个资源,它是唯一并具有一定的规范,对资源的操作方式就是 HTTP 提供的方法(PUT、GET、POST 和 DELETE),资源的表现形式是多样的,比如:JSON、XML、YAML 等。
我们看一下常用的 URI:
- GET: cnblogs.com/getUser/1
- POST: cnblogs.com/createUser
- PUT: cnblogs.com/updateUser/1
- DELETE: cnblogs.com/deleteUser/1
很显然,这种 URI 不符合 REST 对资源的定义,我们尝试修改一下:
- GET: cnblogs.com/user/1
- POST: cnblogs.com/user
- PUT: cnblogs.com/user/1
- DELETE: cnblogs.com/user/1
cnblogs.com/user/1
,这个 URI 一般表述的含义是:Id 为 1 的 User 资源,这个仅仅是表述,URI 并不包含对这个资源的任何操作,所以,像 getUser、createUser 这类操作就不合适,资源的操作是通过 HTTP 提供的方法,还有一点是,比如 POST 中的 cnblogs.com/user
和 cnblogs.com/user/1
有什么不同?第一种 POST,一般是创建一个新的 User 资源,创建完成后,一般会返回这样的一个 URI:cnblogs.com/user/1
,第二种 POST,不是说创建一个 Id 为 1 的 User 资源,而是在 Id 为 1 的 User 资源下创建某种资源,你会发现,好的 URI 设计应该不包含动词。
2.2 状态(State)
首先,REST 是无状态的(Statelessness),我之前是一直不理解状态的含义,好像还把状态和资源格式(XML、JSON)混为一谈,现在想想确实太荒谬了,关于状态的几个要点:
- 状态分为:应用状态(Application State)和资源状态(Resource State)。
- 应用状态:与某一特定请求相关的状态信息。
- 资源状态:反映了某一存储在服务器端资源在某一时刻的特定状态。
- 客户端负责维护应用状态,而服务端维护资源状态。
- 服务器端不保有任何与特定 HTTP 请求相关的资源。
REST 中的无状态其实指的是应用状态,无状态的表现是服务端不保存应用状态,也就是客户端发起与请求相关的状态,应用状态是客户端在发起请求时提供的,那状态转化是什么意思?其实指的是服务端资源状态的转化,表现在客户端中,也就是上面所说的“表现状态转化”。
在 ASP.NET 应用程序中,我们都知道 Session 的概念,意为会话,也就是有关用户请求的会话,应该划分为应用状态,这个会话状态是保存在服务端的,从这一点上来说,这种设计就是 unRESTful 风格,REST 中的无状态,是客户端和服务端交互中所表达的一种概念,有时候,虽然应用状态可能不保存在服务端,但客户端发起的某些请求所表达的含义不恰当,也可以认为是不符合 RESTful 风格,比如客户端发起的请求中包含 Session ID,在服务端看来,客户端发起的这个请求,所表达的含义是要获取某个 Session,具体来说就是会话状态保存在服务端,这个虽然只是一个客户端请求的概念,但也可以认为这种设计是 unRESTful 风格。
总的来说,架构风格不是某一种具体架构,它是一种风格。
REST 参考资料:
3. 六边形架构
上面有关 SOA、REST 的讲述,丝毫没有 DDD 的半点影子,它们并不是为 DDD 而生,在架构设计的时候,你也可以单独使用它们,但对于整体 DDD 架构设计来说,总觉得会有些不对劲,这时候,就需要了解下六边形架构。
六边形架构(Hexagonal Architecture),又称为端口和适配器架构风格,其中的“六”具体数字没有特殊的含义,仅仅表示一个“量级”的意思,六边形的定义只是方便更加形象的理解。
我们知道分层架构的重要作用就是避免耦合的出现,经典分层架构和六边形架构都是分层架构的一种,但是所发挥的作用会有些不同,经典分层架构更多的精力放在抽象的分离上,每个层的职责分的很明确,各个层的依赖关系更加抽象化,从而避免耦合的出现,而在六边形架构中,是用“组件化”的形式来避免耦合的出现,每个业务单元尽可能的最小化,然后把这些业务组件集合起来,用一个锤子把他们都拍扁,所以,在整个集合中,这些小的业务单元都是“平等的”,这种方式用一个词来概括,那就是“扁平化”。
在博文一开始的时候,说过这样一句话:由于核心域位于限界上下文中,我们可以使用多种风格的架构。
那为什么核心域位于限界上下文中,DDD 就可以使用多种风格的架构?我们来分析一下,核心域指的是业务系统中的核心业务逻辑,这个通常用通用语言表述,限界上下文是一种边界,它包裹的是核心域,也就是说,核心域并不是组件化的形式表现,你可以把它看作是一种聚合概念,用限界上下文来进行限定,这个概念在之前的博文中有说明。对于业务系统来说,核心域毫无疑问是核心概念,DDD 的专注点也就是它,开发人员和领域专家会花大量的时间去探讨它,但对于架构设计来说,核心域只是业务上的专注点,并非是架构设计上的核心点,所以,也可以这样说,DDD 和架构设计,其实严格来说应该是两个领域方面的概念,他们的结合才真正构成整体的业务系统,换句话说,最烂的架构设计配上最好的 DDD(业务上的),这也是可以的,因为 DDD 专注的是业务实现,而并非是技术实现。
我们知道,经典分层架构分为四层,而对于六边形架构,一般会分成三层:
- 领域层(Domain Layer):最里面,纯粹的核心业务逻辑,一般不包含任何技术实现或引用。
- 端口层(Ports Layer):领域层之外,负责接收与用例相关的所有请求,这些请求负责在领域层中协调工作。端口层在端口内部作为领域层的边界,在端口外部则扮演了外部实体的角色。
- 适配器层(Adapters Layer):端口层之外,负责以某种格式接收输入、及产生输出。比如,对于 HTTP 用户请求,适配器会将转换为对领域层的调用,并将领域层传回的响应进行封送,通过 HTTP 传回调用客户端。在适配器层不存在领域逻辑,它的唯一职责就是在外部世界与领域层之间进行技术性的转换。适配器能够与端口的某个协议相关联并使用该端口,多个适配器可以使用同一个端口,在切换到某种新的用户界面时,可以让新界面与老界面同时使用相同的端口。
在六边形架构中,领域层和技术没半毛钱关系,可以看作是业务的技术实现,端口层包裹在领域层在外,外部要向和领域层“交流”,则必须通过端口层的“首肯”,反过来,领域层向外面“交流”也是一样,但这种方式一般是技术上的,比如领域对象的管理:
领域层想要获取某一个领域对象,来进行业务操作实现,然后告诉端口层说:“端口小弟,哥需要一个 XXX Domain Model,立马去叫人搞!”,端口小弟心想,老大发话了,得赶紧的啊,然后就在自己胸前,贴出了这样一段告示:能逮到 XXX Domain Model 的适配器杀手们,请速速到俺这里,必有重赏!告示一贴出,适配器杀手们蜂拥而至,然后根据自己的能力来进行判断,毕竟 XXX Domain Model 也不是那么容易擒服,也不是随便一个适配器杀手就能搞定的,这需要一定的能力,最后能做的适配器杀手进行揭此告示。
以上是即兴想到的一个情节,其实就是六边形架构,从内到外的一个过程体现,反过来,从适配器层到领域层也一样,这种方式一般是接受用户请求处理开始。其实,从某种意义上来说,端口层有点像应用层,只不过是拍扁之后的应用层,而适配器层有点像基础设施层,只不过是全能型的基础设施层,六边形是环形结构,所以表述起来更加形象。
还有一点是,端口层的存在,还有利于测试的进行,这些测试不是在领域层进行的,所以它丝毫不会影响领域层的进度,对于端口层的测试,一般是测试领域层中业务的正确性。
以上是六边形架构的一些概念,那再结合 SOA 和 REST,该如何实现呢?因为在六边形架构中,适配器层是以组件性质的方式提供服务,他和领域层进行联系要通过端口层,这个端口层可以看作是服务的一种协议或规范,这就是 SOA 和 REST 的用武之地,来扮演适配器层和端口层的角色。还有一个概念不同点是,服务交互分为服务端和客户端,对于 SOA 和 REST 本身来说,他们的实现就是服务端,但在六边形架构中,他们更像是一个客户端,并且表现形式是组件化的服务,而领域层是服务端,通过端口协议来调用组件服务提供一些操作实现。
SOA、REST 和六边形架构的结合,可以和一开始的那张图进行对比:
说明:本图摘自:《实现领域驱动设计 P115》
参考资料:
距离上一篇有很长的一段时间了,经典分层架构我是用过的,所以会有很多的感触,也有很多的文字需要表达,但对于 SOA、REST 和六边形架构,我并没有真正实质性的用过,所以,对于一个你不曾接触的概念,要把一些东西写出来是很难的,写不出来,那就看书、看文章、找资料,把有些有感触的文字记录下来,然后通过自己的理解再表达出来,这种方式过程虽然很慢,但还是有一定的效果。
关于 DDD 架构设计,还有很多很多的内容,比如 CQRS、事件驱动架构、网格分布式计算等等,这个需要时间来消化。
因为没有实践过,所以难免会有一些问题,还请大家斧正!!!