作为高层次的设计维度,面向领域的策略设计同样涉及系统的体系架构。关于架构体系结构,各种架构风格和模式同样适用于领域驱动设计,但领域驱动设计在设计思想上有其独特的考虑,本节将针对领域驱动设计特有的架构风格展开讨论,包括架构的分层、事件驱动以及架构风格的组合。
1. 架构分层
(1)领域驱动设计核心组件
设计架构分层的前提是明确系统的核心组件,分层体现的就是对这些核心组件的层次和调用关系的梳理。在领域驱动设计中,一般认为存在以下四大组件:
领域组件。代表对整个领域驱动设计的核心,包含对领域、子域、界限上下文等策略设计相关内容,也包含后续所要阐述的所有技术设计组件。领域组件代表抽象模型,并不包含具体实现细节和技术。
基础设施(Infrastructure)组件。这里的基础设施组件范围比较广泛,即可以包括通用的工具类服务,也包括数据持久化等具体的技术实现方式。领域组件中的部分抽象接口需要通过基础设施提供的服务得以实现,所以基础设施组件对领域组件存在依赖关系。
应用组件(Application)。应用组件面向用户接口组件,是系统对领域组件的一种简单封装,通常作为一种门户(Facade)或网关(Gateway)对外提供统一访问入口,在用户接口和领域之间起到衔接作用。同时,因为基础设施组件是对领域组件部分抽象接口的具体实现,所以应用组件也会使用基础设施组件的服务完成具体操作。
用户接口(User Interface)组件。用户接口处于系统的顶层,直接面向前端应用,调用应用组件提供的应用级别入口完成用户操作。
显然,对领域驱动相关的核心组件划分以及各个组件之间的依赖关系并不只有上述的一种方式,无论上述划分方式是否合理或者说还存在其他的方式,都是对以下两个关键问题的阐述:
要从這两个问题中找到合理的组件划分和分层结构,我们需要理解分包原则及其在领域驱动设计中的应用。
(2)分包原则
分包相关有三条原则与分层设计有直接的关系,分别是无环依赖原则、稳定依赖原则和稳定抽象原则。无环依赖原则(Acyclic Dependencies Principle,ADP)已经在第2章中有所阐述,即在组件的依赖关系中不能出现环路,我们可以通过上移、下移、回调等手段打破循环依赖。稳定依赖原则(Stable Dependencies Principle,SDP)认为被依赖者应该比依赖者更稳定,也就是说如果包B还不如包A稳定的话,就不应该让包A依赖包B。稳定抽象原则(Stable Abstractions Principle,SAP)强调组件的抽象程度应该与其稳定程度保持一致,一个稳定的组件应该也是抽象的,这样他的稳定性就不会无法扩展,另一方面一个不稳定的组件应该是具体的,因为他的不稳定性使其内部代码更易于修改。稳定与抽象之间的关系示意可以参考下图。
另一方面,依赖倒置原则(Dependency Inversion Principle DIP)也认为,高层不应该依赖于底层,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象,同样体现了稳定与抽象之间的关系。
基于分包原则,我们可以明确在领域驱动四大组件中,领域组件作为系统的核心理应是抽象且稳定的,也就是说它应该位于系统分层的底端被其他组件所依赖。用户接口组件直接面向用户,通常是最不稳定的,自然处于系统的顶层。而应用组件处于用户接口组件和领域层之间同样没有异议。那么剩下的就是需要明确基础设施组件的定位,也就是回答领域组件的抽象接口谁去实现这一问题。
(3)分层结构
在领域组件中,为了建立完整的领域模型,势必会涉及到数据的管理。数据相关操作对于领域模型而言只是持久化的一种抽象,不应该关联具体的实现方式。比如,我们可以用关系型数据库去实现某个数据操作,有时候根据需要我们同样可以采用各种Nosql技术。显然,无论是关系型数据库操作还是Nosql技术都不应该包含在领域组件中,通常我们会使用接口的方式抽象数据访问操作,然后通过依赖注入方法把实现这些数据访问接口的组件注入到领域模型中,这些数据访问的实现我们就可以统一放在基础设施组件中,也就是说基础设施组件实现了领域组件中的抽象接口。
通过以上分析,我们可以把领域驱动设计中的四种组件分别列为四层,并梳理各个层次之间的关系形成分层结构图(见下图),该图的表现形式与上述各个组件描述是一致的。
传统的分层结构根据是否可以跨层调用可以归为两类,即严格分层架构和松散分层架构,前者认为各个层次之间不允许存在跨层调用的方式,而后者并不对此有严格限制。所以,领域驱动设计分层结构实际上是一种松散分层架构,位于系统流程上游的用户接口层和应用层,以及位于系统流程下游的的具备数据访问功能的基础设施层都依赖于抽象层,事实上已不存在严格意义上的分层概念。领域驱动设计思想认为应该推平分层架构,不使用严格的分层架构来构建系统,平面形架构(见下图)也就应运而生。
在上图中,平面形架构促使我们转换视角重新审视一个系统,划分内部和外部成为架构搭建的切入点,系统由内而外围绕领域组件展开,领域组件位于平面形架构的最内层,应用程序也可以包含业务逻辑,与领域组件构成系统的内部基础架构;而对于外部组件而言,通过各种适配器进行上下文集成,这些适配器包括数据持久化,也包括面向第三方的数据集成。基于依赖注入和Mock机制,适配器组件可以进行方便的模拟和替换。
2. 事件驱动架构
事件驱动作为一种典型的架构风格同样包含在领域驱动设计过程中,并应用于上下文集成的解耦。事件驱动架构的抽象如下图,领域事件由事件源生成,并通过事件发布器进行发布,各种事件的订阅方根据需要进行订阅。订阅方根据自身需求可以直接处理该事件,自身不能处理可以即时转发给其他订阅方,事件作为一种业务数据的载体,也可以进行存储以便后续处理。
上图中的事件源和事件的订阅者都是领域组件中的具体对象,事件的发布器作为一种基础设施通常会有一定的个性化需求,而领域事件的发布与订阅流程的闭环实现可以借助于各种具备发布-订阅风格的中间件。
事件驱动架构的发布-订阅机制非常适合与其他风格进行整合构成复合型架构风格,最典型的就是与管道-过滤器风格进行整合形成EDA+Pipeline组合。管道中流转的数据就是领域事件,而过滤器可以是一个子域中的某个组件,也可以是进行跨子域的界限上下文。图3-10的左半部分就是一个典型的管道-过滤器示例,我们看到事件发布器UserPasswordPublisher发布了一个UserPasswordChanged事件,而订阅该事件的UserPasswordHandler组件对该事件进行处理之后再次发送一个UserPasswordChangeSucceed事件,负责接收UserPasswordChangeSucceed的UserPasswordChangeSucceedHandler组件可以根据需要对该事件进行后续处理。整个过程中能够发送事件的组件实际上就是管道,而处理事件的组件就是过滤器,通过管道和过滤器的组合形成基于领域事件的管道-过滤器风格。下图的右半部分是对这种风格的更高层次的抽象,我们可以看到事件可以通过完整的领域进行交互和集成,特定的领域同时充当了管道和过滤器的角色。
如果对文章感兴趣,可以关注我的微信公众号:程序员向架构师转型,或扫描下面的二维码。
我出版了《系统架构设计:程序员向架构师转型之路》、《向技术管理者转型:软件开发人员跨越行业、技术、管理的转型思维与实践》、《微服务设计原理与架构》、《微服务架构实战》等书籍,并翻译有《深入RabbitMQ》和《Spring5响应式编程实战》,欢迎交流。