如何记录微服务架构:实用指南 - Archyl Blog

微服务功能强大,但以难以记录著称。本实用指南涵盖了常见挑战,展示如何将 C4 模型应用于微服务,并通过 Archyl 的实际示例详细讲解服务边界、通信模式等内容。

如何记录微服务架构:实用指南

记录单体应用是相对直接的。一切都在一个地方,边界显而易见(或者根本不存在),一张图通常就能捕捉核心结构。微服务的文档则是完全不同的挑战。

你有数十个服务,每个由不同的团队负责,每个以自己的节奏演进。服务之间通过 HTTP、gRPC、消息队列和事件流进行通信。一个用户请求可能在返回响应之前经过八个服务。架构是分布式的、异步的、多语言的,而且在不断变化。

然而大多数团队记录微服务的方式,仍然和记录单体应用时一模一样:一张白板图,准确了大约一周。

本指南介绍一种真正可扩展的微服务文档方法。我们将详细分析实际挑战,展示 C4 模型如何映射到微服务概念,并演示团队如何使用 Archyl 在服务演进过程中保持文档的时效性。

为什么微服务文档比你想象的更难

在深入解决方案之前,有必要先明确微服务文档区别于传统架构文档的具体挑战。

关系的组合爆炸

在一个有 10 个模块的单体应用中,模块间关系的最大数量是 45。在一个有 10 个服务的微服务架构中,可能的服务间关系数量相同——但每一个关系现在都涉及网络通信、序列化、错误处理、重试逻辑和潜在的故障模式。两个微服务之间的一个关系所承载的架构意义远超两个模块之间的一次函数调用。

当扩展到 50 或 100 个服务时,关系数量变得难以管理,必须借助结构化工具。静态图表无法在保持可读性的同时捕捉连接的密度。

分布式所有权

在单体应用中,通常由一个团队拥有架构。在微服务架构中,每个团队拥有一个或多个服务。没有任何一个人拥有整个系统的完整心智模型。这意味着文档必须在设计上就是协作式的——没有单一作者能够维护它。

这产生了一个协调问题。如果支付团队改变了 Payment Service 与 Order Service 的通信方式,谁来更新架构文档?实际上,往往没有人去做,于是文档就漂移了。

异步通信

同步的请求-响应模式相对容易记录。Service A 调用 Service B 并获得响应。但微服务架构越来越多地依赖异步模式:发布到 Kafka topic 的事件、放入 RabbitMQ 队列的消息、Webhook、带有最终一致性投影的 CQRS。

这些模式更难可视化,因为生产者和消费者可能彼此并不知道对方的存在。事件总线是中介,记录"Service A 发布了一个 Service B 可能消费的事件"需要与记录直接 API 调用不同的方法。

多语言技术栈

微服务架构通常使用多种编程语言、框架、数据库和通信协议。User Service 可能用 Go 编写并使用 PostgreSQL,而 Recommendation Service 运行 Python 搭配 Redis,Legacy Billing Service 则是一个团队正在逐步绞杀的 Java 单体应用。

文档需要捕捉这种技术多样性,但又不能变成一份技术清单电子表格。团队需要理解的不仅是存在哪些技术,还有为什么特定服务选择了特定技术。

快速演进

微服务被设计为可独立部署和独立演进的。一个团队可能将一个服务拆分为三个,将两个服务合并为一个,或者完全替换一个服务——所有这些都不需要触及任何其他服务。这种速度正是微服务的意义所在,但它也使文档的衰变速度快于任何其他架构风格。

将 C4 模型应用于微服务

C4 模型是记录微服务的最佳框架之一,因为其层次化的缩放级别自然地映射到团队对分布式系统所提出的不同问题。

第 1 级:System Context——生态系统视图

System Context 图回答的是最高层级的问题:存在哪些系统,它们如何与用户和外部服务交互?

对于微服务架构,System Context 图通常将你的平台显示为一个单独的方框——故意隐藏内部微服务的复杂性。这是你向利益相关者、产品经理和入职新成员展示的图表。

一个基于微服务的电商平台的典型 System Context 可能包括:

  • 电商平台(你的系统)——整个微服务集群被视为一个方框
  • 客户——通过 Web 和移动端交互的最终用户
  • 支付服务商——Stripe、Adyen 或其他外部支付网关
  • 物流服务商——第三方物流 API
  • 邮件服务——SendGrid 或类似的事务邮件服务
  • 分析平台——数据仓库或分析工具

关键认识是,在这个层面上,没有人关心你内部的服务分解。他们关心的是你的系统如何融入更广泛的生态系统。

在 Archyl 中,你通过为你的平台创建一个 Software System,并为每个第三方依赖创建外部系统来建模。系统之间的关系捕捉高层级的数据流。

第 2 级:Container——服务全景

这是微服务架构变得可见的地方。在 C4 术语中,每个微服务是一个"Container"——一个独立可部署的代码运行单元。

Container 图是微服务文档中最重要的图。它展示:

  • 架构中的每个微服务
  • 每个服务依赖的数据库、缓存和消息中间件
  • 服务之间的通信模式(同步 vs. 异步)
  • 每个服务使用的技术

以下是电商平台的 Container 图可能的结构:

Systems:
  E-Commerce Platform:
    Containers:
      - API Gateway (Go, Kong)
      - User Service (Go, PostgreSQL)
      - Product Catalog Service (Node.js, MongoDB)
      - Order Service (Java, PostgreSQL)
      - Payment Service (Go, PostgreSQL)
      - Notification Service (Python, Redis)
      - Search Service (Python, Elasticsearch)
      - Event Bus (Kafka)

Relationships:
  - API Gateway -> User Service (REST/JSON, authenticates requests)
  - API Gateway -> Product Catalog Service (REST/JSON, product queries)
  - API Gateway -> Order Service (REST/JSON, order management)
  - Order Service -> Payment Service (gRPC, processes payments)
  - Order Service -> Event Bus (publishes OrderCreated events)
  - Payment Service -> Event Bus (publishes PaymentProcessed events)
  - Notification Service -> Event Bus (consumes order and payment events)
  - Search Service -> Product Catalog Service (syncs product data)

在 Archyl 中,每个微服务成为你的 Software System 内的一个 Container。你在每个容器上设置技术栈,关系同时捕捉同步 API 调用和异步事件流。自动布局功能以可读的方式排列服务,你还可以创建叠加层来突出特定的通信模式。

第 3 级:Component——服务内部

大多数微服务不需要 Component 图。如果一个服务足够小且职责集中(它本应如此),其内部结构从代码中就能理解。

然而,对于那些更大或更复杂的服务——那些已经积累了多重职责或包含重要业务逻辑的服务——Component 图变得有价值。例如,一个 Order Service 可能包含:

  • Order Controller——处理 HTTP 请求
  • Order Processor——编排订单工作流
  • Inventory Checker——验证产品可用性
  • Price Calculator——计算总价、折扣和税费
  • Order Repository——数据访问层
  • Event Publisher——将领域事件发布到 Kafka

选择性地记录 Component 级别的细节。重点关注那些复杂的、关键的、或由多个开发者频繁修改的服务。

第 4 级:Code——通常可以跳过

对于微服务,Code 级别几乎不值得手动记录。每个服务应该足够小,以至于其代码结构从源码本身就能一目了然。如果一个服务复杂到需要 Code 级别的架构图,这本身就是该服务应该被进一步拆分的信号。

记录服务边界

微服务文档中最难的一个方面是捕捉服务为何以这种方式划定边界。当前的服务分解只是一个时间点的快照——但其背后的推理是容易丢失的架构知识。

记录领域模型

如果你的微服务遵循领域驱动设计 (DDD)(它们至少应该松散地遵循),记录限界上下文及其如何映射到服务。这种映射解释了为什么特定功能存在于特定服务中。

使用架构决策记录 (ADR) 来捕捉边界决策:

  • 为什么你将 User Service 从 Auth Service 中分离出来?
  • 为什么 Order Service 拥有购物车而不是创建单独的 Cart Service?
  • 为什么 Search Service 是一个独立部署而不是 Product Service 中的一个模块?

这些决策是你能产出的最有价值的文档。它们使未来的团队免于重新争论已经解决的问题,或意外地推翻经过深思熟虑的设计选择。

在 Archyl 中,你可以将 ADR 直接附加到它们所影响的系统和容器上。当开发者查看 Order Service 时,他们不仅能看到它做什么,还能看到它为何以当前形式存在。

记录 API 契约

每个服务边界都隐含着一个 API 契约。明确记录这些契约:

  • REST API 规范 (OpenAPI/Swagger)
  • gRPC 服务定义 (protobuf)
  • 事件 Schema (Avro, JSON Schema)
  • GraphQL Schema

Archyl 的 API Contract 功能允许你将规范直接链接到暴露它们的容器。当有人需要了解如何与 Payment Service 交互时,API 契约就在架构图上,而不是埋在某个单独的 Confluence 页面中。

记录数据所有权

在微服务中,每个服务应该拥有自己的数据。记录哪个服务拥有哪些数据实体,以及数据如何在服务边界之间共享。这可以防止常见的反模式——团队绕过服务 API 直接访问另一个服务的数据库。

记录通信模式

微服务以多种方式通信,你的文档需要清晰地捕捉这些模式。

同步通信

对于服务间的 REST 和 gRPC 调用,记录:

  • 协议 (HTTP, gRPC)
  • 序列化格式 (JSON, Protobuf)
  • 认证要求(服务间 token、mTLS)
  • 超时和重试策略
  • 熔断器配置

在 Archyl 中,你通过在关系上设置技术来捕捉这些信息。两个容器之间的关系可能被标记为"gRPC / Protobuf / mTLS",让人一目了然地了解通信特征。

异步通信

对于事件驱动的通信,记录:

  • 消息中间件 (Kafka, RabbitMQ, SQS)
  • Topic/Queue 名称及其用途
  • 事件 Schema 和版本策略
  • 投递保证语义(至少一次、精确一次)
  • 消费者组配置

Archyl 的 Event Channel 功能专为此设计。你可以将 Kafka topic 和 RabbitMQ 队列建模为架构中的一等元素,展示哪些服务向它们生产消息、哪些服务从中消费消息。这使异步流与同步流在同一张图中可见。

Service Mesh 和基础设施模式

如果你使用 Istio 或 Linkerd 等 Service Mesh,将基础设施层的通信模式与应用层的模式分开记录。Service Mesh 处理横切关注点如 mTLS、负载均衡和可观测性——这些很重要,但不应使应用层架构图变得混乱。

实际案例:在 Archyl 中记录微服务平台

让我们通过一个具体例子,演示如何使用 Archyl 记录一个中等规模的微服务平台。

步骤 1:建模 System Context

首先为你的平台创建一个 Software System,并为你依赖的每个第三方服务创建外部系统。用描述高层数据流的关系将它们连接起来。

systems:
  - name: FinTech Platform
    type: software_system
    description: "核心银行与支付平台"
  - name: Stripe
    type: external_system
    description: "支付处理"
  - name: Plaid
    type: external_system
    description: "银行账户关联"
  - name: SendGrid
    type: external_system
    description: "事务邮件"

relationships:
  - from: FinTech Platform
    to: Stripe
    label: "Processes payments via"
  - from: FinTech Platform
    to: Plaid
    label: "Links bank accounts via"
  - from: FinTech Platform
    to: SendGrid
    label: "Sends emails via"

步骤 2:绘制服务全景

将每个微服务作为 Container 添加到你的 Software System 中。为每个容器设置技术栈。定义同步和异步通信的关系。

这正是 Archyl Architecture-as-Code 方法的优势所在。你可以在一个 YAML 文件中定义整个 Container 图,提交到 Git,并通过 CI/CD 自动同步。当团队添加新服务或更改通信模式时,他们在同一个 Pull Request 中更新 YAML 文件和代码变更。

步骤 3:添加横切文档

附加 ADR 以捕捉边界决策。将 API 契约链接到服务。创建 Flow 来记录跨多个服务的关键用户旅程(例如,"用户下单"涉及 API Gateway、Order Service、Payment Service、Inventory Service 和 Notification Service)。

步骤 4:分配所有权

使用 Archyl 的所有权功能将每个服务映射到负责的团队。这建立了保持文档时效性的责任制。当平台团队修改 API Gateway 时,他们知道自己有责任更新其文档。

步骤 5:设置自动同步

配置 Archyl 的 CI/CD 集成,在每次合并到主分支时同步你的 Architecture-as-Code 文件。设置合规性规则来检测文档化架构与实际系统之间的漂移。

保持微服务文档的时效性

微服务文档的最大挑战不是创建它——而是保持它的时效性。以下是一些实用策略。

将文档纳入完成定义

如果一个 Pull Request 改变了服务边界、通信模式或 API 契约,架构文档应该在同一个 PR 中更新。当你的文档以代码形式存在于同一个代码仓库中时,这会容易得多。

使用 Archyl 的 AI 发现功能

Archyl 的 AI 驱动发现功能可以分析你的代码库并建议架构文档的更新。它可以检测新服务、变更的依赖关系和更新的技术栈——减少保持文档时效性所需的手动工作。

设置漂移检测

Archyl 的合规性规则让你定义预期模式并检测现实何时偏离文档。例如,你可以创建一条规则,要求每个容器必须至少有一个已记录的关系,或者每个服务必须有一个负责团队。当这些规则被违反时,Archyl 会标记漂移。

定期进行架构评审

安排每季度一次的架构评审,让团队逐一检查其文档化的架构并识别差距。利用 Archyl 的协作功能在评审过程中对图表进行注释、评论和添加待办事项。

常见错误

试图一次记录所有内容

从 Container 图开始。先把它做对并保持时效性,然后再考虑 Component 图或详细的事件 Schema。一张准确的 Container 图比十张部分完成的图更有价值。

忽略异步流

团队通常会记录同步 API 调用但忘记事件驱动通信。这造成了一个不完整的画面,系统一半的行为是不可见的。使用 Archyl 的 Event Channel 让异步流成为一等公民。

将文档视为一次性活动

如果你在初始设计阶段创建了架构文档却从不更新它,那你就浪费了时间。文档的价值来自它随时间保持的准确性,而不是创建它这个动作本身。将更新习惯融入你的开发工作流。

过度记录服务内部结构

抑制为每个服务创建 Component 图的冲动。大多数微服务应该足够简单,以至于其内部结构从代码中就显而易见。将 Component 级别的文档留给真正复杂的服务。

不记录"为什么"

架构图展示的是存在什么。ADR 解释的是它为什么存在。没有"为什么",未来的团队要么盲目维护已经不再合理的决策,要么意外推翻经过深思熟虑的决策。对边界决策、技术选择和通信模式选择大量使用 ADR。

结论

记录微服务架构比记录单体应用更难,但也更重要。微服务的分布式特性意味着没有任何一个开发者能在脑中掌握整个系统。好的文档填补了这个空白——它成为保持团队一致的共享心智模型。

C4 模型提供了正确的框架:用 System Context 描绘全景,用 Container 图作为服务全景的主要文档工件,并在复杂度需要时选择性地添加 Component 图。

Archyl 通过专为微服务设计的功能将这一框架变为现实:Architecture-as-Code 实现 Git 原生的文档管理,Event Channel 处理异步流,所有权映射建立团队责任制,AI 发现实现自动更新,合规性规则实现漂移检测。

目标不是完美的文档。而是文档足够准确以至于有用,并且维护得足够一致以保持有用。从你的 Container 图开始,将文档融入你的开发工作流,然后从那里构建。

准备好记录你的微服务架构了吗?开始使用 Archyl,看看 C4 模型如何为最复杂的分布式系统带来清晰度。