架构元模型定义了模型中使用的概念和使用规则。 —— 《架构师修炼之道》
你可以将其对比于领域模型。
聚合行为
对于领域模型来说,我们也无法直接在代码中实现架构元模型的所有概念。但是,我们所要做的事不断减少模型与代码之间的差异。如果我们不创建模式,而直接开始编写代码,那么我们会收获一堆上帝类。但是,反过来,当我们有一堆上帝类的时候,那么我们就需要从类中把行为都抽取出来。
当我们的贫血模型,拥有了行为,就可以进一步构成富血模型,符合面向对象(OO)的思想。进一步的,我们可以从业务的角度来考虑这个问题,将充血模型改为领域模型。
由内到外剥离,由外到内聚合
对于那些已经采用 DDD 架构的项目来说,往往会遇到一些领域模型不完整、包含非领域相关代码等的情况。
遇到这种情况时,可以尝试:
- 由内到外剥离非模型相关代码。只需要浏览一遍领域模型相关的代码,然后剥离不属于模型的代码;通过依赖注入、工厂模式等方式,建立整洁的 domain 层。
- 由外到内聚合领域模型相关逻辑。这是一个复杂的过程,需要每个使用到模型的调用方,再看是属于领域相关的行为。
第一步可以在短期内快速实现,而第二步则需要一个漫长的过长 —— 取决于项目的大小。
识别模式 1:输入参数
你懂的
识别模式 2:返回参数
你懂的
优化创建
笔者在某个重构项目中,遇到模型的创建逻辑很复杂 —— 参数多、场景多,所以做的第一件事情是:使用工厂模式优化了创建过程。
参考工厂模式。
重命名:统一语言
在 DDD 中强调了统一语言的重要性,为此我们有必要对代码中的模型名称及其行为进行检视。在软件工程实践不好的团队中,你往往会出现对于同一个事件,往往会有多种命名方式 。哪怕你觉得它是不正确的,因为 ownership 的缺乏,也没有人来统一对应的命名。
所以,在我们决定继续往下走之前,先学习一下怎么命名。
计算机科学只存在两个难题:缓存失效和命名。 —— Phil KarIton
原文链接:naming is a process
但是还是更习惯于原来的文章中的:
阶段 | 解释 | 示例 |
---|---|---|
空白 | 没有名称 | doSomething() |
凑合 | 名称不能准确反应元素的含义 | preload() |
沾边 | 名称至少反映了元素某一方面的功能 | DomSomethingEvilToDB() |
反映功能 | 名称直接描述了元素的所有功能 | ParseXmlAndStoreFightToDbAndLocalCacheAndStartProcessing() |
反映角色 | 名称充分地反映了元素在架构中的角色 | StoreFightlightToDatabaseAndStartProcessing |
反映意图 | 名称不仅反映元素的功能,还能反映其目的。 | BeginTrackingFlight() |
领域抽象 | 名称超越了单个元素本身,成为一个新的抽象概念。 | MonitoringPanle.Add(new Flight()) |
偶然间,我看到我找到我书架上的《重构与模式》时,刚好看到一本《实现模式》,顺便看了看,发现书的内容对于本文有启发意义。
书中提及了四五种类型类、状态、消息与流(原行为)、方法,但是对于我们的统一语言工作来说,只需要重命名类、方法、状态就够了。
对应的解释如下:
类
数据的变化比逻辑要繁琐得多,正是这种现象让类有了存在的意义。—— 《实现模式》
对于继承的类来说,它应该遵循这么一些原则:
- 超类名称要简单
- 子类名称要合格
状态
状态包含了变量、字段、常量、局部参数、参数、参数对象等等。
方法
在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目。 —— Eric Evans
容器
离心分离模型:消除二义性
接下来就是处理剩下的 bean、model 等等模型。
在一个系统中,你会存在这么一些不同的 model:
(PS:部分描述可能不准确,欢迎指正)
- 与数据库表结构对应的 DO( Data Object)/ PO(Persistant Object)。
- 查询数据的 Query、Request。
- 对外传输的对象:DTO( Data Transfer Object)。
- 业务层之间的数据对象:VO(Value Object) / BO(Business Object)。
- 访问数据库的:DAO (Data Access Object数据访问对象)。
- 以及我们想要的 DDD 中的实体 Entity
- 还有其它的 POJO( Plain Ordinary Java Object)
但是它们都是 model,所以它们都被扔到 model 中……,又或者是 bean 中……。导致,你有了一个巨大比的 model 层。
所以,在 DDD 又或者是 Clean Architecture,我们重新命名了不同的模式:
- 使用 Command / Request 作为输入参数。其中的 Command 模式在完成后需要发出对应的 Event。
- 使用 Response / DTO / Representation 作为返回结果。
- 对 Entity 大家保持了一致的意见
- 还有 PO / DO 作为作为数据库的存储模型
- DAO 作为数据库的访问模型
- ……
不过,其实你只要不再让使用 model 和 bean,相信会有更多地收获。
提取参数对象
如果一个类包含大量的参数,并且参数中存在一些相似的情形。对于概念统一的情况,可以提取成参数对象。
处理过程逻辑
过程不应该模型的一部分,但是它是领域的一部分。
如 Eric Evans 在所说,区分是否显式表达概念的关键在于:过程是否经常被领域专家谈起,又或者只是计算机程序机制的一部分。
这时候,我们就需要规格(Specification)模式。
下一节:模式是某种场合下对某个问题的一个解决方案的一种结构化展现。 —— Jon Vlissides(GoF 成员)《设计模式沉思录》