软件工程中的领域驱动设计 (DDD)

2025年03月17日 | 阅读 9 分钟

引言

在软件开发这个快节奏的环境中,速度和精确性至关重要,而领域驱动设计(DDD)方法论正是在这个时代应运而生。2004 年,程序员 Eric Evans 通过他的著作《领域驱动设计:软件核心复杂性应对之道》开创了这种方法,该书引入了一种软件系统的架构方法。我们将严格审视 DDD 的关键原则;我们剖析其高级和低级设计标准,并权衡这种方法的优点和缺点。

理解软件开发中的领域

理解领域驱动设计意味着掌握“领域”这一概念在软件中的含义。在这个领域,“领域”指的是应用程序所针对的特定业务环境。领域逻辑——围绕应用程序逻辑的知识领域——也被称为业务逻辑。它是一组规则和指导方针,用于规定业务对象如何交互以处理已建模的数据。

强调在当前的软件工程语言中,领域实际上就是应用程序的业务。无论应用程序的技术多么复杂,只有当它能满足给定业务环境的特定需求时,才能取得成功。

领域驱动设计 (DDD) 解析

领域驱动设计是一种架构方法,它促使软件开发人员从宏观角度审视开发过程。Eric Evans 也认为,在软件开发过程中,最重要的不是技术,而是理解和处理业务领域本身。这一理念与史蒂夫·乔布斯的名言“客户不知道他们想要什么”不谋而合。

DDD 引入了两类设计工具:战略设计工具和战术设计工具。虽然开发人员在日常工作中更常遇到战术设计工具,但要创建系统化、结构良好的软件系统,就必须全面掌握战略设计工具。

设计工具解决了与软件建模相关的问题。这种方法与面向对象设计有共通之处,它要求开发人员不仅要考虑一个对象,还要考虑它的上下文。战略设计的关键组成部分包括:

背景

在战略设计中,“上下文”一词描述了我们可以确定概念的环境。它解释或描述一个事件、事故、声明或想法。

模型

该模型描述了领域的选定方面,并作为核心逻辑。它是解决业务问题的重要工具。

通用语言

通用语言是团队中每个人都用来协调围绕领域模型的所有活动的一种语言。例如,与领域专家和团队成员沟通时,会使用普通的动词和名词,如类、方法、服务对象等。

Domain-Driven Design (DDD) in Software Engineering

限界上下文

限界上下文描述了上下文的边界条件。它是领域模型有意义和适用性的边界。

战术设计是领域驱动设计 (DDD) 方法论的关键步骤。它深入探讨了如何实现,重点是为领域构建模型。在本文中,我们将解开战术设计的主要部分,在相当高的层次上审视概念:实体和值对象;服务;聚合(以及它们生成的工厂);存储库。

Entities (实体)

在面向对象原理的世界中,程序员熟悉诸如类和对象之类的概念。在 DDD 中,实体就像具有属性的类。但它超越了普通概念,拥有贯穿其生命周期的全局生命。虽然属性的情况可能发生变化,但身份不变。从编程的角度来看,实体通常是数据库中的一行,它由值对象组成。

值对象

相反,值对象是不可变的、轻量级的非实体项。这些对象作为重要的去复杂化因素,它们承担了复杂的计算,否则这些计算将无法放在实体上。例如,在以用户为中心的情况下,用户可以是一个对象,其地址成为一个值对象。地址可能会更改多次,但其所有者的身份始终是稳定的。当地址更改时,会实例化另一个地址并分配给用户。

服务

接下来,DDD 中的服务代表没有状态的类,它们独立于实体或值对象。简而言之,服务是位于实体和值对象之间的功能。它是一个独立的个体,既不直接附加到另一个实体,也不仅仅是一个值对象。

聚合

随着项目变大,对象图也随之增长,维护变得更加困难。聚合(在单个事务边界下聚集在一起的实体和值的集合)应运而生。聚合是变化的控制器,其根实体称为聚合根。聚合根控制聚合内其他对象的生命周期。例如,假设用户或订单(聚合的根实体)已被删除。

此操作将删除所有相关实体及其信息。为了保持一致性,聚合通过域事件保证最终一致性。例如,如果用户的地址发生变化,可以通过让一个更新域事件从用户冒泡以更新订单来实现最终一致性。

聚合还有多种应用,包括处理帖子评论;各种类别中问题和答案的详细信息;或交易的详细信息。Hibernate 等 ORM 工具经常使用聚合,尤其是在一对多或多对一关系中。

工厂和存储库

工厂和存储库是聚合整个过程管理中不可或缺的工具。工厂启动聚合的集合;它们提供了一个组织良好的起点。另一方面,存储库用于聚合生命周期的中间和末尾来管理持久性。请记住,最佳实践是为每个聚合根创建一个存储库,而不仅仅是为所有实体。

DDD 中的战术设计仅仅规定了如何解决特定边界内无数的实现问题。构建块包括实体、值对象、服务、聚合和工厂;这些被安排成一个设计良好、健壮且易于维护的架构。理解战术设计对于必须应对现实世界应用程序复杂性并尝试最小化这些复杂性的开发人员来说变得至关重要。

在此背景下,工厂提供了一种结构化的机制来创建聚合,确保聚合遵守封装和一致性原则。它们有助于初始化和组装聚合内的各种元素,从而维护领域模型的整体完整性。

然而,聚合规则固有的灵活性允许开发人员选择其他方法来创建聚合。在某些场景中,尤其是对于更简单的聚合,直接实例化而无需工厂的参与可能可行,而不会损害整体设计原则。

领域驱动设计的优势

  • 工艺改进:DDD 专注于软件开发与业务需求之间关系的方法,无疑能让开发人员在工作中表现更出色。它有助于以适当和有组织的方式解决业务领域中的复杂问题。
  • 灵活性:DDD 有助于使软件架构更加灵活,这使我们能够更好地响应业务需求的变化。它具有很强的适应性,因为它可以与业务环境的变化相匹配。
  • 领域偏好:DDD 确保我们的软件是根据我们的业务规范构建的。DDD 不是试图将所有内容都塞进一个单一的界面,而是强调优先处理我们业务的不同领域,并围绕这些领域构建我们的软件。这样,我们的软件架构就支持我们的核心业务目标和价值观。通过这种方式,学生可以找到相关且有趣的信息;帮助他们做出明智的决策,而不是盲目遵循他人提供的指令。
  • 减少沟通障碍:DDD 使用一个称为“通用语言”的术语来弥合项目团队之间的沟通障碍。通过使用通用语言来加强沟通,从而促进各方之间的协调与合作。

领域驱动设计的缺点

  • 需要领域专业知识:在我看来,要有效实施 DDD,对领域有深入的理解至关重要。如果一个人不了解业务环境的具体细节,就很难将 DDD 付诸实践。
  • 鼓励迭代实践:DDD 建议遵循迭代实践,但一些开发团队可能有不同的方法或偏好。一些团队可能难以采用 DDD 鼓励的迭代方法。
    在我看来,通过遵循聚合规则在 DDD 中适当应用工厂,突出了设计模式和适应性之间具有挑战性的平衡。工厂的结构化方法在创建聚合时是理想的。

尽管工厂提供了一种结构化的聚合创建方法,但聚合规则可能需要根据聚合的复杂性采用不同的策略。与传统软件开发实践相比,DDD 倾向于提供一些好处,例如领域专业知识、更高质量的代码、更顺畅的团队协作,因此,尽管伴随着一些障碍,但它在软件工程领域证明了自己是一种有价值的方法。

领域驱动设计 (DDD) 的关键思想

  • 共享语言:DDD 认为,项目中的每个人,无论是开发人员还是业务人员,都应该使用相同的词语。这就像使用简单的词语,以便每个人都能更好地理解彼此。
  • 限界上下文:DDD 谈论“限界上下文”,它们就像项目中的小区域。它们有助于保持事物井然有序和清晰。它通过说“这里我们讨论这些主题,那里我们谈论那些主题”来帮助保持事物分类。
  • 实体和值对象:在 DDD 中,你有“实体”,它们就像你计算机故事中的主要角色。它们具有独特的身份。然后是“值对象”,它们就像你故事中不会改变的道具或物品。
  • 聚合:一组相互关联的项被视为一个整体。这就像将故事中相互契合的角色和事物组合在一起。
  • 存储库:存储库就像你群组的图书馆。它们帮助你保存和获取你的群组,而无需担心它们在哪里。
  • 服务:DDD 使用“服务”来处理那些不明确属于角色或道具的工作。这就像有一个特别的助手来做困难的工作或组织故事的不同部分。
  • 域事件:把“域事件”想象成你的故事中发生了一件大事的消息。这就像在说,“看!这刚刚改变了,每个人都必须知道。”
  • 战略设计:DDD 专注于做出关于你的软件应该如何协同工作的良好选择。这就像为你的故事制定一个大计划——决定谁做什么以及他们如何相互交流。
    价值驱动开发
    DDD 希望开发人员思考对业务真正重要的东西。这就像在说,“让我们确保我们所做的每一步都为我们分享的整体故事增添价值。”
  • 上下文映射:理解映射有助于你了解项目的不同部分如何协同工作。这就像制作一张地图,以便在你的软件世界的不同部分中移动。
  • 持续改进:DDD 明白,随着你对业务的更好理解,你的故事可能需要改变。这就像在说,“我们的理解在增长。所以让我们继续让我们的故事变得更好。”
  • 测试策略:DDD 推荐与故事难度相匹配的测试方法。这就像查看细节(单元测试),查看不同部分如何协同工作(集成测试)并确保整个故事符合业务需求。

结论

最后,领域驱动设计(DDD)提供了一种有用的方法来制作软件,而不会让人们觉得困难。这就像拥有一种我们都理解的语言,一个有组织的故事和计划,用于创建与它所支持的业务良好协作的软件。DDD 通过关注团队合作、智能规划和持续改进,帮助开发人员创建强大而有用的系统。它还确保他们的工作符合业务不断变化的需求。归根结底,DDD 不仅仅是规则;它是一个故事指南,让开发人员以令人兴奋和基于价值的方式处理真实的业务问题。