初探 Domain driven design

Domain Driven Design 是一个最近开始在台湾红起来的一种设计以及开发方式,他的出现主要是为了解决大型专案中复杂的领域问题,透过与领域专家对话,交换意见,最终归纳出一个专案成员都能理解的共通语言,而这共通语言将不止用来跟团队成员沟通,也会出现在规格文件上,甚至连程序码也是用同样的语言建构出来的。

在学习 Domain Driven Design 的过程中会学到一大堆新名词,如果一股脑的要把这些名词一次学起来并马上套用在专案上,应该很容易就迷失了方向,像是一定要为 Value Object 还有 Entity 做归类啦,还有学到 Bounded Context 跟 Subdomain 之後就要马上拆 module ,这些都有可能不是第一件需要做的事。那除了这些之外, Domain Driven Design 还可以为我们带来什麽呢?对一个 Android 应用程序来说 Domain Driven Design 可以怎麽“落地”呢?为了回答这些问题,先从我在 Domain Driven Design 学到的观念以及心得开始吧!

模型

因为最近有很多朋友都有在看房子,我也在旁边练功吸趴,看了之後才发现平面图里面是有很多学问的,同时也发现每个不同建案的平面图也都是千奇百怪,有的只画出家俱跟隔间,但是没有附上比例尺,所以搞不好到时床放进去的时候所能运用的空间比平面图还小。有的平面图还有鲜艳的颜色,比枯燥乏味的黑白线条还有吸引力。但不管平面怎麽画,实际上住进去的空间还是有很多“细节”是无法从平面图上感受出来的,需要实际走访,规划装潢家俱,或是住上一个礼拜才能感受得到。

这个平面图就是一种“模型”,这个模型有他存在的目的,是一个经过“抽象化”过的概念,这个模型在房子还没真正盖起来时很好用,销售人员使用这个模型来跟客户介绍房子的格局,一张纸做的平面图,比完全用嘴巴解说来的容易沟通,也比一个完整的 3D 模型花费的成本更低,在这时候,销售人员跟客户可以很容易的使用“共通语言”来进行沟通,比起说出“那个角落”、或是“这个长度多少”,在纸上直接指出“阳台的这个角落”,还有用笔画出“厨房这个边的长度”,在沟通上是容易多了。

在专案开发中,我们也可以定义出一个经过“抽象化”过的模型来辅助开发,这个模型不限定於是一个 Data class 或是 Object ,而可以是ㄧ组经过精炼的 class 还有他们的交互行为所形成的概念,他们会是一个具有“高聚合”性质的群体。

Domain

Domain 这个词在这个系列文中已经出现了好几次了,他不是一个很好懂的概念,但是这个词很常在架构的讨论上出现,你可以把 Domain 解读为商业逻辑,或是整个应用程序的核心。如果还是不懂,可以参考看看 Domain-Driven Design 这本书原作者所写的定义:每个软件程序是为了执行使用者的某个活动,或是满足使用者个某个需求,这些使用者应用软件的区域就是软件的领域(Domain)。所以如果以便利贴 App 为例,任何对於便利贴的操作、编辑行为就是这个 App 的 Domain。

Ubiquitous Language

这个是“共通语言”的意思,有一个好的共通语言可以加速专案的开发。举一个大家应该都能深刻体验的例子说明好了:有一天,测试人员发现一个问题,他说 App 操作到一半就 Freeze 了,就回报给 PM ,然後这时候呢,PM 用即时通讯软件将这问题再跟工程师说有发现这问题,这时候工程师就试着用自己的手机重现这个问题,结果试了老半天试不出问题,就跟 PM 说这 bug 无法重现。後来呢,PM 就亲自去找测试人员,要测试人员操作给他看,结果发现了测试人员说的没错,果然有问题!PM 於是又去跟工程师说,你这样很不专业,明明这麽简单就能够重现问题了你怎麽说没有,工程师不服气,叫 PM 操作一次给他看,这个 PM 後来就在某个页面上一直按某个按钮,结果还真的都没反应,於是工程师抢过手机自己操作,结果可以动了!你们猜发生什麽事?结果是按钮太小太难按到,是触控区域的问题!

为什麽会浪费这麽多时间在来回沟通?其中一个原因是因为测试人员的 Freeze 跟工程师对於 Freeze 的定义天差地远!测试人员觉得按了一个按钮没反应就叫做 Freeze ,但是工程师觉得 App 完全卡死到有 ANR(Android not responding) 出现才叫做 Freeze,工程师在操作 App 时发现系统 Back 键还可以按,点击其他区域也都有反应时,就会下意识地觉得这不是 Freeze 的状态,所以没有 “Freeze” 的问题。

要解决这问题也很简单,只要团队定义好“共通语言”就行了,像是下面这样:

  1. Freeze - App 的所有按钮都没有回馈,也没有动画在跑,按 back 键也没有反应。
  2. Laggy - App 要等 1-2 秒後 UI 才看到反应,但不会到完全卡死。
  3. No respond - 点击按钮之後没有预期的页面跳转或是状态改变,但是其他在同一个页面上的按钮是可以操作的。

如果没有共通语言的话,在专案开发上将会有很大的成本用来沟通以及翻译,PM 跟设计师讨论 UI UX 时就要在设计稿上进行图像到语言的翻译,之後 PM 在写需求文件时可能又将这概念翻译成了另外一个字,後来工程师依据设计稿写程序时,xml 是一个名字,ViewModel 又是另一个名字,等到画面做完要接 Server API 时,发现 Server 因为 DB 栏位的名字已经取好了,App 在接资料时也懒得做更换,也依照 Server 的命名来接资料。於是就出现了一个怪异的现象,同一份专案的程序码,明明是同一个概念,却有三个不一样的名字,最要不得的,这三个名字还没有一个可以跟 PM 在规格上写内容对的上!当 PM 回报 Bug 时,工程师们就要当人体翻译机,要想办法找出相对应的 Class 的底在哪里...

从对话中提取模型

对於任何类型的应用程序来说,上面所说的这三个观念都是受用无穷的,就算你没有要设计大型的应用程序也一样很有帮助。下面就来做个情境模拟,现在有两个工程师:阿明跟小美,他们都是便利贴专案的成员,而且刚学完 DDD 的基本概念,已经迫不及待的要马上进行大改造了,刚好又遇到 PM 的新需求:要支援放大、缩小以及平移的功能,於是他们就定了一个会议时间要来好好讨论因应这个新需求的变动:

阿明:诶你觉得我们要从哪先开始?

小美:我觉得可以先从定义模型开始,便利贴应该就是我们的核心模型对吧?

阿明:照理说应该是这样没错,但是我发现一件事,你有没有觉得这整个画面叫做 EditorViewModel 有点怪怪的?叫做编辑器这个名字好吗?这看起来不太像是我平常接触到的编辑器。

小美:什麽意思?哪里不像呢?

阿明:我平常对於编辑器的第一印象就是 Android Studio 或是小画家,功能非常齐全,而且不会跟别人合作。

小美:对诶,真说到重点了, Editor 这个字没有显现出“共同协作”的意图,这样我开始也觉得怪怪的了,那我们改名叫做 CoEditor 如何?

阿明:听起来不错,所以我们会在 CoEditor 进行援放大、缩小以及平移的操作,还可以选择便利贴,对它进行其他操作。

小美:但是这个 CoEditor 拥有所有的便利贴吗?超过视线范围之外的便利贴也要一起显示吗?既然我们的 App 可以支援放大缩小,是不是可以新增一个概念来避免让 CoEditor 拥有所有的便利贴?

阿明:视线范围...啊,对了!那加入视图(ViewPort) 这概念如何?对 ViewPort 做操作的话,可以看到的便利贴数量也跟着做改变很合理吧。

小美:所以你的意思是说 CoEditor 里面还有另外一个类别叫做 ViewPort 吗?会不会太多类别了呢?

阿明:你说的没错,不然这样子好了,我们第一步先重构,还不要支援放大缩小跟平移,但是保留这个弹性,等需要时再加进去就好了。

小美:等等,我突然想到我们有一个 View 叫做 BoardView ,是不是应该要叫做 VewPortView 比较好?

阿明:所以我们要抛弃“白板”这个概念了吗?把便利贴放在白板上看起来也是蛮合理的不是吗?

小美:但是如果是白板的话...应该还要可以在上面画画,以後会想要新增画画的功能吗?

阿明:目前没有往这方向发展的规划,好吧,看起来 VewPortView 这名字蛮合理的。另外还有其他的部分像是选单,我们在现在的架构中的 Domain 层还没有这个概念,只有在 View 层有这概念,我觉得 Domain 层应该要能够表达这点。

小美:那就新增一个 ContextMenu 的类别吧,这部分我也蛮同意的,这部分应该蛮好做的。

阿明:看看时间也差不多了,我等等来整理一下讨论的内容写成会议纪录,明天找时间再接着讨论吧!

从上面的对话中,新的关键字跟不同的解读都慢慢冒了出来,因此而产生了更多更有意义的模型以及共通语言,从 Editor 、CoEditor、ViewPort 到 ContextMenu ,渐渐地理出彼此之间的交互关系以及职责,到这边不知道你有没有发现,其实目前专案程序码中类别的名称都还有改进的空间,而且 View 层跟 ViewModel 层的名称到处都有不一致的现象,这对於未来的维护上会造成很大的问题。

小结

Domain Driven Design 是一个很大的主题,但并不代表我们要全部学完才能应用在自己的专案上,我很喜欢当中的模型与 Ubiquitous Language 的概念,这样一来,我们可以在专案中从被动的角色,转换成主动的角色。被动的部分是因为,从 PM 那边接受新功能的需求,通常会直接使用工程的角度去实作需要的功能,而没有完整理解该功能的存在是为了解决什麽问题,Context 是什麽。而至於主动的部分,学了 DDD 之後,可以主动与 PM 展开对话,从定义名词开始,渐渐的把模糊的需求整理成有意义的模型,像刚刚的对话中就提炼出了 ViewPort 的这个新概念,以後使用 ViewPort 来跟 PM 与设计师沟通时,一定会比“手机画面”这种对於领域知识无意义的名词还要来的明确。


<<:  全端入门Day29_後端程序撰写之一点点的Golang

>>:  Day29,使用Dex、OIDC为你的Kubernetes再上一道锁 (2/2)

Day [2] — this:作用域 — JS之浸猪笼系列

如果你不知道这个系列为什麽叫这种激烈的名字可以看这篇: Day [0] — JS之浸猪笼系列 如果你...

[区块链&DAPP介绍 Day22] Dapp 实战 安装 metamask

今天开始到结束,要进入到实际 Dapp 的应用了,但在应用之前要先会安装 metamask。 因为要...

[Day05] swift & kotlin 入门篇!(3) 基础语法-字串

字串 想当初刚在学习JAVA时 一段 new String("ami") != ...

Day 26 - 影像处理篇 - 用Canvas实作在IE上也可运行的模糊滤镜II - 成为Canvas Ninja ~ 理解2D渲染的精髓

在这篇文章中,我们要来实作上一篇提到的图像模糊演算法~ 在开始之前,因为有个小状况是上一篇文中我们...

方丈的安全观 Day1

老衲是志在参加不在成铁人 肝的硬度决定能力的高度,油的厚度决定了嘴炮的层度! Day1来了!! 台湾...