健康检查。结构化日志。带抖动的重试策略。每个编写 .NET Core 服务“生产就绪清单”的人都会列出同样的内容,这些内容本身没有错。但如果你所在的服务都运行在共享领域模型中,那么这并不是你首先应该关注的清单。
我花了这个月的一部分时间来做一件不在任何人清单上的事:把我们的领域项目打成带版本号的 NuGet 包,推送到私有 Artifactory 源,并构建一个机器人,它唯一的工作就是更新版本号,然后把这个变更传播到所有使用该包的微服务仓库。
这听起来像是在做基础工程,它确实是基础工程。但正是这个基础工程决定了“生产就绪”对于整个服务集群意味着什么,而不是仅仅针对单个服务。
它解决的问题是这样的。一旦你把共享的领域逻辑——实体、值对象、多个服务都需要用到的业务规则——从单体应用中拆分出来放进一个库,你就创建了一个之前不存在的依赖图。每个消费服务现在都对它运行的这个库的版本有自己的看法(隐式的或明确的)。在我们正确打包之前,这种看法是通过复制粘贴和部落记忆来强制执行的。没有人真正知道哪个服务拥有哪个版本的哪条规则。
NuGet 解决了打包问题的一半。Artifactory 提供了一个私有源,使得包不会泄露到组织之外,并且你可以获得谁在何时拉取了什么的审计日志。这些部分本身并不有趣。有趣的部分——真正决定这种方法是帮到你还是悄悄毁掉你——是这个机器人。
机器人监视领域包仓库。当新版本发布时,它会针对每个下游微服务仓库打开一个 PR,更新引用。这就是它的全部工作。没有巧妙的设计,只是把过去常常被遗忘的手动依赖更新工作脚本化了。
在这里,我要和你在半数“正确微服务”文章中看到的观点不同:自动化这个更新是好的,但前提是你承认你刚刚构建了一个内部的 Dependabot,而内部的 Dependabot 需要和外部的 Dependabot 同样的纪律——大多数团队因为“这是我们自己的代码,我们会抓住它”而跳过了这种纪律。
你不会可靠地抓住它。
如果机器人打开 PR,有人像对待日志库的补丁版本更新一样草率地合并领域包更新,那么你就离一个被错误标记的 semver 更新导致破坏性变更在同一个下午降临到十几个服务只有一步之遥。一个共享领域库不是一个叶子依赖。它是承重结构。一个主版本更新需要得到与模式迁移同等的审查,因为从功能上讲它就是模式迁移。
所以真正的生产就绪问题不是“我们有一个更新版本的机器人”,而是“每个消费服务是否都有一个 CI 门禁,在 PR 可合并之前,针对新包版本运行自己的集成套件——而不是之后”。我们第一天并没有这个门禁。我们有机器人打开 PR,绿色构建意味着“它能编译”,还有一个就放在那里的合并按钮。那不是就绪。那是披着就绪外衣的速度。
把门禁做好意味着故意放慢机器人的速度。它仍然立即打开 PR,但在大版本更新时,它不会自动合并,直到消费服务的完整集成套件通过新包测试——不仅仅是单元测试,而是包括那些没人喜欢运行的慢速测试在内的整个套件。这一改变把故障模式从生产环境中的静默破坏转变成了需要有人查看的红色 CI 检查。不激动人心。正确。
这里有一个真实的权衡,我不会假装它不存在:这会减慢领域修复到达每个服务的速度。在旧的复制粘贴世界中,共享值对象中的一个 bug 修复会到达某个服务——只要有人记得去更新,无论他们什么时候记得——很慢,但没有人的构建会因为它而在一夜之间崩溃。在新世界中,修复会快速传播到每个连接到机器人的服务,任何修复与本地假设发生冲突的服务都会立即在 CI 中发现,而不是三周后在事故频道中发现。
我每次都会选择第二种故障模式。快速且可见胜过缓慢且不可见。但代价是真实的,假装不存在是这些系统在周五下午 5 点构建因更新而失败时第一次失去团队信任的原因。
还有一件事值得明确地说:这一切都不会取代通常的清单。一个服务仍然需要健康检查、合理的重试策略,以及实际上能在请求中关联的日志。打包和版本更新自动化为你带来的是一个在整个集群中都成立的生产就绪清单,而不仅仅是对你碰巧仔细测试的那个服务成立。一个服务可以在隔离状态下通过清单上的每一项,然后在共享库以没人标记的方式在其下发生变化时立即宕机。
如果你正在构建共享的 .NET 领域库,但还没有弄清楚谁来审查主版本更新、哪个测试套件必须在更新合并前通过,以及当机器人的 PR 破坏了下游两个服务的东西时谁来负责,那么你没有一个生产就绪的集群。你只有一次性的生产就绪服务,以及一个悄悄决定什么时候不再成立的机器人。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.