DDD领域驱动设计(Domain Driven Design)转

摘要

本文将介绍领域驱动设计(Domain Driven Design)的官方参考架构,该架构分成了Interfaces、Applications和Domain三层以及包含各类基础设施的 Infrastructure。本文会对架构中一些重要组件和问题进行讨论,给出一些分析结论。

目录

  1. 架构概述
  2. 架构详解
    2.1. Interfaces-接口层
    2.1.1. DTO
    2.1.2. Assembler
    2.1.3. Facade
    2.2. Application-应用层
    2.3. Domain-领域层
    2.4. Infrastructure-基础设施层
  3. 关于架构的一些讨论
    3.1. 架构并不能保证领域驱动设计的贯彻与执行
    3.2. Fa?ade是否是必须的?

  4. 架构概述

领域驱动设计(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等在内的一种或多种通信接口。该层主要由Façade、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具体的设计用意和应用场景可参考如下资源:

1.《Core J2EE™ Patterns: Best Practices and Design Strategies,SecondEdition》

2.《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具体的设计用意和应用场景可参考如下资源:

1.《Core J2EE™ Patterns: Best Practices and Design Strategies,SecondEdition》

2.《Patterns of Enterprise ApplicationArchitecture》

2.1.3. Facade

作为一种设计模式同时也是Interfaces层内的一类组件,Façade的用意在于为远程客户端提供粗粒度的调用接口。Façade本身不处理 任何的业务逻辑,它的主要工作就是将一个用户请求委派给一个或多个Service进行处理,同时借助Assembler将Service传入或传出的领域 对象转化为DTO进行传输。以下罗列了Façade的多项作用:

  • 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

实践Façade的过程中最难把握的问题就是Façade的粒度问题。传统的Service均以实体为单位进行组织,而 Façade应该具有更粗粒度的组织依据,较为合适的粒度依据有:一个高度内聚的模块一个Façade,或者是一个“聚合”(特指领域驱动设计中的聚合) 一个Façade.


图6.Façade应用类图(基于《Core J2EE Patterns》插图进行了修改)


图7.Façade应用时序图(基于《Core J2EE Patterns》插图进行了修改)

关于Assembler具体的设计用意和应用场景可参考如下资源:

1.《Core J2EE™ Patterns: Best Practices and Design Strategies,SecondEdition》

2.《Patterns of Enterprise ApplicationArchitecture》

3.《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的接口是面向用例设计的,是控制事务、安全的适宜场所。如果Façade的某一方法需要调用两个以上的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中最常见的一类设施是对象持久化的具体实现。

  1. 关于架构的一些讨论

3.1. 架构并不能保证领域驱动设计的贯彻与执行

虽然一个合适的架构对于实施领域驱动设计是大有必要的,但只依靠架构是不能保证领域驱动设计的贯彻与执行的。实际上,在 这个参考架构上使用Transaction Script的方式进行开法几乎没有任何问题,只要开发人员将领域对象变成“贫血”的“数据载体”对待,在service里实现业务逻辑,那么该参考架构 将成为纯粹的TransactionScript方式。当然反过来看,这也体现了这一架构的灵活性。确保领域驱动设计的贯彻与执行需要整个团队在分析、设 计和开发上没有自始至终地以领域模型为核心开展工作,以面向对象的思想进行设计和编程,才能保证实现领域驱动设计。

3.2. Façade是否是必须的?

尽管在架构中对Façade的定义非常清晰,但在实践中我发现Façade并不是一个容易拿捏的东西。主要问题在于其与service之间的有太多 的重叠与相似之处。我们注意到service是接口是面向一个use case的,因此事务也是追加在service这一层上,于是对于façade而言,99%的情况是,它只是把某个service的某个方法再包裹一下而 已,如果把领域对象和DTO的互转换工作移至service中进行,那么façade将彻底变成空壳,而关键的是:如果service的接口设计是面向和 user case的,那么,毫无疑问,service接口的传入传出参数也都应该是DTO,而这一点也在《Core J2EE™ Patterns: Best Practices and Design Strategies,SecondEdition》和《Patterns of Enterprise ApplicationArchitecture》两书的示例代码中完全印证了。那么,从更为务实角度出发,Façade并非是一种必须的组件。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结