你有没有感觉自己有时候很无力?你是否曾经失去了生活的控制?你是否曾经有过一些最终证明是不可能实现的计划?如果是的话,想象一下你的领域对象有时也会有同样的感觉……
但为什么呢?让我们从定义开始。什么是 领域模型?
领域模型是领域的对象模型,它包含了行为和数据。
—— Martin Fowler《企业应用架构模式》
什么是贫血领域模型?
这是一种应用程序级别的架构设计,对象被设计为数据结构。业务逻辑主要存储在服务、工具、帮助器、处理器、解析器等中。
对象可能具有像 getter、setter、equals、hashcode 和一些覆盖接口(如 Comparable 或 Iterable)的方法。它也可能具有一些简单的逻辑。但是,单个对象没有服务层是无法提供任何业务功能的。
有些人称之为编程反模式。贫血领域模型实际上只是一种 过程式 设计风格,但它也可能出现在 函数式 应用程序中。
过程式代码(使用数据结构的代码)使得添加新函数而无需改变现有数据结构变得容易。相反,面向对象的代码使得添加新类而无需改变现有函数变得容易。 (…)
过程式代码使添加新数据结构变得困难,因为所有函数都必须更改。面向对象的代码使添加新函数变得困难,因为所有类都必须更改。
—— Robert C. Martin《代码整洁之道》
在贫血模型中,数据和行为是分离的。
告诉别问
“告诉别问”是基本的面向对象设计原则之一。让我们看看其中的一个定义:
相比于要求对象提供数据并对该数据进行操作,我们应该告诉对象要做什么。这鼓励将行为移入对象以与数据一起使用。
—— Martin Fowler
让我们以电子商务为例,你可以购买一种产品。在这个例子中,我们将检查女士的发刷。
可能会有以下场景:
- 营销 —— 为了广告目的,我们必须知道这个产品是否为儿童设计。如果是,那么我们将在儿童页面上购买广告。
- 发票 —— 我们想为低税率的产品生成特殊发票。
- 产品页面 —— 如果这个产品是为女性设计的,我们应该在产品页面上添加至少两种粉色。
- 运输 —— 如果快递是唯一的运输选项,那么我们必须选择最可靠的快递,因为那是唯一可能的交付方式。
这些要求中有很多味道。仅凭描述,我们就可以告诉你,ProductService 将充满 if/else 代码块。但这是我们设计的结果。我们每次都必须询问我们的产品,以了解我们应该如何处理它。
例如,许多产品不会为儿童设计(如香烟),因此我们不必每次都询问。这是冗余的,它永远不会改变。这甚至是 危险 的,因为一个粗心的开发人员可能会在广告策略中添加否定标记或编写一些错误的条件,你最终会在迪士尼频道上看到香烟广告… 😅
相反,我们可以设计我们的产品,使其能够描述自己。具有行为的产品将准确地告诉你它的功能,你不必关心不存在的用例。
薄服务的丰富模型
相反,丰富模型侧重于 面向对象编程 原则。行为应该在对象内部,因为那是找到它的最佳位置。你不必找到适当的 Utils、Helper 或 Service 来匹配数据与其行为。
丰富模型中的数据和行为
因此,你可以简化你的逻辑,并减少服务的数量。许多业务功能只能由对象模型提供。该模型具有一致的行为,维护这样的逻辑并添加新功能要容易得多。
在下面的示意图中,你将看到贫血模型和丰富模型之间的比较。它展示了丰富模型的 领域驱动设计 版本。在这种方法中,我使用 聚合 将模型组织成业务概念。在聚合对象内部,可能有许多实体或值对象,这是你唯一可以通过聚合对其进行操作的方式。
一个聚合是我们将一组相关对象视为一个单元进行数据更改的集群。
—— Eric Evans《领域驱动设计》
例如,Order 聚合封装了订单的行为。它就像一个看门人。它由 Price、Line 和 Address 组成,以提供一些业务价值。为了解决 API 的请求,你可以直接调用此聚合来运行逻辑,也可以使用 OrderService(这是一个 领域服务)来提供一些不适合添加到订单聚合中的场景(例如,多个订单之间的操作)。
贫血模型方法通常最终成为服务的网络,有时很难找到最准确的服务。这通常是某些逻辑重复的原因。有时它可能会以 UserService 8000 行代码的形式结束,有时它可能会有多个类,如 UserWithOrderService、VipUserService、UserRegisteredService、UserVerificationService。## 领域层 vs 服务层
服务层的逻辑经常可以转移到聚合、值对象和实体中,这样逻辑就恰好在你所期望的地方。
丰富的模型的目的是使服务层尽可能地薄,以便我们能够将所有应用程序概念分离到领域中——应用程序的核心。
我应该使用什么样的风格?
答案很简单: “这取决于”. 如果领域很深,并且后端有很多业务逻辑——那么使用丰富的模型会更好。当领域是扁平的,并且看起来更像CRUD架构(创建、读取、更新、删除操作)时,最快、最简单的解决方案是可以接受的。即使是初级开发人员也可以实现它,因为没有太多可以损坏的东西。
丰富的模型和DDD始终是一种投资。有时可能会有回报,有时可能没有回报。需要更深入的分析来发现边界并收集所有业务规则。在开始时实现丰富的模型可能会很棘手和笨重,但经过一段时间和许多新功能后,它看起来更合理了。复杂性以更线性、可预测的方式增加。
另一方面,使用贫血模型可能会导致应用程序不再可维护,需要重写或进行一系列大的重构。
总结
贫血模型可能真的很痛苦。一开始看起来并不糟糕,许多开发人员可能想走这条路,并最终将其转化为丰富的模型。问题在于,最终的解决方案很少能令人满意。从贫血到丰富的转变可能比预期的更加困难(特别是当数据库结构复杂时)。这有时可能是一个解决方案,但这应该是一个有意识的决定。
保持务实是好的。问题是“在我的应用程序中,逻辑最复杂的地方在哪里,它将经常在未来发生变化?”一些具有最大复杂性的模块可能实现丰富的模型,一些不那么重要的模块——那些“足够好”的模块——可能仍然以旧的、传统的方式存在。DDD区分了三种优先级: 核心领域、支持领域 和 通用领域。其中,核心领域是业务的最重要部分——竞争优势。通用领域是最不重要的,甚至可以在市场上购买这个解决方案(例如邮件平台、注册平台、通信工具)。支持领域则处于中间地位。
所以不要太激进,但如果需要,不要犹豫选择丰富的模型,并教你的团队DDD! 😉
译自:https://medium.com/@michal.cwiekala/anemic-model-86b73539d3da
评论(0)