夜雨聆风学习资料网

ARTICLE · 1150270

第25讲:软件服务设计

第25讲:软件服务设计
随着软件系统从单体应用逐步演进为分布式系统、云原生应用和微服务架构,软件开发的重点已经不再局限于类、函数、模块或算法的设计,而是越来越多地转向服务的职责划分、接口定义、交互协议、业务协作、质量保障与生命周期管理。
服务设计(Service Design)正是连接业务需求与软件实现的重要环节。它决定了系统的功能如何被划分为相对独立的服务,服务之间如何交换信息,业务规则如何在不同组件之间保持一致,以及系统如何在故障、负载变化和需求演进的情况下持续运行。
在软件工程语境下,服务设计并不是一个完全统一的术语。在面向服务的架构(SOA)、微服务架构、API 设计和分布式系统设计中,它通常侧重于软件服务的边界、契约、交互和运行质量;在服务科学与服务设计(Service Design)研究中,它还可能涉及用户体验、服务流程、服务接触点和服务交付机制。本讲以软件工程中的服务设计为主线,同时吸收服务科学、领域驱动设计和软件架构领域的相关思想,并将围绕服务概念、服务设计的内涵、设计原则、实施过程、常见问题、工程示例及典型应用场景展开,帮助软件开发者建立从业务需求到可运行服务的系统化设计思维。
1. 服务的概念与基本内涵
1.1 什么是服务
在日常生活中,服务通常指一方为另一方提供某种能力或价值的活动。例如,银行提供转账服务,物流企业提供配送服务,医院提供预约与诊疗服务。
在软件工程中,服务可以理解为:一个具有明确职责、能够通过约定的接口被调用,并向其他软件组件或外部使用者提供特定业务能力或技术能力的逻辑单元。
服务不一定是独立部署的进程,也不一定对应一个微服务。它可以存在于单体应用内部,也可以运行在独立进程、容器、云平台或第三方系统中。
例如,一个电子商务系统可能包含以下服务:
  • 用户服务:负责用户注册、身份资料和账户状态管理。
  • 商品服务:负责商品信息、价格展示和库存查询。
  • 订单服务:负责订单创建、订单状态和订单生命周期管理。
  • 支付服务:负责支付请求、支付结果处理和退款。
  • 物流服务:负责配送单生成、物流状态查询和配送跟踪。
  • 通知服务:负责发送电子邮件、短信或应用内消息。
这些服务共同实现一个完整的业务流程,但每个服务只承担相对明确的职责。
1.2 软件服务的主要特征
结合面向服务架构、分布式系统和微服务领域的研究,软件服务通常具有以下特征。
(1)明确的能力与职责
服务应当能够清晰地回答两个问题:它向外提供什么能力?它负责维护哪些业务规则?
例如,订单服务负责订单状态的合法转换,而不是简单地提供一个可以随意修改所有订单字段的数据库接口。
(2)显式的服务接口
服务通过接口向调用方公开能力。接口可以采用 HTTP API、消息主题、RPC 方法、事件订阅或语言内部接口等形式。接口应当明确请求参数、返回结果、错误语义、权限要求和调用约束。
(3)契约约束
服务提供者与调用者需要遵循一组共同约定,即服务契约(Service Contract)。契约通常包含:
  • 接口地址、操作名称和请求方法。
  • 请求参数、数据类型与校验规则。
  • 响应结构、错误码和异常语义。
  • 身份认证、授权和数据访问约束。
  • 超时、重试、幂等性及版本兼容要求。
  • 服务质量要求,例如延迟、可用性和吞吐量。
  • 契约不仅是接口文档,也是服务协作的基础。
(4)封装与实现独立性
调用方应依赖服务提供的契约,而不是依赖其内部实现细节。例如,订单服务可以从关系型数据库迁移到其他存储系统,只要外部契约与业务语义保持兼容,调用方通常就不必随之修改。
需要注意,接口封装并不意味着服务之间不存在依赖,而是要求这些依赖受到明确约束。
(5)可组合性
服务能够与其他服务组合,以完成更复杂的业务流程。例如,订单创建、库存预留、支付处理和物流安排可以共同构成电商交易流程。服务组合可以通过同步调用、异步消息、事件驱动或工作流引擎实现。
(6)可演进性
业务规则、接口和运行环境都会发生变化。良好的服务设计应尽可能降低局部修改对其他服务的影响,并支持逐步升级、版本迁移和兼容性验证。
(7)可治理性
服务需要能够被发现、配置、监控、授权、测试和维护。服务数量增加后,命名规范、接口目录、依赖关系管理、日志与追踪、版本治理等机制会变得越来越重要。
1.3 服务与相关概念的区别
软件开发中经常将服务、模块、组件、API 和微服务混为一谈,但它们并不处于完全相同的抽象层次。

概念

核心含义

与服务的关系

函数或方法

执行一段计算或操作

可以是服务内部的实现单元

模块

按职责组织代码与数据

可以包含一个或多个服务

软件组件

具有相对明确接口的可复用软件单元

组件可以实现服务,也可以依赖服务

API

调用软件能力的接口集合

是服务对外暴露能力的一种方式

软件服务

通过契约提供明确能力的逻辑单元

本章讨论的核心对象

微服务

面向特定业务能力、可独立部署和演进的服务架构单元

是服务的一种架构组织方式

Web 服务

通常通过 Web 协议进行交互的软件服务

是软件服务的常见实现形式

尤其需要注意:API 不等于服务,微服务也不等于服务的全部。 一个服务可以暴露多个 API;一个 API 网关可以代理多个服务;一个单体应用也可以在内部采用良好的服务边界。
1.4 服务的主要分类
从设计目的来看,软件服务可以分为以下几类。
2. 服务设计的概念、目标与基本原则
2.1 什么是服务设计
服务设计(Service Design)是指围绕特定业务目标,对服务的职责边界、内部能力、外部接口、数据模型、交互方式、依赖关系、运行机制及质量属性进行系统规划与定义的过程。
在软件工程中,服务设计的核心不是简单地把程序拆成多个服务,而是回答以下问题:.
  1. 设计什么服务?应当识别哪些独立的业务能力?
  2. 服务负责什么?每个服务的职责边界和业务规则是什么?
  3. 服务如何协作?服务之间通过同步调用、异步消息还是事件进行交互?
  4. 服务如何被使用?如何定义接口、数据格式、错误语义与安全约束?
  5. 服务如何运行?如何满足性能、可用性、可靠性和可观测性要求?
  6. 服务如何演进?如何支持业务变化、接口升级和系统迁移?
W3C 的 Web Services Architecture 文献将服务交互中的技术描述与语义契约加以区分:前者说明消息格式、数据类型、传输协议和调用端点等,后者则说明调用服务的目的、要求及预期效果。这一思想对现代 API 和微服务设计仍具有重要参考价值。[1] 
从工程角度看,服务设计可以概括为:服务设计=业务建模+边界划分+契约设计+交互设计+质量设计。
这是一种用于组织设计工作的概念模型,并非唯一的标准公式。
2.2 服务设计与其他设计活动的关系
服务设计不是孤立的活动,而是软件需求、架构、详细设计、实现和运维之间的连接环节。
服务设计与相关活动的主要区别如下。

设计活动

主要关注点

典型产出

需求分析

用户需要什么能力

用例、需求规格、业务规则

软件架构设计

系统整体如何组织

架构视图、组件关系、部署结构

服务设计

服务如何划分、协作与演进

服务目录、边界说明、接口契约

API 设计

外部调用者如何使用接口

OpenAPI 文档、请求与响应模型

详细设计

服务内部如何实现

类图、算法、模块设计

运维设计

服务如何稳定运行

监控指标、告警、容灾和恢复方案

这些活动在实际项目中可以迭代开展,并不一定严格按照表格中的顺序执行。
2.3 服务设计的主要目标
2.3.1 业务目标与软件结构一致
服务边界应尽量对应业务能力、业务规则和责任边界,而不是单纯依据数据库表、开发语言或技术层次划分。
例如,将订单管理、支付处理和库存管理分别建模,通常比创建一个同时处理所有业务规则的“通用业务服务”更容易理解与维护。但如果订单和库存业务尚未复杂到需要独立部署,也可以先将它们实现为同一应用内的模块。
2.3.2 降低耦合,提高内聚
内聚性反映一个服务内部的功能是否围绕相对一致的职责组织;耦合性则反映服务之间的依赖程度。
高内聚意味着服务内部的规则和数据围绕同一业务目的组织。低耦合意味着一个服务的内部变化不必引起大量其他服务的修改。
应当特别避免两类设计:
  • 职责过多的服务:一个服务承担用户、订单、支付、库存等大量无关职责。
  • 过度细分的服务:一个简单业务操作被拆成多个极小服务,导致每次请求都需要频繁跨网络调用。
服务数量并不是设计质量的直接指标。
2.3.3 提高可复用性与互操作性
服务应当提供明确、稳定、具有业务意义的能力,使不同客户端、系统或团队可以在遵守契约的前提下使用它。
不过,复用并不意味着所有业务都必须调用同一个庞大、抽象的通用服务。复用需要以稳定的业务语义和合理的依赖关系为前提。
2.3.4 满足质量属性
服务设计还需要考虑非功能性需求,包括:
  • 性能:响应时间、吞吐量和资源消耗。
  • 可靠性:故障隔离、重试、超时与恢复能力。
  • 可扩展性:负载增长时的扩容能力。
  • 安全性:认证、授权、数据保护和审计。
  • 可维护性:代码理解、测试和修改成本。
  • 可观测性:日志、指标、分布式追踪和告警。
  • 可演进性:契约兼容、版本管理和渐进式迁移。
这些属性之间有时会相互冲突。例如,跨服务同步调用可能更容易实现即时响应,却也可能增加延迟和故障传播风险。
2.4 服务设计的核心原则
服务设计没有一组适用于所有项目的机械规则,但可以遵循以下原则。
原则一:业务能力优先
先识别业务能力与规则,再决定服务边界。不要仅根据数据库表、技术框架或代码目录决定服务划分。
原则二:契约明确
服务接口应明确成功条件、失败条件、业务语义、数据格式和权限要求。仅定义一个 URL 或方法签名是不够的。
原则三:封装内部实现
调用方不应依赖服务的内部数据库结构、私有类和实现细节。服务应控制业务数据的合法修改路径。
原则四:控制依赖
减少不必要的跨服务调用、共享数据库和循环依赖,避免局部变化扩散为全系统修改。
原则五:将质量属性纳入设计
在设计阶段确定超时、并发、授权、审计、监控和恢复策略,而不是等到系统出现故障后再补充。
原则六:允许持续演进
通过自动化测试、契约检查、兼容性策略和架构反馈,逐步调整服务设计,而不是假设第一次划分就永远正确。
Thomas Erl 在 SOA Principles of Service Design 中系统讨论了标准化服务契约、松耦合、服务抽象、自主性、无状态性和可组合性等服务设计原则。[2]  这些原则具有参考价值,但不应被理解为所有业务服务都必须无状态、完全独立或使用相同粒度。设计仍需结合具体业务和运行约束。
3. 服务设计的主要内容
一个完整的服务设计至少需要考虑服务边界、服务契约、数据、交互、质量和治理六个方面。
3.1 服务边界设计
服务边界是服务设计中最重要、也最难的一项决策。它决定哪些业务规则和数据由一个服务负责,哪些能力需要通过接口与其他服务协作。
领域驱动设计(Domain-Driven Design,DDD)为服务边界识别提供了一套重要的分析方法。Eric Evans 在 Domain-Driven Design: Tackling Complexity in the Heart of Software 中提出以领域模型和通用语言组织复杂业务软件的思路。DDD 中的限界上下文(Bounded Context)强调:一个领域模型应当在明确的边界内保持一致的含义。
例如,“商品价格”在不同业务环境中可能具有不同语义:
  • 商品目录中的标价。
  • 营销活动中的促销价格。
  • 订单创建时采用的成交价格。
  • 财务系统中的结算金额。
这些数据虽然都与价格有关,但其业务规则、变化频率和责任主体可能不同,因此不能仅因为字段名称相似,就将它们设计为一个共享的价格服务。
服务边界设计可以重点考察以下问题:
  • 该业务能力是否具有明确、独立的业务目的?
  • 相关规则是否需要共同维护?
  • 哪些数据应由该服务负责写入和维护?
  • 这些功能是否需要一起变更和发布?
  • 将它们拆开后,是否会产生大量网络调用或跨服务事务?
  • 该边界是否符合团队的职责划分和运维能力?
关于 DDD 与微服务的关系,Zhong 等人在 2024 年发表于 IEEE Transactions on Software Engineering 的研究专门分析了 DDD 在微服务设计中的应用与挑战,说明服务边界识别依然是微服务工程中的重要难题。[5] 
3.2 服务接口与契约设计
服务契约是服务提供者与调用者之间的正式约定。
以订单服务为例,以下接口都可以用于创建订单,但它们的业务语义不同:
  • POST /orders:创建一个新订单。
  • POST /orders/{id}/cancel:请求取消订单。
  • GET /orders/{id}:查询订单详情。
一个好的接口不仅需要明确调用方式,还应当明确业务前置条件。例如,已发货订单是否允许取消?重复提交创建请求会发生什么?用户能否查询其他用户的订单?
建议将契约设计分成四个层次:

层次

需要明确的内容

语法契约

字段、类型、格式、必填项

业务契约

前置条件、业务规则、状态变化、结果含义

运行契约

超时、限流、重试、幂等性和可用性

安全契约

身份认证、访问权限、敏感数据处理和审计

接口契约可以通过 OpenAPI、JSON Schema、Protocol Buffers、AsyncAPI 等技术进行描述,具体选型取决于通信协议和开发环境。
重要原则:接口应表达业务能力,而不只是暴露数据库操作。
例如,POST /orders/{id}/cancel 比PATCH /orders/{id} 并直接允许调用方任意修改status 字段更能体现业务意图,也更便于在服务内部实施合法性校验。
3.3 服务数据设计
服务数据设计需要回答三个问题:
  • 谁是某项业务数据的权威维护者?
  • 其他服务通过什么方式获取或订阅这些数据?
  • 数据变化如何在多个服务之间保持业务一致性?
在微服务架构中,通常建议每个服务对自己负责的业务数据拥有明确的写入控制权。其他服务可以通过 API 查询、订阅领域事件,或者维护经过定义的本地读模型。
例如,订单服务可以保存订单创建时的商品名称、成交价格和数量快照。这样即使商品目录中的名称或标价发生变化,历史订单仍然能够保持正确的业务含义。
需要区分三种常见做法:
  • 共享数据库表:多个服务直接读写同一组业务表。实现方便,但容易造成结构耦合与发布协调。
  • 服务拥有数据:每个服务控制自己的数据修改,通过契约提供外部访问。边界更清晰,但跨服务查询和一致性处理更复杂。
  • 复制或投影数据:通过事件或同步机制,在其他服务维护查询所需的数据副本。可以降低实时调用依赖,但必须处理延迟、重复消息和数据重建。
并不是所有项目都必须采用独立数据库。小型应用可以在同一数据库中采用不同的逻辑模块和访问权限;关键是明确数据责任,避免多个服务随意修改同一业务对象。
3.4 服务交互设计
服务之间的交互主要分为同步交互和异步交互。
两种方式并非互相排斥。一个订单创建接口可以同步返回订单编号,而后续的通知、物流安排和统计更新通过事件异步完成。
交互设计还需要明确以下内容:
  • 调用超时和取消机制。
  • 失败重试与退避策略。
  • 幂等性和重复消息处理。
  • 服务发现与负载均衡。
  • 调用链中的身份和权限传递。
  • 事件顺序、重复投递及最终一致性。
  • 故障隔离与降级策略。
需要注意,网络请求超时并不代表服务端没有执行操作。例如,订单可能已经创建成功,但响应在网络传输中丢失。因此,对创建订单、发起支付等操作,应通过幂等键、业务唯一约束或操作状态查询来避免重复执行。
3.5 服务质量设计
服务设计不能只考虑功能正确性,还必须考虑服务在真实运行环境中的行为。
性能
应确定关键接口的响应时间目标、吞吐量、并发量以及峰值负载。设计时应关注整个调用链,而不仅是单个服务的执行时间。
可靠性
应明确服务依赖发生故障时的处理方式,例如超时、有限重试、熔断、隔离、降级和恢复。
安全性
应考虑认证、授权、最小权限、传输加密、敏感信息保护、输入校验和审计日志。内部服务并不天然可信,不能仅因为请求来自内部网络就跳过权限验证。
可观测性
每个服务应提供足以定位问题的日志、指标和追踪信息。对于跨服务流程,统一的请求标识和分布式追踪可以帮助识别故障发生在哪个环节。
可维护性
服务应具备自动化测试、清晰的配置管理、可复现的构建流程和明确的运维责任。
3.6 服务治理设计
随着服务数量增加,仅靠代码和接口文档已经难以管理整个系统。服务治理通常包括:
  • 服务目录与接口文档管理。
  • 服务所有者和维护责任登记。
  • API 版本和兼容性策略。
  • 身份认证与权限管理。
  • 依赖关系、变更风险和弃用管理。
  • 统一的监控、告警与审计规范。
  • 服务质量目标和运行成本管理。
治理的目的不是为每次修改增加审批环节,而是让团队能够在服务数量增长时保持系统的可理解性、安全性和可控性。
4. 服务设计的实施过程
服务设计不是一次性完成的文档编写工作,而是一个从业务理解到架构验证、从实现反馈到持续改进的迭代过程。对于大多数业务系统,可以采用以下八个阶段。
4.1 第一阶段:业务需求分析
服务设计首先应当从业务问题出发,而不是从技术框架出发。
例如,开发一个电商订单系统时,需要先明确:
  • 用户如何选择商品和提交订单?
  • 系统如何校验商品价格与库存?
  • 订单在什么情况下可以取消?
  • 支付失败后,订单和库存应当如何处理?
  • 用户重复提交请求时,系统应当如何响应?
  • 系统需要满足什么样的响应时间、并发量和可用性目标?
可以采用用例分析、用户故事、事件风暴(Event Storming)和业务流程建模等方法整理这些问题。
本阶段的主要产出包括业务场景、业务规则、核心实体、状态转换和质量需求。
4.2 第二阶段:领域分析与服务识别
领域分析的目标是将复杂业务拆解为相对独立的业务能力。
以电商系统为例,可以先识别商品管理、订单管理、库存管理、支付处理和物流配送等候选能力,再通过业务规则分析确定它们是否需要成为独立服务。一个实用的方法是建立领域事件与业务动作清单:

业务动作

产生的业务结果

候选责任主体

提交订单

订单已创建

订单服务

预留库存

库存已预留

库存服务

完成扣款

支付成功

支付服务

确认发货

配送任务已建立

物流服务

订单已取消

订单取消状态已记录

订单服务

这里的“候选责任主体”不是最终结论。还需要分析状态归属、事务边界和团队责任,才能确定是否拆分为独立部署的服务。
4.3 第三阶段:确定服务职责和边界
对于每个候选服务,建议编写一份简短的服务设计说明,至少包括:
  • 服务名称和业务目的。
  • 负责的业务规则和数据。
  • 对外提供的主要能力。
  • 不负责的业务内容。
  • 依赖的其他服务。
  • 允许的调用方式和交互方向。
  • 需要满足的关键质量属性。
例如,订单服务负责订单状态、订单金额快照和订单生命周期,但不应直接修改支付服务内部的支付记录,也不应直接操作库存服务的内部数据库表。
判断服务边界是否合理的关键,不是服务是否足够小,而是业务规则是否能够在清晰的边界内保持一致。
4.4 第四阶段:设计服务接口与契约
接口设计应从调用场景出发,明确调用方真正需要什么能力。
以创建订单为例,可以定义如下接口:
http
POST /api/v1/orders
Authorization: Bearer <token>
Idempotency-Key: 9f0c...example
Content-Type: application/json
请求示例:
json
{
"items": [
{
    "productId": "P1001",
    "quantity": 2
}
      ],
    "shippingAddressId": "ADDR2001"
}
成功响应示例:
json
{
"orderId": "ORD202610090001",
"status": "PENDING_PAYMENT",
"totalAmount": 199.80,
"currency": "CNY"
}
以上数据均为教学示例,接口字段和金额不代表任何实际系统。设计时还需要明确:
  • 商品是否存在、是否允许购买。
  • 价格由哪个服务确定。
  • 运费与优惠如何计算。
  • 库存不足时返回什么结果。
  • 相同幂等键再次提交时返回什么结果。
  • 认证失败、参数错误和业务冲突如何区分。
  • 响应中的金额精度和货币单位如何表达。
例如,可以使用400 表示请求格式或参数不符合要求,使用401 表示未通过身份认证,使用403 表示没有相应权限,使用409 表示请求与当前业务状态冲突。具体状态码应当与接口语义保持一致。
4.5 第五阶段:设计服务交互与异常处理
服务间调用不能只设计正常流程,还必须设计失败流程。
对于创建订单的场景,应当考虑:
  • 订单保存成功,但事件发布失败。
  • 库存预留成功,但支付失败。
  • 支付服务处理成功,但响应丢失。
  • 消息被重复投递。
  • 下游服务长时间不可用。
  • 用户在订单处理期间重复提交请求。
对于跨服务业务事务,可以采用 Saga 模式,将业务流程分解为多个本地事务,并在必要时执行补偿操作。[10] 
如果服务在保存数据库记录后还需要发布事件,可以考虑事务性发件箱(Transactional Outbox)模式,将业务数据和待发布事件写入同一个本地数据库事务,随后由独立的发布过程发送消息。[11] 
这些模式并非所有项目都必须使用。简单业务流程可能只需要本地事务与同步调用;只有在跨服务一致性和故障恢复需求足够明确时,才有必要引入更复杂的机制。
4.6 第六阶段:架构评审与原型验证
在大量实现之前,应当优先验证风险最高的设计假设。例如:
  • 高并发下库存预留是否会超卖?
  • 支付请求超时后,系统如何判断实际支付结果?
  • 一个下游服务失效是否会拖垮整个调用链?
  • 服务拆分后,关键查询是否需要过多跨网络调用?
  • 服务间的数据最终一致性能否满足业务要求?
  • 可使用接口原型、性能测试、故障注入、契约测试和架构评审等方法验证这些问题。
  • 评审应同时检查业务正确性和运行质量,而不仅是接口命名或代码风格。
4.7 第七阶段:实现、测试与部署
实现阶段应当让服务契约、业务逻辑和测试保持一致。建议建立以下测试层次:
  • 单元测试:验证业务规则和状态转换。
  • 接口测试:验证参数、响应、错误码和权限约束。
  • 契约测试:验证调用方与提供方对接口的理解是否一致。
  • 集成测试:验证服务、数据库和消息系统之间的协作。
  • 端到端测试:验证完整业务流程。
  • 非功能测试:验证性能、安全性、故障恢复和资源使用。
部署时还应考虑配置管理、健康检查、滚动升级、回滚策略和数据库变更兼容性。
4.8 第八阶段:运行反馈与持续演进
服务上线并不意味着设计结束。通过日志、指标、分布式追踪、故障记录和业务指标,可以判断服务设计是否真正满足需求。
例如,如果订单详情页面需要调用六个服务才能生成一个响应,可以考虑:
  • 是否存在不必要的同步依赖?
  • 是否需要聚合接口或面向前端的后端(BFF)?
  • 是否需要构建读模型?
  • 是否有必要调整服务边界?
服务设计应根据证据持续改进,而不是为了追求某种架构风格而不断拆分服务。
5. 服务设计中存在的主要问题与解决思路
服务化能够改善模块边界和系统演进能力,但也会引入网络通信、数据一致性、部署和运维等复杂性。Martin Fowler 对微服务权衡的分析明确指出,微服务的收益和代价需要结合组织与系统实际情况判断,不能将其视为单体架构的普遍替代方案。[7,8,9] 
5.1 服务边界划分不合理
这是最常见的问题之一。
表现形式
  • 服务职责过宽,形成新的“巨型服务”。
  • 服务过细,业务操作被拆分成大量微小调用。
  • 多个服务反复维护相同的业务规则。
  • 一个业务需求需要同时修改多个服务。
  • 服务之间形成循环依赖。
产生原因
团队可能按照技术层次或数据库表划分服务,而没有充分理解业务规则;也可能过度追求独立部署,忽视服务之间真实的业务联系。
解决思路
优先进行领域建模,明确限界上下文、聚合和业务不变量;同时分析功能是否需要一起变化、数据是否需要一起保持一致。
如果多个候选服务始终需要共同修改、共同部署,或者频繁进行同步调用,就应重新审视边界。
5.2 服务间耦合过强
服务虽然在物理上独立部署,但仍可能在逻辑上高度耦合。
例如,订单服务、支付服务和库存服务共享同一组数据库表,或者订单接口直接依赖支付服务的内部数据结构。此时,任何数据库变更都可能影响多个服务,所谓独立部署就难以真正实现。
建议采用以下措施:
  • 明确每个服务的数据所有权。
  • 通过契约访问其他服务的数据。
  • 使用稳定的业务标识,而不是暴露内部数据库主键规则。
  • 通过契约测试约束接口变化。
  • 减少循环依赖和跨服务直接写入。
5.3 分布式事务与数据一致性问题
在单体应用中,多个业务操作可能通过一个数据库事务共同提交;拆分为多个服务后,每个服务可能拥有独立的数据存储,原有的本地事务无法自动覆盖整个业务流程。
例如,订单创建成功并不代表库存一定已经预留,支付成功也不代表订单服务一定已经收到支付结果。常见的处理方法包括:

方法

适用情况

主要代价

本地数据库事务

业务状态位于同一数据存储内

无法直接覆盖多个独立服务

Saga

多个服务需要协同完成较长业务流程

需要补偿逻辑和流程监控

事务性发件箱

数据更新必须可靠地通知下游

需要事件发布和重复消费处理

最终一致性

允许不同服务的数据短时间不一致

需要定义延迟、重试与修复机制

保持单一事务边界

强一致性要求高且业务边界适合合并

可能限制独立扩展与部署

Saga 的核心是通过本地事务、继续执行和补偿操作管理跨服务业务过程,而不是把多个数据库事务神奇地变成一个全局原子事务。[10] 
5.4 服务故障传播
一个服务发生故障时,可能通过同步调用链影响其他服务。
假设一个请求依次调用订单、库存、支付、优惠和用户服务。即使每个服务单独看起来比较可靠,只要依赖链足够长,整个请求成功完成的概率也可能下降。
主要应对措施包括:
  • 为远程调用设置合理超时。
  • 只对适合重试的失败进行有限重试。
  • 使用指数退避和随机抖动减少重试风暴。
  • 对重复操作使用幂等机制。
  • 在必要时使用熔断和舱壁隔离。
  • 对非关键功能实施降级。
  • 通过监控和追踪定位故障源。
需要特别注意,重试并不总是安全的。对于支付、订单创建等写操作,必须先明确操作的幂等性和结果查询机制。
5.5 接口版本与兼容性问题
当服务提供者修改字段、改变错误码或调整业务语义时,调用方可能无法正常工作。
例如,原接口返回:
json
{
    "orderId": "ORD1001",
    "status": "PAID"
}
如果服务提供者将status 改成嵌套对象,或者直接删除原字段,依赖该接口的客户端可能发生故障。
建议采用以下策略:
  • 优先进行向后兼容的增量变更。
  • 明确字段是否必填及其默认语义。
  • 为破坏性变更设计版本迁移方案。
  • 通过自动化契约测试验证兼容性。
  • 监控旧接口使用情况,制定清晰的弃用周期。·
版本号本身不能替代兼容性设计。即使使用/v2,如果两个版本之间的语义没有定义清楚,仍然可能产生系统性问题。
5.6 安全与隐私风险
服务数量增加后,接口数量、身份关系和数据流向也会更加复杂。
常见风险包括:
  • 只验证用户是否登录,却没有检查用户是否有权访问目标资源。
  • 内部服务默认信任所有内部请求。
  • 在日志中记录密码、令牌、支付凭据等敏感数据。
  • 允许调用方直接修改受保护的业务状态。
  • 服务间使用长期有效且权限过大的凭据。
  • 缺少敏感操作审计。
应当在服务契约和架构层明确身份认证、资源级授权、最小权限、凭据管理、加密和审计要求,并将安全测试纳入持续交付流程。
5.7 可观测性不足
如果系统只有各服务自己的日志,排查跨服务问题就可能需要大量人工分析。
例如,用户报告“付款成功但订单仍显示待支付”,开发者需要判断:
  • 支付服务是否真的处理成功?
  • 事件是否成功发布?
  • 消息是否被消费?
  • 订单状态更新是否失败?
  • 是否存在重复事件或乱序处理?·
建议统一使用请求标识和追踪标识,建立结构化日志、关键业务指标、分布式追踪和告警机制。同时记录业务流程状态,使运维人员能够判断问题发生在什么阶段。
5.8 过度微服务化与治理成本
服务数量越多,并不意味着系统越先进。
过度拆分会增加网络调用、部署单元、接口契约、测试组合、权限配置和运维负担。小型团队尤其容易因为服务数量增加而失去开发效率。
Fowler 的相关分析指出,微服务需要成熟的自动化部署、基础设施管理和运维能力作为支撑。[7] 
因此,对于业务简单、团队规模较小或尚未稳定的项目,模块化单体往往是更合理的起点。可以先在一个部署单元中建立清晰的模块边界,待确实出现独立扩展、独立发布或组织协作需求时,再逐步拆分服务。
5.9 常见问题的综合对照

问题

主要表现

优先改进方向

边界不清

频繁跨服务修改

重新进行领域分析

耦合过强

共享表、循环调用

明确契约与数据所有权

数据不一致

订单、库存、支付状态不匹配

事务边界、Saga、发件箱

故障传播

下游故障拖垮上游

超时、隔离、降级

接口不兼容

升级导致调用方失败

契约测试、渐进式迁移

安全不足

越权、敏感信息泄露

授权、最小权限、审计

监控不足

无法还原调用链

日志、指标、追踪

过度拆分

运维复杂、开发变慢

模块化单体、按需拆分

6. 服务设计的具体示例:简化的电商系统
本节使用一个简化的电商系统,将前面介绍的服务设计方法应用于真实的软件工程问题。示例中的接口、状态和金额仅用于说明设计思路。
6.1 业务需求
假设需要开发一个电商系统,支持用户浏览商品、创建订单、预留库存、完成支付和查询订单状态。系统需要满足以下业务规则:
  1. 用户只能访问自己有权查看的订单。
  2. 商品价格由可信的业务服务确定,不能直接信任客户端提交的价格。
  3. 库存不足时不能确认库存预留成功。
  4. 相同请求重复提交时,不应重复创建订单或重复扣款。
  5. 支付结果可能延迟到达,订单状态需要支持异步更新。
  6. 任何失败都应有明确的业务状态和恢复方案。
  7. 系统需要记录关键状态变化,以支持追踪和审计。
这些需求同时包含功能需求、业务不变量和非功能性需求,因此不能只通过几个 CRUD 接口解决。
6.2 服务识别与职责划分
经过领域分析,可以得到以下候选服务。
此图表示逻辑职责,不代表所有模块都必须部署成独立微服务。对于小型项目,这些职责可以先作为一个应用内部的模块实现。
这种设计的关键在于,每个服务拥有明确的业务责任。订单服务可以发起库存预留请求,但不能绕过库存服务直接修改库存表;支付服务可以发布支付结果,但不应直接修改订单服务内部的订单记录。
6.3 设计服务契约
订单服务可以提供以下接口。

接口

方法

主要作用

创建订单

POST /api/v1/orders

校验请求并发起订单流程

查询订单

GET /api/v1/orders/{id}

查询订单及其当前状态

取消订单

POST /api/v1/orders/{id}/cancel

在满足条件时取消订单

查询支付状态

GET /api/v1/orders/{id}/payment

获取与订单关联的支付状态

其中,创建订单接口需要明确:
  • 调用方的身份和权限。
  • 商品标识、购买数量和收货地址。
  • 服务端的价格校验与金额计算规则。
  • 幂等键的使用范围与有效期。
  • 创建成功与业务失败的响应语义。
  • 订单进入待支付状态的准确含义。
尤其需要避免将“订单已创建”误解为“支付已完成”或“库存已最终扣减”。状态语义必须在接口契约中明确说明。
6.4 设计订单状态机
订单状态不能仅仅是一个可以被任意修改的字符串,而应当反映业务生命周期。
支付成功路径与取消路径应由合法业务事件触发,而不是由客户端任意指定目标状态。
实际系统还可能需要PENDING_INVENTORY、PAYMENT_PROCESSING、PAYMENT_FAILED、REFUND_PENDING、SHIPPED 等状态。
状态设计必须结合具体业务。例如,支付失败是否允许重新支付?支付超时是否代表最终失败?已支付订单是否可以取消?这些问题不能仅靠技术实现自行决定,而应由业务规则确定。
6.5 设计完整的交互流程
下面采用一个示例流程,展示订单创建、库存预留和支付的协作关系。
该图是为了讲解而简化的流程,不意味着必须采用这种唯一实现方式。
在实际系统中,可以使用编排器管理流程,也可以使用事件驱动的 Saga。具体选择取决于流程复杂度、可观测性要求、团队经验和一致性要求。
6.6 处理失败与补偿
设计必须说明正常流程中断后会发生什么。

故障场景

设计策略

商品无效或价格校验失败

拒绝创建订单并返回明确业务错误

库存不足

不确认订单进入可支付状态,返回库存不足结果

订单保存成功但事件未发出

使用事务性发件箱或可恢复的发布机制

支付请求超时

查询或核实支付状态,不直接假定支付失败

支付成功但通知延迟

保持处理中状态,等待事件或主动对账

取消订单时库存已预留

释放相应库存,并记录补偿结果

重复收到支付成功事件

使用事件标识和业务状态校验,避免重复处理

需要特别注意,支付补偿不是简单地把数据库状态改回去。如果支付机构已经扣款,就可能需要退款或人工对账,而不是仅将订单标记为取消。
6.7 简化的接口实现示例
以下使用 Python 展示订单服务中的核心设计思想。代码省略数据库、身份认证、外部服务客户端及持久化细节,不能直接作为生产系统使用。n

python

from dataclasses import dataclassfrom decimal import Decimalfrom enum import EnumclassOrderStatus(str, Enum):PENDING_PAYMENT="PENDING_PAYMENT"PAID="PAID"CANCELLED="CANCELLED"@dataclassclassOrder:    order_id: str    user_id: str    total_amount: Decimal    status: OrderStatusdefcreate_order(    user_id: str,    items: list[dict],    idempotency_key: str,    product_service,    order_repository,) -> Order:# 1. 检查请求幂等性    existing = order_repository.find_by_idempotency_key(        user_id, idempotency_key    )if existing isnotNone:return existing# 2. 校验商品并使用可信的服务端价格    total = Decimal("0.00")for item in items:        product = product_service.get_product(item["productId"])        quantity = item["quantity"]if quantity <=0ornot product.is_available:raiseValueError("商品不可购买或数量无效")        total += product.price * quantity# 3. 创建订单;生产实现还需处理并发幂等与数据库事务    order = Order(order_id=order_repository.new_id(),user_id=user_id,total_amount=total,status=OrderStatus.PENDING_PAYMENT,    )    order_repository.save(        order,idempotency_key=idempotency_key,    )return order
这段代码体现了几个重要设计原则:
  • 调用方不能直接决定订单金额。
  • 订单状态由服务内部业务逻辑控制。
  • 幂等性是接口行为的一部分。
  • 商品查询通过服务接口完成,而不是直接读取商品服务的内部数据库。
但示例仍存在工程上的简化:幂等键检查与订单保存必须正确处理并发竞争,订单保存后需要可靠地发出后续事件,库存预留还应有明确的生命周期与补偿机制。因此,实际实现还需要数据库约束、事务性发件箱或其他可靠的协作机制。
6.8 本案例的设计总结
通过电商案例,可以看到服务设计并不只是将代码分成几个项目。它需要同时回答业务职责、数据所有权、接口契约、状态转换、服务交互和失败恢复等问题。
服务边界合理,能够降低业务规则的耦合;契约明确,能够减少团队之间的沟通成本;可靠性与一致性设计充分,才能使服务在真实的分布式环境中正确协作。
7. 服务设计的典型应用场景
服务设计并不限于微服务架构。只要软件系统需要明确职责、定义接口并协调多个能力,就可以运用服务设计思想。
7.1 企业信息系统
典型系统包括 ERP、CRM、供应链管理、财务系统和人力资源系统。
这些系统通常涉及多个业务部门、复杂规则、遗留系统以及不同的数据责任主体。服务设计有助于明确业务能力边界,降低系统集成和长期维护成本。
例如,采购、库存、财务和供应商管理可以分别建模,但是否独立部署,应根据事务需求、组织边界和运维能力决定。
7.2 电商与交易平台
电商系统需要处理商品、订单、库存、支付、促销、物流和售后等多种业务能力。
服务设计在此类系统中尤其重要,因为它直接影响:
  • 交易流程的正确性。
  • 高并发下的库存一致性。
  • 支付与退款的状态管理。
  • 促销规则的独立演进。
  • 故障时的订单恢复和业务对账。
订单、库存和支付往往需要协作,但不应简单地将所有业务规则放入同一个大型服务中,也不能为了独立部署而忽视跨服务事务的复杂性。
7.3 云原生与微服务应用
云原生系统通常采用容器化、自动化部署、服务发现、弹性扩缩容和可观测性平台。
在这种场景下,服务设计不仅决定代码结构,还影响服务的部署边界、资源配置、故障隔离和发布策略。
如果某个业务能力需要独立扩容、由独立团队维护或具有不同的安全与可用性要求,那么独立服务可能具有明显优势。
但如果一个业务流程需要频繁的内部函数调用,且没有独立部署和扩展需求,将它拆成远程服务反而可能降低性能并增加复杂性。
7.4 开放 API 与第三方系统集成
开放平台通常需要向合作伙伴提供稳定的业务接口,例如支付接口、物流接口、身份认证接口和数据查询接口。
服务设计在这里主要关注:
  • 契约的可理解性和稳定性。
  • 认证、授权和访问限制。
  • 请求签名、幂等键和防重放。
  • 速率限制和异常响应。
  • 版本兼容、变更通知和弃用策略。
  • 操作审计和调用统计。
这类场景中,接口契约本身往往就是重要的产品组成部分。
7.5 物联网与实时数据处理
物联网平台可能包含设备接入、遥测数据处理、规则引擎、告警、设备管理和数据分析等服务。
由于设备数量和消息流量可能很大,服务设计需要考虑事件驱动、批处理、背压、消息分区、数据保留和故障恢复。
例如,设备告警可以通过事件发布与订阅机制处理,而设备控制命令可能需要更明确的确认、超时和重试策略。
7.6 人工智能应用与智能体系统
现代 AI 应用通常需要组合模型推理、检索增强生成(RAG)、向量检索、文档处理、权限校验、工具调用和结果审计等能力。
服务设计可以将这些能力组织为职责清晰的服务,避免把模型调用、数据访问、业务授权和结果处理混合在一个难以维护的组件中。
例如,文档问答系统可以将文档解析、索引构建、检索、模型推理和权限过滤分别组织为模块或服务。但是否独立部署,应取决于负载、团队协作和安全要求。
7.7 遗留系统改造与现代化
在企业软件现代化过程中,原有系统可能采用单体架构、共享数据库或专有通信协议。
服务设计可以帮助识别业务能力边界,建立新的接口层,并通过适配器或防腐层逐步隔离旧系统与新系统。
一种常见策略是渐进式迁移:先将稳定、边界清晰的能力封装为服务,再逐步迁移业务逻辑和数据,而不是一次性重写全部系统。
7.8 不同场景的设计重点

应用场景

设计重点

主要风险

企业信息系统

业务边界、权限、数据治理

遗留依赖与跨部门协调

电商交易

状态机、幂等、事务一致性

超卖、重复扣款、状态不一致

云原生系统

部署自治、故障隔离、可观测性

运维复杂度与依赖传播

开放 API

契约稳定、安全、兼容性

越权、滥用、破坏性变更

物联网平台

事件流、吞吐量、背压

消息积压与数据丢失

AI 应用

能力分层、权限、模型治理

延迟、成本与结果不可控

遗留系统改造

渐进迁移、适配与边界隔离

双重维护和迁移风险

8. 服务设计方法的选择与工程实践建议
8.1 如何选择合适的服务架构
服务设计与服务部署架构是两个不同的问题。一个系统可以具有良好的服务设计,却采用单体部署;也可以将多个服务独立部署,却仍然存在严重的业务耦合。
常见架构选择包括模块化单体、面向服务架构(SOA)和微服务架构。

比较维度

模块化单体

SOA

微服务

部署方式

通常统一部署

通常具有多个服务

强调独立部署与演进

模块边界

由代码结构和约束维护

由服务契约与集成机制维护

由业务边界、契约与部署边界共同维护

服务通信

主要是进程内调用

常见于跨系统通信

常见于轻量级 API 和事件通信

数据管理

可使用统一数据库

可集中或分布式管理

倾向于服务拥有自己的业务数据

运维复杂度

相对较低

取决于集成规模

通常较高

适用情境

小型系统、初期产品、边界尚在探索的业务

企业集成、多系统协作

复杂业务、独立扩展和独立发布需求明确的系统

这只是常见倾向,并不是严格的定义边界。例如,SOA 同样可以采用轻量级协议,模块化单体也可以具有非常严格的内部服务契约。
Martin Fowler 的微服务指南特别强调架构权衡:微服务能够强化模块边界、支持独立部署,但也增加分布式通信、数据一致性和运维方面的成本。[6] 
8.2 服务设计中的常见反模式
在工程实践中,以下做法值得警惕。
  • 按数据库表拆分服务:表的边界不一定等于业务边界。
  • 为了使用微服务而拆分:缺少明确收益,却增加部署和运维成本。
  • 直接暴露内部数据结构:让调用方依赖数据库模型,导致接口难以演进。
  • 建立共享数据库写入机制:不同服务能够随意修改同一业务对象,削弱责任边界。
  • 只设计成功路径:忽略超时、重复请求、消息丢失和补偿处理。
  • 把所有服务调用都设计成同步请求:增加延迟,并可能形成长调用链。
  • 过度依赖异步消息:虽然降低同步耦合,却可能使业务流程难以追踪。
  • 把接口文档当作完整契约:只记录字段,不定义业务状态、权限和失败语义。
  • 缺乏服务所有者:没有明确团队负责接口、运行质量和变更管理。
8.3 面向软件开发者的服务设计检查清单
在设计评审或编码之前,可以使用下面的清单检查设计是否充分。
服务设计自查表
1、业务与边界
  • 服务有明确的业务目的和职责范围
  • 已明确服务负责维护的数据与业务规则
  • 已识别跨服务依赖及不必要的循环依赖
  • 已判断是否确实需要独立部署
2、接口与契约
  • 请求、响应、错误语义和业务前置条件清晰
  • 认证、授权与数据访问约束明确
  • 已考虑接口兼容性与版本演进
  • 写操作已定义幂等性和重复请求处理
3、交互与可靠性
  • 已定义超时、重试和故障隔离策略
  • 已考虑跨服务数据一致性和补偿机制
  • 异步事件有明确的发布、消费与重复处理策略
  • 关键业务状态可以查询和恢复
4、运行与治理
  • 有日志、指标、追踪和告警方案
  • 有单元测试、契约测试和集成测试
  • 有安全审计与敏感数据保护措施
  • 有服务负责人、部署和回滚方案
  • 重置检查 复制检查结果 
该清单可以用于代码评审、架构评审、服务拆分讨论或上线前检查。并非所有服务都需要采用同等复杂的设计机制;重点是让设计决策与业务风险相匹配。
总  结
服务设计是现代软件工程中连接业务需求、软件架构、接口实现和运行治理的重要活动。它不仅决定系统由哪些服务组成,也决定这些服务如何协作、如何维护业务数据、如何应对故障,以及如何在业务变化时持续演进。
本讲的主要结论如下。
第一,服务是具有明确职责、通过契约提供业务或技术能力的软件逻辑单元。服务可以采用不同的实现和部署方式,不应将服务简单等同于微服务。
第二,服务设计应当从业务能力和领域规则出发,合理确定服务边界,并通过清晰的接口契约、数据责任和交互机制降低耦合。
第三,服务设计不仅涉及功能接口,还需要系统考虑性能、可靠性、安全性、可观测性、可维护性和可演进性等质量属性。
第四,分布式服务会带来数据一致性、网络故障、重复消息、版本兼容和运维治理等问题。Saga、事务性发件箱、幂等处理和分布式追踪等技术可以解决部分问题,但也会引入额外复杂性,应根据实际需要采用。
第五,服务设计是一项持续演进的工程实践。服务边界需要通过业务理解、原型验证、运行反馈和架构评审不断调整。
对于软件开发者而言,最重要的并不是掌握尽可能多的服务框架,而是建立正确的设计思维:先理解业务,再划分职责;先定义契约,再实现交互;先明确失败语义,再设计正常流程;最后通过测试和运行反馈持续验证设计。
参考文献
一、经典著作与学术研究

[1] BOOTH D, HAAS H, MCCABE F, et al. Web Services Architecture. W3C Working Group Note, 2004. 网址:https://www.w3.org/TR/2004/NOTE-ws-arch-20040211/ 

主要贡献:定义 Web 服务架构中的服务、请求者、提供者、服务描述与语义契约等概念,是理解服务交互和契约设计的基础文献。

[2] ERL T. SOA Principles of Service Design. Prentice Hall, 2008. 相关资料:Pearson 出版资料及书籍节选

主要贡献:系统阐述面向服务设计中的标准化契约、松耦合、服务抽象、自治、无状态性与可组合性等原则。

[3] EVANS E. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.

主要贡献:提出领域驱动设计的核心思想,重点讨论领域模型、通用语言、限界上下文以及复杂业务软件的建模方法。该书可通过出版社或图书馆目录检索。

[4] NEWMAN S. Building Microservices: Designing Fine-Grained Systems. 2nd ed. O’Reilly Media, 2021.

主要贡献:从服务边界、信息隐藏、耦合、团队组织和系统演进等角度讨论微服务设计与工程实践。

参考资料:Building Microservices, Second Edition 

[5] ZHONG C, LI S, HUANG H, et al. Domain-Driven Design for Microservices: An Evidence-Based Investigation. IEEE Transactions on Software Engineering, 2024, 50(6): 1425–1449.

DOI:https://doi.org/10.1109/TSE.2024.3385835 

主要贡献:从证据角度研究领域驱动设计在微服务中的应用,为服务边界识别的方法、收益与实际挑战提供研究依据。

[6] ÖZKAN O, BABUR Ö, VAN DEN BRAND M. Domain-Driven Design in Software Development: A Systematic Literature Review on Implementation, Challenges, and Effectiveness. 2023.

预印本:https://arxiv.org/abs/2310.01905 

主要贡献:系统梳理 DDD 的实施方式、应用效果和实践挑战,适合进一步研究领域建模、限界上下文和领域事件。

二、架构设计与工程实践资料

[7] FOWLER M. Microservices. 2014.

网址:https://www.martinfowler.com/articles/microservices.html 

主要贡献:讨论微服务架构的基本特征、服务边界、独立部署和演进设计。

[8] FOWLER M. Microservice Trade-Offs. 2015.

网址:https://www.martinfowler.com/articles/microservice-trade-offs.html 

主要贡献:分析微服务在模块化、部署自治、分布式通信、数据一致性与运维复杂度方面的权衡。

[9] FOWLER M. Microservice Prerequisites. 2014.

网址:https://martinfowler.com/bliki/MicroservicePrerequisites.html 

主要贡献:讨论采用微服务之前应具备的自动化部署、基础设施管理和持续交付能力。

[10] AMAZON WEB SERVICES. Saga Pattern. AWS Prescriptive Guidance.

网址:https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html 

主要贡献:介绍 Saga 如何通过本地事务、流程协调和补偿操作管理跨微服务业务流程。

[11] AMAZON WEB SERVICES. Transactional Outbox Pattern. AWS Prescriptive Guidance.

网址:https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html 

主要贡献:解释数据库更新与消息发布之间的双写问题,以及事务性发件箱、重复消息处理和事件顺序等工程问题。

[12] WORLD INTELLECTUAL PROPERTY ORGANIZATION. CWS/13/19. 2025.

网址:https://www.wipo.int/edocs/mdocs/cws/en/cws_13/cws_13_19.pdf 

主要贡献:提供 Web API 与面向服务设计原则方面的补充材料,可用于进一步了解标准化服务契约及接口治理。

相关学习资料