【读书笔记】《Go With The Domain》5. DDD Lite
📔【读书笔记】《Go With The Domain》5. DDD Lite
2023-10-4
| 2024-3-24
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Mar 24, 2024 03:12 AM
Parent item
领域
当我开始在GO中工作时,社区并没有积极关注DDD(领域驱动设计)和整洁结构等技术。我多次听到:“不要在Golang做Java!”,“我已经在Java看到了,请不要!”。
那时我已经在PHP和Python拥有将近10年的经验。我已经看到了太多的失败的案例。我记得所有这些“八千”(具有8K+代码行的方法)和没有人想维护的应用程序。我去检查这些丑陋的怪物的旧GIT历史,一开始代码修改看上去没什么问题。但是随着时间的流逝,小而无辜的问题开始变得更加重要,更严重。幸运的是,我还看到了DDD和整洁架构如何解决这些问题
是Golang与众不同?还是在Golang编使用微服务能解决这些问题吗?

本来应该那么美好

现在,在与多人交流经验看了很多代码库之后,我的观点比 3 年前干净了一些。不幸的是,我现在根本不认为仅仅使用 Golang 和微服务就能让我们摆脱我之前遇到的所有这些问题。我开始真实地回忆起过去的糟糕时光。
由于代码库相对较新,它不太明显。由于 Golang 设计,它不太明显。但我确信,随着时间的推移,越来越多没人愿意维护遗留 Golang 应用程序。
幸运的是,三年前,尽管受到冷遇,我并没有放弃。我决定尝试使用 DDD 以及之前在 Go 中对我有用的相关技术。在 Milosz 的带领下,我们领导的团队三年来都成功地使用了 DDD、整洁架构以及 Golang 中所有相关的、不太流行的技术。
它们使我们能够以恒定的速度开发应用程序和产品,无论代码的年龄如何。从一开始就很明显,从其他技术中以 1:1 的方式迁移模式是行不通的。最重要的是,我们没有放弃惯用的 Go 代码和微服务架构 - 它们完美地结合在一起!今天我想和大家分享第一个最简单的技术——DDD lite。

Golang 中的 DDD 现状

在撰写本章之前,我在 Google 上查阅了几篇关于 Go 中 DDD 的文章。我在这里会很残酷:他们都忽略了使 DDD 发挥作用的最关键点。如果我想象我在没有任何 DDD 知识的情况下阅读这些文章,我就不会被鼓励在我的团队中使用它们。这种肤浅的做法也可能是 DDD 在 Go 社区中至今仍未流行的原因。
在本书中,我们尝试展示所有必要的技术,并以最务实的方式进行实践。在描述任何模式之前,我们首先提出一个问题:它给我们带来了什么?这是挑战我们当前思维的绝佳方式。我确信我们可以改变 Go 社区对这些技术的接受程度。我们相信它们是实施复杂业务项目的最佳方式。我相信我们将帮助确立 Go 作为一种伟大语言的地位,不仅可以用于构建基础设施,还可以用于构建商业软件。

你需要慢一点才能快一点

以最简单的方式实施你正在从事的项目可能很诱人。当你感受到来自“高层”的压力时,这就更诱人了。那么我们是否会使用微服务呢?如果需要的话,我们会直接重写服务吗?这个故事我听过很多次,但很少有一个圆满的结局。确实,走捷径可以节省一些时间,但仅限于短期临时的。
让我们考虑任何类型的测试的例子。您可以在项目开始时跳过编写测试。显然,您会节省一些时间,管理层也会很高兴,显然项目交付得更快。
但从长远来看,这条捷径并不值得。当项目发展时,您的团队将开始害怕做出任何改变。最终,您将花费的时间总和将高于从一开始就实施测试的时间。从长远来看,你会因为一开始就为了快速性能提升而牺牲质量而放慢速度。另一方面 - 如果项目不重要并且需要快速创建,您可以跳过测试。这应该是一个务实的决定,而不仅仅是“我们更了解,我们不会制造错误”。
DDD 的情况也是如此。当你想使用DDD时,一开始你会需要更多的时间,但长期来看节省的时间是巨大的。然而,并不是每个项目都复杂到足以使用 DDD 等先进技术不存在质量与速度之间的权衡。如果你想长期快速发展,就需要保持高质量

有任何证据可以证明吗?

如果您两年前问我这个问题,我会说:“好吧,我觉得它效果更好!”。但是仅仅相信我的话可能还不够。有许多教程表明了一些愚蠢的想法,并声称他们没有任何证据来工作 - 让我们不要盲目信任它们!
高质量开发和地址了开发的差异
高质量开发和地址了开发的差异
只是提醒一下:如果某人有几千名 Twitter 粉丝,仅此一点并不能成为信任他们的理由!
幸运的是,两年前的一本书《加速:精益软件和 DevOps 的科学:构建和扩展高性能技术组织》发布了。简而言之,本书描述了影响开发团队绩效的因素。但这本书之所以出名,是因为它不仅仅是一套未经验证的想法,而是基于科学研究。
我最感兴趣的是展示如何让团队成为表现最佳的团队的部分。本书展示了一些显而易见的事实,例如介绍 DevOps、CI/CD 和松散耦合架构,这些都是高绩效团队的重要因素。
💡
如果你不熟悉 DevOps 和 CI/CD 之类的东西,您可以从以下书籍开始学习:The Phoenix Project 和 The DevOps Handbook 。
书中告诉我们什么是表现最佳的团队。
我们发现,只要系统是松耦合的,构建和维护它们的团队也会是松耦合的,所有类型的系统都可以实现高性能。
这一关键的架构属性使团队能够轻松测试和部署单个组件或服务,即使组织及其运营的系统数量不断增长,也就是说,它允许组织随着规模的扩大而提高生产力。
那么我们使用微服务就完成了吗?如果这一章足够的话我就不会写这一章了。
  • 对系统设计进行大规模更改而不需要其他团队进行任何更改
  • 无需与团队外部人员沟通和协调即可完成工作
  • 部署和发布其产品或按需服务,无论其依赖于何种其他服务
  • 按需进行大部分测试,无需集成测试环境
  • 在正常工作时间内执行部署,停机时间可以忽略不计
不幸的是,在现实生活中,许多所谓的面向服务的架构不允许彼此独立地测试和部署服务,因此无法使团队获得更高的性能。
如果忽略这些特征,那么部署在容器上的最新微服务架构并不能保证更高的性能。
为了实现这些特性,设计系统是松散耦合的——也就是说,可以彼此独立地进行更改和验证。
仅使用微服务架构并将服务拆分为小块是不够的。如果以错误的方式完成,就会增加额外的复杂性并减慢团队的速度。 DDD 可以在这方面为我们提供帮助。我多次提到 DDD 术语。 DDD到底是什么?

什么是DDD(领域驱动设计)

让我们从维基百科的定义开始:领域驱动设计(DDD)的概念是代码的结构和语言(类名、类方法、类变量)应该与业务领域相匹配。例如,如果您的软件处理贷款申请,它可能具有 LoanApplicationCustomer 等类以及 AcceptOfferWithdraw 等方法。
嗯,这不是完美的。它仍然缺少一些最重要的点。
还值得一提的是,DDD 是在 2003 年引入的。那是很久以前的事了。一些提炼可能有助于将 DDD 置于 2020 年和 Go 的背景下。
💡
注意:如果您对 DDD 创建时的一些历史背景感兴趣,您应该查看 DDD 创建者 Eric Evans 所著的《Tackling Complexity in the Heart of Software》
我简单的DDD定义是:确保以最佳方式解决有效问题。在此之后,以您的业务理解的方式实现解决方案,而无需从需要技术语言的任何额外翻译中进行翻译。如何实现?

编码是一场战争,获胜需要策略!

我想说“ 5天的编码可以节省15分钟的计划”。
在开始编写任何代码之前,您应该确保解决有效的问题。这听起来很明显,但是根据我的经验,它听起来并不容易。通常,工程师创建的解决方案实际上并没有解决企业要求的问题。我将一组有助于我们编码的模式命名为DDD战略模式
根据我的经验,DDD战略模式通常会被忽略。原因也很简单:我们都是开发人员,我们喜欢编写代码,而不是与“业务人员”交谈。不幸的是,当我们在地下室关起门来而不与任何业务人员交谈,这种方法有很多弊端:缺乏对业务人员的信任,同时也缺乏对系统如何运作(来自业务人员和工程师)的知识,并解决了错误的问题 - 这些只是一些最常见的问题。
好消息是,在大多数情况下,这是由于缺乏事件风暴等适当的技术造成的。它们可以给双方带来优势。同样令人惊讶的是,与业务人员交谈可能是工作中最令人愉快的部分之一!除此之外,我们将从适用于代码的模式开始。它们可以给我们带来 DDD 的一些优势。它们也会更快地为您提供帮助。如果没有战略模式,我想说你只能拥有 DDD 所能提供的 30% 的优势。我们将在下一章中回到DDD战略模式。

Go 中的 DDD Lite

经过相当长的介绍,终于到了接触一些代码的时候了!在本章中,我们将介绍 Go 中DDD战术模式的一些基础知识。请记住,这只是一个开始。还需要几章来涵盖整个主题。 DDD 战术模式最关键的部分之一是尝试直接在代码中反映领域逻辑

重构trainer服务

我们要重构的第一个(微)服务是trainer。我们现在将保持其他服务不变——我们稍后再处理。该服务负责保持教练的日程安排并确保我们在一小时内只能安排一次训练。它还保留有关可用时间(教练的日程安排)的信息。最初的实现并不是最好的。即使逻辑不多,代码也开始变得混乱。根据经验,我也有一些感觉,随着时间的推移,情况会变得更糟。
即使这不是有史以来最糟糕的代码,我也可以想象,一段时间后,一些新功能将到来,情况会更糟。
同时,这里也很难在这里对依赖项进行mock模拟,因此也没有单元测试。

第一个规则 - 在实施领域时,以字面意思反映业务逻辑

在实现领域时,不应该考虑带有setters和getters的“ORM Like”实体或者虚拟数据结构之类的结构(译注:也就是所谓的“贫血”模型)。相反,您应该将它们视为具有行为的类型
当您与您的业务利益相关者交谈时,他们会说“我正在安排13:00的培训”,而不是将预约培训”的属性状态设置为“13:00”。他们也不会说:“您无法将属性状态设置为'Training_scheduled'”。相反,“如果某个小时不可用,则无法安排培训”。
如何将其直接体现在代码里?
一个可以帮助我们实施的问题是:“业务会理解我的代码而无需任何额外的技术术语翻译吗?”。您可以在该片段中看到,即使是非技术人员也能理解何时可以安排培训。
这种方法的成本不高,有助于解决复杂性,以使规则更容易理解。即使变化不大,我们也摆脱了将来会变得更加复杂这条“if”的墙。
现在,我们还可以轻松添加单元测试。什么是好的:我们不需要在这里mock任何需要依赖的东西。测试也是一份文档,可帮助我们了解Hour的行为。
现在,如果有人问“我什么时候可以安排训练”这个问题,你可以很快回答。在更大的系统中,这类问题的答案甚至不太明显——我花了几个小时多次试图找到某些对象以意想不到的方式被使用的所有地方。下一条规则将在这方面为我们提供更多帮助。

测试辅助

在创建领域实体的测试中拥有一些辅助非常有用。例如:newExampleTrainingWithTimenewCanceledTraining 等。它也使我们的领域测试更具可读性。自定义断言(例如assertTrainingsEquals)也可以节省大量重复。 github.com/google/go-cmp 库对于比较复杂结构非常有用。它允许我们将域类型与私有字段进行比较,跳过一些字段验证或实现自定义验证功能。
提供常用构造函数的Must版本也是一个好主意,例如MustNewUser。与普通的构造函数相反,如果参数无效,它们会Panic(对于测试而言,这不是问题)。

第二条规则:始终在内存中保留有效状态

根据我的观察,当您确定您使用的对象始终有效时,它有助于避免很多 ifs 和 bug。您也会更加自信,因为您知道您无法使用当前代码做任何愚蠢的事情。
我害怕做出一些改变,因为我不确定它的副作用。如果不确信您正确使用代码,开发新功能就会慢得多!
我们的目标是仅在一个地方进行验证(良好的 DRY),并确保没有人可以更改 Hour 的内部状态对象的唯一公共 API 应该是描述行为的方法。没有愚蠢的 getter 和 setter!我们还需要将类型放入单独的包中,并将所有属性设为私有。
我们还应该确保类型内部的没有违反任何规则。
不好的例子:(对外的接口没有体现业务功能,导致要实现一个业务功能的话需组合多个API调用)
好的例子:(对外API直接提现要实现的功能,功能中的业务逻辑由内部api实现)

第三条规则 - 领域需要与数据库无关

有些人说,领域受数据库客户端影响是可以的。根据我们的经验,严格保留领域而没有任何数据库影响力最佳。最重要的原因是:
  • 领域类型不是由使用的数据库解决方案来塑造的,它们应仅由业务规则塑造
  • 我们可以以更优化的方式将数据存储在数据库中
  • 因为GO的设计像注释一样缺乏“魔术”,使得系统方案受ORM或任何数据库解决方案影响更大
💡
领域优先方法。如果项目足够复杂,我们甚至可以花 2-4 周的时间在领域层上工作,仅实现内存数据库。在这种情况下,我们可以更深入地探索这个想法,并在稍后选择数据库。我们所有的实现都只是基于单元测试。 我们尝试了几次这种方法,而且效果总是很好。在这里设置一些时间框也是个好主意,以免花费太多时间。 请记住,这种方法需要良好的关系和来自业务人员的大量信任!如果您与业务的关系不太好,赶紧去使用战略 DDD 模式改善这种情况。
接下来我们介绍Repository接口并假设其有效。我将在下一章中更深入地介绍这个主题。
💡
您可能会问为什么 UpdateHourupdateFn func(h *Hour) (*Hour, error) ,这是为了更好地处理数据库事务。您可以在Repository模式(下一章)中了解更多信息。

使用领域对象

我对 gRPC 端点进行了一次小重构,以提供一个比 CRUD 更“面向行为”的 API。更好地体现了领域的新特点。根据我的经验,维护多个小方法比维护一个允许我们更新所有内容的“上帝”方法要容易得多。
现在的实现更加简单且更容易理解。我们这里也没有逻辑——只是一些编排。我们的 gRPC 处理程序现在有 18 行,并且没有领域逻辑!
架构设计
  • 读书笔记
  • 技术架构
  • 【读书笔记】《Go With The Domain》13. 在 CI/CD 管道中运行集成测试【读书笔记】《Go With The Domain》4. 何时远离DRY(Don’t Repeat Yourself )
    目录