前言:

DDD是一种架构思想,而不是一套框架。

Domain Primitive:

何为DP,他是DDD中的“基础数据结构”,就像Java中的int,string一样,是我们学习的必经之路。这么说有点抽象,接下来通过一个案例来说明。

用户注册功能,需要输入用户的名字,电话(带区号的座机),地址。并且后台需要根据电话信息来统计哪个区域注册用户最多。那么就可以看出来电话需要自己的校验逻辑,来判断号码是否合法,以及拆分出区号。

传统的方式:

public class User {String name;String phone;String address;
}public class RegistrationServiceImpl implements RegistrationService {public User register(String name, String phone, String address) throws ValidationException {// 校验逻辑if (name == null || name.length() == 0) {throw new ValidationException("name");}if (phone == null || !isValidPhoneNumber(phone)) {throw new ValidationException("phone");}// 此处省略address的校验逻辑// 取电话号里的区号,然后通过区号找到区域内的SalesRepString areaCode = null;String[] areas = new String[]{"0571", "021", "010"};for (int i = 0; i < phone.length(); i++) {String prefix = phone.substring(0, i);if (Arrays.asList(areas).contains(prefix)) {areaCode = prefix;break;}}SalesRep rep = salesRepRepo.findRep(areaCode);// 最后创建用户,落盘,然后返回User user = new User();user.name = name;user.phone = phone;user.address = address;return userRepo.save(user);}private boolean isValidPhoneNumber(String phone) {String pattern = "^0[1-9]{2,3}-?\\d{8}$";return phone.matches(pattern);}
}

看完以上的代码,可能有的人会问,有些判断是可以再DTO中使用@validation注解来做判断,这样说确实是没有问题,不过validation注解并不是万能的,遇到复杂情况还是需要在业务层里写代码来判断。

从上面这段代码,我们可以看到如下几个问题:

▍问题1 - 接口的清晰度:
从编译的角度来出发,方法在编译的时候是不存在参数名称的,只会留下参数类型,所以就如下

User register(String, String, String);

所以如果参数在非法的情况下编译时是无法发现问题的,只有在执行的时候到了业务层,才会暴露出问题。

举一个更明显的例子,就是目前我看到项目里最多的情况

User findByName(String name);
User findByPhone(String phone);
User findByNameAndPhone(String name, String phone);

从上面的代码相信大家都不陌生,在查询功能中很常见,在这个场景下,由于入参都是 String 类型,不得不在方法名上面加上 ByXXX 来区分, 而 findByNameAndPhone 同样也会陷入前面的入参顺序错误的问题,而且和前面的入参不同,这里参数顺序如果输错了,方法不会报错只会返回 null,而这种 bug 更加难被发现。 这里的思考是,有没有办法让方法入参一目了然,避免入参错误导致的 bug ?

▍问题2 - 数据验证和错误处理

if (phone == null || !isValidPhoneNumber(phone)) {throw new ValidationException("phone");}

这段代码可以看出来是判断电话号码是否合法,现在的是带区号的电话号码,那么如果以后需求加入其他类型的号码,那岂不是需要在这里从新修改,并且如果其他地方也有判断,那岂不是要改很多地方。
这时肯定会有人提出使用ValidationUtils来进行判断,可是当大量的判断逻辑充斥在一个类中,就打破了Single Responsibility单一性原则。

▍问题3 - 业务代码的清晰度

// 取电话号里的区号,然后通过区号找到区域内的SalesRepString areaCode = null;String[] areas = new String[]{"0571", "021", "010"};for (int i = 0; i < phone.length(); i++) {String prefix = phone.substring(0, i);if (Arrays.asList(areas).contains(prefix)) {areaCode = prefix;break;}}

很明显上面一段就是传说中的胶水代码,所谓胶水代码就是从一些入参里抽取一部分数据,然后调用一个外部依赖获取更多的数据,然后从新的数据中再抽取部分数据用作其他的作用。解决胶水代码最好的办法就是把它抽离出来做成一个方法:

//从号码中获取区号
private static String findAreaCode(String phone) {for (int i = 0; i < phone.length(); i++) {String prefix = phone.substring(0, i);if (isAreaCode(prefix)) {return prefix;}}return null;
}
//判断该区号是否存在
private static boolean isAreaCode(String prefix) {String[] areas = new String[]{"0571", "021"};return Arrays.asList(areas).contains(prefix);
}

原来的代码变为:

//获取区号
String areaCode = findAreaCode(phone);
//找到区号负责人
SalesRep rep = salesRepRepo.findRep(areaCode);

而为了复用以上的方法,可能会抽离出一个静态工具类 PhoneUtils 。但是这里要思考的是,静态工具类是否是最好的实现方式呢?当你的项目里充斥着大量的静态工具类,业务代码散在多个文件当中时,你是否还能找到核心的业务逻辑呢?

问题的解决:

先来分析一下刚刚遇到问题,首先我们发现校验电话号码在业务层里让代码很,然后我们发现分离电话号码区号在代码里也很乱,判断区号是否存在也在代码里更乱,明明只是一个注册的业务,我确写了很多电话号码相关的东西,是不是有病?所以当你怀疑自己有病的时候,就可以考虑一下,这个电话号码,不就是tm的隐藏概念。

对没有错,DP的第一个原则就是发现隐藏概念,并且把它显性的展示出来。二话不说,直接把号码抽离出来成一个DP

public class PhoneNumber {private final String number;public String getNumber() {return number;}public PhoneNumber(String number) {if (number == null) {throw new ValidationException("number不能为空");} else if (isValid(number)) {throw new ValidationException("number格式错误");}this.number = number;}public String getAreaCode() {for (int i = 0; i < number.length(); i++) {String prefix = number.substring(0, i);if (isAreaCode(prefix)) {return prefix;}}return null;}private static boolean isAreaCode(String prefix) {String[] areas = new String[]{"0571", "021", "010"};return Arrays.asList(areas).contains(prefix);}public static boolean isValid(String number) {String pattern = "^0?[1-9]{2,3}-?\\d{8}$";return number.matches(pattern);}}

可以看出来我们在构造方法里就给它加入了判断,并且把刚刚的方法作为了属性。
我们再看看实现了DP后原来的代码长什么样子:

public class User {UserId userId;Name name;PhoneNumber phone;Address address;RepId repId;
}public User register(@NotNull Name name,@NotNull PhoneNumber phone,@NotNull Address address
) {// 找到区域内的SalesRepSalesRep rep = salesRepRepo.findRep(phone.getAreaCode());// 最后创建用户,落盘,然后返回,这部分代码实际上也能用Builder解决User user = new User();user.name = name;user.phone = phone;user.address = address;return userRepo.saveUser(user);
}

接口调用改为:

service.register(new Name("xx"), new Address("xxxxx"), new PhoneNumber("0571-12345678"));

可以看出,只要是传入的参数,就必定是满足条件的,并且代码比之前清晰了很多,号码相关的操作,就只用操作号码这个DP就可以。

总结:

▍Domain Primitive 的定义

让我们重新来定义一下 Domain Primitive :Domain Primitive 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有行为的 Value Object 。

DP是一个传统意义上的Value Object,拥有Immutable的特性
DP是一个完整的概念整体,拥有精准定义
DP使用业务域中的原生语言
DP可以是业务域的最小组成部分、也可以构建复杂组合

(参考:阿里技术专家详解 DDD 系列- Domain Primitive)

DDD之Domain Primitive(DP)相关推荐

  1. 阿里技术专家详解 DDD 系列- Domain Primitive

    导读:对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战,无论是 94 年 GoF 的 Design Patterns , 99 年的 Martin Fowler 的 Refactor ...

  2. Domain Primitive 使用推荐

    一.前言 最近对团队的很多同学代码进行了 Code Review ,发现存在很多问题. 其中一个问题就是普遍代码内聚不够,将原本需要对象提供的方法外泄给使用者. 如一个对象里包括状态字段,使用方需要根 ...

  3. 【转】阿里技术专家详解 DDD 系列 第一讲- Domain Primitive

    导读 对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战,无论是 94 年 GoF 的 Design Patterns , 99 年的 Martin Fowler 的 Refactor ...

  4. DDD 系列(一)Domain Primitive

    Domain Primitive 前言 Domain Primitive 案例1(注册) 实现方式一 接口的清晰度 数据验证和错误提示 业务代码的清晰度 可测试性 解决方案 接口的清晰度 数据验证和错 ...

  5. DDD领域驱动设计(DP、Entity介绍;DDD实现流程;DDD聚合Aggregate;限界上下文(Bounded Context))

    DDD(Domain Driven Design) DP(Domain Primitive) define: 一切模型.方法.架构的基础,是指在特定领域.拥有精准定义.可以自我验证.拥有行为的对象,可 ...

  6. 关于DDD中Domain的思考

    2019独角兽企业重金招聘Python工程师标准>>> 本文既不推销UML,也不推广DDD,更不涉及各种论战.-- 作者 某天又一次打开关于DDD(领域驱动设计)的PDF文档时,自己 ...

  7. 基于DDD的项目结构

    前言 本篇文章主要,通过<基础项目结构(二)>和<基础项目结构(一)>技术基础,再结合现今DDD驱动模型.表述一下自己的代码结构思路. 架构 DDD系统采用传统的分层架构.其中 ...

  8. 基础为零?如何将 C++ 编译成 WebAssembly

    作者| 张翰(门柳) 出品|阿里巴巴新零售淘系技术部 本文知识点提炼: 1.如何使用 Emscripten 把 C++ 编译成 wasm. 2.如何使用 wasi-sdk 把 C++ 编译成 wasm ...

  9. 每天审核淘宝性感图的工程师,竟然还做了这个

    作者|胡佳洁(佳婕) .黄锦池(尘漠),曲烈(汤问) 出品|阿里巴巴新零售淘系技术部 导读:获取高置信标注的大规模数据集是有监督学习算法的一个难点问题,训练集中的噪声标签会严重降低模型的精度.通过所提 ...

最新文章

  1. mysqll底层分享(一):MySQL索引背后的数据结构及算法原理
  2. leetcode212. 单词搜索 II
  3. idea 提升幸福感 常用设置(重装机配置)
  4. birt脚本for循环语句_python循环语句(while amp; for)
  5. java 进制转换算法_算法笔记_033:十六进制转八进制(Java)
  6. NumPy、TensorFlow和scikit-learn简介
  7. 前端开发---ppt展示页面评论区展示
  8. Linux 命令(134)—— groupmod 命令
  9. python滑动手机屏幕_appium+python自动化24-滑动方法封装(swipe)
  10. 190522每日一句
  11. 服务器系统备份还原到虚拟机,一秒还原,一秒备份,系统重装「新手学识4」虚拟机--时光倒流...
  12. 计算机硬件工程师主要干什么,计算机硬件工程师主要学习什么内容
  13. 最新TP开源的淘宝客系统/推券客CMS系统+功能强大
  14. 管理经济学【六】之 成本分析
  15. 怎么使用股票委托下单接口?
  16. Ubuntu16 桌面卡死 重启桌面
  17. 疫情过后,这8个专业最有前景,快看看有你的专业吗?
  18. 11,你听说过vue过渡动画了嘛?没有吧? 众里寻他千百度,百度不一定全面?
  19. 创建手机页面弹出键盘的时候背景图片被挤上去的解决办法
  20. MacOS 软件 Adobe Illustrator 2022 - AI 安装使用详细教程

热门文章

  1. bwa是linux系统下软件,bwa 软件用法简介
  2. C++递归删除目录下所有文件(macOS和windows两种)
  3. https://bbs.csdn.net/topics/603607898
  4. 美女福利图片API接口,免费好用
  5. nginx负载均衡配置,宕机自动切换
  6. 第一次搭建成功MySQL数据库
  7. js中动态函数的一些用法尝试
  8. 《东周列国志》第八十八回 孙膑佯狂脱祸 庞涓兵败桂陵
  9. 天呐,嘉立创的单、双面板又又又降价了,同行还怎么玩儿?
  10. 2020年最新主板型号排行榜