
作者:潘吉祥
在学习领域驱动设计之前,我们先来思考一下,自己为何来了解它,它能为现在的自己带来什么改变?
下面给出我作为一个新手给出不久前和现在正在面临的困难:
半年前,我刚从大学毕业,带着一身武艺、怀揣热烈的梦想开始求职之旅。事实证明,梦想是美好的,现实是苍白的,自己是很菜的,最后在一个没线的城市的创业公司从事Java后端开发。嘛,既来之,则安之,我相信自己能够学到很多东西的!
然而,都2020年了(一个月前还是2020年的),还在搞JSP;这也就算了,数据库不知是经了几手,大小写,驼峰杂乱无章,字段重复,表意重复;这也能忍,一个controller处理业务,if-else一层套一层(事实证明,else是个错误的发明),打断点都调不出它的逻辑,我以为他会往下,然而直接跳出去了!
小的事故不用说,结算的逻辑维护一次,出一次事故,最严重的一次,给十几个客户少付了一共一万多,100多个少付10块钱的都不用提了。自那以后凡是结算有需求的我都拒绝修改,“如果非改不可,那么我就要新起项目!”,最后我忍无可忍。
战战兢兢到了12月份,公司开始开拓新的业务,机缘巧合,新业务系统的逻辑只有我知道,于是我一人从数据库到后端到前端全揽了,这样也好,我只信任自己写的代码。
当项目完成地只剩下边边角角的时候,我再度审视项目代码,发现还是有很多重复性代码,这已经是我review code三四次之后的结果了。虽然将绝大部分的重复代码通过继承或者工具类的方式进行了精简,但这始终很别扭。
因为那些抽离出来的逻辑在概念上并不应该属于某个父类或者成为一个完全独立的工具类。我无数次想要在实体类中添加方法来避免工具类的到处分散,但这与我以往学习的编码流程是不一样的,最后我还是没有这样做。
虽然不想承认,因为这个项目没有其他人参手,是我一个人搞的,但是我确实能够在开发中感觉出来,项目的可扩展性非常差。这在我不断地修改表设计的时候有明显的苗头,表结构一旦修改,相关的绝大部分逻辑都要修修补补,简直了!
现在已经想不起来是怎么再次了解到了领域驱动设计,说再次是因为在上学的时候就已经听说过了,那个时候没有深入了解。当时我认为那就是个嚎头,来来回回还是模块化思想那一套,然而我错了。为此,如今必须要承受重构自己亲手写的项目(心痛吐血)
如果读者遇到了和我一样的情况,那么你应该学习DDD,我说应该,没有说必须,在我看来,DDD的设计理念是现在大多坏的设计经过不断思索、改良、重构之后最终必定要走向的结果。如果你的团队或者你个人有足够的能力和创新力。然而学习DDD,能够让你少走弯路。
废话不多说,让我们赶快进入干货学习:
作为入门,首先让我来否定一下不少传统的坏味道的开发方式(或者这也是读者当下正在使用的方式):
拿出一张纸=》写出项目基本的功能模块=》打开Navicat=》新建数据=》新建表=》打开代码生成工具=》生成实体类、crud service、controller=》编写页面=》调用controller=》数据展示、修改=》OK。
如果你是这样的开发,请在评论区打出“真实”
很不幸,这确实是坏味道的方式,这不是我学习了DDD之后,按照书里的定义来评判它是坏味道。而是我独自开发一个完整的项目之后的感受,不得不说,这样的开发确实敏捷,只要表设计的足够完美,业务流程足够清楚,就能快速开发所需的项目。
但这样的项目确实是坏的,长久来看(如果你的项目开始盈利,并且规模开始扩大、客户需求变多,业务开始更加复杂),这样一种以数据驱动开发的模式最后将演变为我最初接触到的糟糕项目。我也相信我接手的那个项目创建之初没有现在这么糟糕,然而如今它确实成为了一个没人敢维护的炸弹。
关于具体的入门示例,限于篇幅就省略了,这里推荐一个写的不错的博文,建议读者看看
https://developer.aliyun.com/article/716908?spm=a2c6h.14164896.0.0.4da7603d95QAPA
由于是入门,我们先来了解两个重要的概念,以改变我们之前的一些思维认知。
以下概念说明参杂了作者自身的一些理解,难免会将某些概念范围缩小,但这是不可避免的,因为方法论的落地必须承载于一个特定的场景和案例,有不同意见的读者也可以提出自己的理解。
|实体(entity)
首先需要说明的一点是,之前完全没有接触过DDD相关理念的读者抛弃你脑海中关于实体的理解和印象,先入为主有时候是个很麻烦的东西。
实体:就是很表面的意思:实实在在存在的一种东西。这可能还是不够表面,举个例子:一个人,一只猫,一片树叶,都是一个实体。他们有两个重要的特性:独一无二性和周期动态性。
就开发者而言,凡是满足以上两个特性的都可以称作实体,我们也应该将其建模为一个实体,比如订单,它虽然不是世界上客观存在的一个事物(看得见,摸得着,闻得见),但是它满足以上两个条件(唯一标识的订单号,不断变化的状态,拥有概念上的生命周期),也能够称之为实体。
实体既然是周期动态性的,那么,通常来说,都会有自己的行为方法。因为它是一个活的概念,比如订单要改变自己的状态,而且这种方法的定义是要符合领域术语的。因此我们不应该简单定义为一个setStatus(),从语义层面来看,它非常模糊,这在领域驱动设计中是忌讳的。我们应该命名为changeStatus()。
当然了现实中我们通常用int类型来代表一个订单周期中的不同状态,如果我们调用changeStatus(),并给他传入了一个int 值,我们达到了我们的目的,但这样仍然是不够的。因为它还是不能够表名一种具体的语义,即使我们开发者可以对照数据库注释查看这个int值到底对应什么状态,但这并不是一种解决办法。解决方案是我们应该将状态设计为一个值对象(关于详细的说明,后续的篇章会给出,这里只是作为一种入门)。
|值对象(value object)
值对象是DDD非常重要的一个部件,我们通常忽略值对象的存在,是因为数据驱动开发模式下将实体和值对象混为一团。
Java中常见的值对象包括integer、string等等,如果我们定义一个username类专门用来描述这个属性,它也称之为一个值对象(class),由于它又再领域中充当一种数据类型,像string类型,我们又称之为值类型(type)。
这里我们给值对象(类型)下一个通用的定义:
它是一种描述和度量实体属性的概念上的集合,通常来说值对象不是客观存在的一种事物,更确切一些,在语言未发明之前,值对象是不存在的(当然你也可以说人既是一种概念又是一个客观存在,但事实上,人即使在没有意识到自己是个人之前,人这种客观事物确实是存在的)。
比如名字,它不是一个客观存在的东西,只是用来标志一个人的描述,你可以叫张三,我也可以叫张三,它没有唯一性可言;抛开叫张三的这个人不说,张三这个名字本身是永恒不变的,也不具备动态周期性。同样的比如度量单位厘米、时间单位小时等等,他们都只是一种存在于概念中的且没有唯一性和周期性的存在,没有人的意识,这些都是不存在的(如果你认为时间是客观存在的,那么你应该去看看爱因斯坦的相对论,然后仔细思索思索了)。
引申:笔者看过一些相关的DDD文章,其中描述了一个概念“源值(data primitive)”,简称为DP,(上面给出的链接中也使用了这一概念)其实这个概念与上面的值对象是一样的,若读者看到DP相关概念,可以将二者等价。以Java为例,狭义地来讲,我们称Java语言本身提供的原始对象为 DP,像基本数据类型和string等,推广开来,我们自定义的凡是满足值对象的特征的对象都可以称为DP(关于值对象的特征后面篇章给出)。
DDD入门首篇就介绍到这里,如果感兴趣可以关注,笔者将后续更新相关内容。
【推荐阅读】
听说又有兄弟因为用YYYY-MM-dd 被锤了...
又发现一款牛逼的 API 敏捷开发工具
排名前 16 的 Java 工具类,哪个你没用过?
佩服,CompletableFuture讲这么详细
Java项目权威排名,结果出乎意料!
是什么让我放弃了restful api?了解清楚后我全面拥抱GraphQL
delete后加 limit是个好习惯么
看片神器!有点敏感!2小时删除!
骚操作 !IDEA 防止写代码沉迷插件 !
同事写了一个update,误用一个双引号,生产数据全变0了!