我是如何将同事的代码改成DDD风格的

DDD是领域驱动设计的简写。前段时间听群友说行业里少有DDD的代码案例,进而对DDD没有一个感性的认识。我想这是行业里普遍存在的现象吧。所以,我就有了写此文的想法。 本文开篇介绍了行业里比较普遍的代码风格,接着,我采用DDD风格对其进行修改。 我无意说服读者要按照我认为的DDD的风格来写代码,只是想告诉大家,这个世界上,还存在另一种代码风格。 如果各位觉得这样的风格好,可以尝试一下。非常欢迎大家反馈,平时太少人和我交流这些了。 文章标题说的是“同事的代码”,其实只是为了让此文更具传播,没别的意思。 如果你觉得此文对你有帮助,麻烦转发。干货好文不易。谢谢。 本文虽是以Java语言为案例演示,也希望对其它语言的读者朋友有帮助。 行业里普遍的代码风格,简称A风格 代码结构如下: ├── domain domain模块被同事认为是用于存放专门和DB打交道的类的地方 - src/main/java/com/xx/domain/account/repository/AbcLoginInfoRepository.java - src/main/java/com/xx/domain/account/AbcLoginInfo.java ├── repository-impl - 包路径太长省略/AbcLoginInfoRepositoryImlp.java ├── server - src/main/java/com/xx/server/login/LoginService.java - src/main/java/com/xx/server/login/LoginController.java - src/main/java/com/xx/server/login/AuthCodeVo.java - src/main/java/com/xx/server/login/UserInfoVo.java - src/main/java/com/xx/config/AbcWebMvcConfigurer.java Server模块 A风格下,整个业务系统的业务逻辑都在此模块中。 LoginController.java 实现http服务: @Controller @RequestMapping public class LoginController { @Autowired LoginService loginService; // 省略一些不重要的代码 @GetMapping(value = "/login") @ResponseBody public UserInfoVo login(String code) throws IOException { UserInfoVo userInfoVo = loginService.login(code, httpServletResponse); httpServletResponse.sendRedirect("/"); return userInfoVo; } @GetMapping(value = "/logout") @ResponseBody public boolean logout() { return loginService.logout(httpServletRequest,httpServletResponse); } } UserInfoVo.java是返回给前端的用户信息的结构体: ...

2024-02-26 · 4 min · 849 words · 翟志军 Jack Zhai

这10年,我所经历的领域驱动设计(DDD)

笔者2011年入行时,运气好,遇到了我的恩师simon杨。 当时,我们几个还不知道什么叫SSH(Spring、Struts、Hibernate)的毕业生和一个高级程序员基于DDDLib就开始实践领域驱动设计。现在想想还是觉得不可思议。一毕业就开始接触这门DDD技艺。 我记得当时simon杨经常谈如何利用抽象、解耦,在不增加复杂性的同时实现简单性、一致性、灵活性、可扩展性。至于如何实现CRUD,那是具体实现的问题,可以放在最后做。当他谈到某个精妙的设计时,眼神里都满是光。 在他的教导下,我就开始似懂非懂地读《领域驱动设计》、《企业应用架构模式》、《敏捷软件开发——原则、模式与实践》等这些高层设计类的书籍。 也许你会说,对于一个刚毕业的人学习这些“高层”设计,是不是太早了,毕竟你连CRUD都还不熟练。 在我工作10年之后看来,越早学习这些,越好。当你习惯使用CRUD的思维方式来看所有的问题时,你的思维方式已经固化,是很难接受DDD这套思维方式的。我尝试过说服不同工作经验的人使用DDD,无果。 2013年,在国内,也只有Jdon网站讨论DDD。而我们已经跟着simon杨在项目上实践DDD两年了。 2014年左右,为了验证了我是否真的理解DDD,我利用一次比赛的机会采用DDD的方式设计了一个BlackJack的游戏的核心。到此,我才算是对DDD有了一个更深入的理解。 但是,在2014年以后到现在的2021年,我就再也没有接触过DDD的项目了。而我,只能在自己的工作内容范围内实践,比如某个功能、某个模块。 值得说的是,我最近3年做的是DevOps相关的工作,但是DDD的思维方式依然能帮助我很好的完成工作。 比如在实现流水线时,将构建工具逻辑与流水线流程逻辑解耦、在设计 版本号时,将构建工具本身的版本号与流水线流程生成的版本号分离。 如果你不熟悉DevOps也没有关系,你只需要知道DDD贯穿着整个系统设计的每一个细节。 现在市场上把DDD吹得很高大上,似乎只有在架构上做DDD,才叫DDD。又或者非得和微服务关联在一起,才叫DDD。 其实不然,DDD的原意是领域驱动设计。只要你是使用领域知识来驱动你的设计,这个设计可以是方法级别的设计、类级别的设计、前端UI的设计等一切设计,你都可以叫DDD。 也许,这样的话听起来就像“色即是空,空即是色”一样让人不知所云。 没办法,10年了,我也没有找到非常好的让人一下就懂这种思维方式的方法。 如果非得要我介绍一下DDD的思维是什么,我觉得就是:不停地问问题是什么,解决方案是什么。然后优先从问题域着手设计。在确定问题域设计得差不多了,才开始实现解决方案。 这里要提个问,如果拿到一个需求时,先设计MySQL的表结构,再根据表结构导出类。这样的方式符合DDD的思维方式吗?如果不符合,与DDD的思维方式有什么区别? 最后,我想说的是《领域驱动设计》我读过3遍了,也实践这么多年了,我依然记不清“战略设计”、“柔性设计”等这些术语的准确定义。我也建议各位不要去记那些术语的定义,更不要去争论那些术语的定义,而是要从DDD的原意开始去理解DDD。也就是每当遇到一个术语,你就提问:它是帮助你领域驱动设计的呢。

2024-02-26 · 1 min · 20 words · 翟志军 Jack Zhai

如何将DDD应用到基础设施设计?

前段时间在面试的时候,面试官问到:你是如何将DDD(领域驱动设计)应用到基础设施的? 我很惊讶,终于有人问我这个问题了。 在过去从事基础设施(DevOps、SRE、运维)的这5年里,我经常说起DDD是一种思维模式,可以应用到任何的领域,包括基础设施的设计。 但是,从来没有人像这位面试官问起我具体的做法。 为什么没有人问?原因大概是这两个概念通常是不会放在一起的。大多数开发不会深入理解基础设施的设计,而大多数从事基础设施设计的人是不会接触到DDD。而且,开发人员对于DDD的理解,也仅局限于用它开发业务系统。 我就是那少部分人,即做基础设施的设计,又觉得自己懂DDD的人。 说回问题本身。 我所理解的DDD 我首先会向提问的人澄清我所理解的DDD。 为什么要这样呢?很久以前有一次面试,因为我说我擅长DevOps,面试官就认为我不懂GitOps。然后在这个点上他就认为我不适合,不再问我DevOps方面的问题。我只能说没有缘份。 我是这样解释DDD的: 就像开发一个象棋游戏,不论你要开发手机端,还是web端,象棋规则本身都是不变的。这个规则本身就可以理解为“领域”。 其它所有的技术(包括架构)都是具体实现,它们应该由“领域”来驱动设计。 当时的解释与以上的解释大差不差。 将DDD应用到基础设施设计的具体做法 那么该如何将DDD应用到基础设施的设计呢? DDD的思维方式要求我们首先问:我们要设计的软件的领域(核心)问题是什么? 基础设施的领域问题是什么?我的回答是配置。 我认为基础设施的搭建、维护,本质就是配置的设计、部署、维护。 寻找领域(本质)问题的能力是DDD的核心能力。 为了让读者更好理解,我们以一个一个基于云上的虚拟机的分布式系统为例。它的基础设施就包括:vpc、LB、MQ、DB等。 要搭建、维护这一套基础设施。 根据“配置管理是基础设施设计的核心问题”,我首先将基础设施的所有的配置放在清单代码(并不一定是一个文件)中,如下: vpc: # .... LB: # ... DB: # ... MQ: # ... APP1: mq_addr: "{{ MQ.addr }}" db_host: "{{ DB.addr }}" APP2: mq_addr: "{{ MQ.addr }}" db_host: "{{ DB.addr }}" app1_addr: "{{ APP1.addr }}" 从清单中,你看不出它使用何种部署方式、部署顺序。你只知道APP1引用了MQ和DB,APP2会调用APP2这样纯粹的领域知识。 现实通常是多个环境,所以,我一开始就会将不同环境的值从清单上抽离到独立的文件夹中。 第二步,我才会考虑如何部署它们。这时,我通过两个工具实现: Terraform负责云基础设施; Ansible负责业务基础设施。 Ansible是可以直接读取我们的YAML格式配置文件。而Terraform代码引用YAML代码,就没有那么方便了。 ...

2024-01-27 · 1 min · 78 words · 翟志军 Jack Zhai

这就是领域驱动设计(DDD)的作用?

面对需求,我们首先想到的是什么 在家电IoT这个领域里,通常都会需要实现家电的分享。比如老婆分享家里的电饭煲给老公,让老公控制电饭煲。 拿到这样一个需求,通常大脑里想到的就是增加一张家电分享表来实现: appliance_user_shared master_user_id shared_user_id appliance_id 然后再修改家电列表的实现,因为需要将别人分享给自己的家电也要能获取到。 同时,别忘记了,我们还要修改所有对于家电的操作的实现。比如业务规则中说明,家电的主人才能对家电的名称进行修改。 就这样,我们实现了用户对于一个家电的分享。这时的业务模型可以表示如下: 过不了多久,产品经理跟你说,如果一个用户家里有10个家电以上,一个个家电的分享,这种体验太糟糕了,我希望将一个家庭分享给其他人,这样,可以将家庭下所有的家庭一下子分享给另一个人了。 当然,我们同样可以将这个需求,看作是添加一个家庭分享表,再修改一下家电列表、家电名称修改、家电控制……这是A方案。 A方案的领域模型表示如下: 但是,我们还有一个B方案。 当我们仔细思考时,我们似乎掉进了产品经理挖的坑:产品觉得通过增加一个“分享家庭”的概念来实现多个家电的分享。 事实上,我们服务器端实现并不一定需要这样做。前端APP可以有一个家电分享的操作界面,但是,服务器在实现这个web api时,只需要在找到这个家庭下的所有家电,然后重用原来单家电分享的概念了,就可以了。 这样做,就非常符合软件设计的开闭原则:对扩展开放,对修改关闭。 基于B方案,我们不需要对家电列表等功能进行修改。同时,你还会发现,某天产品经理抽风,觉得一次性分享所有的家电不好,如果能在一个家庭基础下实现部分家电分享的功能,是不是更好?A方案就头大了。而B方案可以很轻松的应对。 B方案的领域模型表示如下: 说回来,我们应该警惕产品经理或需求人员帮我们做软件设计。 面对需求时,我们的大脑里,第一想到的是数据库表如何设计、如何实现改动最小、是使用微服务呢,还是使用七边形架构……这类技术问题时,我们的设计是技术驱动设计的。 如果我们大脑里第一思考的是什么单家电分享、家庭分享和单家电分享之间是什么关系……这类领域问题,然后基于这些思考,建立一个领域相关的知识体系——以领域模型为体现。如果我们的软件是基于此领域模型进行设计,就是:领域模型驱动软件设计。 按《领域驱动设计》中,作者所说的: 领域驱动设计是一种思维方式,也是一组优先任务。 领域建模 要搞清楚,什么是领域建模,就必须搞清楚什么是软件的核心。 我们来看看《DDD》前言里所写的: 一些设计因素是技术上的。软件的网络、数据库和其他技术方面的设计耗费了人们大量的精力。很多书籍都介绍过如何解决这些问题。大批开发人员很注意培养自己的技术,并紧跟每一次技术进步。 然而很多应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也是无济于事。成功的设计必须系统地考虑软件的这个核心方面。 这个核心方面指的就是领域知识。 而领域建模就是消化吸收大量知识(领域相关),最后产生一个反映深层次领域知识并聚集于关键概念的模型。这也就是领域驱动设计的实质。 上文中,关于家电分享的例子,从A方案到B方案,实际上就是随着我们对领域知识的理解更深入,领域建模的一个过程。 小结 就如《DDD》作者所言:很多因素可能会导致项目偏离轨道,如官僚主义、目标不清、资源缺乏,等等。但是真正决定软件复杂性的是设计方法。当复杂性失去控制时,开发人员就无法很好的理解软件,因此无法轻易、安全地更改和扩展它。 领域驱动设计这种思维方式,再加上一整套的设计实践、技术和原则,就能帮助我们控制真正的复杂性。这就是领域驱动设计的作用。 而这种思维要求我们在面对领域问题时,优先考虑的是领域问题,而不是技术问题。 注意,这并不是说我们不考虑技术如何实现。这是优先级问题。 最后,以上举的例子并不是最终的模型。因为家电分享这个概念并不是最根本的概念。更根本概念是家电的权限。这是一个更层次的领域模型:

2017-11-22 · 1 min · 39 words · 翟志军 Jack Zhai