领域驱动设计(Domain Driven Design)参考架构详解
1. 架构概述
领域驱动设计(Domain Driven Design)有一个官方的sample工程,名为DDDSample,官网:http://dddsample.sourceforge.net/,该工程给出了一种实践领域驱动设计的参考架构,本文将对此该架构进行简单介绍,并就一些重要问题进行讨论。
该架构分成了Interfaces、Applications和Domain三层以及包含各类基础设施的Infrastructure。下图简略描述了它们之间的关系:
图1 领域驱动设计风格的架构草图(来自于DDDSample官网)
下图是详细架构:
图2 领域驱动设计参考架构
作为参照,下图展示了传统TransactionScript风格的架构,可以看出,两者的差异并不是太大(对于Fa?ade来说,它是一种可选设施,如果系统架构中省略Fa?ade,则DTO与领域对象的互换工作可在service中进行),这也从则面说明推行领域驱动设计的关键并不在架构上,而在于整个团队在分析、设计和开发上没有自始至终地以领域模型为核心开展工作,以面向对象的思想进行设计和编程。
Transaction Script风格的架构具有明显的“数据”与“操作”分离的特征,其和领域驱动设计风格的架构在两个类组件上有质的区别,一个是领域对象,一个是Service。领域驱动设计的架构核心目标是要创建一个富领域模型,其典型特征是它的领域对象具有丰富的业务方法用以处理业务逻辑,而Transaction Script风格的领域对象则仅仅是数据的载体,没有业务方法,这种领域也被称作“贫血的领域对象”(Anemic Domain Objects)。在Service方面,领域驱动设计的架构里Service是非常“薄“的一层,其并不负责处理业务逻辑,而在TransactionScript风格的架构里,Service是处理业务逻辑的主要场所,因而往往非常厚重。
图3. 数据与操作分离的Transaction Script风格的架构
2. 架构详解
2. 1 Interfaces-接口层
领域驱动设计对Interfaces的定位是:
Thislayer holds everything that interacts with other systems, such as web services,RMI interfaces or web applications, and batch processing frontends. It handlesinterpretation, validation and translation of incoming data. It also handlesserialization of outgoing data, such as HTML or XML across HTTP to web browsersor web service clients, or DTO classes and distributed facade interfaces forremote Java clients.
该层包含与其他系统进行交互的接口与通信设施,在多数应用里,该层可能提供包括Web Services、RMI或Rest等在内的一种或多种通信接口。该层主要由Facade、DTO和Assembler三类组件构成,三类组件均是典型的J2EE模式,以下是对三类组件的具体介绍:
2.1.1 DTO
DTO- DataTransfer Object(数据传输对象),也常被称作VO-Value Object(值对象)。基于面向对象技术设计的领域对象(即通常所说的“实体”)都是细粒度的,将细粒度的领域对象直接传递到远程调用端需要进行多次网络通信,DTO在设计之初的主要考量是以粗粒度的数据结构减少网络通信并简化调用接口。以下罗列了DTO的多项作用:
- Reduces network traffic
- Simplifies remote object and remote interface
- Transfers more data in fewer remote calls
- Reduces code duplication
- Introduces stale transfer objects
- Increases complexity due to synchronization and version control
图4. DTO应用时序图(基于《Core J2EE Patterns》插图进行了修改)
值得一提的是,DTO对实现一个独立封闭的领域模型具有积极的作用,特别是当系统使用了某些具有自动脏数据检查(automatic dirty checking)机制的ORM框架时,DTO的优势就更加明显,否则就会存在领域对象在模型层以外被意外修改并自动持久化到数据库中的风险或者是像Hibernate那样的框架因未开启OpenSessionInView (注:开启OpenSessionInView有副作用,一般认为OpenSessionInView不是一种好的实践)而导致Lazy Loading出现问题。
关于DTO具体的设计用意和应用场景可参考如下资源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
2.1.2 Assembler
在引入DTO后,DTO与领域对象之间的相互转换工作多由Assembler承担,因此Assembler几乎总是同DTO一起出现。也有一些系统使用反射机制自动实现DTO与领域对象之间的相互转换,Appache的Commons BeanUtils就提供了类似的功能。应该说这两种实现各有利弊,使用Assembler进行对象数据交换更为安全与可控,并且接受编译期检查,但是代码量明显偏多。使用反射机制自动进行象数据交换虽然代码量很少,但却是非常脆弱的,一旦对象属性名发生了变化,数据交互就会失败,并且很难追踪发现。总体来说,Assembler更为直白和稳妥。
图5. Assebler应用类图(基于《Core J2EE Patterns》插图进行了修改)
关于Assembler具体的设计用意和应用场景可参考如下资源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
2.1.3 Facade
作为一种设计模式同时也是Interfaces层内的一类组件,Facade的用意在于为远程客户端提供粗粒度的调用接口。Facade本身不处理任何的业务逻辑,它的主要工作就是将一个用户请求委派给一个或多个Service进行处理,同时借助Assembler将Service传入或传出的领域对象转化为DTO进行传输。以下罗列了Facade的多项作用:
- Introduces a layer that provides services to remote clients
- Exposes a uniform coarse-grained interface
- Reduces coupling between the tiers
- Promotes layering, increases flexibility and maintainability
- Reduces complexity
- Improves performance, reduces fine-grained remote methods
- Centralizes security management
- Centralizes transaction control
- Exposes fewer remote interfaces to clients
实践Facade的过程中最难把握的问题就是Facade的粒度问题。传统的Service均以实体为单位进行组织,而Facade应该具有更粗粒度的组织依据,较为合适的粒度依据有:一个高度内聚的模块一个Facade,或者是一个“聚合”(特指领域驱动设计中的聚合)一个Facade.
图6. Facade应用类图(基于《Core J2EE Patterns》插图进行了修改)
图7. Facade应用时序图(基于《Core J2EE Patterns》插图进行了修改)
关于Assembler具体的设计用意和应用场景可参考如下资源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
- 《Design Patterns: Elementsof Reusable Object-Oriented Software》
2.2 Application-应用层
领域驱动设计对Application的定位是:
Theapplication layer is responsible for driving the workflow of the application,matching the use cases at hand. These operations are interface-independent andcan be both synchronous or message-driven. This layer is well suited forspanning transactions, high-level logging and security. The application layeris thin in terms of domain logic - it merely coordinates the domain layerobjects to perform the actual work.
Application层中主要组件就是Service,在领域驱动设计的架构里,Service的组织粒度和接口设计依据与传统Transaction Script风格的Service是一致的,但是两者的实现却有着质的区别。TransactionScript风格的Service是实现业务逻辑的主要场所,因此往往非常厚重。而在领域驱动设计的架构里,Application是非常“薄”的一层,所有的Service只负责协调并委派业务逻辑给领域对象进行处理,其本身并真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了,这是区别系统是Transaction Script架构还是Domain Model架构的重要标志。
不管是Transaction Script风格还Domain Model风格,Service都会与多种组件进行交互,这些组件包括:其他的Service、领域对象和Repository 或 DAO。
图8. Service应用时序图(基于《Core J2EE Patterns》插图进行了修改)
Service的接口是面向用例设计的,是控制事务、安全的适宜场所。如果Facade的某一方法需要调用两个以上的Service方法,需要注意事务问题。
2.3 Domain-领域层
领域驱动设计对Domain的定位是:
Thedomain layer is the heart of the software, and this is where the interestingstuff happens. There is one package per aggregate, and to each aggregatebelongs entities, value objects, domain events, a repository interface andsometimes factories.
Thecore of the business logic belongs in here. The structure and naming ofaggregates, classes and methods in the domain layer should follow theubiquitous language, and you should be able to explain to a domain expert howthis part of the software works by drawing a few simple diagrams and using theactual class and method names of the source code.
Domain层是整个系统的核心层,该层维护一个使用面向对象技术实现的领域模型,几乎全部的业务逻辑会在该层实现。Domain层包含Entity(实体)、ValueObject(值对象)、Domain Event(领域事件)和Repository(仓储)等多种重要的领域组件。
2.4 Infrastructure-基础设施层
领域驱动设计对Infrastructure的定位是:
Inaddition to the three vertical layers, there is also the infrastructure. As thethe picture shows, it supports all of the three layers in different ways,facilitating communication between the layers. In simple terms, theinfrastructure consists of everything that exists independently of ourapplication: external libraries, database engine, application server, messagingbackend and so on.
Also,we consider code and configuration files that glues the other layers to theinfrastructure as part of the infrastructure layer. Looking for example at thepersistence aspect, the database schema definition, Hibernate configuration andmapping files and implementations of the repository interfaces are part of theinfrastructure layer.
Whileit can be tricky to give a solid definition of what kind of code belongs to theinfrastructure layer for any given situation, it should be possible tocompletely stub out the infrastructure in pure Java unit/scenario tests andstill be able to use the domain layer and possibly the application layer towork out the core business problems.
作为基础设施层,Infrastructure为Interfaces、Application和Domain三层提供支撑。所有与具体平台、框架相关的实现会在Infrastructure中提供,避免三层特别是Domain层掺杂进这些实现,从而“污染”领域模型。Infrastructure中最常见的一类设施是对象持久化的具体实现。
3. 关于架构的一些讨论
3.1 架构并不能保证领域驱动设计的贯彻与执行
虽然一个合适的架构对于实施领域驱动设计是大有必要的,但只依靠架构是不能保证领域驱动设计的贯彻与执行的。实际上,在这个参考架构上使用Transaction Script的方式进行开法几乎没有任何问题,只要开发人员将领域对象变成“贫血”的“数据载体”对待,在service里实现业务逻辑,那么该参考架构将成为纯粹的TransactionScript方式。当然反过来看,这也体现了这一架构的灵活性。确保领域驱动设计的贯彻与执行需要整个团队在分析、设计和开发上没有自始至终地以领域模型为核心开展工作,以面向对象的思想进行设计和编程,才能保证实现领域驱动设计。
3.2 Facade是否是必须的?
尽管在架构中对Facade的定义非常清晰,但在实践中我发现Facade并不是一个容易拿捏的东西。主要问题在于其与Service之间的有太多的重叠与相似之处。我们注意到Service是接口是面向一个use case的,因此事务也是追加在Service这一层上,于是对于Facade而言,99%的情况是,它只是把某个Service的某个方法再包裹一下而已,如果把领域对象和DTO的互转换工作移至Service中进行,那么Facade将彻底变成空壳,而关键的是:如果Service的接口设计是面向和user case的,那么,毫无疑问,Service接口的传入传出参数也都应该是DTO,而这一点也在《Core J2EE Patterns: Best Practices and Design Strategies, SecondEdition》和《Patterns of Enterprise ApplicationArchitecture》两书的示例代码中完全印证了。那么,从更为务实角度出发,Facade并非是一种必须的组件。