DDD
是 Domain driven design
(领域驱动设计)的简称,是一种软件设计和开发的方法论,特别适用于复杂业务领域软件设计和开发。
目前国内很多大型公司正是在实用 DDD
进行业务开发。那为什么不使用经典和相对稳定的 MVC
架构,却使用 DDD
架构进行业务开发?那 DDD
有哪些优点,又有哪些缺点呢?
带着以上问题我们接着往下看~
那就先来说说 DDD
的优点:
面向对象设计,数据行为绑定,告别贫血模型
降低复杂度,分而治之
优先考虑领域模型,而不是切割数据和行为
准确传达业务规则,业务优先
代码即设计
它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现业务和技术统一的架构演进
领域知识共享,提升协助效率
增加可维护性和可读性,延长软件生命周期
中台化的基石
当然 DDD
也不是万能的,DDD也是有些场景是不适用的。那接下来我们就说说 DDD
的缺点:
性能 :DDD
是基于聚合来组织代码,对于高性能场景下,加载聚合中大量的无用字段会严重影响性能,比如报表场景中,直接写 SQL 会更简单直接。
事务 :DDD
中的事务被限定在限界上下文中,跨多个限界上下文的场景需要开发者额外考虑分布式事务问题。
难度系数高,推广成本大 :DDD
项目需要领域专家专家,且需要特别熟悉业务、建模、OOP
,对于管理者来说评估一个人是否真的能胜任也是一件困难的事情。
当然说到 DDD
也离不开 MVC
,那为什么现在既有用 MVC
的设计理念,又有使用 DDD
架构的呢?那我就来对比两者之前的区别。
MVC
: 上来就可以开干,短平快,前期用起来很香,整体开发效率也更高,所以对于紧急,或者不那么重要的项目,我会直接用 MVC
怼,不好的地方就是,后面会越来越复杂,可能最后就是一坨屎山,但是很多时候,比如老板进度催的紧,我哪想到那么多以后呢?
DDD
: 前期需要花大量时间设计好领域模型,对于一些基础组件,或者一些核心服务,如果对象模型非常复杂,建议采用 DDD
,前期可能会稍微痛苦一些,但是后期维护起来会非常方便。
User Interface —— 用户界面层。提供与用户/调用者交互的接口,可以是View,也可以是Restful api,还可以是二进制形式的tcp协议接口等。
Application —— 应用服务层。组合domain和infrastructure,完成具体的业务逻辑。
Domain —— 业务领域层。是ddd中的核心层,内聚所有的业务逻辑,保持领域的一致性。通常他可能会用到infrastructure层的公共组件。
Infrastructure —— 基础设施层。提供公共服务组件,比如validation、登录态校验、trace日志记录等等。
对比于MVC架构:
还有一种 六边形架构图
在遵循分层架构思想的基础上,引入了六边形架构风格,对内对外均通过适配器+端口的方式呈现:
是不是很简单呢?
门面层,对外以各种协议提供服务,该层需要明确定义支持的服务协议、契约等。包含:
dto
包括request和response两部分,通过它定义入参和出参的契约,在dto层可以使用基础设施层的validation组件完成入参格式校验;
controller
支持不同访问协议的控制器实现,比如:http/restful风格、tcp/二进制流协议、mq消息/json对象等等。
controller使用基础设施层公共组件完成许多通用的工作:
应用服务层,组合domain层的领域对象和基础设施层的公共组件,根据业务需要包装出多变的服务,以适应多变的业务服务需求。
应用服务层主要访问domain领域对象,完成服务逻辑的包装。
应用服务层也会访问基础设施层的公共组件,如rabbitmq,完成领域消息的生产等。
组装器,负责将多个domain领域对象组装为需要的dto对象,比如查询帖子列表,需要从Post(帖子)领域对象中获取帖子的详情,还需要从User(用户)领域对象中获取用户的基本信息。
组装器中不应当有业务逻辑在里面,主要负责格式转换、字段映射等职责。
业务领域层,是我们最应当关心的一层,也是最多变的一层,需要保证这一层是高内聚的。确保所有的业务逻辑都留在这一层,而不会遗漏到其他层。按照ddd(domain driven design)理论,主要有如下概念构成:
领域实体。有唯一标识,可变的业务实体对象,它有着自己的生命周期。比如社区这一业务领域中,‘帖子’就是一个业务实体,它需要有一个唯一性业务标识表征,同时他的状态和内容可以不断发生变化。
领域值对象。可以没有唯一性业务标识,且一旦定义,他是不可变的,它通常是短暂的。这和java中的值对象(基本类型和String类型)类似。比如社区业务领域中,‘帖子的置顶信息’可以理解为是一个值对象,不需要为这一值对象定义独立的业务唯一性标识,直接使用‘帖子id‘便可表征,同时,它只有’置顶状态‘和’置顶位置‘,一旦其中一个属性需要发生变化,则重建值对象并赋值给’帖子‘实体的引用,不会对领域带来任何负面影响。
领域对象工厂。用于复杂领域对象的创建/重建。重建是指通过respostory加载持久化对象后,重建领域对象。
领域服务。区别于应用服务,他属于业务领域层。
可以认为,如果某种行为无法归类给任何实体/值对象,则就为这些行为建立相应的领域服务即可。比如:转账服务(transferService),需要操作借方/贷方两个账户实体。
传统意义上的util static方法中,涉及到业务逻辑的部分,都可以考虑归入domain service。
领域事件。领域中产生的一些消息事件,通过事件通知/订阅的方式,可以在性能和解耦层面得到好处。
仓库。我们将仓库的接口定义归类在domain层,因为他和domain entity联系紧密。仓库用户和基础实施的持久化层交互,完成领域对应的增删改查操作。
仓库的实际实现根据不同的存储介质而不同,可以是redis、oracle、mongodb等。
鉴于现在社区服务的存储介质有三套:oracle、redis、mongodb,且各个存储介质的字段属性名不一致,因此需要使用translator来做翻译,将持久化层的对象翻译为统一的领域对象。
翻译器。将持久化层的对象翻译为统一的领域对象。
翻译器中不应当有业务逻辑在里面,主要负责格式转换、字段映射等职责。
聚合根(AggregateRoot):聚合本身也是一个实体,聚合可以包含其他实体,其他实体不能脱离聚合而单独提供服务,比如一篇文章下的评论,评论必须从属于文章,没有文章也就没有评论。仓库层(repository)也必须是以聚合为核心提供服务的。
基础设施层提供公共功能组件,供controller、service、domain层调用。
对domain层repository接口的实现,对应每种存储介质有其特定实现,如oracle的mapper,mongodb的dao等等。repository impl会调用mybatis、mongo client、redis client完成实际的存储层操作。
权限校验器,判定客户端是否有访问该资源的权限。提供给User Interface层的Controller调用。
异常分类及定义,同时提供公共的异常处理逻辑,具体由ExceptionHandler实现。
transport完成和第三方服务的交互,可以有多种协议形式的实现,如http+json、tcp+自定义协议等,配套使用的还有Resolver解析器,用于对第三方服务的请求和响应进行适配,提供一个防腐层(AnticorruptionLayer,DDD原书P255)的作用。
提供事务管理,交给Spring管理。
日志模块,记录trace日志,使用log4j完成。
消息资源管理,交给Spring统一管理。
通过上述描述相信大家也应该了解了一些关于 DDD
架构的设计理念和方法。也希望本篇文章能够帮助大家,最好也是能够通过一些相关的代码联系和一些开源项目去理解 DDD
这个业务架构。
那么本期分享就到这里啦,我们下期再见!