|
领域驱动设计(DDD)的概念源于2004年著名建模专家Eric Evans发表的书籍:《Domain-Driven Design – Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计—软件核心复杂性应对之道),池建强在2011年发表的一篇文章《领域驱动设计和实践》中是这样形容DDD的:
领域驱动设计事实上是针对OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术架构进行了分层规划,同时对每个类进行了策略和类型的划分。
本文主要介绍为什么我们在恒拓开源内部推广DDD,我们如何通过开发 DDDLib 和 Koala 等工具来完善这一过程,推广过程中遇到了哪些问题,以及我们如何解决这一问题。
为什么选择DDD
传统的模式的最大优点在于开发人员非常熟悉,开发成本低,但它也有一些问题:
采用DDD开发模式之前,传统的开发模型是最流行的Model-Dao-Service-UI开发模型,通常是基于事务脚本(Transaction Script)和表模块(Table Module)模式的实现,这种模式通常是先设计表,再建模,实现容易依赖特定的表的一些特性,如存储过程。基于表的设计模式容易带来以下几个问题:
- 业务建模完全是表的复制,无法真实反映业务。
- 核心业务分散在各个地方,非常危险,修改扩展难,且难以阅读。
这种开发模式适合一些需求小,后续维护扩展需求小的中小型项目,但在大型企业级系统或产品,扩展维护或需求变量非常多的情况下,缺点也非常明显。
相对而言,DDD则有以下四点好处:
1、面向对象,模型真实反映业务现实:使用DDD领域驱动设计,模型通常是业务的真实反映,业务集中在领域而不是分散在各Service中,有利于对业务的理解。
2、使用领域统一建模语言:有利于业务沟通与建模: DDD倡导先对业务建模,而非关注表或脚本的设计;在建模过程中,由于领域本身是对真实业务的反映与建模,因此与业务专家更容易沟通,打破技术与业务的沟通隔阂。
3、可重用性高:DDD中,领域层为核心,每个领域对象都是一个相对完整的内聚的业务对象描述,所以可以形成直接的复用。基于领域建模的设计,并不会依赖特定的数据库及特性,模型是可以完全重用且没有技术上的冲突。
4、业务越复杂,DDD的优势越明显:领域模型采用OO设计,通过将职责分配到相应的模型对象或Service,可以很好的组织业务逻辑,当业务变得复杂时,领域模型显出巨大的优势。
DDDLib登场
DDD本质上是一种思想,并不是新技术。在恒拓开源,由我们的杨宇老师和陈操总共同创作的DDDLib库,是对DDD思想的核心支持与实现。
DDDLib是一整套支持DDD思想实现的类库,DDDLib下还是使用的 Hibernate、JPA或MyBatis、noSQL等技术为实现。
如同DDD所要求样,使用DDDLib 的项目分层图为:
- 用户界面/展现层
- 用于向用户展现信息,处理用户在界面上的请求,比如struts,tapestry,springMVC等页面框架。
- 应用层
- 用来处理应用的活动,不包含领域中的业务逻辑。
- 应用层可以用来处理一些与领域概念无关的拦截性质的工作,比如日志,事务等。此外,应用层也可以用来处理一些既不属于展现层,也不属于领域层,而是属于目前应用相关的一些服务。比如资金转账的业务的读取输入功能(读取输入不是转账的核心业务含义)。
- 领域层
- 此层是DDD的核心:领域对象,领域服务,仓储接口均位于此层。
- 领域的信息,是业务软件的核心所在。
- 需要保留业务对象的状态,对业务对象及其状态持久化的操作交给基础设施层。
- 领域层应该遵从以下原则:除非业务发生变化,否则其他任何变化均不应该影响到领域层。这些其他变化包括:不同的展现框架,不同的页面展现内容,是否要分页,是否支持手机客户端,是否公开WebService,是否提供OpenAPI等等。
- 基础设施层
DDDLib的核心实现如下:
上图就是使用 DDDLib 项目的整体技术架构图,也表明了DDDLib的整体原则:
- 领域层是业务核心,这一层不依赖任何特定的技术框架,保证它的业务纯洁性。DDDLib中的领域层只依赖JDK、DDDLib的Domain库以及仓储接口及其它自定义接口。
- 使用仓储和查询通道作为与存储介质相关的操作接口,隔离对特定数据库技术或存储介质的依赖。
- 提供多种不同的IoC容器实现及InstanceFactory实例工厂,隔离对特定IOC 技术的依赖。
- 领域层包括值对象、领域对象以及领域服务三个要素,领域层不是数据库操作层而是业务建模层。许多开发者在使用的过程中,最终还是把领域层作为数据库操作层来使用,对实体的方法也是以数据库的操作行为为标准 ,如查询,新增,删除一个实体,最终依然回归到以数据库为中心的方向去了,这是需要避免的。
- 改变以数据库为中心的核心是意识到业务行为才是核心,数据库存储是支撑。意识到数据库是支撑非常关键,业务上的任何行为,在系统中最终需要存储记录,数据库存储是对业务实现的支撑,也可以使用文件,缓存或云空间等其它存储介质。想像一下,使用数据库进行设计的项目,最终就限定了存储介质为特定的数据库,如果下一次需要更换为云空间或缓存等其它存储形式,就会发现整个系统需要重新设计开发,但使用DDDLib,只需更换仓储实现,提供一个云空间的实现就行了,核心业务逻辑完全不需要变动与修改。
DDDLib在实现过程中也经历了内部的不少争议,经过很多次的讨论和打磨形成了现在的格局。在下面的部分,我将介绍DDDLib在几个重要组件上的实现细节。
DDDLib仓储的实现
从DDDLib 1.0到3.5版本,仓储实现历经几个阶段,分别是:
- 给每个领域对象定义一个仓储接口及一个仓储实现。
这种仓储实现非常受争议,开发人员并不认可这种方式,仓储接口及实现非常多,一方面导致项目类太多,并且也带来编码的重复操作。
- 与spring data整合,给每个领域对象定义仓储接口,无须定义实现。这种模式对前面的模式有了优化,只定义接口不定义实现,但是spring data这种依赖方法名,参数来进行查询的模式,针对一些复杂的查询,难以胜任。
- 提供默认的hibernate及JPA的通用仓储接口。
为每个仓储定义一个接口,这种模式慢慢的不被接受,使用spring data带来的优化方案,也有非常多的问题,后面根据JPA的实体管理思路,于是形成了通用仓储接口及不同技术实现的思路,定义一个通用的仓储接口,包括通用的增删改查数据库行为。
- 支持MyBatis的通用仓储接口
DDDLib的JPA及hibernate仓储实现,这个方案是一个较佳的方案,所有领域实体使用通用的仓储,避免了大量重复代码,但DDDLib一直是基于Hibernate/JPA提供的实现与技术支持,在项目的使用过程中,经常会遇到不适合使用Hibernate/JPA模式的项目,对MyBatis的需求也非常大,这种情况下,Koala团队定义实现了MyBatis的仓储实现,并保证其与JPA/Hibernate模式下API的一致性。
DDDLib中的DTO
DTO,数据传输对象,领域对象虽然有数据(属性),但是领域对象上面还带有操作,在某些场合不适合进行传输,有些时候传输还需要序列化。但并不是所有的领域对象属性都可以暴露,而且有些属性可能要合并,可能要分解,之后才有利于前端的使用。于是就有了专门用来传输数据的DTO,只有属性,没有操作,必要的时候加上序列化标记,实现远程调用。
这是DDD中DTO的作用,但是DTO同时也带来了实体与DTO的转换性能问题,在大数据量下尤其明显。
DDDLib中的数据库支撑行为类
在DDDLib实现中,提供了Repository以及QueryChannelService两个接口,分别使用在领域层以及应用层,都是对数据库的操作接口。
SQL/HQL/JPQL写在哪
在使用DDDLib的过程中,不同的持久层框架的SQL语言不一样,比如MyBatis使用的是SQL,Hibernate使用的是 HQL,JPA下使用的是JPQL。
这些语句写在哪儿在公司也经历过一番争议与变更,历史如下:
1、写在代码中
public static Resource newResource(String name,String identifier,String level,String menuIcon){ Resource resource = null; List<Resource> resources = Resource.getRepository().find("select r from Resource r where r.name = ? " + "and r.identifier = ?", new Object[]{name,identifier}, Resource.class); ...}
it知识库:DDD &amp; DDDLib在恒拓开源的发展历程与推广经验,转载需保留来源!
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。