不容置疑的鲜有真理,多为裹着权威的谬误。

01. 引言

作为一名患有重度代码洁癖的程序员,最让我苦恼的就是在工作中去修改别人写的代码的Bug了。每每看到大量晦涩、冗余、没有细细思考就胡乱写就的代码,我都得先忍着性子去一行一行地理解这个人想要通过这一长串代码(通常是放在一个无所不能的Service里面的)去做什么事的时候,我就越来越觉得所有程序员都应该去学学设计模式,至少面向对象语言的程序员们是非常有必要的。

但另一方面,设计模式在很多情况下都作为一种可有可无的软性技能存在,并且很多时候由于项目赶工的因素,所以大部分软件开发团队也不会强制每位成员必须100%去遵循特定的设计模式。没有一种设计模式是非遵循不可的,这就是尴尬之处。

可是话又说回来,遵循常见的设计模式尽管在一开始可能显得有点繁琐,可是我觉得是利大于弊的,是能显著提高代码质量,减少程序的Bug数量的。

作为领域驱动设计(Domain Driven Design, DDD)的忠实粉丝,我想在这篇文章中讲讲领域驱动设计的优点。

我和领域驱动设计的初次邂逅还是在第一家公司工作的时候。那个时候对设计模式甚至都知之甚少,可是有一天我被项目组长分配去修改一个此前从没接触的微服务模块的Bug。克隆好这个模块的代码试着看了一下,看到各种不知所云的application、domain、infrastructure的包名,骨子里都刻着传统Web三层架构的基因的我顿时感到手足无措了。于是不得不去了解一下这是种什么设计模式。最终找到了Eric Evans的《领域驱动设计》这本书。可是Eric Evans在这本书里主要从思想层面阐述了这种模式语言,所以理解曲线对我来说有些过于陡峭了。然后我找来了Vaughn Vernon的《实现领域驱动设计》这本书,这本书更多的是从具体实现去阐释领域驱动设计,读完觉得比Eric Evans的书更通俗易懂些。最后读了国内作者张译的《解构领域驱动设计》,又加深了对于领域驱动设计的理解。(顺便提一下,《解构领域驱动设计》的作者张译就是我第一家公司的技术总监,也是《实现领域驱动设计》的中文译者。现在想来也好荣幸能和这样优秀的前辈共事过)。

02. 什么是领域驱动设计

领域驱动设计是对面向对象设计的一种补充,一开始是Eric Evans在《领域驱动设计》一书中展示的一整套设计模式语言。

领域驱动设计,顾名思义,即把关注点放在领域建模的程序设计,即以领域专家的视角建立的与该领域匹配的软件模型。技术实现则是完成系统业务目标的辅助手段。明确地划分了领域与技术实现之间的边界,隔离技术实现对领域建模的干扰。

03. 领域、子域和限界上下文

理解领域、子域和限界上下文的三个概念对于理解什么是领域驱动设计至关重要。

一般来说,领域(Domain)是软件开发过程涉及到的现实世界的业务概念,是需求分析师写的需求文档内的业务需求,也是软件系统中程序需要实现的功能。

“领域”的概念既可以广义地泛指一个组织所涉及的所有业务,也可以狭义地特指“领域”之下的其中一个“子域”。继续以上面的图书电商系统为例,整个图书电商系统可以是一个“领域”,图书电商系统下的产品业务、订单业务、物流业务也可以是三个不同的“领域”,产品业务、订单业务和物流业务同时也是隶属于图书电商“领域”的“子域”。

限界上下文则是由明确的边界圈定的语境,这种边界既可以是多个系统组成的系统集群的系统边界,也可以是多模块项目中的模块间的边界,还可以是语言的包目录划分的界限。理想情况下,一个限界上下文只容纳一个子域,但也可能容纳多个子域。

一个良好实施领域驱动设计的项目应该体现“高内聚、低耦合”的设计原则,高度内聚的术语应该放到一个上下文内,并尽可能减少上下文之间的依赖,各领域在自治的边界内完成属于该领域的工作。以图书电商系统的“用户”这个术语为例,在登录系统、获取系统访问权限时,用户是被放在“权限认证”的上下文语境下讨论的;在设定图书的作者时,用户则是被放在“产品”的上下文语境下讨论的。尽管“权限认证”上下文的“用户”与“产品”上下文的“作者”很可能代表一个人,但是它在不同的上下文中代表的角色和身份是不一样的。在这种情况下,我们就应该在不同的上下文中针对不同语境创建不同的领域模型。

04. 领域驱动设计的架构

领域驱动设计并不强制使用特定架构,因此,我们可以根据需要选择不同风格的架构。下面列举一些在DDD社区广泛应用的架构。

4.1 分层架构下的DDD

分层架构下的DDD模式由经典的Web三层架构(包含用户界面层、业务逻辑层和数据访问层)改良而来。分层架构的DDD在用户界面层和业务逻辑层之间引入了应用层,同时将业务逻辑层更名为领域层,数据访问层更名为基础设施层,表明该层次不仅用于封装数据访问的技术实现的语义。

DDD之父Eric Evans对各层做了简单的描述:

层次 职责
用户界面层(展现层)
负责向用户展现信息以及解释用户命令
应用层
定义软件要完成的任务,并指挥表达领域概念的对象来解决问题。不包含业务逻辑,不保留业务状态,但保留应用任务的进度状态
领域层
业务软件的核心。用于表达业务概念、业务状态信息以及业务规则。
基础设施层
为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件等

分层架构遵循“技术为本,用户至上”的认知规则,层次越往上,就越面向用户和业务;层次越往下,就越通用,越面向技术。层间关系是正交的。至于引入应用层,则是为了给调用者提供完整的业务用例,使调用者无需与细粒度的领域模型直接协作。

自顶向下的分层结构并不意味着我们必须使用自顶向下的依赖模式。根据面向对象设计的依赖倒置原则(Dependency Inversion,DI),高层模块不应该依赖于低层模块,二者都应该依赖于抽象。遵循这一原则,作为调用者的高层模块应该依赖低层模块的抽象。这样的依赖方式可以避免高层模块受制于低层模块。

以一个使用分层架构DDD的书店系统的订单上下文为例,我们可以将不与具体技术实现挂钩的领域事件发布者接口放到领域层,将基于 RabbitMQ 的领域发布者实现放到基础设施层(也即防腐层)。

com.bookstore.order.domain 包下的 Event Publisher 接口:

				
					package com.bookstore.order.domain;

public interface EventPublisher {
    void publish(Event... events);
}
				
			

com.bookstore.order.infrastructure.messaging包下的 Event Publisher 实现:

				
					package com.bookstore.order.infrastructure.messaging;

public class RabbitEventPublisher implements EventPublisher {

    @Override
    public void publish(Event... events) {

        Stream.of(events).parallel().forEach(event -> {
            if (event instanceof OrderCreated) {
                queueOrderCreated.offer((OrderCreated) event);
            }
            //...
        });
    }
}
				
			

依赖于EventPublisher的Order应用服务调用EventPublisher发布订单生成的领域事件:

				
					package com.bookstore.order.application;

public class OrderApplicationService {
	private EventPublisher eventPublihser;

    public void createOrder(OrderingRequest request) {
        Order order = Order.create().ofBook(request.getBookId()).ofAmount(request.getAmount());
        BookReview bookReview = bookClient.check(order);
        if (bookReview.isUnavailable()) {
            throw InvalidOrderException.unavailableBook();
        }
        orderRepo.save(order);
        OrderCreated orderCreated = new OrderCreated(order.id(), order.bookId(), order.amount(),
                order.createdAt());
        eventPublisher.publish(orderCreated);
    }
}
				
			

这样,如果后续想要更换消息中间件实现,只需要替换掉基础设施层的 RabbitEventPublisher,领域层的 EventPublisher 接口及依赖该组件的下游调用者不受影响。层次之间的变化互不干扰。

4.2 六边形架构下的DDD

相比于分层架构从上到下的层级结构,六边形架构呈现出从外到内的层级结构,将领域逻辑封装在六边形的边界内,通过入口和出口两个方向的端口和适配器与外界通信。就像洋葱一样,最有价值的部分层层包裹在洋葱皮内。

引用

  1. 《领域驱动设计——软件核心复杂性应对之道》(Eric Evans 著,人民邮电出版社)
  2. 《实现领域驱动设计》(Vaughn Vernon 著,电子工业出版社)
  3. 《解构领域驱动设计》(张译 著,人民邮电出版社)