Logo
Section 1 / 7

“无映射”策略

了解“无映射”策略及其适用场景和实现方法。

导入

“无映射”策略

了解“无映射”策略及其适用场景和实现方法。

简介

在前面的章节中,我们讨论了 Web 层、应用层、领域层和持久化层,以及每一层在实现用例时所承担的职责。

然而,我们几乎没有触及一个令人头疼却又无处不在的话题:各层模型之间的映射问题。我敢打赌,你一定曾经参与过这样的讨论——是否应该在多个层之间使用相同的模型,以避免编写额外的映射代码。

这种争论通常如下所示:

支持映射的开发者:

如果我们不在层之间进行映射,就必须在两个层中使用同一个模型,这会导致层与层之间高度耦合!

反对映射的开发者:

但如果我们 确实 在层之间做映射,就会产生大量样板代码,对于许多用例来说这是过度设计,因为它们只是执行 #key# CRUD:创建、读取、更新和删除 #key# 操作,且各层本就使用相同的模型!

像大多数技术争论一样,双方的观点都有其合理之处。接下来,我们将探讨几种常见的映射策略及其优缺点,帮助这些开发者做出更明智的选择。

第一种策略,就是完全不做映射。

不做映射

...
如果端口接口使用领域模型作为输入和输出模型,则无需在层之间进行映射。

上图展示了我们示例应用 BuckPal 中“转账”用例所涉及的关键组件。

在 Web 层,Web 控制器调用 SendMoneyUseCase 接口来执行该用例,而该接口接收一个 Account 对象作为参数。这意味着 Web 层和应用层都需要访问 Account 类——它们共享同一个模型。

在应用的另一侧,持久化层与应用层之间也存在同样的关系。

由于所有层都使用相同的模型,因此无需在它们之间实现任何映射逻辑。

影响与后果

但采用这种设计会带来哪些后果呢?

...

Web 层和持久化层可能对模型有特殊要求。例如,若 Web 层通过 REST 暴露模型,则模型类可能需要一些注解,用于定义如何将字段序列化为 #key# JSON:JavaScript 对象表示法 #key#。同样地,如果我们在持久化层使用了 #key# ORM:对象关系映射 #key# 框架,也可能需要添加一些定义数据库映射的注解。

在当前设计中,所有这些特殊需求都必须在 Account 领域模型类中处理,即使领域层和应用层对此毫不关心。这违反了单一职责原则,因为 Account 类可能因 Web 层、应用层或持久化层的变化而被迫修改。

除了技术性需求外,每一层还可能需要在 Account 类中添加特定的自定义字段,这可能导致领域模型变得碎片化——某些字段仅在一个层中有意义,而在其他层中毫无用途。

但这是否意味着我们永远不应采用“不做映射”的策略?当然不是。

尽管这种方式看起来有些“脏”,但在某些场景下,它完全是合理且有效的选择。

考虑一个简单的 #key# CRUD:创建、读取、更新和删除 #key# 用例。我们真的有必要把字段从 Web 模型映射到领域模型,再从领域模型映射到持久化模型吗?其实没有必要。

那么,领域模型上的 #key# JSON:JavaScript 对象表示法 #key# 或 #key# ORM:对象关系映射 #key# 注解真的会造成困扰吗?即便持久化层发生变化时我们需要调整一两个注解,那又如何?

只要所有层所需的结构和数据完全一致,“不做映射”就是一个完全可接受的设计方案。

然而,一旦我们在应用层或领域层中不得不处理 Web 层或持久化层的具体细节(除了注解之外),我们就应当转向其他的映射策略。

这里给开头那两位开发者一个启示:即使我们过去选择了某种映射策略,未来仍然可以根据需要进行调整。

Course Image

根据我的经验,许多用例最初都是简单的 #key# CRUD:创建、读取、更新和删除 #key# 操作。随着业务发展,它们可能会演变为具有丰富行为和复杂校验的完整业务用例,届时才真正需要更复杂的映射策略。但也有很多用例会一直保持 #key# CRUD:创建、读取、更新和删除 #key# 的简单状态,这时我们会庆幸当初没有投入精力去实现复杂的映射机制。

测试架构元素

“双向”映射策略