自动化 API 交付——API 一致性:生成代码和 API 定义

本章内容涵盖:

  • 处理 API 服务器、客户端和文档一致性的问题
  • 生成符合规范的服务器代码
  • 生成符合规范的 OpenAPI 定义
  • 生成符合规范的 SDK

假设你正在构建一个公开的 REST API。你已经设计好了 OpenAPI 定义,并通过了第3章和第4章中描述的代码规范检查(linting)和破坏性变更检查。你已将 API 构建并部署到 API 网关,使其上线。同时,你还根据 API 定义生成了 API 参考文档,并发布到开发者门户。运行情况良好。

但现在你的 API 使用者抱怨,发布的 API 参考文档与实际运行的 API 行为有不一致的地方。举例来说,文档中说明某个 API 端点的 200 OK 响应总会包含某个字段,但使用者实际调用时该字段缺失。API 参考文档与部署的 API 之间存在差异。你如何确保 API 服务器和客户端 SDK 的实现都符合你的 OpenAPI 定义呢?

API 参考文档是开发者门户上的面向人类的内容,用于描述你所提供的 API 操作。通常,你是通过某个工具或上传 OpenAPI 定义到开发者门户,由门户自动生成该文档。API 使用者依赖这份文档了解 API 的功能。如果文档与实际行为不符,会造成困惑,降低最终开发者的体验,甚至导致他们提交缺陷工单,增加你的支持成本。随着 API 规模扩大,这种文档问题可能严重阻碍 API 的广泛采用。

确保 API 服务器、客户端 SDK 和 API 参考文档符合 OpenAPI 定义,主要有两种方法:

  1. 生成一致性(Generated Consistency)
    通过 OpenAPI 定义生成 API 服务器的桩代码(stub code),或者反过来由服务器代码生成 OpenAPI 定义。无论哪种方式,客户端 SDK 和 API 参考文档都是基于该 OpenAPI 定义生成。这里的桩代码是指生成的软件对象,表示 API 调用中的请求和响应消息,以及控制器和路由逻辑。
  2. API 一致性测试(API Conformance Testing)
    运行测试(包括在 API 网关的运行时检查),验证 API 服务器返回的响应是否符合 OpenAPI 定义。

本章重点介绍第一种方法,即如何在 APIOps 工作流中使用生成一致性技术。通常,这一步会应用在 APIOps 工作流的构建阶段,如图6.1所示。

本章内容介绍如何生成服务器桩代码、OpenAPI 定义和 SDK 客户端,以确保它们之间的一致性。此外,我还会讨论保持这些生成产物同步时需要考虑的几个要点。附录D中包含了本章所需工具和代码的安装、启动和停止 Kong API 网关的相关信息。请确保在继续本节内容之前,已完成所有工具的安装和配置。要练习本章内容,请前往本书代码仓库的 chapter6 目录,地址为 github.com/apiopsbook/...。本章内容更适合开发人员阅读,但 API 产品负责人了解 API 一致性问题的概况也会有所帮助。

6.1 API 一致性问题

请考虑以下场景:你的用户发现某个端点------例如 GET /v1/catalog/products/{id}------的已发布 API 参考文档与该端点在实际线上服务器上的行为存在差异。他们在响应中没有看到一个名为 keywords 的字段,而你的 API 参考文档明确说明该字段是必需的。图6.2 展示了 API 参考文档与 API 实际行为之间的这种偏差。在图示中,API 服务从 API 网关接收请求,并将请求编排转发给多个上游微服务进行处理。

这是怎么发生的呢?原因有很多。可能是负责构建该 API 服务的开发人员在开发时,手动编写了控制器逻辑,参考了设计阶段的 OpenAPI 定义。在这个手动过程中,开发者可能忽略了响应中应包含 keywords 属性。设计阶段的 OpenAPI 定义是 API 应实现功能的唯一真实来源(SOT),但开发者手动编写了 API 服务控制器代码,导致在将 OpenAPI 定义转化为代码时出现了人为错误。也可能是在开发过程中需求发生了变化,keywords 属性不再是必需的。开发者更新了代码,但没有同步更新 OpenAPI 定义文件。在这种情况下,开发团队没有将 OpenAPI 定义视为更新代码的唯一真实来源。

无论是哪种情况,最终结果都是开发者将 API 服务部署到 API 网关,但发布的 API 参考文档依然基于原始的 API 定义。于是,API 实现与已发布的 OpenAPI 定义之间产生了偏差(见图 6.3)。

需要有一种方法来确保控制器代码和 OpenAPI 定义保持同步。一个方案是开发者在有新需求时,始终记得更新原始的 API 定义。缺点是,这依赖于人的记忆,而且更新必须在两个地方完成------代码和 API 定义。如果开发者忘记更新 API 定义(发布 API 参考文档所依赖的文件),除非有人手动检查 API 参考文档的一致性,或者有 API 使用者提交缺陷工单投诉,否则无法发现问题。

另一种方案是只在一个地方更新------OpenAPI 定义,并从该定义生成 API 服务控制器代码、API 客户端 SDK 以及 API 参考文档。也就是说,不仅将设计阶段的 OpenAPI 定义作为唯一真实来源(SOT),还通过自动生成 API 服务桩代码、API 客户端 SDK 和参考文档,最大限度减少人为翻译错误的可能。此方法需要使用工具,根据 OpenAPI 定义为指定的编程语言和应用框架生成 API 服务桩代码。每当开发者收到变更 API 请求或响应的需求时,先更新 OpenAPI 定义,然后再从中重新生成 API 服务控制器桩代码。图 6.4 展示了这个过程。

让我们来看一下如何在一个示例的 Java 项目中应用这个方法,使用 OpenAPI Generator 库作为服务器桩代码的生成工具。

6.2 从 OpenAPI 生成代码

你可以通过将 OpenAPI 定义视为 API 的权威规范,并使用代码生成工具从中生成 API 服务控制器、请求和响应对象,来确保你的 API 行为符合 OpenAPI 定义。本节将通过一个 Java Spring Boot 应用的示例来演示如何操作。即使你不熟悉 Java 和 Spring Boot,也不用担心------我已经准备好了可以直接运行的示例。如果你熟悉 Spring Boot,有些关于项目结构的部分你可以跳过。虽然我会详细介绍代码生成库创建的文件,但本节的目标不是教你 Java 或 Spring Boot,也不是推荐 Spring Boot 应用的结构方式,而是展示如何使用 OpenAPI 定义作为唯一真实来源(SOT)生成服务的控制器、模型和路由代码的示例。

6.2.1 基于 OpenAPI 的 Spring Boot

假设你的团队负责人要求你在构建 API 时应用代码生成方法。技术要求是 API 服务应为 Java Spring Boot 应用,并且你需要使用 OpenAPI Generator 从 OpenAPI 定义文件生成控制器、路由逻辑、请求和响应对象,该定义文件作为唯一真实来源(SOT)。

注意:如果你熟悉 Java 和 Spring Boot,可以使用 Visual Studio Code (VS Code) 或你喜欢的 Java IDE,在本书代码仓库的 chapter6 目录中完成这个示例。如果不熟悉,可以查看 chapter6-completed 目录中的完成示例。

OpenAPI Generator(github.com/OpenAPITool... OpenAPI 文档为多种编程语言、框架和文档格式生成 API 客户端代码、服务器代码和文档。如果你按照附录 D 操作,应该已经安装了 openapi-generator-cli(github.com/OpenAPITool... 是一个命令行的 Node 应用,用来下载并运行 OpenAPI Generator 的 Java 可执行文件。

进入 chapter6 目录,新建一个名为 product-catalog-service 的文件夹。然后运行下面的 openapi-generator-cli 命令,生成你的应用程序,模型对象放在 com.acmepetsupplies.model 包中,控制器桩代码放在 com.acmepetsupplies.api 包中。

ini 复制代码
openapi-generator-cli generate \
--input-spec product-catalog-v1-0.oas.yaml \    #1
--generator-name spring \                       #2
--output product-catalog-service \
--additional-properties= \                      #3
library=spring-boot, \                           #4
apiPackage=com.acmepetsupplies.controller, \   #5
modelPackage=com.acmepetsupplies.model, \      #6
configPackage=com.acmepetsupplies.configuration, \  #7
basePackage=com.acmepetsupplies, \              #8
useTags=true                                   #9
  • #1 用于生成 Spring 项目的 OpenAPI 定义文件
  • #2 用于生成 Spring 项目的生成器
  • #3 针对该 Spring 生成器的附加配置
  • #4 Spring 生成器的子模板,选项为 spring-boot 或 spring-cloud
  • #5 生成的 API 控制器类所在包
  • #6 生成的 API 模型类所在包
  • #7 生成的配置类所在包
  • #8 生成的 Spring Boot 应用主类的基础包
  • #9 生成控制器类名时是否使用 OpenAPI 标签

提示:--additional-properties 参数后续的配置项之间不要有空格。如果你不想手动输入完整命令,可以运行 chapter6 文件夹下的辅助脚本 listing-6-1-generate-spring-boot-app.sh(Windows 下为 listing-6-1-generate-spring-boot-app.bat)。

查看生成的文件夹,README.md 文件说明项目由 OpenAPI Generator 创建。openapitools.json 文件包含了生成项目所使用的 OpenAPI Generator 版本信息。你也可以用这个文件为命令行界面(CLI)指定配置快捷方式,但这不在本章范围内(详情见 github.com/OpenAPITool... 文件列出了这次生成的所有文件。pom.xml 是 Maven 项目的项目对象模型文件,包含构建和配置项。OpenAPI Generator 创建了一个 Maven 项目,Maven 会根据 pom.xml 来构建项目。.openapi-generator-ignore 文件用于指定重新生成时排除覆盖的文件名和模式。src 目录包含你的 Java 源码和测试代码。

生成文件目录结构示例如下:

css 复制代码
.
├── .openapi-generator
│   ├── FILES
│   └── VERSION
├── .openapi-generator-ignore
├── README.md
├── openapitools.json
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── acmepetsupplies
    │   │           ├── OpenApiGeneratorApplication.java
    │   │           ├── RFC3339DateFormat.java
    │   │           ├── configuration
    │   │           │   ├── HomeController.java
    │   │           │   └── SpringDocConfiguration.java
    │   │           ├── controller
    │   │           │   ├── ApiUtil.java
    │   │           │   ├── ProductsApi.java
    │   │           │   └── ProductsApiController.java
    │   │           └── model
    │   │               ├── Error.java
    │   │               └── Product.java
    │   └── resources
    │       ├── application.properties
    │       └── openapi.yaml
    └── test
        └── java
            └── com
                └── acmepetsupplies
                    └── OpenApiGeneratorApplicationTests.java

现在查看 src 文件夹里的源码。进入 src/main/java/com/acmepetsupplies 文件夹,controller/ProductsApi.java 文件是一个控制器接口桩,包含了路由信息,通过 Java 的 @RequestMapping 注解告诉 Spring Boot 框架将 GET /v1/catalog/products/{id} 的请求路由到该控制器,并且响应内容类型为 application/json。它有一个 viewProduct(id) 方法,该方法名基于 API 定义文件中对应接口的 operationId。viewProduct(id) 方法提供了一个默认实现,返回的是 chapter6/product-catalog-v1-0.oas.yaml OpenAPI 文件中示例响应。@Tag、@Operation 和 @ApiResponse 注解包含了来自 OpenAPI 定义的文档元数据,这些元数据可以用来从代码重新生成 OpenAPI 定义。

下面是 ProductsApi 接口的代码片段:

less 复制代码
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
    date = "2022-12-31T13:20:55.039973Z[Europe/London]")
@Validated
@Tag(name = "Products", description = "A product is an item for sale on the store.")
public interface ProductsApi {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();
    }

    @Operation(
        operationId = "viewProduct",
        summary = "View a product's details",
        tags = { "Products" },
        responses = {
            @ApiResponse(responseCode = "200", description = "OK", 
                content = {
                    @Content(mediaType = "application/json", 
                        schema = @Schema(implementation = Product.class))
                }),
            ...
        },
        security = {
            @SecurityRequirement(name = "ApiKeyAuth")
        }
    )
    @RequestMapping(
        method = RequestMethod.GET,
        value = "/v1/catalog/products/{id}",
        produces = { "application/json" }
    )
    default ResponseEntity<Product> viewProduct(
        @Parameter(name = "id", description = "Product identifier", required = true)
        @PathVariable("id") UUID id
    ) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    String exampleString = "{ "keywords" : [ "keywords", "keywords", "keywords", "keywords", "keywords" ], "price" : 100, "name" : "Acme Uber Dog Rope Toy", "description" : "Acme Uber Dog Rope Toy provides hours of fun for your dog. Great for bouncing, throwing and catching.", "id" : "dcd53ddb-8104-4e48-8cc0-5df1088c6113" }";
                    ApiUtil.setExampleResponse(request, "application/json", exampleString);
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }
}
  • #1 OpenAPI 文档中 API 操作的元数据
  • #2 OpenAPI 文档中 API 响应的元数据
  • #3 路由请求到 GET /v1/catalog/products/{id} 端点,映射到 viewProduct 方法
  • #4 如果请求头 Accept 是 application/json,默认实现返回示例响应
  • #5 如果请求头中不包含 Accept,则返回 501 Not Implemented 错误

controller/ProductsApiController.java 文件实现了 ProductsApi 接口。在实际项目中,控制器通常包含业务逻辑,或者调用包含业务逻辑的服务对象,可能从数据库获取数据。本示例中,请你重写 viewProduct(id) 方法,返回一个固定的对象,展示你可以返回自定义响应。

首先,在 ProductsApiController.java 文件开头(package 声明之后)添加如下导入:

java 复制代码
import io.swagger.v3.oas.annotations.Parameter;
import java.math.BigDecimal;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;

然后在 ProductsApiController.java 文件内添加以下代码,为 viewProduct 方法赋予一个返回硬编码 Product 对象的新实现:

less 复制代码
@Override
public ResponseEntity<Product> viewProduct(
    @Parameter(name = "id", description = "Product identifier", required = true)
    @PathVariable("id") UUID id) {
    Product product = new Product();
    product.setId(id);
    product.setName("Acme Uber Dog Rope Toy");
    product.setDescription("Acme Uber Dog Rope Toy provides hours of fun for your dog.");
    product.setPrice(new BigDecimal(50));
    Set<String> keywords = new HashSet<>(Arrays.asList("rope", "toy", "dog"));
    product.setKeywords(keywords);
    return new ResponseEntity<Product>(product, HttpStatus.OK);
}
  • #1 重写 ProductsApi 接口中定义的 viewProduct 方法
  • #2 使用 Swagger 注解描述 ID 方法参数,以便从代码生成 OpenAPI 定义
  • #3 使用 Spring 框架的 @PathVariable 注解将方法参数绑定到 URI 模板变量
  • #4 创建并返回一个硬编码的 Product 对象

OpenAPI Generator 在 model 包中生成了两个文件------model/Product.java 和 model/Error.java。Product.java 类由控制器用于成功响应(2xx),而 Error.java 应由应用的错误处理代码返回。当序列化成 JSON 消息时,Error 类符合 OpenAPI 文档中定义的错误响应模式。目前还没有设置错误处理代码来使用 Error.java 类。

为此,我们创建一个错误处理器,拦截控制器抛出的异常,返回 Error 类型的实例。新建 ApplicationExceptionHandler.java 文件,放在 src/main/java/com/acmepetsupplies/controller 目录,代码如下:

scala 复制代码
package com.acmepetsupplies.controller;

import com.acmepetsupplies.model.Error;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.UUID;

@RestControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(
        Exception ex, @Nullable Object body, HttpHeaders headers,
        HttpStatus status, WebRequest request) {
        Error apiError = new Error();
        apiError.setId(UUID.randomUUID());
        apiError.setStatus(status.value());
        switch (status.series().value()) {
            case 4:
                apiError.setCode("validation." + status.series().name().toLowerCase());
                break;
            case 5:
                apiError.setCode("error." + status.series().name().toLowerCase());
                break;
            default:
                apiError.setCode(status.series().name().toLowerCase());
                break;
        }
        apiError.setTitle(status.getReasonPhrase());
        switch (status.value()) {
            case 400:
                apiError.setDetail("Bad request. Please check the request is valid.");
                break;
            case 404:
                apiError.setDetail("Resource not found. Please check the path and resource identifier in your request");
                break;
            case 429:
                apiError.setDetail("Too many requests. Request quota exceeded in time window. Try again soon");
                break;
            default:
                apiError.setDetail(status.getReasonPhrase());
                break;
        }
        return new ResponseEntity<>(apiError, headers, status);
    }
}
  • #1 拦截控制器异常,应用错误处理逻辑
  • #2 自定义所有异常类型的响应体的方法
  • #3 根据 HTTP 状态码设置错误对象的详细信息

你已经生成了 Spring Boot 项目,创建了控制器,并写了一些通用错误处理代码。现在需要告诉 Spring Boot 如何自动配置和启动你的项目。通过命令行运行以下命令启动应用:

arduino 复制代码
mvn spring-boot:run

该命令会启动你的 Spring Boot 应用,并使其在 localhost:8080 上可用。使用你喜欢的 HTTP 客户端,使用任意 UUID 作为产品 ID 发送请求,你会看到响应中返回了对应的产品对象。下面示例使用 curl 并配合 jq 格式化输出:

bash 复制代码
curl --silent --request GET http://localhost:8080/v1/catalog/products/612b4280-b5c0-4ad5-bce7-ede7ab2b80fc | jq .

Windows 用户提示:上述命令在 Cmd.exe 下有效,但 PowerShell 中 curl 是 Invoke-WebRequest 的别名,需改用 curl.exe。

返回示例:

json 复制代码
{
  "id": "612b4280-b5c0-4ad5-bce7-ede7ab2b80fc",
  "name": "Acme Uber Dog Rope Toy",
  "description": "Acme Uber Dog Rope Toy provides hours of fun for your dog.",
  "price": 50,
  "keywords": [
    "rope",
    "dog",
    "toy"
  ]
}

你获得了成功响应,这很好。但如果请求一个不是 UUID 的产品 ID,会怎样?应用会校验输入参数吗?请记住你没有写任何校验逻辑。用一个简单的非 UUID 字符串请求试试:

bash 复制代码
curl --silent --request GET http://localhost:8080/v1/catalog/products/not-a-uuid | jq

返回:

json 复制代码
{
  "id": "d207deea-cef0-4598-ae86-ebb56340b4ac",
  "status": 400,
  "code": "validation.client_error",
  "title": "Bad Request",
  "detail": "Bad request. Please check the request is valid."
}

应用正确校验了你的输入,并返回了符合 OpenAPI 定义错误模式的响应。Spring Boot 应用框架使用控制器和模型对象上的验证注解来校验用户输入。

恭喜!你已经成功通过从 OpenAPI 定义 SOT 生成代码,生成了你的 API 服务。你可以通过 Ctrl-C 停止运行服务,但现在先保持运行状态。下一节,我将展示如何将你的 API 服务部署到 API 网关。

6.2.2 部署到网关

假设下一步,团队负责人要求你将 API 服务部署到 API 网关(图 6.5 中的第3步)。API 网关是一个反向代理和集中入口点,用于将外部请求转发到你的后端 API 服务。除了路由功能,API 网关还可以处理其他跨领域的功能,例如身份认证、协议转换、限流和监控。通过 API 网关,你可以实现外部客户端接口与内部后端服务的解耦。

在本节中,我将带你一步步完成将 API 部署到 Kong 的过程,Kong 是一个流行的开源 API 网关。虽然我以 Kong 网关为例,但本节的重点并不是教你如何使用某个特定的 API 网关技术。目标是展示:对于你正在构建的这个公共 API,API 参考文档必须反映所有构成 API 外部可观察行为的组件的实际行为。这包括 API 服务和 API 网关。正如你将看到的,API 网关可能返回 API 服务本身不知道的响应,但这依然需要被纳入 API 参考文档中。

当你将 API 服务部署到网关时,你实际上是在外部请求和 API 服务之间创建了一条路由。默认情况下,Kong 监听 8000 端口的公共 HTTP 流量。通过更新 chapter6/kong/kong.yaml 文件,设置 Kong 将任何匹配 GET /v1/catalog/products/(id) 路径且 Host 头为 api.acme-pet-supplies.com 的请求转发到运行在 8080 端口的产品目录 API 服务,如下所示。

yaml 复制代码
_format_version: "1.1" 
services:
  - name: Product_Catalog_API
    protocol: http
    host: localhost
    port: 8080
    path:  /
    routes:
      - tags:
          - Product Catalog
        name: viewProduct
        methods:
          - GET                           #1
        hosts:                            #1
          - api.acme-pet-supplies.com     #1
        paths:                            #1
          - /v1/catalog/products/(.+)$    #1
        strip_path: false                 #1
    plugins: #1
      - name: key-auth       #2
        config: 
          key_names:
          - x-api-key
consumers:
- username: my_api_consumer
  keyauth_credentials:
  - key: my_secret_api_key

#1 将带有 Host 头为 api.acme-pet-supplies.com 的 GET /v1/catalog/products/{id} 请求路由到运行在 localhost:8080 的后端 API 服务。

#2 key-auth 插件设置 API 密钥认证。

注意:在 Docker 中运行 Kong 的用户(例如 Windows 用户)应将 listing 6.4 中的 host 值设置为 host.docker.internal。这个特殊的 DNS 名称解析为主机 Windows 计算机使用的内部 IP 地址,使得在 Docker 中运行的 Kong 实例能够将请求路由到运行在 Windows 主机上的后端产品目录 API 服务。

kong.yaml 文件包含了用于将请求路由到 Kong 网关的声明式配置。根据此配置,所有发送到你的 API 网关的请求必须提供 Host 头为 api.acme-pet-supplies.com,才能被路由到后端 API 服务。

OpenAPI 定义 SOT 中的 securityScheme 部分指定该 API 受 API 密钥保护。API 网关通常是实现 API 密钥认证的地方。API 密钥用于识别调用 API 的应用程序(即 API 使用者)。listing 6.4 中的配置启用了 Kong 的 key-auth 插件,要求 API 使用者发送 API 密钥。也就是说,API 使用者需要在请求中带上 x-api-key 头,值为 my_secret_api_key,才能成功访问服务器。

进入 chapter6/kong 目录,运行命令 kong start -c kong.conf 启动 Kong。(Windows 用户可使用 chapter6/kong 文件夹中的 run_kong_docker.bat 脚本启动 Kong。如果你使用的是类 Unix 操作系统,也可以使用 start_kong.sh 和 stop_kong.sh 辅助脚本启动和停止 Kong。)然后,使用你喜欢的 API 客户端,发送请求到 API 网关,并在请求中提供 Host 和 x-api-key 头。下面是使用 curl 发送请求的示例:

bash 复制代码
curl --silent --header "Host: api.acme-pet-supplies.com" \
     --header "x-api-key: my_secret_api_key" \
     --request GET http://localhost:8000/v1/catalog/products/612b4280-b5c0-4ad5-bce7-ede7ab2b80fc | jq .

你应该会在控制台看到返回的产品响应。恭喜你!你已经成功将 API 部署到网关,并向你的安全公共 API 发起了请求。

6.2.3 发布到开发者门户

假设现在你的团队负责人要求你将 API 定义 SOT 发布到开发者门户,作为 API 参考文档(见图 6.6)。

开发者门户是 API 使用者在想了解如何使用公共 API 时访问的权威文档。开发者门户通常包含 API 用户指南和教程,帮助开发者快速上手使用 API。它们还包含 API 参考文档,展示 API 支持的操作。在实际应用中,你可以将 API 参考文档上传到第三方开发者门户服务,使用你所用 API 网关平台自带的开发者门户,甚至自己搭建一个开发者门户。对于本示例场景,在 chapter6 目录下创建一个名为 dev-portal 的文件夹,用来存放和发布你创建的 API 参考文档。

使用 Redocly CLI 从你的 API 定义 SOT 生成 API 参考文档。Redocly CLI 生成一个三栏响应式布局的 API 参考页面,左侧栏包含搜索框和导航菜单。如果你按照附录 D 的说明操作,应该已经安装了 Redocly CLI。在 chapter6 目录中运行以下命令,生成一个无依赖的静态 HTML 文件作为 API 参考文档:

bash 复制代码
redocly build-docs product-catalog-v1-0.oas.yaml --output dev-portal/reference.xhtml

进入 dev-portal 文件夹,通过运行 http-server --port 9000 启动一个 HTTP 服务器,来发布该 HTML 文件。然后在浏览器中打开 http://localhost:9000/reference.xhtml ,即可查看渲染后的 API 参考文档。

恭喜你,已经成功发布了 API 参考文档。目前你的 API 参考准确记录了 GET /v1/catalog/products/{id} 接口的 200 成功响应。但如果 API 定义 SOT 的行为发生了变化,文档不再准确该怎么办?记住,这种情况会导致 API 参考文档和代码不同步。接下来,我们来看当 SOT 更新时,如何同步代码和 API 参考文档的变化。

6.2.4 SOT 更新

假设你收到需求变更,导致 API 定义 SOT 需要更新。团队中的业务分析师告诉你,接口响应中需要新增两个必填字段:reviewRatingnumberOfReviews,都是整数类型。下面是更新后的 product-catalog-v1-1.oas.yaml 文件中 Products schema 里新增字段的片段:

yaml 复制代码
reviewRating:
  maximum: 5
  minimum: 1
  type: integer
  description: Average product review rating.
  format: int32
  example: 5
numberOfReviews:
  type: integer
  description: Number of product reviews.
  format: int32
  example: 10

因为 OpenAPI 定义是 API 操作的 SOT,故将其更新为 product-catalog-v1-1.oas.yaml 以反映新需求。现在你需要重新生成 API 服务中响应模型对象,以支持这两个新字段并进行填充。为了保持 API 参考文档与 API 服务行为一致,也需要基于新的 SOT 更新 API 参考文档。

你可以从更新后的 product-catalog-v1-1.oas.yaml 文件重新生成 API 服务代码,但不希望覆盖对 ProductsApiController.java 文件的自定义修改。为此,在 product-catalog-service/.openapi-generator-ignore 文件中添加 **ProductsApiController.java,防止该文件被覆盖。然后,切换到 chapter6 目录,运行下面命令根据 product-catalog-v1-1.oas.yaml 生成代码:

ini 复制代码
openapi-generator-cli generate \
  --input-spec product-catalog-v1-1.oas.yaml \      #1
  --generator-name spring \
  --ignore-file-override product-catalog-service/.openapi-generator-ignore \  #2
  --additional-properties=library=spring-boot, \
  apiPackage=com.acmepetsupplies.controller, \
  modelPackage=com.acmepetsupplies.model, \
  configPackage=com.acmepetsupplies.configuration, \
  basePackage=com.acmepetsupplies, \
  useTags=true

#1 使用更新后的 SOT 定义

#2 指定忽略文件位置,防止覆盖 ProductsApiController.java

提示:你也可以根据操作系统,运行辅助脚本 listing-6-5-generate-spring-boot-app.shlisting-6-5-generate-spring-boot-app.bat 来生成代码。

注意,ProductsApiController.java 文件不会被更新,但重新生成的 Product.java 文件现在包含了 reviewRatingnumberOfReviews 字段的 getter 和 setter 方法。

使用 OpenAPI Generator Maven 插件

Java 开发者也可以使用 Maven 目标(即可执行的插件任务)来生成项目代码。openapi-generator-maven-plugin 可以让你在 Maven 项目内部生成服务器代码。这样,你可以使用 Maven 命令自动生成代码。

pom.xml 文件的 plugins 部分添加如下内容,用于将生成的代码输出到 target 目录:

xml 复制代码
<plugins>
  <plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.2.1</version>
    <executions>
      <execution>
        <id>buildApi</id>
        <goals>
          <goal>generate</goal>
        </goals>
        <configuration>
          <inputSpec>${project.basedir}/../product-catalog-v1-1.oas.yaml</inputSpec>   <!-- #1 -->
          <generatorName>spring</generatorName>
          <library>spring-boot</library>
          <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>  <!-- #2 -->
          <modelPackage>com.acmepetsupplies.model</modelPackage>
          <apiPackage>com.acmepetsupplies.controller</apiPackage>
          <invokerPackage>com.acmepetsupplies</invokerPackage>
          <configOptions>
            <useTags>true</useTags>
            <configPackage>com.acmepetsupplies.configuration</configPackage>
          </configOptions>
        </configuration>
      </execution>
    </executions>
  </plugin>
</plugins>

#1 引用 API 定义文件的位置

#2 指定忽略文件

接下来,创建一个 Maven 插件目标,将生成的文件复制到 src/ 文件夹中。可以使用 Maven AntRun 插件,如下配置:

xml 复制代码
<plugin>
  <artifactId>maven-antrun-plugin</artifactId>    <!-- #1 -->
  <version>3.0.0</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <target>
          <move todir="src/main">
            <fileset dir="target/generated-sources/openapi/src/main"/>
          </move>
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

#1 Maven AntRun 插件,用于将生成的源文件从 target 目录复制到 src/main/

然后执行 mvn clean compile,这会清理 target 目录并在编译阶段生成并复制文件。注意,直接运行 mvn openapi-generator:generate 不会生效,会报错。

更新 ProductApiController.java 类,给返回的 Product 对象的 reviewRatingnumberOfReviews 字段设置任意整数值。下面是更新后的 viewProduct 方法示例:

less 复制代码
@Override
public ResponseEntity<Product> viewProduct(
        @Parameter(name = "id", description = "Product identifier", required = true)
        @PathVariable("id") UUID id) {
    Product product = new Product();
    product.setId(id);
    product.setName("Acme Uber Dog Rope Toy");
    product.setDescription("Acme Uber Dog Rope Toy provides hours of fun for your dog.");
    product.setPrice(new BigDecimal(50));
    Set<String> keywords = new HashSet<>(Arrays.asList("rope", "toy", "dog"));
    product.setKeywords(keywords);
    product.setNumberOfReviews(11);      // #1
    product.setReviewRating(3);          // #1
    return new ResponseEntity<Product>(product, HttpStatus.OK);
}

#1 设置评论数量和评分字段

停止正在运行的应用(按 Ctrl-C),然后运行 mvn spring-boot:run 重启应用以应用最新更改。由于你的应用已通过 API 网关路由,可以通过 API 客户端向网关发送请求,查看更新后的响应:

bash 复制代码
curl --silent --header "Host: api.acme-pet-supplies.com" \
     --header "x-api-key: my_secret_api_key" \
     --request GET http://localhost:8000/v1/catalog/products/612b4280-b5c0-4ad5-bce7-ede7ab2b80fc | jq .

你应看到包含两个新字段的响应:

perl 复制代码
{
    "id": "612b4280-b5c0-4ad5-bce7-ede7ab2b80fc",
    "name": "Acme Uber Dog Rope Toy",
    "description": "Acme Uber Dog Rope Toy provides hours of fun for your dog.",
    "price": 50,
    "keywords": [
        "rope",
        "dog",
        "toy"
    ],
    "reviewRating": 3,        // #1
    "numberOfReviews": 11     // #1
}

#1 注意响应中新增的字段。

现在你的 API 已经按照新需求正常工作,并且更新后的接口可以通过 API 网关访问。但你的开发者门户中的 API 参考文档已不同步,必须更新。

6.2.5 将 SOT 重新发布到门户

接下来的任务是从 SOT API 定义重新生成 API 参考文档,使其与 API 的行为保持一致。首先,按 Ctrl-C 停止正在运行的 HTTP 服务器实例。然后,在 chapter6 目录下运行以下命令,删除旧的 API 参考文档,并从更新后的 API 定义生成新的文档:

bash 复制代码
rm dev-portal/reference.xhtml
redocly build-docs product-catalog-v1-1.oas.yaml --output dev-portal/reference.xhtml

进入 dev-portal 文件夹,运行 http-server --port 9000 启动 HTTP 服务器。打开浏览器访问 http://localhost:9000/reference.xhtml ,即可查看更新后的 API 参考文档。文档中在 viewProduct 接口的 200 响应中包含了两个新增属性(reviewRating 和 numberOfReviews),与 API 的实际行为保持一致。

6.2.6 生成客户端 SDK

在刚才的示例中,你没有生成客户端 SDK。假设现在你被要求从 SOT 为产品目录 API 生成一个 Java SDK。API 客户端 SDK 是为 API 使用者提供的一个编程语言库,允许他们通过 SDK 来调用 API,而不是自己直接通过代码发起 HTTP 请求。SDK 封装了底层的 HTTP 客户端,用于向你的 API 服务器发送请求,如图 6.7 所示。

OpenAPI Generator 允许你以生成服务器代码的方式来生成客户端库------只需指定要使用的生成器名称及其对应的选项。根据你指定的客户端生成器,你还可以选择用于发起 HTTP 请求的底层 HTTP 客户端。

提示 :要查看 OpenAPI Generator 中可用的生成器列表,可以在命令行运行 openapi-generator-cli list,或者访问 openapi-generator.tech/docs/genera...

为了演示这个过程,在 chapter6 目录下创建一个名为 java-client 的文件夹,然后运行以下命令生成 Java API 客户端 SDK 项目:

ini 复制代码
openapi-generator-cli generate \
  --input-spec ../product-catalog-v1-1.oas.yaml \
  --generator-name java \                     #1
  --output java-client \
  --additional-properties=\
library=okhttp-gson, \                        #2
invokerPackage=com.acmepetsupplies.client, \
apiPackage=com.acmepetsupplies.api, \
modelPackage=com.acmepetsupplies.model, \
groupId=com.acmepetsupplies,                  #3
artifactId=acmepetsupplies-java-client       #4

注释

#1 客户端 SDK 生成器名称

#2 使用的库模板:OkHttp HTTP 客户端和 Gson JSON 处理器

#3 Maven 项目的 groupId

#4 Maven 项目的 artifactId,用于命名生成的 JAR 文件

提示 :你也可以根据操作系统使用 chapter6 目录下的辅助脚本 listing-6-7-generate-java-client.shlisting-6-7-generate-java-client.bat

OpenAPI Generator 会在 chapter6/java-client/src/main/java/com/acmepetsupplies/api/ProductsApi.java 生成一个类,其中包含 viewProduct(id) 方法,利用底层的 OkHttp 客户端调用你的 API。该方法代码片段从文件第 70 行开始,如下所示:

scss 复制代码
public Product viewProduct(UUID id) throws ApiException {
    ApiResponse<Product> localVarResp = viewProductWithHttpInfo(id);
    return localVarResp.getData();
}

为了测试这个库,在项目中编写一个测试类,发送请求并打印响应(无需断言)。使用 Visual Studio Code (VS Code) 或你喜欢的 Java 编辑器打开 java-client 目录,查看生成的文件。打开 src/test/java/com/acmepetsupplies/api/ProductsApiTest.java 文件,并按照下面示例更新。(注意移除 @Disabled 注解)

java 复制代码
public class ProductsApiTest {
    @Test
    public void viewProductTest() throws ApiException {
        ProductsApi api = new ProductsApi();     //1
        api.getApiClient().setBasePath("http://localhost:8000"); //2
        api.getApiClient().addDefaultHeader("Host", "api.acme-pet-supplies.com"); //2
        api.getApiClient().setApiKey("my_secret_api_key");         //2

        UUID id = UUID.randomUUID();
        Product response = api.viewProduct(id);      //3
        System.out.println(response);
    }
}

注释

#1 初始化包装 API 客户端的 ProductsApi 类

#2 使用底层 API 客户端设置 Host 头和 API Key

#3 调用 API 操作,发送 HTTP 请求

接着运行测试,你可以通过命令行执行 mvn test,控制台会打印出产品 API 的响应结果。

如果想将该库打包成 JAR 文件,供 API 使用者作为依赖添加到他们的 Java 项目中,可以运行 mvn install。该命令会创建可重用的库文件 target/acmepetsupplies-java-client-1.0.jar,并且将其复制到本地 Maven 仓库,使其他 Maven 项目可用。你也可以将该库发布到外部 Maven 仓库,但这超出了本书范围。

生成客户端 SDK 和生成服务器代码一样快捷,你可以在 API 定义 SOT 更新时随时重新生成 SDK。除了 OkHttp 客户端,这个生成器还支持其他 Java HTTP 客户端,包括 OpenFeign、Spring RestTemplate、Jersey 等。

如果你开发了一个公共 API 并公开了 OpenAPI 定义,却不提供语言 SDK,一些 API 使用者可能会自己生成 SDK。因此,从内部用多种语言生成 SDK 来验证生成的类和方法是否符合预期,是个不错的做法。例如,OpenAPI operationId 可以以数字开头,但 Java 方法名不能以数字开头,因此生成 Java 客户端时,OpenAPI Generator 会自动为方法名前加前缀以保证合法性。

6.2.7 从 SOT 生成的优势

在你刚才完成的示例中,可以看到 API 需求的变化首先反映在 SOT 上,然后基于 SOT 生成产物------服务器代码、客户端 SDK 和参考文档。下一章中,你还会看到可以基于 SOT 生成测试用例。根据团队数量不同,采用这种方式利用 SOT 非常适合并行开发。一旦 SOT 达成一致,服务器端、客户端、测试或文档团队就可以分别根据统一的 API 定义生成所需产物(见图 6...)

关于代码和 API 文档一致性,从 SOT(单一真实来源)生成代码的另一个优势是速度。与需要创建或维护架构测试以检查代码和文档一致性相比,代码生成的速度更快,维护开销也相对较低。无需编写或维护测试,开发者可以直接从命令行快速重新生成 API 服务的存根代码。这种速度非常契合迭代式 API 设计所需的灵活性。实际上,API 设计通常不是线性的、有明显设计和构建阶段的过程。即使在构建阶段,API 设计的某些方面仍可能随着团队对需求、开发系统和技术限制的了解不断更新。每当出现这种情况,采用生成方式,你可以轻松地从更新后的 SOT 重新生成所有产物,继续开发。

采用 SOT 方法时,我发现保留 API 定义的两种视图很有用:未来视图(to-be view)和当前视图(as-is view)。未来视图是面向未来的设计视图,包含了反映 API 应该具备功能的变更,但这些变更可能尚未生成代码。当前视图是准确反映当前已实现 API 的定义。例如,在你刚完成的示例中,新增两个字段的需求变更首先体现在 product-catalog-v1-1.oas.yaml(未来视图)中。此时,由于该变更尚未在 API 服务中实现,未来视图与表示当前实现的 product-catalog-v1-0.oas.yaml(当前视图)存在两个字段的差异。当然,当从 product-catalog-v1-1.oas.yaml 重新生成 API 服务代码后,product-catalog-v1-1.oas.yaml 就变成了当前视图。

在设计优先(也叫契约优先、规范优先,名称不同但含义类似)的方法中,即在 API 开发前先创建 API 定义 SOT,团队成员在不同 API 产品生命周期阶段需要这两种视图。设计阶段,API 设计师和治理团队关注未来视图;而随着 API 开发从构建阶段进入发布阶段,关注点转向当前视图,因为这才是发布到开发者门户供 API 使用者访问的定义。有些团队发现,从代码生成当前视图比较有用,因为这能最准确反映已实现的内容。

6.2.8 处理来自网关的响应

从 API 定义生成服务器代码时存在一些挑战,其中之一是 API 响应可能并非来自你的 API 服务,而是来自位于服务前端的组件。因此你需要配置相关组件,使其返回符合要求格式的响应。例如,API 网关可能负责验证消费者的 API Key,若验证失败,会返回相应的错误响应(如 401 错误)。在你的示例应用中测试这一点,发送一个不带 API Key 的请求,运行以下命令:

bash 复制代码
curl --include --header "Host: api.acme-pet-supplies.com" \
  --request GET http://localhost:8000/v1/catalog/products/612b4280-b5c0-4ad5-bce7-ede7ab2b80fc

你会得到类似以下的网关响应:

makefile 复制代码
HTTP/1.1 401 Unauthorized
Date: Thu, 15 Dec 2022 06:57:52 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
WWW-Authenticate: Key realm="kong"
Content-Length: 45
X-Kong-Response-Latency: 0
Server: kong/3.0.0

{
  "message":"No API key found in request"
}

你有两个选择:(1)更新 API 定义 SOT 中 401 响应,使其反映该响应消息结构;或者(2)配置 Kong 返回与你错误响应对象结构相匹配的输出(含字段 ID、status、code、title 和 detail)。写本文时,这需要编写自定义 Kong 插件。

从外部 SOT API 定义生成 API 服务代码能保证一致性,但前提是 API 网关尽可能少地对请求和响应做转换。假设某些原因(例如支持遗留功能)导致 API 网关必须在转发请求到 API 服务前做转换,比如修改路径或查询字符串、添加或删除头部、修改请求体中的属性。Kong API 网关可以通过 Request Transformer 插件实现这些转换。用于生成 API 参考的 SOT API 定义必须准确反映外部 API 行为,但由于网关的复杂转换逻辑,该定义对生成 API 服务代码帮助有限。在微服务架构中,除了 API 网关外,还有其他组件可能会在请求抵达 API 服务前处理请求。例如,策略引擎组件(如 Open Policy Agent,OPA)可能拦截请求并返回授权错误响应。由于 API 服务之外的组件会在请求流中加入逻辑,建议结合 API 一致性生成方法和 API 一致性测试(下一章讨论),以保证 API 参考的一致性。

6.2.9 定制代码生成

OpenAPI Generator 支持超过 40 种编程语言。在示例中,你使用命令行工具 openapi-generator-cli 运行生成命令,但 OpenAPI Generator 也提供了集成 Maven、Gradle(Java 构建工具)、SBT(Scala)、Cake(C#)、Bazel(多语言)等构建自动化工具的插件。

注意:本节重点讲解了 OpenAPI Generator,除此之外还有其他开源 OpenAPI 代码生成器。例如,NSwag (github.com/RicoSuter/N...) 可以从 OpenAPI 规范生成 C# 控制器存根;OpenAPI Client and Server Code Generator (github.com/deepmap/oap...) 可生成 Go 代码。

如果 OpenAPI Generator 内置的生成器不满足需求,你可以通过三种方式扩展自定义代码生成逻辑。第一种是稍微修改内置生成器使用的模板。比如,你想修改 Spring Boot 生成器的模板,去掉生成的控制器上带的 @Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = ...) 注解。此时,在 chapter6 目录执行如下命令,提取 Spring Boot 生成器模板到 out 目录:

css 复制代码
openapi-generator-cli author template \
  --generator-name spring --library spring-boot \
  --output out

注释

#1 表示自定义模板的选项

#2 拉取模板用于本地定制

#3 输出模板目录

提示 :你也可以根据操作系统使用 chapter6 目录下的辅助脚本 listing-6-9-generate-templates.shlisting-6-9-generate-templates.bat

OpenAPI Generator 使用 Mustache 模板语言(mustache.github.io,由 JMustache 实现 github.com/samskivert/... template-test/out/apiController.mustache 模板,删除其中的 {{>generatedAnnotation}} 行。

然后运行 OpenAPI Generator,指定修改后的模板目录,生成项目,示例如下,项目会生成到 myapp 目录:

css 复制代码
openapi-generator-cli generate \
  --generator-name spring --library spring-boot \
  --template-dir out \                 #1
  --output myapp \                    #2
  --input-spec product-catalog-v1-1.oas.yaml  #3

注释

#1 指定包含修改后模板的目录

#2 生成代码的输出目录

#3 用于生成代码的 API 规范文件

提示 :同样可使用 chapter6 目录下的辅助脚本 listing-6-10-generate-from-custom-template.shlisting-6-10-generate-from-custom-template.bat

打开生成的 myapp/src/main/java/org/openapitools/api/V1ApiController.java 文件,会发现 @Generated 注解已被移除。

除了使用内置模板外,你还可以通过创建用户自定义模板或定义新的生成器来实现更深入的定制。这些高级定制超出本书范围,更多内容请参见:openapi-generator.tech/docs/custom...

6.3 从代码生成 OpenAPI

与从 OpenAPI 定义生成代码相反,你也可以反向操作。为了确保参考文档与代码的一致性,可以先从代码生成 OpenAPI 定义,然后再从生成的定义创建其他产物(例如客户端 SDK 和 API 参考文档)。在这种代码优先(code-first)的方法中,你首先在应用代码中做出需要反映在 API 参考中的任何变更,然后再从应用代码生成 OpenAPI 定义。

6.3.1 使用 springdoc 生成 OpenAPI

考虑下面这个场景:用户反馈你的线上 API 返回了 numberOfReviewsreviewRating 字段,但参考文档中并没有这些字段。(这与之前你看过的生成场景正好相反。)你的 OpenAPI 定义文件中使用了 additionalProperties:false,表示响应中不应该有定义之外的额外属性。所以文档和 API 行为不一致,因为实际的 API 返回了额外字段。团队希望你通过从应用代码生成 API 参考文档来解决这个问题。

在 Spring Boot 中,你可以引入 Java 库,这些库能根据项目的配置、类和注解自动推断并生成 OpenAPI 定义。两个常见的库是 springdoc-openapispringdoc.org)和-321g/) SpringFoxspringfox.github.io/springfox)。... springdoc-openapi

生成好的 product-catalog-service 已经包含了 springdoc-openapi 库作为依赖。默认情况下,该库会将 OpenAPI 定义以 JSON 格式暴露在以下地址:

bash 复制代码
http://server:port/context-path/v3/api-docs

以 YAML 格式暴露在:

bash 复制代码
http://server:port/context-path/v3/api-docs.yaml

(这里的 context-path 是指访问该 web 应用时使用的名称路径。)此外,该库还支持通过 Swagger UI 在浏览器中可视化和交互 API,地址是:

bash 复制代码
http://server:port/context-path/swagger-ui.xhtml

你还可以通过在 Java 类中添加 Swagger 注解来为生成的 API 定义添加额外信息,如图 6.9 所示。

试着在你的示例项目中实践一下。在 chapter6/product-catalog-service 文件夹中,如果应用还没有运行,可以在命令行运行 mvn spring-boot:run 启动应用。然后访问以下 URL 查看输出:

太好了,你已经成功地从代码自动生成了 OpenAPI 定义!

6.3.2 生成的定义需要注意的事项

使用 springdoc-openapi 库,你已经在 Spring Boot API 服务代码中添加了 API 描述信息,并从中生成了 API 定义。其他静态和动态语言也有类似的代码库支持这类操作,比如 Python 应用中的 Django REST Framework(www.django-rest-framework.org/api-guide/s... flask-smorest(flask-smorest.readthedocs.io)。./)

小贴士:可以访问 OpenAPI.tools(openapi.tools)了解更多-8e8i853e2p2ae96e/) OpenAPI 生成库和其他工具。

与从 API 定义生成代码的方法一样,你也需要关注可能来自 API 网关(或堆栈中位于 API 服务之前的其他组件)的 API 响应。如果 API 网关对请求和响应消息进行了大量转换,那么从 API 服务生成的 API 定义就无法准确反映 API 网关对外暴露的 API 行为。

你还需要在代码中添加足够的描述性 API 信息,包括示例值。我发现有些团队容易忽略添加示例和其他 schema 约束(如 maxLength、minLength、字符串模式表达式等)。你可以采用迭代的方式,不断审查生成的 API 定义,确保它包含良好的 schema 描述和对文档阅读者有用的示例。

有些团队不直接从 API 定义生成代码,而是先编写 API 服务代码,再从代码生成 OpenAPI 定义,然后将生成的定义与设计阶段的 SOT 定义进行对比,修正差异。还有一些团队先用代码构建 API 原型以获得更多信息(技术约束、需求等),再从原型生成 API 定义,之后基于生成的定义反复迭代和完善 API 设计。虽然这被称为代码优先(code-first),但也可以看作是在面对大量未知的情况下进行探索性工作,收集支持设计的更多信息。在这种场景下,能够从代码生成定义非常有用,这也模糊了传统上代码优先和设计优先在创建 API 定义时的界限。

总结

API 一致性是指确保发布的 API 参考文档与 API 服务器行为及 API 客户端 SDK 保持一致。

确保 API 一致性的一种方法是从 OpenAPI 定义生成各种工件(如 API 服务代码、API 客户端 SDK 和 API 参考文档)。在这种方法中,API 定义即是关于预期或未来行为的"事实单一来源"(SOT)。

从 OpenAPI 生成工件的优点是速度快(相比编写 schema 测试),并且允许团队基于达成一致的 API 定义 SOT 并行工作。

另一种确保 API 行为与 API 参考文档匹配的方法是从 API 服务代码生成 API 定义,再利用该定义创建参考文档和客户端 SDK。

发布的 API 参考文档必须考虑来自 API 服务之外组件(例如 API 网关)的响应。

OpenAPI Generator 是一个流行的库,用于从 OpenAPI 定义生成服务器代码、客户端 SDK 和参考文档,并且支持多种定制生成代码的选项。

相关推荐
SmalBox29 分钟前
【渲染流水线】[几何阶段]-[顶点着色]以UnityURP为例
架构
VisuperviReborn1 小时前
react native 如何与webview通信
前端·架构·前端框架
uhakadotcom1 小时前
Dask 框架深入浅出教程
面试·架构·github
数据智能老司机2 小时前
自动化 API 交付——API工件的CI/CD(二):构建阶段与API配置部署
架构·api·devops
数据智能老司机3 小时前
自动化 API 交付——API 一致性:模式测试
架构·api·devops
数据智能老司机3 小时前
自动化 API 交付——API 设计评审:检查那些无法自动化的内容
架构·api·devops
我们从未走散3 小时前
面试题-----微服务业务
java·开发语言·微服务·架构
喵叔哟3 小时前
41.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--网关集成Swagger
微服务·架构·.net
Java技术小馆4 小时前
Gemini Storybook AI驱动的交互式故事创作
java·程序员·架构