MVC 作为一个古老且经典的软件设计模式,基于分层架构的思想,在软件开发领域有广泛的应用。
但在软件系统日益复杂的今天,单纯的分层架构已经显得力不从心,不能应对当前复杂系统的架构需要。
在传统的 MVC 架构下,大概有两种实践,一种是重 Model,另一个是轻 Model。重 Model 把很多的业务逻辑都放在 Model 层。轻 Model 要么把业务逻辑放在 C 层,要么引入单独的 Service 层,编写公用的业务逻辑。
对于简单的业务这样做完全没有问题,而且能够很快的实施,但一旦业务变得复杂,各种业务逻辑相互交织,就很容易产生层层交织的面条式代码,难以理解和维护。
为了解决这个问题,引入 DDD 是一个解决方案,通过 DDD 提供设计方法论,能够很好的指导我们进行领域建模,但 DDD 庞大且难以掌握,极度依赖个人经验,这给引入 DDD 带来了比较大的挑战。
所以,笔者就在思考,我们能否在 MVC 的基础上,分步骤的引入 DDD 的设计思想和原则,一个一个的解决我们面临的问题,而不是一上来就完全遵循 DDD 。
基于这样的思路,得以形成成文,下面从多个方面谈谈笔者的想法。
我们将 DDD 的核心概念纳入到 MVC 中,通过 DDD 的核心概念指导架构。
限界上下文是领域驱动中很重要的概念,通俗的来讲,我们需要通过划分限界上下文,将一个大的问题划分为若干个子问题,每个子问题都对应一个限界上下文,不同的限界上下文之间是低耦合的,它们之间的交互需要被严格限制。
通过限界上下文将一个大的领域,拆分出若干个独立的子域,每个子域解决其特定的问题。
为了在 MVC 中实践 DDD,我们首先需要合理的划分系统中存在的限界上下文,然后在每个限界上下文下合理的解决问题。
一个限界上下文下包含了对应的领域模型和领域服务,下面分别介绍。
领域模型可能会让我们很自然的想到 MVC 的 Model 层。他们之间的区别是领域模型是与数据库无关的,领域模型是在内存中的存在,由单独的持久化层(Repository)进行存储数据。
在 MVC 中,Model 没有更细的划分,但在 DDD 中,我们将其划分为实体(Entity)和值对象(Value Object)。实体与值对象的区别在于实体需要一个唯一标志,且有自己完整的生命周期,值对象则并不需要这些,值对象看起来就像一个复杂一点的属性,比如订单就是一个实体,而配送地址配送地址就是一个值对象。
领域模型关注实体以及其相关对象的领域约束和生命周期,比如订单实体需要有一个配送地址,那么这个地址的检查应该是领域约束的一部分,没有配送地址的订单就不能够被创建。
在领域模型中还有一个很重要的概念就是聚合(Aggregate),聚合就是一组相关对象的集合,它是数据修改和访问的单元,每个聚合都有一个聚合根(Aggregate Root)和边界。比如订单的上下文中,订单就是聚合根,订单的附属信息比如配送地址、联系人的修改和访问都需要通过订单对象进行。
在领域模型中,和 MVC 最大的区别是我们通过对象模型来构建领域模型,而不是通过数据库表记录,我们关注是领域对象本身,而不是如何存储它们。
在领域模型中,我们关注是一个订单及相关对象这样的个体个体间的业与务关系,而没有关注不同订单之间全局的关系,假如我们要实现按某些条件拆分订单,这样的逻辑领域模型是无法处理的,因为它缺少全局的信息。这个时候我们可以将其放在领域服务处理。
领域服务还能处理其他很多需求,比如唯一性验证,某些业务规则的验证等等,在笔者看来,凡是在这个领域之内,无法放在领域模型的逻辑都可以放在领域服务下,但这应该是最后的选择,我们需要保证领域模型已经足够完备。
在 MVC 的角度,领域服务将原来分散在 Controller 或者 Model 的领域逻辑统一起来,作为单独的领域服务,领域服务再对上层提供服务。
结合 MVC,DDD 中的应用层对应 MVC 的 Controller 层,它的主要目的接收外部数据输入,然后组装各个领域服务,对外提供能力。
此外,应用层的职责还包括对客户端输入数据的校验,用户访问权限的检查等。
对于一个系统来说,验证是保证系统功能正确的基础,很多地方有需要验证,有的验证是数据合法性的校验,有的验证是业务规则的校验,也有访问权限的校验。
在笔者接触的 MVC 结构下的感觉是,验证的处理比较随意,只要完成目的就行了,并没有考虑在什么位置是最合适的,最终出现的问题就是业务逻辑分散。一个业务逻辑需要找多个地方的代码才能串联起来。
结合 DDD,笔者认为可以把原来分散的各处的、不统一的验证逻辑分为三个部分,应用层验证、领域服务验证和领域模型验证,下面从外到内分别说明:
应用层验证主要处理外部输入的验证和权限的验证,而外部输入的验证又可以通过 Open API (https://openapis.org) 来定义,同时解决 API 定义、数据验证和 API 文档的问题,不用再维护单独的 API 文档,能够实现代码及文档。
更进一步,可以和前端打通,实现 sdk 的自动生成,实现部分验证逻辑的复用,在前后端分离的大趋势下减少同样的逻辑实现两次,降低前后端分离产生更多的不一致性。
应用层的验证可以比做网关,它的作用是屏蔽从外网到内网的非法请求。而领域服务就是我们的内网,这里需要处理的验证都是和业务相关的,且跨多个实体的。比如用户邮箱的不可重复性。
通过领域服务的验证,我们能够保证在这个领域的全局内,所有的领域对象不会存在错误和冲突,保证系统是满足业务规则的。
领域服务的验证还可以进一步探索如何把验证规则抽象出来,以一种统一的方式来表达和编写,能够同时实现单个验证和批量验证,解决批量操作存在的性能问题,也能实现各种验证的灵活组合。这样代码层面来看就更加直观,便于修改和维护。这也是笔者在探索的东西。
最后是领域模型的验证,如果说领域服务关注于全局,领域模型则关注于个体,个体与个体之前的关系是怎样的,需要满足怎样的约束条件。
比如订单需要一个必填的收货地址,如果缺失收货地址,这个订单就不能够被创建,通过领域模型的验证,保证数据的完备性。
这一层的验证比较容易做的,很多框架都提供了相关的验证机制,我们只需要拿来用就行了。
本文在 MVC 的基础上最小化的引入 DDD 的限界上下文、领域模型、领域服务和应用层的概念,并与 MVC 的概念结合在一起,希望探索一个快速上手实践 DDD 的方法。如果读者你有什么疑问或想法,欢迎与我交流。
本文到此结束,更多精彩文章,尽在「代码写诗」,微信搜索「代码写诗」公众号即可关注。