DDD分层

为什么分层

引用《领域驱动设计模式、原理与实践》

为了避免将代码库变成大泥球(BBoM)并因此减弱领域模型的完整性且最终减弱可用性,系统架构要支持技术复杂性与领域复杂性的分离。引起技术实现发生变化的原因与引起领域逻辑发生变化的原因显然不同,这就导致基础设施和领域逻辑问题会以不同速率发生变化

每一层都有各自的职责,显然这也是符合SRP的

如何分层

DDD的标准形态

  1. User Interface是用户接口层,主要用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给Application层的接口
  2. Application层是应用层,负责多进程管理及调度、多线程管理及调度、多协程调度和维护业务实例的状态模型。当调度层收到用户接口层的请求后,委托Context层与本次业务相关的上下文进行处理
  3. Domain层是领域层,定义领域模型,不仅包括领域对象及其之间关系的建模,还包括对象的角色role的显式建模
  4. Infrastructure层是基础实施层,为其他层提供通用的技术能力:业务平台,编程框架,持久化机制,消息机制,第三方库的封装,通用算法,等等

根据DDD细化业务逻辑层

模块

结合maven的module,项目中现在分了八个module

<modules>    <module>generator-assist-dao</module> <!-- 生成的dao -->    <module>generator-assist-client-api</module> <!-- swagger api yaml -->    <module>assist-client-api</module> <!-- 生成的api -->    <module>assist-controller</module> <!-- controller -->    <module>assist-service</module> <!-- domain -->    <module>assist-infrastructure</module> <!-- infrastructure -->    <module>assist-common</module> <!-- 基础common -->    <module>start</module> <!-- 启动入口及test --></modules>

start

入口模块

包结构:

  • start 只有一个启动类
  • test 单元测试

除了启动类,还有单元测试

generator-assist-dao

生成的dao类

包结构:

  • repository
    • model 与数据库对应的实体类
  • repository
    • mapper mybatis的mapper

现在实践落地时,这个模块是个空模块,why?

DDD中明确了repository概念,并属于domain层,但dao是对底层数据库的封装,具体实现类放在infrastructure层更合理

在COLA中,作者也是为了领域层的纯洁性,依赖反转了,repository接口定义在domain层,而实现在infra层

但在落地时,domain与infra出现了循环依赖,COLA把实现放在了app层,这样有些另类,所以暂时先把repository全部放在了service层

迷思:

1、基于mybatis的实现,mapper本身是接口,repository实现类放在domain层,不要接口,这样满足DDD分层规则,但离DIP差了一步

2、在《DDD之熵》中提过

DDD引入repository放在了领域层,一是对应聚合根的概念,二是抽象了数据库访问,,但DDD限界上下文可能不仅限于访问数据库,还可能访问同样属于外部设备的文件、网络与消息队列。为了隔离领域模型与外部设备,同样需要为它们定义抽象的出口端口,这些出口端口该放在哪里呢?如果依然放在领域层,就很难自圆其说。例如,出口端口EventPublisher支持将事件消息发布到消息队列,要将这样的接口放在领域层,就显得不伦不类了。倘若不放在位于内部核心的领域层,就只能放在领域层外部,这又违背了整洁架构思想

3、是不是有别的理论支撑解决问题2

generator-assist-client-api

为了生成api的swagger yaml文件

包结构:

  • swagger-spec
    • all swagger所有yaml文件的整合文件
    • apis swagger定义的api
    • models swagger定义的api中的model
  • swagger-templates 模板文件

assist-client-api

通过swagger生成的api接口与api中的model

包结构:

  • client
    • api swagger生成的api接口
    • model swagger生成的request,response对象

assist-controller

controller层,放置controller

包结构:

  • controller 所有的controller
  • xxljob xxljob补偿任务

按DDD分层规范,controller属于ui层,处理restful请求

  • 接受请求 —— 由spring提供能力
  • 请求格式校验及转换 —— 格式校验遵循java Validation规范
  • 权限校验 —— 由网关处理
  • 路由请求 —— 网关处理
  • 记录请求 —— 专门Accessfilter处理
  • 回复响应 —— 由spring提供能力

为什么还有一个xxljob包,从能力区分,xxljob放到infra层才对。这个原因类似generator-assist-dao模块,xxljob的handler需要调用application service,需要依赖service module

因此可以把xxljob作为远程请求的一个入口,与controller一样归在ui层

这儿引出一点思考,controller真的是ui层吗?能划分到别的层吗?

有几种设计思路

  1. ui层完全归属于大前端,不在后端,也就不在ddd中,后端都是从application service开始
  2. controller归于ui
  3. controller归于infra,controller毕竟是依赖具体底层框架能力的adapter

controller是基于springboot的具体实现

从上面的分析,可以看出controller逻辑上是归到infra层,但物理上不能放到infra模块;也不能简单把controller看作MVC中的C,还有很多像xxljob样的入口

  1. 入口会有很多,如controller、xxljob,还有mq等等
  2. 还有进程内的,如event,应用层,基础设施层,领域层都有event,怎么区分event是个问题
  3. application serivce与domain service区分也常常给人带来烦恼

这儿是否可以借鉴《DDD之形》中的端口和适配器架构

把controller看作driving adapter,既然区分这么复杂,那可不可以简单点,加厚controller,整合入口与application service

简单点分成两部分:远程服务与本地服务

  • 远程服务:定义会跨进程服务,分为资源(Resource)服务、供应者(Provider)服务、控制器(Controller)服务与事件订阅者(Event Subscriber)服务
  • 本地服务:所有远程服务需要调用领域服务,必须经过本地服务才能调用;明确隔离外界与领域,防止领域模型外泄

assist-service

domain层,但现在还是三层结构的思路,什么类都有,app service,domain service,dto,event 甚至还有基础设施层类

包结构

  • BO
  • builder
  • common
  • component
  • convertor
  • domain
  • dto
  • event
  • interceptor
  • listener
  • model
  • repository
  • service
  • thrid
  • valid

assist-infrastructure

基础设施层

包结构

  • config 配置信息
  • adapter 外部调用封装
    • clients 外部调用实现
    • pl 服务接口的契约 published language
  • dp domain primitive 这是不是应该在domain层
  • common 公共类,(InvoiceType与InvoiceTypeEnum的问题)
  • event
    • publish 事件发布者,此包为空,直接依赖spring不需要自实现了
  • exception 异常类
  • gateway 网关,封装访问外部系统或资源行为的对象
    • api 外接接口
    • dto 外接接口dto
    • wechat 外部名称
  • local
    • pl
  • ports
    • clients 外部调用接口
  • repository
    • model
  • resources 资源
  • service 依赖外部的service
  • util 工具类

现在的包结构很丰富,最常见的包就是gateway,配合acl与外部交互

U表示上游(Upstream)的被依赖方,D表示下游(Downstream)的依赖方。防腐层(ACL)放在下游,将上游的消息转化为下游的领域模型

结合generator-assist-dao模块的问题,是否可以扩大ACL,而不仅限于gateway中,像资源库一样,不必完全遵循DDD只抽象repository,像访问第三方应用,缓存,消息都可以抽象出来,契合端口履行的职责一样


改造

<modules>    <module>generator-assist-dao</module> <!-- 生成的dao -->    <module>generator-assist-client-api</module> <!-- swagger api yaml -->    <module>assist-client-api</module> <!-- 生成的api -->    <module>assist-ohs</module> <!-- ohs -->    <module>assist-service</module> <!-- domain -->    <module>assist-acl</module> <!-- acl -->    <module>start</module> <!-- 启动入口及test --></modules>

assist-controller

根据上面的分析,这一层可以更厚实些

改名为assist-ohs

OHS,open host service 开放主机服务,定义公开服务的协议,包括通信的方式、传递消息的格式(协议)

包结构

  • remote
    • controller
    • openapi
    • xxljob
    • subscribe
  • local
    • appservices
    • pl (plush language) request,response
    • convertor

assist-service

domain层

包结构

  • domain 领域对象
  • service 领域服务
  • factory 领域对象工厂
  • builder 领域对象构造器

assist-acl

扩大了基础设施层,隔离领域层与外部依赖,对所有外部环境一视同仁,无需针对资源库做特殊化处理,如此也可保证架构的简单性,repository、client、cahce...

领域层依赖port接口

包结构

  • config 配置信息
  • port 依赖外部接口
    • repository 数据库接口
    • client 第三方系统接口
    • publisher 消息接口
    • cache 缓存接口
  • adapter port的具体实现
    • repository
    • pl
    • client

总结

模块划分以及包结构还只是一家之言,一是有充足的理论体系支撑,不管按DDD标准,还是变形,更多地有理有据,与团队、也与自己达成一致;二是domain的抽象,一切都是为了领域模型的稳定性和扩展性,形只是表象

我们这个项目还是太注重了形,最重要的domain还是过弱

本文分享自微信公众号 - 码农戏码(coder-game),作者:朱先生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 再议DDD分层

    线条2:这条线没有了,在MVC里面这线是常见的,applicaton与domain没分开,但DDD中这条线是不推荐的,就算在松散分层架构中也一般不使用,除非简单...

    码农戏码
  • 聊聊DDD的分层架构

    在《领域驱动设计——软件核心复杂性应对之道》一书中Eric Evans将应用架构分为以下层级:

    心平气和
  • DDD实战篇:分层架构的代码结构

    不同于其它的架构方法,领域驱动设计DDD(Domain Driven Design)提出了从业务设计到代码实现一致性的要求,不再对分析模型和实现模型进行区分。也...

    ThoughtWorks
  • DDD领域驱动的三种分层架构

    来源:https://www.jianshu.com/p/a775836c7e25

    JAVA日知录
  • DDD领域驱动设计实战-分层架构

    整洁架构、CQRS、六边形架构等微服务架构都旨在“高内聚低耦合”。那DDD分层架构又如何?

    JavaEdge
  • DDD(领域驱动设计)jpatable主键生成策略RBAC打造通用WEB权限

    下面参考了DDD官方的结构,总结了前辈们的相关经验,再根据自身对微服务和DDD学习和理解,做了一个用SpringCloud搭建的最基本的结构例子。个人才疏学浅,...

    stys35
  • 为何我用DDD重构了才刚上线的新项目?

    很多人初次接触DDD会有些反感,特别是对DO、DTO、PO这些对象转来转去反感,也有人质疑这样做影响性能。

    Java艺术
  • DDD实战课--学习笔记

    我认为,要想应用 DDD,首要任务就是要吃透 DDD 的核心设计思想,搞清楚 DDD、微服务和中台之间的关系。中台本质是业务模型,微服务是业务模型的系统落地,D...

    郑子铭
  • 可落地的DDD(5)-战术设计

    本篇是DDD的战术篇,也就是关于领域事件、领域对象、聚合根、实体、值对象的讨论。也是DDD系列的完结篇。 这一部分在我们团队争论最多的,也有很多月经贴,比如对资...

    方丈的寺院
  • [半翻] 设计面向DDD的微服务

    许多技术概念和模式,例如充血模型(对应我们常写贫血模型)、值对象、聚合和聚合根规则。

    有态度的马甲
  • 一个微服务+DDD(领域驱动设计)的代码结构示例

    下面参考了DDD官方的结构,总结了前辈们的相关经验,再根据自身对微服务和DDD学习和理解,做了一个用SpringCloud搭建的最基本的结构例子。个人才疏学浅,...

    EalenXie
  • 【第三格】如何实现领域驱动设计

    从Eric Evans写下经典名著Domain-Driven Design: Tackling Complexity in the Heart of Softw...

    张逸
  • 读《中台架构与实现》

    最早是在极客时间知道欧创新老师的,我也是他的课程《DDD实战课》的订阅者,后来欧老师基于这门课程做更多的实践与思考,完成了《中台架构与实现:基于 DDD 和微服...

    oec2003
  • 从架构演进谈 DDD 兴起的原因以及与微服务的关系

    我们先不讨论DDD的定义, 先梳理一下DDD火起来的背景, 根据我学习的套路, 永远是为什么为先,再是解决什么问题,是什么东西, 最后如何使用。我们都知道这些年...

    孙玄@奈学教育
  • 解惑领域驱动设计的若干问题

    作者 | 张逸 最近重读Eric Evans的经典《领域驱动设计》,正如Eric提倡我们要去发现隐式概念一般,这次重读也让我发现了许多隐藏的DDD知识。恰好今...

    张逸
  • DDD实战进阶第一波(十五):开发一般业务的大健康行业直销系统(总结篇)

    用户1910585
  • 在分布式系统中使用 DDD

    无论我们使用单体、SOA、微服务、中台或者其他架构,都需要解决如何组织代码这个问题,DDD 并不是一个技术,而是指导我们组织代码的一种思想,这种思想也并不是凭空...

    ThoughtWorks
  • DDD究竟是个啥?怎么落地?

    领域驱动设计(Domain Driven Design,以下简称DDD)这个话题,经常会出现在各大技术圈。特别是微服务出现后,海内外的大佬们又把微服务和DDD放...

    ImportSource
  • DDD实战进阶第一波(二):开发一般业务的大健康行业直销系统(搭建支持DDD的轻量级框架一)

    本系列文章 DDD实战进阶第一波(一):开发一般业务的大健康行业直销系统(概述) DDD实战进阶第一波(二):开发一般业务的大健康行业直销系统(搭建支持DDD的...

    用户1910585

扫码关注云+社区

领取腾讯云代金券