DDD方法中并没有指定使用特定的架构。领域中的BC被封装为高内聚的模块,这种特性让DDD对架构并没有太大侵入性。架构可以应用于领域内部的结构,也可以包围着领域模型,系统中可以采用多种风格的架构。

架构是指构成一个系统的主要元素及它们之间的主要关联,这些元素和关联能够反映该系统的本质特征。

选择架构应该了解架构的来源和所要解决的问题,从业务和问题出发,避免滥用架构。例如分布式应用的架构有REST、DO(分布式对象)和RPC,REST架构是Web风格的架构,而DO和RPC是面向企业应用的,技术和架构也不是一个概念,例如用了HTTP不一定就是Web架构。

以建筑为例,苏州园林是一种建筑风格,欧洲的哥特式教堂也是一钟建筑风格,在苏州园林中放个哥特式教堂就让让人感觉画风突兀。

分层架构

分层架构是一种历史悠久的架构,通过分层架构,可以将系统按不同职责组织成有序层次,由于这种划分往往比较容易界定,也算是最常见和最受欢迎的一种架构,有一个说法是:“如果你不知道要用什么架构,那就用它。

Fowler在《Patterns of Enterprise Application Architecture》中对分层的定义是:

当我们说一个系统是分层架构的时候,你可以把这个软件想象成一个有很多层的蛋糕的样子,其中每一层放在它的下一层上。较高层使用诸多较低层定义和提供的服务,但较低层并没有察觉较高层的存在。另外,每一层都会对其上层隐藏更低的层。

介绍分层的文章和例子都比较多,不再赘述,简要说下DDD中的分层模型。

在分层架构中,我们将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用层逻辑的依赖,因为它们不属于业务逻。将一个复杂的系统分为不同的层,每层都应该具有良好的内聚性,并且只依赖于比其自身更低的层。[Evans, Ref, P16]

一个DDD中的分层架构可以如下图所示,通过把领域层单独分离出来,负责表达业务概念,业务状态以及业务规则,形成对领域知识的集中并形成业务软件的核心。


DDD 领域驱动设计 - 架构(分层/六边形/RESTful)_自动化测试

DDD分层架构


不过从分层架构图中可以发现,将基础设施层放入底层是存在缺点的,领域层依赖于基础设施层,这对领域层的内聚性产生影响。一个解决方案就是依赖倒置。

依赖倒置

依赖倒置的原则(DIP)由Robert C. Martin提出,核心的定义是:

高层模块不应该依赖于底层模块,两者都应该依赖于抽象

抽象不应该依赖于实现细节,实现细节应该依赖于接口

按照DIP的原则,领域层就可以不再依赖于基础设施层,基础设施层通过注入持久化的实现就完成了对领域层的解耦,采用依赖注入原则的新分层架构模型就变成如下所示:


DDD 领域驱动设计 - 架构(分层/六边形/RESTful)_自动化测试_02

DIP分层


采用了依赖注入方式后,其实可以发现事实上已经没有分层概念了。无论高层还是底层,实际只依赖于抽象,整个分层好像被推平了,这就引入下一个架构六边形架构。

六边形架构(Hexagonal architecture)

六边形架构是Alistair Cockburn在2005年提出,解决了传统的分层架构所带来的问题,实际上它也是一种分层架构,只不过不是上下或左右,而是变成了内部和外部。在《实现领域驱动设计》一书中,作者将六边形架构应用到领域驱动设计的实现,六边形的内部代表了application和domain层。外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。

DDD 领域驱动设计 - 架构(分层/六边形/RESTful)_领域模型_03

六边形架构

按照领域分层的模型,在应用层和领域层内置后,一个典型的六边形架构应用有两个端口,一个端口对应用户接口层,用于应用控制,一个对应数据访问层,用于数据获取和持久化。每个端口都可以对应几个适配器,该应用可以被自动化测试,系统层面的回归测试,用户交互操作,远程HTTP调用,REST调用或者其他。在数据方面,通过配置使用外部的数据库,可以是Oracle数据库,mock的数据库,测试数据库或生产数据库,从而实现应用和外部数据库的解耦。

另外值得一提的是,在六边形架构中,自动化测试和用户具有同等的地位,在实现用户界面的同时就需要考虑自动化测试。它们对应相同的端口。六边形架构不仅让自动化测试这件事情成为设计第一要素,同时自动化测试也保证应用逻辑不会泄露到用户界面,在技术上保证了层次的分界。

使用六边形架构的时候,我们应该根据用例来设计应用程序,而不是需要支持的客户数量来设计。任何客户都可能向不同的端口发出请求,但是所有的适配器都使用相同的API。

六边形架构的功能非常强大,可以作为基层架构并用于支持系统的其他架构。


面向服务架构

SOA

REST

REST表述性状态传递( Representational State Transfer),是 Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。

REST全称是 Resource Representational State Transfer,通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:


  1. Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等;
  2. Representational:某种表现形式,比如用JSON,XML,JPEG等;
  3. State Transfer:状态变化。通过HTTP动词实现。

作为一种被广泛使用,甚至被滥用的架构流行语,很多人了解REST是把它作为一种Web通信协议,但实际REST是这是一种架构风格。架构风格之于架构就像设计模式之于设计一样。它将不同架构实现所共有的东西抽象出来,使得我们在谈及到架构时不至于陷人技术细节中。

作为一种架构风格,就需要遵循其提出的一系列架构级约束。REST的约束有:


  • 客户-服务器(Client-Server),通信只能由客户端单方面发起,表现为请求-响应的形式。
  • 无状态(Stateless) 通信的会话状态(Session State)应该全部由客户端负责维护。
  • 缓存(Cache) 响应内容可以在通信链的某处被缓存,以改善网络效率。
  • 统一接口(Uniform Interface) 通信链的组件之间通过统一的接口相互通信,以提高交互的可见性。
  • 分层系统(Layered System)通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
  • 按需代码(Code-On-Demand,可选)

如果一个系统满足了上面所列出的约束,那么该系统就被称为是RESTful的。当然一旦遵循了以上约束,也意味着这个系统就会享受到这种架构风格带来的好处。实际上HTTP1.1也正是基于REST架构风格来设计的,REST架构也是Web之所以取得成功的技术架构方面因素的总结。

符合REST原则的系统将具有更好的松耦合性。通常来讲,添加新资源并在已有资源中创建到新资源的链接是非常简单的。另外基于 REST的系统也是非常容易理解的,因为此时系统被分为很多较小的资源块,每一个资源块都可以独立地测试和调试,并且每一个资源块都表示了一个可重用的人口点。HTTP设计本身以及URI成熟的重写与缓存机制使得 RESTful HTTP成为一种不错的架构选择,该架构具有很好的松耦合性和可伸缩性。

REST和DDD

领域模型通过REST直接暴露给外界并不被推荐,要将 DDD与 RESTful HTTP合并起来使用,推荐的方式是:为系统接口层单独创建一个限界上下文,再在此上下文中通过适当的策略来访问实际的核心模型。它将系统接口看作一个整体,通过资源抽象将系统功能暴露给外界,而不是通过服务或者远程接口。

另一种方法用于需要使用标准媒体类型的时候。如果某种媒体类型并不用于支持单个系统接口,而是用于一组相似的客户端.服务器交互场景,此时我们可以创建一个领域模型来处理每一种媒体类型。这样的领域模型甚至可以在服务器和客户端之间进行重用。

这也是一种由外向里的、横切式的方法。在上面提到的工作组例子中,有多种常用的格式可以用于领域模型。比如 iCal格式(一种标准的互联网日历格式)。我们首先选择一种媒体类型,即 iCal,然后再根据这种格式创建领域模型。该模型可以用于任何能够理解 ical格式的系统,比如服务器程序,或者 Android客户端。在采用这种方法时,服务器需要处理多种类型的媒体类型,而同一种媒体类型又可以用于多个服务器。

如何在以上两种方法之间进行选取呢?这在很大程度上取决于系统设计者对可重用性上的要求。第一种方法比较适合更加专属的系统,而第二种方法更适合那些通用的系统。


两个建议:


  • 设计好REST的API接口后,通过Application层去顺序调用合适的领域对象,这也正是Application层的价值所在。
  • 不要盲目的遵循教条的理论。我们应该尽可能遵循各种理论和最佳实践,但是如果不行的时候就先把这些放到一边去(当然这需要在慎重考虑后)。