写点什么

领域驱动设计 101 - 分层

用户头像
luojiahu
关注
发布于: 2021 年 04 月 10 日
领域驱动设计101 - 分层

一、分层解决的问题

分层架构是现有软件开发的普遍架构模式,大家对于分层已经非常熟悉。为什么要做分层?一个马上可以想到的理由是层次化可以使软件更加清晰。那么这里所说的清晰指的具体是什么,可以再仔细进行一下讨论。


假设一个软件没有采用分层架构,那么他可能遇到哪些问题?


首先,由于没有清晰的分层,整个软件将没有比整体更加细致的划分,为了了解任何一个功能,需要阅读的代码将不可预知,从而学习成本未知

其次,由于没有分层,软件的各个部分(逻辑上的)之间将没有清晰的界限,这很有可能导致各个部分之间互相耦合、互相影响,任何一个部分的调整可能意味着与其相关的所有其他部分都需要调整,也就是说,软件的维护成本也很高

最后,由于没有分层,逻辑可能散播在各个部分当中,为了对一个逻辑进行测试,可能不得不运行整个软件。这意味着低成本、高频的自动化测试将无法实施,从而持续集成将变得不够现实。


那么,如果采用了分层架构,上面的问题都将得到比较好地解决。


对于学习成本,首先,由于软件有固定的层次,在整体了解的基础上,通过掌握层次的定义,将会得到一个软件各个部分的粗略划分,各个部分之间的交互关系也在层次的关系之上得到体现。其次,由于层次是固定的,当对软件当中的某一个业务流程学习掌握之后,其他的业务流程很可能只是业务流程上的差异,在软件内部的逻辑交互上很可能是大同小异的。也就是说,学习成本曲线在某一个时间点将呈现下降态势。

对于软件的维护来说,分层之后各个层次之间将得到较好的解耦和隔离(正确恰当地应用分层的前提下),当有新的需求到来时,根据实现需要所要调整的规模大小不同,可能只需要调整部分层次。例如,对于展示需求的变化,仅需要调整展示层,领域层无需调整。

对于自动化测试来说,由于进行了分层,意味着各个层次都相对独立,各个层次内部的每一个单元都可以被更加容易地看做是独立的单元,可以通过采用 Mock/Stub 等方式对与其他层次的交互进行模拟,从而实现低成本地大规模自动化测试,作为持续集成的基础。


对于上面所说的分层所提供的能力进行进一步分析,分层所提供的便利可以归纳为这些关键词:隔离、解耦、独立等等。这些关键词概括为一个总的理念,即:关注点分离 - 将系统进行分离设计,使各个部分都可以在进一步的分析和实现中得到单独关注,而不用考虑与其关联的其他部分


总的来说,分层提供了这样一种价值每一层都只代表程序中的一个方面,层次划分使得每一个方面的设计都更具有内聚性,更容易被解释,更容易被维护。

二、核心所在 - 领域层

考察一个常见的或者普遍的分层划分:展现层、应用层、领域层(业务层)、存储层。各个层次所负责的职责,如其名字所示。


有一定经验的软件开发者都会有这样的印象,展现形式的调整是需求调整当中比重最大的。UI 可能频繁调整布局、颜色、展示要素等等,有些展现的调整并不会带来后端系统的调整,有些调整可能仅仅带来后端展现层输入或者输出的调整。


同时,我们往往还有另外一种印象,存储层通常很难调整,因为对于需要数据存储的软件来说,数据是相对重要并且复杂的部分,对其进行调整、迁移、修改成本通常不小。这是由存储层更加接近计算机底层这一事实所决定的。


存储层难以调整还往往建立在这样一种认识之上,那就是存储层位于更加基础的位置,对其进行调整,意味着位于其上的其他层可能不得不调整。这里不展开讨论,但是需要说明的是,这种观点建立的基础并不正确:业务领域建立在数据模型基础之上。按照领域驱动设计的观点,结合依赖倒置,正确的依赖关系应该是:数据模型依赖于业务领域,业务领域位于更加基础的位置。


我们不再详细讨论应用层的价值和调整成本的大小。简单来说,应用层同样是相对比较容易发生调整的部分,这是由于其面向具体的应用场景所决定的。


我们已经逐渐接近了讨论的重点:**领域层是分层的核心所在。**展现层和应用层更可能被变化的需求驱动而调整,存储层一方面由于复杂性和底层特性难于调整,另一方面并不是应用软件的基础所在,其存在形式可能为:普通文件、内存、关系型数据库以及如分布式缓存等的新型存储等等,并且可能随着系统的发展而进行调整。


通过分层,我们分离出了最重要、最核心的关注点所在:领域层。在领域层,富含最核心的业务对象和规则,相比展现层和应用层,更加稳定;相比于存储层,更加清晰而富有解释性。通过将关注点集中在领域层,软件设计和开发更容易去除具体展现、场景的限制,发现问题领域中的本质和真实规则。

三、分层之间的联系

在强调了分层的价值和核心之后,还是要回归具体实现:完成分层之后,各个层次之间应该如何交互?


有一些熟知的规则:


避免反向依赖

分层模式下面,每个层次都有其对应的位置,从调用的方向看依次是:展现层、应用层、领域层、存储层。和调用方向一致,依赖也应该是这样的,不应该出现反向的依赖。

这里有一个特殊点:领域层和存储层的关系,从调用关系上看,应该是领域层调用存储层实现数据的持久化或者访问。但是,从实现上看,一种更加优秀的实现应该是:在领域层定义存储接口,在存储层具体实现这些接口定义。通过这种方式,实现了所谓的依赖倒置。更加详细地说,这带来了这样的好处: 领域层不再需要知道存储层的具体实现,从而使得无感替换存储层称为可能


尽量避免跨层依赖

一种严格的分层观点认为,不应该出现跨层的调用,例如应用层直接调用存储层,或者展现层直接调用领域层。严格遵循这种限制可以一定程度上简化层次之间的依赖关系,从而为更加方便的调整提供了可能。但另一方面,严格遵循这种要求也意味着更高的成本,在某些场景下面,这种成本可能会超出依赖简化带来的收益。

**所以是否严格遵循避免跨层依赖的要求,应该从具体的实际场景出发。**如果是比较大型的应用软件、含有复杂业务规则的系统或者面临频繁需求调整的业务系统,严格遵循这种原则可能会得到更大的收益。而对于那些相对比较稳定、单纯的软件系统来说,适度打破这个规则可能并没有什么问题。


避免循环依赖

事实上,通过限制避免跨层和反向依赖,已经限制了层次之间循环依赖的可能性。出现循环依赖的可能仅仅存在同一层之内。

出现循环依赖不仅以为着后续调整的困难,还通常可能伴随着潜在的软件缺陷,因此应该尽量避免。

发布于: 2021 年 04 月 10 日阅读数: 34
用户头像

luojiahu

关注

喜欢思考组织、过程、产品的后端开发 2017.01.08 加入

还未添加个人简介

评论

发布
暂无评论
领域驱动设计101 - 分层