一、背景介绍

我们团队一直在持续推进业务系统的体系化治理工作,在这个过程中我们沉淀了自己的DDD脚手架项目。脚手架项目是体系化治理过程中比较重要的一环,它的作用有两点:

(1)可以对新建的项目进行统一的规范;

(2)对于指导老项目进行DDD的改造提供指导。

本文主要是梳理和总结了DDD脚手架使用中的编码规范以及遇到的问题。

二、脚手架的理论基础

DDD相关的应用架构有很多种,比如四层架构,洋葱架构,六边形架构,整洁架构等。这些应用架构都有各自的特点和不同。但是他们的总体思想都是相似的,主要是通过分层来实现功能和关注点的隔离。达到的目标是领域层不依赖任何其他外部实现,这样就能保证核心业务逻辑的干净和稳定。

左图是整洁架构的示意图,左图为分层,右图表示各个分层的变化频率和抽象层级。整洁架构主要分为4层:

(1)Frameworks&Drivers层:这一层表示系统依赖的外部系统,比如数据库、缓存、前端页面等。这一层是变化频率最高的,也是需要和我们的核心业务逻辑做隔离的。

(2)Interface Adapters层:这一层是一个适配层,主要负责外部系统和内部业务系统的适配,这一层的主要作用就是外部系统和内部系统的适配和协议转换。

(3)Application Business Rules: 应用业务规则层,可以理解为用例层,这一层表示整个应用可以提供哪些用例级别的功能和服务。这一层也是对第4层中的核心业务规则的编排层。

(4)Enterprise Business Rules: 这一层就是最为核心的业务逻辑层,这一层不包含任何和技术相关的内容,只包含业务逻辑。

三、脚手架介绍及使用

使用命令如下:

mvn archetype:generate
-DarchetypeGroupId=com.jd.jr.cf
-DarchetypeArtifactId=ddd-archetype
-DarchetypeCatalog=local
-DarchetypeVersion=0.0.1-SNAPSHOT
-DinteractiveMode=false
-DgroupId=com.jd.demo.test //从这一行开始需要根据项目名称修改
-DartifactId=demo-test
-Dversion=1.0.0
-Dpackage=com.jd.demo.test
-DappName=demo-test -s D:/git/settings.xml // 本地 git配置文件

生成完的项目结构如下:

|--- adapter                     -- 适配器层 应用与外部应用交互适配
| |--- controller -- 控制器层,API中的接口的实现
| | |--- assembler -- 装配器,DTO和领域模型的转换
| | |--- impl -- 协议层中接口的实现
| |--- repository -- 仓储层
| | |--- assembler -- 装配器,PO和领域模型的转换
| | |--- impl -- 领域层中仓储接口的实现
| |--- rpc -- RPC层,Domain层中port中依赖的外部的接口实现,调用远程RPC接口
| |--- task -- 任务,主要是调度任务的适配器
|--- api -- 应用协议层 应用对外暴露的api接口
|--- boot -- 启动层 应用框架、驱动等
| |--- aop -- 切面
| |--- config -- 配置
| |--- Application -- 启动类
|--- app -- 应用层
| |--- cases -- 应用服务
|--- domain -- 领域层
| |--- model -- 领域对象
| | |--- aggregate -- 聚合
| | |--- entities -- 实休
| | |--- vo -- 值对象
| |--- service -- 域服务
| |--- factory -- 工厂,针对一些复杂的Object可以通过工厂来构建
| |--- port -- 端口,即接口
| |--- event -- 领域事件
| |--- exception -- 异常封装
| |--- ability -- 领域能力
| |--- extension -- 扩展点
| | |--- impl -- 扩展点实现
|--- query -- 查询层,封装读服务
| |--- model -- 查询模型
| |--- service -- 查询服务

整体的分层架构图如下:

四、脚手架编码规范

1、Api模块编码规范:

  • Api模块是专门用于定义对外接口的模块,所以这个模块中只包含接口定义,出入参定义,尽量不依赖其他包。
  • Api中的接口定义类以xxxxResource(或者xxxxService)结尾。这条规范完全是为了和老的应用保持一致。
  • Api接口的入参尽量不要使用Java中的原子类型(Primitive Type), 需要将入参定义为单独的类。 最好是继承现有的BaseRequest类。
  • Api接口的出参统一使用泛型类对真实的返回类型进行包装。
  • 出入参类都以DTO结尾。
  • 出入参中尽量不适用枚举值类型的成员变量。

2、Adapter/Controller模块编码规范:

  • 这一层中需要将出入参的DTO和业务层的VO/DO对象进行转换。
  • 这一层不要包含任何的业务逻辑,只包含参数转换和业务无关的校验逻辑。
  • 接口返回值缓存类的逻辑,可以放在这个模块中实现,因为这个动作不包含业务逻辑。

3、App模块编码规范:

  • 这个模块中的类统一以Case结尾。
  • 这一层主要是对底层业务逻辑进行编排。可以直接调用Domain层的port定义。跨域的服务调用也可以放在这个模块中。
  • 这一层可以直接调用Domain模块中定义的Repository服务。
  • 事务处理:如果是跨多个聚合的业务逻辑需要放在一个事务中,需要在这一层开启和提交事务。

4、Domain层编码规范:

  • DomainService命名统一以Service为后缀。
  • Entity实体类的命名不用后缀。 值对象类的定义统一以VO结尾。
  • DomainService逻辑中可以调用Repository和Port中定义的接口。
  • DomainService可以操作多个聚合,实体和值对象。
  • Entity实体类可以有构造函数,builder,getters。 不要直接放开所有属性的setters,防止业务代码随意修改实体的属性。
  • 编写业务逻辑需要遵守原则:优先将业务逻辑放在Entity和VO中,然后才是放在聚合中,最后才放在DomainService中。
  • 依赖反转原则:Domain层依赖的外部接口都要定义在Domain模块的port包中。Domain层只面向接口编程,不依赖接口实现类。

5、Adapter/Repository和Rpc模块编码规范:

  • Repository实现类中需要将接口入参中的DO对象转换为PO对象后再调用数据库存储。
  • Repository和聚合的关系是一对一的关系。一个Repository有唯一的对应的聚合。
  • 如果Repository中需要开始事务可以在Repository实现类中开启事务。
  • Rpc层最好是对外部接口的出参和入参定义一个防腐层对象,命名统一以DTO结尾。

五、常见问题及解决办法

**Q1、**Api模块对外提供的jar包中是否要引用其他应用的jar包?

A1: 有一些场景,A应用的Api接口的入参需要引用其他应用的包中的类。比如A应用发出了一个事件,B应用提供了一个接口来处理这个事件,那B应用是否要引用A应用的包中的事件定义类呢? 理想情况,最好是B应用定义一个自己的类,这样B应用就不会依赖A应用的包。

**Q2、**Api包中是否能包含枚举类的定义?

A2:最好不要在Api包中对外暴露内部的枚举值定义。因为枚举值是需要在Domain模块中定义和使用的,不适合通过jar包的形式暴露给外部。 如果确实有需求要暴露给外部应用(比如为了让接口调用方方便的知道入参中的值有哪些),可以将枚举类的定义放在同一的common包中。这样Domain模块和对外提供的jar包都可以引用common包。

**Q3、**数据存储是否要使用统一版本号?

A3: 对于新应用,最好是使用统一的版本号,这样在更新数据库的时候就可以统一使用版本号当做乐观锁。但是对于遗留系统而言,启用版本号的成本比较高,因为需要梳理所有对实体进行变更的点,要求所有的点都统一使用版本号。所以要根据情况来确定是否使用。

Q4、对于一些偏流程性的业务,频繁的调用外部rpc接口。如果每个rpc接口都添加一个防腐层对象的话,会降低开发效率。是否可以不定义防腐层对象?

A4:最好是定义防腐层对象,短期可能降低一些开发效率,但是从长期和代码标准话的角度看,还是值得的。

作者:京东科技 史纪军

来源:京东云开发者社区 转载请注明来源

【实践篇】DDD脚手架及编码规范的更多相关文章

  1. 前端编码规范(4)—— CSS 和 Sass (SCSS) 规范

    CSS and Sass (SCSS) style rules ID and class naming ID和class(类)名总是使用可以反应元素目的和用途的名称,或其他通用名称.代替表象和晦涩难懂 ...

  2. 自己总结的C#编码规范--7.文档下载 & 总结

    今天终于把这一系列的编码规范写完了,这个编码规范算上前面阅读相关书籍,前前后后总共花了一个月的时间,也算是个人的呕心沥血之作了. 本来也没打算把这个系列写的这么长,但是在写的过程中自己搜了相关的网上资 ...

  3. 自己总结的C#编码规范--前言&目录

    最近在为公司编写c#编码规范,以前对这方面研究不多,只是觉得代码能够出自己的意思就可以了. 我参考了以下资料 C# Coding Conventions NET设计规范约定惯用法与模式(第2版) 编写 ...

  4. Go语言安全编码规范-翻译(分享转发)

    Go语言安全编码规范-翻译 本文翻译原文由:blood_zer0.Lingfighting完成 如果翻译的有问题:联系我(Lzero2012).匆忙翻译肯定会有很多错误,欢迎大家一起讨论Go语言安全能 ...

  5. web前端编码规范

    简要介绍 本文通过参考百度腾讯等前端编码规范(链接建文末),得出个人习惯的编码规范.个人编码规范采用在不影响可读性的情况下能省就省,尽量简洁,不需要就直接去掉. 最佳原则不管是个人编码规范还是团队编码 ...

  6. Android的编码规范

    一.Android编码规范 1.学会使用string.xml文件 在我看来,当一个文本信息出现的次数大于一次的时候就必须要使用string.xml 比如一个保存按钮 , 不规范写法: <Butt ...

  7. PHP 高级编程(1/5) - 编码规范及文档编写

    PHP 高级程序设计学习笔记20140612 软件开发中的一个重要环节就是文档编写.他可以帮助未来的程序维护人员和使用者理解你在开发时的思路.也便于日后重新查看代码时不至于无从下手.文档还有一个重要的 ...

  8. 【原】JAVA SE编码规范

    /* * 编码规范: * 1.所有的命名遵循"见名知意"的原则 * 2.所有的命名不允许使用汉字或拼音 * 3.Java的工程命名建议使用小写,比如:oa.crm.cms... * ...

  9. 浅谈Android编码规范及命名规范

    前言: 目前工作负责两个医疗APP项目的开发,同时使用LeanCloud进行云端配合开发,完全单挑. 现大框架已经完成,正在进行细节模块上的开发 抽空总结一下Android项目的开发规范:1.编码规范 ...

  10. PHP编码规范PSR-2

    .note-content { font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeit ...

随机推荐

  1. 在 Transformers 中使用对比搜索生成可媲美人类水平的文本 🤗

    1. 引言 自然语言生成 (即文本生成) 是自然语言处理 (NLP) 的核心任务之一.本文将介绍神经网络文本生成领域当前最先进的解码方法 对比搜索 (Contrastive Search).提出该方法 ...

  2. pytest在conftest里添加钩子函数进行失败处截图,并绑定到allure报告里

    在conftest里要先定义一个selenium之类的driver. @pytest.hookimpl(tryfirst=True, hookwrapper=True)def pytest_runte ...

  3. SpringBoot 使用 Sa-Token 完成路由拦截鉴权

    一.需求分析 在前文,我们详细的讲述了在 Sa-Token 如何使用注解进行权限认证,注解鉴权虽然方便,却并不适合所有鉴权场景. 假设有如下需求:项目中所有接口均需要登录认证校验,只有 "登 ...

  4. 鼠标移入select options会触发mouseleave 事件处理方案

    近来遇到一项目有一侧边工具菜单,在鼠标mouseenter事件打开对应的详细操作列表,当mouseleave时进行关闭,然操作列表中有一个select , 每当鼠标移入select options 时 ...

  5. Hadoop的核心配置文件

    1. core-site.xml:该配置文件包含了Hadoop通用配置,例如Hadoop的文件系统和I/O设置.Hadoop日志目录.Hadoop缓存设置等. 2. hdfs-site.xml:该配置 ...

  6. 为teamcity的代码语法检查工具pyflakes增加支持python2和python3

    TeamCity和pyflakes TeamCity是一款由JetBrains公司开发的持续集成和部署工具,它提供了丰富的功能来帮助团队协作进行软件开发.其中包括代码检查.自动化构建.测试运行.版本控 ...

  7. Rust函数参数传递的一个观点

    Q5: 一个函数的观点A5: Rust中的每个函数都是自治的,在每一个函数体中,相当于重新开辟了一个新的领域.将参数传递给函数参数,与let声明一个绑定是一样的规则. 1 ``` 2 // 所有权语义 ...

  8. 抓包分析RST报文

    大家好,我是蓝胖子,今天我们来分析下网络连接中经常出现的RST信号,连接中出现RST信号意味着这条链接将会断开,来看下什么时候会触发RST信号,这在分析连接断开的原因时十分有帮助. 本文的讲解视频已经 ...

  9. Grafana 系列-GaC-2-Grafana Terraform Provider 基础

    系列文章 Grafana 系列文章 Terraform 系列文章 概述 前文最后总结了我的工具选型: Grafana Terraform provider Jsonnet 我们今天先简单介绍 Graf ...

  10. Linux网络编程(学习笔记)

    文中python代码来自老师的教学代码,感谢我的老师~~ 1. linux网络数据处理过程: 网卡->协议栈->网络 1)应用层输出数据 socket层->协议层->接口层 2 ...