PHP,DDD,CQRS,Event Sourcing,Kubernetes,Docker,Golang

0%

[译]开始使用领域驱动设计 - 《Domain-Driven Design in PHP》第1章

本篇博文由本博客(http://www.veitor.net)经原文翻译,转载请注明出处。

有什么大惊小怪的呢?如果你已经阅读了Vaughn Vernon和Eric Evans关于这个话题的书籍,那么你可能对我们即将要谈论的会很熟悉,因为我们大量借鉴了他们书中的定义和解释。 领域驱动设计(DDD)是帮助我们成功理解和构建软件模型设计的一种方法。 它为我们提供了策略(strategic)和战术(tactical)建模工具,以帮助设计符合我们业务目标的高质量软件。

本书的主要目的是向你展示领域驱动设计中战术(tactical)模式的代码示例。如果您想了解更多关于策略(strategic)模式和主要的领域驱动设计知识,您应该阅读由Vaughn Vernon著作的《Domain Driven Design Distilled》或由Eric Evans著作的《Driven Design Reference: Definitions and Pattern Summaries》

重要的是,领域驱动设计不是指关于某些技术的,而是关于围绕业务和使用某些技术去提高价值的开发知识的。一旦你有能力去理解你公司的业务,你才能够参与到软件建模过程中并构建出通用语言(Ubiquitous Language)。

为什么领域驱动设计很重要?

软件不仅仅是代码,代码很少是我们的最终目标。代码只是解决业务问题的媒介,所以为什么代码去使用和业务不同的用语呢? 领域驱动设计强调确保业务和软件代码使用相同的用语。 一旦打破这个阻碍,我们就不需要在代码和业务之间转化和繁琐的同步了,业务信息会保留下来。每个人都致力于探索业务领域,而不仅仅是程序员。 最终的软件是通用语言的唯一事实。(译者注:我个人理解是最终完成的软件代码,就是业务规则的最终表现,看懂代码即看懂业务)

领域驱动设计也为策略和战术设计提供了一个框架。策略设计根据业务价值确定最重要的开发领域,战术设计建立实战测试的构建块和模式的领域模型。

领域驱动设计的三大要点

领域驱动设计是一种交付软件的方法,它关注于三大要点:

  1. 通用语言(Ubiquitous Language):领域专家和软件开发人员共同为正在开发的业务领域构建通用语言,没有所谓的“我们”和“他们”,而始终是“我们”。开发软件是商业投资,而不仅仅是成本。 构建泛在语言所涉及的努力有助于在所有团队成员中传播深入的领域洞察力。软件开发是一种商业投资,而不仅仅是代价花费,团队成员构建通用语言所付出的努力有助于大家深入领域。
  2. 策略设计(Strategic Design):领域驱动设计解决了业务方向背后的策略,而不仅仅是技术方面。它有助于确定内部关系和初期的预警反馈制度。在技术方面,战略设计通过为如何实现面向服务的架构提供动力来保护每项业务服务。
  3. 战术设计(Tactical Design):领域驱动设计为迭代软件和交付提供了工具和构建模块。 战术设计工具产生的软件不仅是正确的,而且也是可测试的,且不易出错。

通用语言(Ubiquitous Language)

整合限界上下文(第12章介绍)放一起,通用语言是领域驱动设计的主要优势之一。

上下文(Context)
就目前而言,限界上下文是一个围绕系统的有概念的边界。边界内的通用语言有着特定的语境含义,边界外部的概念可能有着与其内部不同的含义。

所以,如何去发现、探索和获取这个特殊的语言,强调以下几点:

  • 确定关键业务流程、业务获取的信息和业务输出展现的信息
  • 创建一个术语定义的词汇表
  • 用文档记录重要的软件概念
  • 与团队其他成员(开发人员和领域专家)分享和丰富收集到的知识。

自从领域驱动设计诞生以来,构建通用语言过程的新技术出现了。其中最重要、常用的一种就是事件风暴(Event Storming)。

事件风暴(Event Storming)

Alberto Brandolini在博客文章中解释了事件风暴及其优点,他实施的事件风暴比我们做得更简便,事件风暴的形式是一场讨论会,目的为了迅速探索复杂业务领域:

  • 强大的:事件风暴允许我与许多参与讨论的人能够在几小时内想出一个完整业务的综合模型,而不是花上几周时间。
  • 诱人的:事件风暴的思想是将有疑问的人和会解答的人聚集在同一个房间内,一起去构建模型。
  • 具有效率的:所得到的模型与领域驱动设计实现风格完全一致(尤其适合事件溯源(Event Sourcing)方式),并且可以快速确定上下文和聚合边界。
  • 简单的:事件风暴会议中所用到的符号是非常简单的,并没有可能会让人听不懂的UML。
  • 有趣的:我总是在组织讨论会的过程中感到快乐,大家都充满活力,并且比预期时间还要早完成任务。

如果你想要更多了解事件风暴(Event Storming),浏览一下Brandolini著作的书《Introducing EventStorming》

何时考虑使用领域驱动设计?

领域驱动设计不是一个银弹;与软件中的所有内容一样,这取决于上下文。作为一个经验法则,使用其来简化你的领域,且不会增加复杂性。

如果您的应用程序是以数据为中心的,并且您的用例场景主要处理数据库中的每一行数据并执行CRUD操作(即创建,读取,更新和删除),则你不需要域驱动设计。相反,你公司唯一需要的是在你的数据库前面有一个fancy face(译者注:fancy face实在找不到合适的中文来解释)。

如果你的应用少于30个用例场景,使用Symfony或Laravel等框架去处理业务逻辑可能会更简单。

如果你的应用多于30个用例场景,你的系统可能会逐渐成为可怕的大泥球(Big Ball of Mud)。如果你确定你的系统将会更复杂,你应该使用领域驱动设计来处理这个复杂性。

如果你知道你的应用正在发展并且可能经常做改动,领域驱动设计肯定会有助于管理系统的复杂性并随着时间的推移来重构您的模型。

如果你不理解你正在开发的领域,是因为它是新的并且之前没有人遇到过的,这可能对你来说意味着领域足够的复杂而去开始使用领域驱动设计,在这种情况下,你需要去与领域专家紧密合作来得到一个正确的领域模型。

困难的部分

应用领域驱动设计并不容易,这需要花费时间和精力去了解业务领域、术语、调查、和领域专家一起合作,而不是一味的写代码。你需要让领域专家参与进来,需要开放和健康的持续对话来将他们的口中所说的话语转化为软件。最重要的是,我们必须努力避免技术方面的思考,首先去认真思考每个对象的行为和通用语言。

战略设计概述

为了提供域驱动设计战略方面的总体概述,我们将使用Jimmy Nilsson著作《Applying Domain-Driven Design and Patterns》里的一种方法,考虑两个不同的空间:问题空间和解决方案空间。

在问题空间中,领域驱动设计使用领域和子域来将公司想要解决的问题进行分组。以在线旅行社(OTA)为例,他们的问题在于处理如预订机票和酒店等事情。这样的领域可以分组成不同的子域,如定价,库存,用户管理等。

在解决方案空间,领域驱动设计提供了两种模式:限界上下文(Bounded Context)和上下文映射(Context Map)。其目标是通过定义上下文之间的交互和交互细节来实现已经确定的子域。我们继续使用上述的OTA例子,每个子域都将通过实现限界上下文来解决。例如,思考一个由团队为定价管理(Pricing Management)子域开发的Web应用程序和一个现成的用户管理(User Management)子域。上下文映射将显示两个限界上下文之间如何关联起来。在上下文映射中,我们可以看到两个限界上下文之间存在什么关系(如:客户-供应方关系,合作关系)。理想的方法是通过一个限界上下文来实现一个子域,但这并非总是可行。在实施方面,当遵循领域驱动设计时,你最终将做成分布式架构。正如你可能已经知道的那样,分布式架构单体架构更复杂,那么为什么这种方式很有趣,尤其对于大型公司?是不是真的值得那样做?嗯,是值得的。

事实证明,分布式架构可以提高公司的整体生产力,因为这种方式为你的产品划分了边界,并且每个边界由专门团队来开发。

如果你的领域(即你想要解决的问题)并不复杂,应用领域驱动设计的战略设计部分可能会增加不必要的成本并且降低开发速度。

如果你想更多的了解领域驱动设计的战略设计部分,你应该看一下Vaughn Vernon著作《Implementing Domain- Driven Design》的前三章内容,或者看Eric Evans的《Domain-Driven Design: Tackling Complexity in the Heart
of Software》一书,他们都关注这个方面。

相关行为:微服务和自包含系统

还有一些其他相关的行为来遵循着领域驱动设计进行架构,如微服务和自包含系统。James Lewis和Martin Fowler在微服务指南中定义了微服务概念:

微服务架构是一种将单个应用程序作为一套小型服务开发的方法,每个服务都运行在自己的进程中,并且通过轻量级通信机制(通常是一个HTTP资源API)进行相互通信。这些服务是围绕业务建立的,也可以通过自动化方式来独立部署。 这些服务几乎不需要统一管理,因此每个服务可以用不同的编程语言编写,也可以使用不同的数据存储技术。

如果你想了解更多关于微服务的知识,他们的指南是一个很好的开始。这与领域驱动设计有关系吗?Sam Newman在他的《构建微服务》书中解释过:微服务是领域驱动设计限界上下文的实现。

除了微服务,另一个就是自包含系统(Self-Contained Systems,SCS),根据自包含系统网站

自包含系统是一种架构,专注于将功能拆分到许多独立的系统中,并使得这些小系统协作形成逻辑完整的系统。这将避免但的单体应用持续发展并最终导致不可维护的问题。在过去几年里,我们已经看到了其在中大型项目中的优势,其思想是遵循一定的规则将一个大系统分割成几个小的自包含系统。

该网站在也指出了自包含系统的7个特点:

  • 每一个自包含系统都是自治的,对于自包含系统内的数据、处理逻辑和所有渲染界面的代码都包含在内。一个自包含系统可以完成它自己的用例场景,而不依赖于其他可用系统。
  • 每一个自包含系统都归一个团队拥有。这并不意味着只有这一个团队才可以去修改代码,而是该团队对代码修改拥有最终话语权,如是否允许通过一个git的代码合并请求。
  • 与其他自包含系统或第三方系统通信是尽可能的异步形式。在自包含系统自身请求响应的生命周期内不应该对其他自包含或第三方系统进行同步请求,这将对系统间解耦、减少故障影响并且支持自治。其目的就是关于时间上进行解耦:即使其他系统临时故障,而这个自包含系统也应该能正常工作。即使技术上的通信是同步的,也应该要达到这个目的。
  • 一个自包含系统可以有一个可选的服务API。因为其自身拥有web界面来与用户进行交互,而不用专门通过UI服务来与用户交互。但是,这个API对于移动客户端或者其他自包含系统仍然是有用的。
  • 每一个自包含系统要真正实现任何有意义的功能的话,其必须要包含数据和业务逻辑,一个自包含系统必须由其自己来实现功能特性。
  • 一个自包含系统应该通过其自带的用户界面给用户带来其自身的功能特性,因此自包含系统应该不与其他自包含系统共享UI,但系统之间仍然是可以有链接到对方。但是,异步集成意味着即使其他自包含系统的UI挂了本系统还是能正常使用。为了避免紧耦合,自包含系统不应该与其他自包含系统共享业务代码。为自包含系统创建合并请求或者使用公用类库是一个很好的选择,如:数据库驱动类库或OAuth客户端类库等。

小练习
与你的同事讨论分布式架构的优点和缺点,思考使用不同的编程语言、部署方式和过程、基础设施结构和责任等。

总结

在这一章你已经了解了:

  • 领域驱动设计不是关于技术的,它实际是关于通过关注模型来为你所在领域提供价值的。每个人都参与到发现领域的过程中,开发人员和领域专家组团基于通用语言(Ubiquitous Language)去构建知识库。
  • 领域驱动设计提供战术和战略建模工具去设计高质量的软件。战略设计针对业务方向,帮助定义内部关系、通过定义边界来技术性的保护每个业务服务。战术设计为迭代设计提供有用的构建块。
  • 领域驱动设计只有在某些特定情况下才有意义,它不是每个软件问题的银弹,所以你是否大量使用它取决于你所处理问题的复杂程度。

-领域驱动设计是一项长期的付出,需要积极努力。领域专家将被要求与开发人员紧密合作,并且开发人员需要去思考业务。客户才该是最终获得满意的人。

实施领域驱动设计需要努力。如果这很容易,每个人都将能写出高质量的代码。准备好,因为你很快将了解到如何去写代码,这些代码在你阅读时会很好的描述出你公司的业务!