4. 服务契约与接口设计:WSDL与RESTful API设计、服务契约优先原则、版本管理策略
服务契约,说白了就是服务提供方和消费方之间的一纸「合同」。合同上写清楚了:你能给我什么、我需要给你什么、咱们怎么通信。没有这份合同,服务之间的调用就是一场混乱。
我见过太多项目,上来就写代码,接口说改就改,结果联调时鸡飞狗跳。嗯,这一章我们就来聊聊怎么把这份「合同」写好、管好。
4.1 服务契约优先原则
什么叫契约优先?就是先定义接口,再写实现代码。这个顺序不能乱。
核心思想:接口是合同,实现是履约。合同没签好,履约就是瞎忙活。
我个人习惯的做法是:
- 先定义数据结构——请求和响应的XML/JSON Schema
- 再定义操作——服务提供哪些方法、URL路径
- 然后生成代码骨架——用工具从契约生成服务端和客户端代码
- 最后填充业务逻辑
这样做的好处很明显:
- 服务提供方和消费方可以并行开发
- 接口变更在早期就能被发现
- 生成的代码类型安全,减少运行时错误
注意:契约优先不是「写完契约就不改了」。契约也需要迭代,但每次变更都要走版本管理流程。
4.2 WSDL与SOAP接口设计
WSDL(Web Services Description Language)是SOAP服务的契约描述语言。它用XML描述了服务的所有细节:
- types:数据类型定义(XML Schema)
- message:消息结构
- portType:操作列表(相当于接口)
- binding:绑定协议(SOAP over HTTP等)
- service:服务地址
一个典型的WSDL片段长这样:
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/order">
<types>
<xsd:schema>
<xsd:element name="CreateOrderRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="customerId" type="xsd:string"/>
<xsd:element name="items" type="tns:ItemList"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>
<portType name="OrderPortType">
<operation name="createOrder">
<input message="tns:CreateOrderRequest"/>
<output message="tns:CreateOrderResponse"/>
</operation>
</portType>
</definitions>
我在项目中遇到过一个问题:WSDL文件太长了,动辄上千行。后来我们学会了拆分——把公共的数据类型定义放在独立的XSD文件中,用<xsd:import>引入。这样WSDL本身只描述接口,数据模型可以复用。
经验之谈:WSDL设计时,尽量使用文档/字面量(document/literal)风格,避免RPC/编码(RPC/encoded)风格。前者更灵活,后者已经过时了。
4.3 RESTful API设计
RESTful API现在更流行。它的契约不是WSDL,而是OpenAPI规范(以前叫Swagger)。
设计RESTful API,我遵循几个原则:
- 资源导向——URL表示资源,不是操作。比如
/orders,不是/createOrder - HTTP动词表达操作——GET查、POST增、PUT全量更新、PATCH部分更新、DELETE删
- 状态码语义化——200成功、201创建成功、400参数错误、404不存在、500服务器错误
- 版本号放在URL或Header中——比如
/v1/orders或Accept: application/vnd.myapp.v1+json
一个OpenAPI 3.0的示例:
openapi: 3.0.0
info:
title: 订单服务
version: 1.0.0
paths:
/orders:
get:
summary: 查询订单列表
parameters:
- name: status
in: query
schema:
type: string
responses:
'200':
description: 成功返回订单列表
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Order'
post:
summary: 创建订单
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'201':
description: 订单创建成功
关键区别:SOAP/WSDL面向操作,REST/OpenAPI面向资源。选哪个取决于你的场景——企业级集成、事务性要求高的场景用SOAP;互联网应用、移动端场景用REST。
4.4 版本管理策略
服务一旦上线,接口就会面临变更。怎么管?我总结了几种策略:
| 策略 | 实现方式 | 适用场景 | 缺点 |
|---|---|---|---|
| URL路径版本 | /v1/orders vs /v2/orders |
简单直观,客户端容易理解 | URL膨胀,长期维护成本高 |
| 请求Header版本 | Accept: application/vnd.myapp.v1+json |
URL干净,适合网关路由 | 调试不方便,需要工具查看Header |
| 查询参数版本 | /orders?version=1 |
临时过渡,快速验证 | 容易混淆,不推荐长期使用 |
| 语义化版本 | 通过兼容性规则(向后兼容/不兼容) | 内部服务,团队可控 | 需要严格的兼容性检查工具 |
我个人比较推荐URL路径版本加语义化版本的组合。对外暴露的接口用URL版本,清晰明了;内部服务之间用语义化版本,配合兼容性检查工具自动验证。
避坑指南:我曾经在一个项目里,团队为了省事,直接修改了现有接口的字段,没有升级版本号。结果消费方没同步更新,线上数据全部解析失败。那次事故之后,我们定了一条铁律:任何不兼容的变更都必须升级主版本号。
4.5 契约设计的核心流程
把上面的知识点串起来,契约设计的完整流程是这样的:
4.6 契约的兼容性检查
契约设计好了,怎么保证变更不会破坏现有消费方?你需要做兼容性检查。
对于WSDL/SOAP:
- 向后兼容:新增可选字段、新增操作是兼容的;删除字段、修改字段类型是不兼容的
- 向前兼容:消费方用新契约调用老服务,通常更难保证
对于REST/OpenAPI:
- 兼容的变更:新增端点、新增可选参数、新增响应字段
- 不兼容的变更:删除端点、修改参数类型、删除响应字段、修改URL路径
工具推荐:对于OpenAPI,可以用openapi-diff或spectral做自动化兼容性检查。我习惯在CI/CD流水线里加一步——每次PR合并前自动检查契约变更是否兼容,不兼容的变更必须升级主版本号。
4.7 小结
服务契约是SOA的基石。契约优先原则让你在写代码之前就想清楚接口;WSDL和OpenAPI是两种主流的契约描述语言,各有适用场景;版本管理策略决定了你的服务能活多久。
嗯,这一章的内容就到这里。记住一句话:契约定得好,联调没烦恼。