状态资源分类和《领域驱动设计》
柏舟 新冠4年 05-25
最近读了《领域驱动设计》对软件编写的状态有了更深刻的认识。软件的状态可以分为以下几类:
- 基础设施的状态:文件系统、数据库、网络通信等等。
- 领域对象的状态:领域的数据对象、领域的存储、队列/栈、分配器、定时器等等。
标准的耦合
这个标准是模糊的,根据《领域驱动设计》,领域的数据对象分为实体(entity)和值对象(value object)。对于实体的状态存储,常见的做法是通过数据库的锁和原子操作进行管理,这表面上看是基础设施的状态问题,实际上是将领域的状态问题转移到了基础设施上。一旦软件引入redis缓存等其它存储,同一个实体可能多次存储,这时就会发现实体本身的特性决定了它的存储方式,存储的复杂性大致分为接口、数据结构和基础设施三个方面:
- 接口的需求,比如幂等;
- 回滚,记录的需求:比如是采用日志记录的方式还是只记录当前状态;
- 存储的基础设施:直接使用文件系统还是数据库。
领域驱动设计
《领域驱动设计》(DDD)这本书主要讨论了领域对象的状态问题和复杂度问题。如果一个程序完全由没有状态的函数构成,只需要将输入输出抽象成管道,实现中间过程就可以了。但问题是系统内部存在实体对象,这个对象存在以下特征:
- 通过标识而不是属性区分;
- 需要关注生命周期,变化存在连续性;
- 唯一性,操作的竞争性。
架构模型
由于实体对象的特点,DDD将对象分为实体和值对象,在这个基础上建立了service, repository, aggregate, factory.
- Service(活动): 有一些活动不是任何对象,它们没有状态,除了承载活动/操作之外没有任何意义。
- Aggregate(实体操作): 聚合实体对象相关的操作,确保领域知识不泄漏,比如一些规则约束,内部值对象的关系等等。
- Factory(实体创建): 用于封装复杂的创建过程,确保内部结构不泄漏。
- Repository(获得实体): 查询接口。
架构本身并不是很重要,对于MVC来说,MC是领域层,Model对应实体和值对象,Controller对应剩余的抽象;而View是应用层,MVC与DDD没有什么区别,只是DDD将业务规则区分得更细,更适合复杂的系统。DDD的核心观点是领域相关的逻辑封装到领域层中,而不要泄露到应用层。
为了达到这个目标,他做了以下几个方面的努力:
领域建模语言
团队内部使用领域语言。目的是:
- 建立一个框架模型,开发人员可以更好的学习,与不同背景的人交流。
- 模型与实现绑定,有利于分析问题,持续维护开发。
这本质上是一个实践与知识的循环,通过领域模型和语言固化下来。因为代码和知识本身就是一种资产,需要人不断的维护,一个好的模型文档能够大幅降低新人的学习成本和后续开发难度,从而降低项目的管理成本。
控制复杂度
- 隐式概念变为显示概念:Specification。使用声明式风格,但是很难扩展到框架之外,一旦出现了意想不到的情况,代码质量就会下降。
- 状态和无状态分离。
- 重构中重新建立模型框架。这边书介绍了很多重构中重新设计领域模型和如何重构代码的经验,但是我觉得这个部分价值一般,因为这部分与实践高度相关,懂的自然懂,不会的看了用处也不大。
总结
相比于MVC,DDD通过将领域对象(Aggregate)的生命周期与操作区分,确实可以处理更复杂的系统。但是假如实体之间存在规则,就需要在实体和实体之间再封装Aggregate,这种递归的结构在书中并没有得到好的解决,在实践中,也很容易泄漏到应用层中。
领域驱动设计非常强调代码实现与领域模型一致,并且通过抽象封装管理领域的复杂性。