概念

DDD 的一大好处是不需要使用特定的架构,由于核心域位于限界上下文中,我们可以在整个系统中使用多种风格的架构。在选择架构风格和架构模式时应将软体质量考虑在内,同时避免滥用架构风格和架构模式;对架构风格和模式的选择收到功能需求的限制,换句话说,在没有功能需求的情况下,我们是不能对软体质量做出评判的,也不能做出正确的架构选择。

分层

分层架构被认为是所有架构的鼻祖,它支持 N 层架构系统,因此被广泛应用于 Web、企业级应用和桌面应用,在这种架构中,我们将一个应用程序或者系统分为不同的层次。

分层架构的重要原则是每层只能与其下方的层发生耦合,按照严苛程度又可以细分为严格分层架构和松散分层架构,前者只允许与直接位于其下方的层发生耦合,后者则放宽到允许与任意上层与任意下层发生耦合。事实上,较低层也可以和较高层发生耦合,但局限于采用观察者模式(Observer)或者协调者模式(Mediator)。

作者指出用户界面只用于处理用户显示和用户请求,不应该包含领域或业务逻辑。应用服务位于应用层,与领域服务不同,应用服务本身并不处理业务逻辑但却是领域模型的直接客户。并且应用服务应该是轻量的,主要协调对领域对象的操作。例如:

如果应用服务比上述功能复杂得多,通常意为著领域逻辑渗透到应用服务中了,此时也就说明领域模型将变成贫血模型,因此最佳方案是将应用层做薄。

依赖倒置原则

依赖倒置原则可以改进分层架构,即底层服务应该依赖于高层组件所提供的介面。

采用依赖倒置原则,使领域层和基础设施层都只依赖于由领域模型所定义的抽象介面。

六边形架构(埠与适配器)

使用依赖注入的架构自然具有了埠与适配器风格。我们通常将客户与系统交互的地方称为前端,将获取数据、存储持久化、发送数据的地方称为后端,但是六边形架构提倡用一种新的视角看待整个系统。在外部区域,不同客户均可提交输入;内部系统则用于获取持久化数据,并对程序输出进行存储。

图中每种客户都有自己的适配器,它用于将客户输入转化为程序内部API所能理解的输入,六边形每条边代表不同类型的埠,埠要么处理输入要么处理输出。例如,可能ABC三者使用HTTP协议,D使用了AMQP协议。埠没有明确的定义,它是非常灵活的概念。

针对图右侧的适配器,我们可以理解为持久化适配器,用于访问先前存储的聚合实例,或者保存新的聚合实例。我们可以通过不同的方式实现资源库,例如关系型资料库、基于文档的存储、分散式缓存和内存存储等。

例子

这段代码很常见,通过 JAX_RS 发布的 RESTful 资源,当请求到达 HTTP 的输入埠时,相应的适配器将对请求的处理委派给应用服务

六边形架构的好处在于,我们可以轻易开发用于测试的适配器。整个应用程序和领域模型可以在没有客户和存储机制的条件下进行设计开发,在测试时我们可以方便的对 ProductService 进行替换,无需考虑它是应该支持 HTTP/REST 还是 SOAP 或者是消息埠等问题。

如果你采用的是严格分层架构,那么你应该考虑推平这种架构,采用埠与适配器。六边形架构也可以用来支持系统中的其他架构,例如 CQRS、SOA、MapReduce 等。

面向服务架构

即 SOA,我们先看一些 SOA 的原则:

我们可以将这些原则和六边形架构结合,如下图,服务边界位于最左侧,领域模型位于中心。消费方可以通过 REST、SOAP 和消息机制获取服务。

这里有一个共识的观点:业务服务可以由任意数量的技术服务来提供。技术服务可以是 REST 资源、SOAP 介面或者消息类型。业务服务强调业务战略,即如何对业务和技术进行整合。

REST

REST 是一种架构风格,架构风格之于架构就像设计模式之于设计一样,它将不同架构所共有的东西抽象出来,使得我们不至于陷入技术细节中。分散式系统架构存在著多种架构风格,包括客户端-服务端和分散式对象风格。符合REST原则的系统具有更好的松耦合性和可伸缩性,且容易被理解,每个资源块都可以独立测试和调试。

REST 在伺服器端对资源有一个唯一标识,每个URL都需要指向某个资源,另一个关键是无状态通信。客户端可以通过两种不同的方式在不同资源间进行转移,一是上面所提到的超媒体,一种是伺服器端的重定向。

虽然 REST HTPP 具有诱惑力但是我们不建议将领域模型直接暴露在外,这样会使得系统介面非常脆弱,我们有两种方式将 DDD 和 RESTful HTTP 合并使用。第一是为系统介面单独创建一个限界上下文,通过资源抽象将系统功能暴露给外界;第二是创建一个领域模型来处理每一种媒体类型。

命令和查询职责分离——CQRS

将领域数据映射到界面显示中,就是 CQRS,在对象层面 CQRS 意味著:

  1. 如果一个方法修改了对象的状态,该方法是一个命令,它不应该返回数据,在 Java 中这样的方法应该声明为 void
  2. 如果一个方法返回了数据,该方法便是一个查询,此时它不应该通过直接或间接的手段修改对象的状态,在Java中这样的方法应该以其返回的数据类型进行声明

我们现在讲资源库只支持 add 或者 save 方法,同时只有一个查询方法 fromId,禁止其他例如通过属性过滤的查询方法,此时的模型称为命令模型(Command Model),但是我们仍需要向用户展示数据,此时我们创建第二个模型,专门用于优化查询称之为查询模型(Query Model)

查询模型是一种非规范化的数据模型,并不反映领域行为,只用于数据展示。

事件驱动架构

作者举例了一个 Linux 下的 Shell 命令阐述消息的管道与过滤器机制。主要特点是将一个业务流程的大问题分解为若干小问题,使得分散式处理更容易理解和管理。

参考资料

《实现领域驱动设计》美·弗农著


推荐阅读:
相关文章