在 Python 的世界里,我们经常引用 Python 之禅:“应该有一种——最好只有一种——显而易见的方法来实现它。” [ 1 ] 不幸的是,随着项目规模的扩大,最显而易见的做事方式并不总是能够帮助您管理复杂性和不断变化的需求的方式。
我们在本书中讨论的技术和模式都不是新的,但它们大多是 Python 世界中的新内容。本书并不能取代该领域的经典著作,例如 Eric Evans 的《 领域驱动设计》 或 Martin Fowler 的《企业应用程序架构模式》 (均由 Addison-Wesley Professional出版)——我们经常引用这些著作并鼓励您去阅读。
但文献中所有经典的代码示例往往都是用 Java 或C++/#编写的,如果你是 Python 用户,并且很长时间没有使用过这两种语言(或者实际上从未使用过),那么这些代码列表可能会相当……令人费解。另一本经典教材《Fowler 的重构》 (Addison-Wesley Professional)的最新版本 用 JavaScript 编写是有原因的。
TDD、DDD 和事件驱动架构
按照知名度排序,我们知道三种管理复杂性的工具:
- 测试驱动开发(TDD) 可帮助我们构建正确的代码,让我们能够重构或添加新功能,而无需担心回归问题。但要充分发挥测试的效果却并非易事:我们如何确保测试运行得尽可能快?我们如何从快速、无依赖的单元测试中获得尽可能多的覆盖范围和反馈,同时将速度较慢且不稳定的端到端测试数量降至最低?
- 领域驱动设计(DDD)要求我们集中精力构建良好的业务领域模型,但如何确保我们的模型不会受到基础设施问题的束缚并且不会变得难以改变?
- 通过消息集成的松散耦合(微)服务(有时称为 反应式微服务)是管理跨多个应用程序或业务领域的复杂性的成熟解决方案。但如何让它们与 Python 世界中的现有工具(Flask、Django、Celery 等)相适应并不总是显而易见的。
本书的目的是介绍几种经典的架构模式,并展示它们如何支持 TDD、DDD 和事件驱动服务。我们希望它能成为以 Pythonic 方式实现它们的参考,并希望人们可以将其作为进一步研究该领域的第一步。
谁应该读这本书
亲爱的读者,我们对您有以下几点假设:
- 您已经接近一些相当复杂的 Python 应用程序。
- 您已经看到了尝试管理这种复杂性所带来的一些痛苦。
- 您不一定了解 DDD 或任何经典的应用程序架构模式。
我们围绕示例应用构建架构模式的探索,逐章构建。我们在工作中使用 TDD,因此我们倾向于先展示测试列表,然后再展示实现。如果您不习惯先测试,一开始可能会感觉有点奇怪,但我们希望您能很快习惯先看到代码“被使用”(即从外部),然后再看到代码在内部是如何构建的。
我们使用了一些特定的 Python 框架和技术,包括 Flask、SQLAlchemy 和 pytest,以及 Docker 和 Redis。如果您已经熟悉它们,那也没什么坏处,但我们认为这不是必需的。本书的主要目的之一是构建一种架构,其中特定的技术选择只是次要的实现细节。
您将学到的内容的简要概述
本书分为两部分;以下是我们将要涵盖的主题及其所属章节。
#第一部分
领域建模和 DDD(第1、2和7[url=https://www.cosmicpython.com/book/chapter_02_repository.html]章[/url])
在某种程度上,每个人都学到了这样的教训:复杂的业务问题需要以领域模型的形式反映在代码中。但是,为什么在不涉及基础设施问题、我们的 Web 框架或其他问题的情况下,这似乎总是如此困难?在第一章中,我们概述了领域建模和 DDD,并展示了如何开始使用没有外部依赖关系的模型和快速单元测试。稍后,我们将回到 DDD 模式,讨论如何选择正确的聚合,以及这种选择与数据完整性问题的关系。
存储库、服务层和工作单元模式(第2、4和5[url=https://www.cosmicpython.com/book/chapter_04_service_layer.html]章[/url])
在这三章中,我们介绍了三种密切相关且相互加强的模式,它们支持我们保持模型不受外部依赖的愿望。我们围绕持久存储构建一个抽象层,并构建一个服务层来定义系统的入口点并捕获主要用例。我们展示了这一层如何使我们的系统轻松构建精简入口点,无论是 Flask API 还是 CLI。
关于测试和抽象的一些想法(第 3 章和第5[url=https://www.cosmicpython.com/book/chapter_05_high_gear_low_gear.html]章[/url])
在介绍第一个抽象(存储库模式)之后,我们借此机会对如何选择抽象以及它们在选择软件耦合方式方面的作用进行了一般性讨论。在介绍服务层模式之后,我们将讨论如何实现测试金字塔 以及如何在尽可能高的抽象级别编写单元测试。
#第二部分
事件驱动架构(第8 - 11章)
我们引入了另外三种相互加强的模式:领域事件、消息总线和处理程序模式。 领域事件是一种载体,用于捕捉这样一种思想:与系统的某些交互是其他交互的触发器。我们使用消息总线来允许操作触发事件并调用适当的处理程序。我们继续讨论如何将事件用作微服务架构中服务之间集成的模式。最后,我们区分命令和事件。我们的应用程序现在基本上是一个消息处理系统。
命令查询责任分离CQRS([chapter_12_cqrs])
我们给出了一个带有和不带有事件的命令查询责任分离的示例。
依赖注入([chapter_13_dependency_injection])
我们整理了显式和隐式依赖关系并实现了一个简单的依赖注入框架。
第 1 部分:构建支持领域建模的架构
大多数开发人员从未见过领域模型,只见过数据模型。
— 西里尔·马特赖尔DDD 欧盟 2017
我们与大多数开发人员讨论过架构,他们总觉得事情可以变得更好。他们经常试图挽救不知何故出错的系统,并试图将一些结构重新放回泥潭。他们知道他们的业务逻辑不应该到处散布,但他们不知道如何修复它。
我们发现,许多开发人员在被要求设计新系统时,会立即开始构建数据库模式,而将对象模型视为事后考虑。这就是一切开始出错的地方。相反,行为应该放在首位,并推动我们的存储需求。毕竟,我们的客户不关心数据模型。他们关心系统做什么;否则他们只会使用电子表格。
本书的第一部分介绍了如何通过 TDD 构建丰富的对象模型(在[chapter_01_domain_model]中),然后我们将展示如何使该模型与技术问题脱钩。我们展示了如何构建持久性无关的代码以及如何围绕我们的领域创建稳定的 API,以便我们可以积极地进行重构。
为了实现这一点,我们提出了四种关键的设计模式:
如果你想要一张图来了解我们要去的地方,请查看 [part1] 末尾的应用程序组件图,但如果还没有理解,也不用担心!我们将在本书的这一部分逐一介绍图中的每个框。
图 1. [part1]末尾的应用程序组件图
我们还花了一点时间来讨论 耦合和抽象,并通过一个简单的例子来说明我们如何以及为何选择抽象。
三个附录是对第一部分内容的进一步探讨:
- [appendix_project_structure]是我们示例代码的基础架构的描述:我们如何构建和运行 Docker 镜像,在哪里管理配置信息,以及如何运行不同类型的测试。
- [appendix_csvs]是一种“布丁的证明”类型的内容,展示了将我们的整个基础设施(Flask API、ORM 和 Postgres)换成涉及 CLI 和 CSV 的完全不同的 I/O 模型是多么容易。
- 最后,如果你想知道如果使用 Django 而不是 Flask 和 SQLAlchemy 这些模式会是什么样子,那么[appendix_django]可能会让你感兴趣。
1:领域建模
本章将介绍如何以与 TDD 高度兼容的方式使用代码对业务流程进行建模。我们将讨论域建模的重要性,并介绍域建模的几个关键模式:实体、值对象和域服务。
我们的领域模型的占位符图示是我们的领域模型模式的简单视觉占位符。我们将在本章中补充一些细节,随着我们进入其他章节,我们将围绕领域模型构建事物,但您应该始终能够在核心处找到这些小形状。
图 1. 我们的领域模型的占位图
什么是领域模型?
在简介中,我们使用术语业务逻辑层 来描述三层架构的中心层。在本书的其余部分,我们将改用术语领域模型。这是来自 DDD 社区的一个术语,可以更好地表达我们的意图(有关 DDD 的更多信息,请参阅下一个侧边栏)。
领域是描述您要解决的问题的一种花哨方式。您的 作者目前为一家家具在线零售商工作。根据您谈论的系统,领域可能是采购和采购、产品设计或物流和交付。大多数程序员每天都在尝试改进或自动化业务流程;领域是这些流程支持的一组活动。
模型是捕捉有用属性的过程或现象的映射。人类非常擅长在头脑中建立事物的模型。例如,当有人向你扔球时,你几乎无意识地就能预测它的运动,因为你有一个物体在空间中运动方式的模型。你的模型绝不是完美的。人类对物体在接近光速或真空中的行为有着糟糕的直觉,因为我们的模型从未被设计来涵盖这些情况。这并不意味着模型是错误的,但它确实意味着一些预测超出了它的范畴。
领域模型是企业主对其业务的思维导图。所有商务人士都有这样的思维导图——它们是人类思考复杂流程的方式。
你可以判断出他们何时浏览这些地图,因为他们使用商业用语。在复杂系统上进行协作的人们之间自然会产生行话。
想象一下,你,我们不幸的读者,突然和你的朋友和家人乘坐外星飞船被送到距离地球数光年之外的地方,并且必须从基本原理出发,弄清楚如何导航回家。
在最初几天里,你可能只是随意按按钮,但很快你就会知道哪些按钮有作用,这样你就可以互相发出指令。你可能会说:“按下闪烁的小玩意旁边的红色按钮,然后把那个大杠杆扔到雷达装置旁边。”
几周之内,你就会更准确地使用词语来描述飞船的功能:“增加货舱三的氧气含量”或“打开小型推进器”。几个月后,你就会掌握描述整个复杂过程的语言:“开始着陆程序”或“准备扭曲”。这个过程会非常自然地发生,无需任何正式的努力来建立共享的词汇表。
在平凡的商业世界中也是如此。业务利益相关者使用的术语代表了对领域模型的精炼理解,其中复杂的想法和流程被归结为一个单词或短语。
当我们听到我们的业务利益相关者使用不熟悉的单词或以特定方式使用术语时,我们应该倾听以了解更深层的含义,并将他们来之不易的经验编码到我们的软件中。
我们将在本书中使用真实世界的领域模型,特别是我们当前工作中的模型。MADE.com 是一家成功的家具零售商。我们从世界各地的制造商处采购家具,然后销往欧洲各地。
当您购买沙发或咖啡桌时,我们必须想出如何最好地将您的货物从波兰、中国或越南运送到您的客厅。
从高层次来看,我们有独立的系统负责购买库存、向客户销售库存以及向客户发货。中间系统需要通过根据客户的订单分配库存来协调该流程;请参阅分配服务的环境图。
探索领域语言
理解领域模型需要时间、耐心和便利贴。我们与业务专家进行了初步对话,并就领域模型的第一个最小版本的词汇表和一些规则达成一致。只要有可能,我们就会要求提供具体示例来说明每条规则。
我们确保用业务术语( DDD 术语中的通用语言)来表达这些规则。我们为对象选择容易记住的标识符,以便更容易讨论示例。
更多点击标题