vlambda博客
学习文章列表

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

PART I

Getting Started with Microservice Development Using Spring Boot

在这一部分中,您将学习如何使用 Spring Boot 的一些最重要的特性来开发微服务。

本部分包括以下章节:

  • 第 1 章微服务简介
  • 第二章Spring Boot简介
  • 第 3 章创建一组协作微服务
  • 第 4 章使用 Docker 部署我们的微服务
  • 第 5 章使用 OpenAPI 添加 API 描述
  • 第 6 章添加持久性
  • 第 7 章开发响应式微服务

Adding an API Description Using OpenAPI

API(例如 RESTful 服务)的价值在很大程度上取决于使用它的难易程度。良好且易于访问的文档是 API 是否有用的重要部分。在本章中,我们将学习如何使用 OpenAPI 规范 来记录我们可以从微服务环境外部访问的 API。

正如我们在第 2 章中提到的,Spring Boot 简介,OpenAPI 规范,以前称为 Swagger 规范,是其中之一记录 RESTful 服务时最常用的规范。许多领先的 API 网关都原生支持 OpenAPI 规范。我们将学习如何使用开源项目 springdoc-openapi 来生成此类文档。我们还将学习如何嵌入 API 文档查看器 Swagger UI 查看器,它既可用于检查 API 文档,也可用于向 API 发出请求。

在本章结束时,我们将为 product-composite-service 微服务公开的外部 API 提供基于 OpenAPI 的 API 文档。该微服务还将公开一个 Swagger UI 查看器,我们可以使用它来可视化和测试 API。

本章将涵盖以下主题:

  • springdoc-openapi使用介绍
  • 将 springdoc-openapi 添加到源代码中
  • 构建和启动微服务环境
  • 试用 OpenAPI 文档

技术要求

有关如何安装本书中使用的工具以及如何访问本书源代码的说明,请参阅:

  • 第 21 章适用于 macOS
  • 第 22 章 适用于 Windows

本章代码示例均来自$BOOK_HOME/Chapter05中的源码。

如果您想查看本章中对源代码应用的更改,即查看使用 springdoc-openapi 创建基于 OpenAPI 的 API 文档所花费的内容,您可以将其与 第 4 章使用 Docker 部署我们的微服务。可以使用自己喜欢的diff工具,对比两个文件夹,即$BOOK_HOME/Chapter04$BOOK_HOME/Chapter05

springdoc-openapi使用介绍

springdoc-openapi 使得将 API 的文档与实现 API 的源代码保持在一起成为可能。 springdoc-openapi 可以通过检查代码中的 Java 注释在运行时动态创建 API 文档。对我来说,这是一个重要的特征。如果 API 文档与 Java 源代码在一个单独的生命周期中维护,它们将随着时间的推移而相互分歧。在许多情况下,这会比预期的更快发生(根据我自己的经验)。

springdoc-openapi创建之前,另一个开源项目SpringFox(http://springfox.github.io/springfox/),提供了类似的功能。近年来,SpringFox 项目没有得到积极维护,作为对此的反应,创建了 springdoc-openapi 项目。可以在 https://springdoc 找到 SpringFox 用户的迁移指南。 org/#migrating-from-springfox

与往常一样,将组件的接口与其实现分开是很重要的。在记录 RESTful API 方面,我们应该将 API 文档添加到描述 API 的 Java 接口中,而不是添加到实现 API 的 Java 类中。为了简化更新 API 文档的文本部分(例如,较长的描述),我们可以将描述放在属性文件中,而不是直接放在 Java 代码中。

除了动态创建 API 规范之外,springdoc-openapi 还附带了一个名为 Swagger UI 的嵌入式 API 查看器。我们将配置 product-composite-service 服务 为其 API 公开 Swagger UI。

尽管 Swagger UI 在开发和测试阶段非常有用,但出于安全原因,它通常不会在生产环境中公开用于 API。在许多情况下,API 使用 API 网关公开。如今,大多数 API Gateway 产品都支持基于 OpenAPI 文档公开 API 文档。因此,不是暴露 Swagger UI,而是将由 springdoc-openapi 生成的 API 的 OpenAPI 文档导出到可以以安全方式发布 API 文档的 API 网关。

如果 API 预计将由第三方开发人员使用,则可以设置一个开发人员门户,其中包含文档和工具,例如用于自我注册。 Swagger UI 可用于开发人员门户,以允许开发人员通过阅读文档了解 API 并使用测试实例试用 API。

第 11 章保护对 API 的访问,我们将学习如何使用 OAuth 2.1 锁定对 API 的访问。我们还将学习如何配置 Swagger UI 组件以获取 OAuth 2.1 访问令牌,并在用户通过 Swagger UI 试用 API 时使用它们。

以下屏幕截图是 Swagger UI 的示例:

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.1:Swagger UI 示例

目前,截图中一些不重要的部分已被上图中的“…”替换。我们将在本章稍后部分回到这些细节。

要启用 springdoc-openapi 来创建 API 文档,我们需要在构建文件中添加一些依赖项,并向定义 RESTful 服务的 Java 接口添加一些注释。如上所述,我们还将 API 文档的描述性部分放在属性文件中。

如果部分文档已放置在属性文件中以简化 API 文档的更新,那么在与源代码相同的生命周期和相同的版本控制下处理属性文件是很重要的。否则,它们就有可能开始偏离实施,即过时。

随着 springdoc-openapi 的引入,让我们看看如何通过对源代码进行必要的更改来开始使用它。

将 springdoc-openapi 添加到源代码中

添加 关于由 product-composite-service 微服务,我们需要在两个项目中更改源码:

  • product-composite-service:这里我们在Java应用类中设置springdoc-openapi的配置,ProductCompositeServiceApplication,并描述与 API 有关的一般信息。
  • api:这里我们将给Java接口添加注解,ProductCompositeService ,描述每个 RESTful 服务及其操作。在这个阶段,我们只有一个RESTful服务,一个操作,接受HTTP GET请求到/product-composite/{productId},即用于请求有关特定产品的复合信息。

用于描述 API 操作的实际文本将放置在 application.yml 中"Code-In-Text--PACKT-">product-composite-service 项目。

在开始使用 springdoc-openapi 之前,我们需要将其作为依赖项添加到 Gradle 构建文件中。那么,让我们从它开始吧!

将依赖项添加到 Gradle 构建文件

springdoc-openapi 项目分为多个模块。对于 api 项目,我们只需要包含我们将用于记录的注释的模块API。我们可以将它添加到api项目的构建文件中,build.gradle ,如下:

implementation 'org.springdoc:springdoc-openapi-common:1.5.9'

product-composite-service 项目需要一个功能更全面的模块,其中包含 Swagger UI 查看器和对 Spring WebFlux 的支持。我们可以在构建文件中添加依赖,build.gradle,如下:

implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.9'

这就是需要添加的所有依赖项;现在进行配置。

将 OpenAPI 配置和通用 API 文档添加到 ProductCompositeService

要在 product-composite-service 微服务中启用 springdoc-openapi,我们必须 添加一些配置。为了保持源代码紧凑,我们将它直接添加到应用程序类中,ProductCompositeServiceApplication.java

如果您愿意,可以将 springdoc-openapi 的配置放在单独的 Spring 配置类中。

首先,我们需要定义一个返回OpenAPI bean的Spring Bean。源代码如下所示:

@Bean
public OpenAPI getOpenApiDocumentation() {
  return new OpenAPI()
    .info(new Info().title(apiTitle)
      .description(apiDescription)
      .version(apiVersion)
      .contact(new Contact()
        .name(apiContactName)
        .url(apiContactUrl)
        .email(apiContactEmail))
      .termsOfService(apiTermsOfService)
      .license(new License()
        .name(apiLicense)
        .url(apiLicenseUrl)))
    .externalDocs(new ExternalDocumentation()
      .description(apiExternalDocDesc)
      .url(apiExternalDocUrl));
}

从上面的代码中,我们可以看到配置中包含了关于 API 的一般描述信息,例如:

  • API 的名称、描述、版本和联系信息
  • 使用条款和许可信息
  • 指向有关 API 的外部信息的链接(如果有)

api* 变量用于配置 OpenAPI 使用 Spring @Value 注释从属性文件初始化 bean。这些如下:

  @Value("${api.common.version}")         String apiVersion;
  @Value("${api.common.title}")           String apiTitle;
  @Value("${api.common.description}")     String apiDescription;
  @Value("${api.common.termsOfService}")  String apiTermsOfService;
  @Value("${api.common.license}")         String apiLicense;
  @Value("${api.common.licenseUrl}")      String apiLicenseUrl;
  @Value("${api.common.externalDocDesc}") String apiExternalDocDesc;
  @Value("${api.common.externalDocUrl}")  String apiExternalDocUrl;
  @Value("${api.common.contact.name}")    String apiContactName;
  @Value("${api.common.contact.url}")     String apiContactUrl;
  @Value("${api.common.contact.email}")   String apiContactEmail;

实际值在属性文件中设置, application.yml,如下:

api:
  common:
    version: 1.0.0
    title: Sample API
    description: Description of the API...
    termsOfService: MY TERMS OF SERVICE
    license: MY LICENSE
    licenseUrl: MY LICENSE URL
    externalDocDesc: MY WIKI PAGE
    externalDocUrl: MY WIKI URL
    contact:
      name: NAME OF CONTACT
      url: URL TO CONTACT
      email: [email protected]

属性文件还包含 springdoc-openapi 的一些配置:

springdoc:
  swagger-ui.path: /openapi/swagger-ui.html
  api-docs.path: /openapi/v3/api-docs
  packagesToScan: se.magnus.microservices.composite.product
  pathsToMatch: /**

配置参数有以下用途:

  • springdoc.swagger-ui.pathspringdoc.api-docs。 path 用于指定嵌入式 Swagger UI 查看器使用的 URL 在路径 /openapi 下可用。在本书的后面部分,当我们在前面添加不同类型的边缘服务器并解决安全挑战时,这将简化所使用的边缘服务器的配置。有关详细信息,请参阅以下章节:
    • 第 10 章使用 Spring Cloud Gateway 将微服务隐藏在边缘服务器之后李>
    • 第 11 章保护对 API 的访问
    • 第 17 章实现 Kubernetes 功能以简化系统架构替换 Spring Cloud Gateway 部分
    • 第 18 章使用服务网格提高可观察性和管理用 Istio Ingress Gateway 替换 Kubernetes Ingress 控制器 部分
  • springdoc.packagesToScanspringdoc.pathsToMatch 控制在哪里代码库 springdoc-openapi 将搜索注释。我们可以给 springdoc-openapi 的范围越窄,扫描的执行速度就越快。

详情请参考应用类ProductCompositeServiceApplication.javaproduct-composite-service 项目中的 Code-In-Text--PACKT-">application.yml 属性文件。我们现在可以继续查看如何将特定于 API 的 文档添加到 Java 接口 ProductCompositeService.java api 项目中的代码>。

将特定于 API 的文档添加到 ProductCompositeService 接口

为了记录实际的 API 及其 RESTful 操作,我们将添加一个 @Tag 注释到apiProductCompositeService.java中的Java接口声明> 项目。对于 API 中的每个 RESTful 操作,我们将添加一个 @Operation 注释,以及 @ApiResponse 注释在相应的 Java 方法上,用于描述操作及其预期响应。我们将描述成功和错误响应。

除了在运行时读取这些注释之外,springdoc-openapi 还将检查 Spring 注释,例如 @GetMapping 注释,以了解哪些输入参数操作需要什么,如果产生成功的响应,响应将是什么样子。为了了解潜在错误响应的结构,springdoc-openapi 将查找 @RestControllerAdvice@ExceptionHandler 注释。在第 3 章创建一组协作微服务,我们添加了一个实用程序类,GlobalControllerExceptionHandler.java,在 util项目。

此类使用 @RestControllerAdvice 进行注释。有关详细信息,请参阅全局 REST 控制器异常处理程序部分。异常处理程序负责处理 404 (NOT_FOUND)和 422 (UNPROCESSABLE_ENTITY) 错误。为了让 springdoc-openapi 也正确记录 400 (BAD_REQUEST ) Spring WebFlux 在发现请求中不正确的输入参数时生成的错误,我们还为 @ExceptionHandler 400 (BAD_REQUEST) 错误-Text--PACKT-">GlobalControllerExceptionHandler.java

资源级别的 API 文档,对应于 Java 接口声明,如下所示:

@Tag(name = "ProductComposite", description =
  "REST API for composite product information.")
public interface ProductCompositeService {

对于API操作,我们提取了 @Operation@ApiResponse 属性文件的注释。注释包含属性占位符,例如 ${name-of-the-property},springdoc-openapi 将使用它来查找实际运行时属性文件中的文本。 API操作记录如下:

@Operation(
  summary =
    "${api.product-composite.get-composite-product.description}",
  description =
    "${api.product-composite.get-composite-product.notes}")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description =
    "${api.responseCodes.ok.description}"),
  @ApiResponse(responseCode = "400", description =
    "${api.responseCodes.badRequest.description}"),
  @ApiResponse(responseCode = "404", description =
    "${api.responseCodes.notFound.description}"),
  @ApiResponse(responseCode = "422", description =
    "${api.responseCodes.unprocessableEntity.description}")
})
@GetMapping(
  value = "/product-composite/{productId}",
  produces = "application/json")
ProductAggregate getProduct(@PathVariable int productId);

springdoc-openapi 将能够从前面的源代码中提取有关操作的以下信息:

  • 该操作接受对 URL /product-composite/{productid} 的 HTTP GET 请求,其中 URL 的最后一部分 {productid},用作请求的输入参数。
  • 一个成功的响应会产生一个对应于 Java 类的 JSON 结构,ProductAggregate
  • 如果发生错误,HTTP 错误代码 400, 404422 将与正文中的错误信息一起返回,如 @ExceptionHandler 在 Java 类 GlobalControllerExceptionHandler.javautil 项目,如上所述。

对于 @Operation@ApiResponse注解,我们可以直接使用属性占位符,不用Spring @Value注解。实际值在属性文件 application.yml 中设置,如下所示:

api:
  responseCodes:
    ok.description: OK
    badRequest.description: Bad Request, invalid format of the request. See response message for more information
    notFound.description: Not found, the specified id does not exist
    unprocessableEntity.description: Unprocessable entity, input parameters caused the processing to fail. See response message for more information

  product-composite:
    get-composite-product:
      description: Returns a composite view of the specified product id
      notes: |
        # Normal response
        If the requested product id is found the method will return information regarding:
        1. Base product information
        1. Reviews
        1. Recommendations
        1. Service Addresses\n(technical information regarding the addresses of the microservices that created the response)

        # Expected partial and error responses
        In the following cases, only a partial response be created (used to simplify testing of error conditions)

        ## Product id 113
        200 - Ok, but no recommendations will be returned

        ## Product id 213
        200 - Ok, but no reviews will be returned

        ## Non-numerical product id
        400 - A **Bad Request** error will be returned

        ## Product id 13
        404 - A **Not Found** error will be returned

        ## Negative product ids
        422 - An **Unprocessable Entity** error will be returned

前面的配置中,我们可以了解到:

  • ${api.responseCodes.ok.description} 等属性占位符将被翻译为 确定。请注意基于 YAML 的属性文件的层次结构:
    api: 响应代码: ok.description: 好的 
  • 多行值以 | 开头,类似于属性 api.get-composite-product.description.notes。另请注意,springdoc-openapi 支持使用 Markdown 语法提供多行描述。

详情见ProductCompositeService.java Code-In-Text--PACKT-">api 项目和属性文件,application.yml,在 product-composite-service 项目。

如果您想了解有关如何构建 YAML 文件的更多信息,请查看规范:https://yaml.org/spec/1.2/spec.html

构建和启动微服务环境

在我们可以试用 OpenAPI 文档之前,我们需要构建并启动微服务环境!

这个可以用以下命令完成:

cd $BOOK_HOME/Chapter05
./gradlew build && docker-compose build && docker-compose up -d

您可能会遇到关于端口 8080 已分配的错误消息。这将如下所示:

ERROR: for product-composite Cannot start service product-composite: driver failed programming external connectivity on endpoint chapter05_product-composite_1 (0138d46f2a3055ed1b90b3b3daca92330919a1e7fec20351728633222db5e737): Bind for 0.0.0.0:8080 failed: port is already allocated

如果是这种情况,您可能忘记了从上一章中删除微服务环境。要找出正在执行的容器的名称,请运行以下命令:

 docker ps --format {{.Names}}

上一章中的微服务环境仍在运行时的示例响应如下:

chapter05_review_1
chapter05_product_1
chapter05_recommendation_1
chapter04_review_1
chapter04_product-composite_1
chapter04_product_1
chapter04_recommendation_1

如果您在命令的输出中找到其他章节的容器,例如,来自 Chapter 4使用 Docker 部署我们的微服务 ,和前面的例子一样,你需要跳转到该章节的源代码文件夹并关闭它的容器:

cd ../Chapter04
docker-compose down

现在,您可以调出本章缺少的容器:

cd ../Chapter05
docker-compose up -d

请注意,该命令仅启动了缺少的容器 product-composite,因为其他容器已经成功启动:

Starting chapter05_product-composite_1 ... done

要等待微服务环境启动并验证它是否正常工作,您可以运行以下命令:

./test-em-all.bash

随着微服务的成功启动,我们可以继续尝试使用 product-composite 微服务使用其嵌入式 Swagger UI 公开的 OpenAPI 文档观众。

试用 OpenAPI 文档

要浏览 OpenAPI 文档,我们将使用嵌入式 Swagger UI 查看器。如果我们打开http://localhost:8080/openapi/swagger-ui.html 在网络浏览器中的 URL,我们将看到一个类似于以下屏幕截图的网页:

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.2:带有 Swagger UI 查看器的 OpenAPI 文档

在这里,我们可以确定以下内容:

  1. The general information we specified in the springdoc-openapi OpenAPI bean and a link to the actual OpenAPI document, /openapi/v3/api-docs, pointing to http://localhost:8080/openapi/v3/api-docs.

    请注意,这是可以导出到 API 网关的 OpenAPI 文档的链接,如上面的 使用 springdoc-openapi 的介绍 部分所述。

  2. API 资源列表;在我们的例子中,ProductComposite API。
  3. 在页面底部,有一个部分我们可以检查 API 中使用的模式。

继续检查 API 文档,如下所示:

  1. 单击 ProductComposite API 资源以展开它。您将获得资源上可用的操作列表。您只会看到一个操作,/product-composite/{productId}。
  2. 单击它以展开它。您将看到我们在 ProductCompositeService Java 接口中指定的操作的文档: <图类=“媒体对象”> 读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.3:ProductComposite API 文档

在这里,我们可以看到以下内容:

  • 操作的单行描述。
  • 包含有关操作的详细信息的部分,包括它支持的输入参数。请注意 @ApiOperation< 中 notes 字段中的 Markdown 语法如何/code> 注释已经很好地呈现了!

如果您向下滚动网页,您还将找到有关预期响应及其结构的文档,均适用于普通 200 (OK ) 反应...

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.4:200 响应的文档

…以及我们之前定义的各种4xx错误响应,如下图所示:

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.5:4xx 响应的文档

对于每个记录的潜在错误响应,我们可以了解其含义和响应正文的结构。

如果我们向上滚动到参数说明,我们会找到 Try it out 按钮。如果我们点击按钮,我们可以通过点击Execute按钮填写实际参数值并向API发送请求。例如,如果我们输入 productId 123,我们会得到如下响应:

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.6:发送现有产品请求后的响应

我们将得到一个预期的200(OK)作为响应代码和响应正文中我们已经熟悉的JSON结构和!

如果我们输入了错误的输入,例如-1,我们会得到一个正确的错误码作为响应码,422,以及响应正文中相应的基于 JSON 的错误描述:

读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第5章使用OpenAPI添加接口描述

图 5.7:发送带有无效输入的请求后的响应

请注意,响应正文中的 message 字段清楚地指出了问题:"Invalid productid: -1"。

如果您希望 尝试在不使用 Swagger UI 查看器的情况下调用 API,可以复制相应的 curl< /code> 命令来自 Responses 部分并在终端窗口中运行它,如前面的屏幕截图所示:

curl -X GET "http://localhost:8080/product-composite/123" -H "accept: application/json"

太好了,不是吗?

概括

良好的 API 文档对于其被接受至关重要,而 OpenAPI 是记录 RESTful 服务时最常用的规范之一。 springdoc-openapi 是一个开源项目,通过检查 Spring WebFlux 和 Swagger 注释,可以在运行时动态创建基于 OpenAPI 的 API 文档。可以从 Java 源代码中的注释中提取 API 的文本描述,并将其放置在属性文件中以便于编辑。 springdoc-openapi 可以配置为将嵌入式 Swagger UI 查看器引入微服务,这使得阅读微服务已公开的 API 并从查看器中试用它们变得非常容易。

现在,如何通过添加持久性(即将其数据保存在数据库中的能力)来为我们的微服务注入活力呢?为此,我们还需要添加更多 API,以便我们可以创建和删除由微服务处理的信息。前往下一章以了解更多信息!

问题

  1. springdoc-openapi 如何帮助我们为 RESTful 服务创建 API 文档?
  2. springdoc-openapi 支持哪些 API 文档规范?
  3. springdoc-openapi OpenAPI bean 的用途是什么?
  4. 命名一些 springdoc-openapi 在运行时读取的注释以动态创建 API 文档。
  5. YAML 文件中的代码“: |”是什么意思?
  6. 如何重复调用使用嵌入式 Swagger UI 查看器执行的 API 而无需再次使用查看器?