1. 概述
記錄API是構建應用程序的必要組成部分。它是一份我們與客户共享的合同,同時詳細記錄了我們的集成點的工作方式。文檔應易於訪問、理解和實施。
在本教程中,我們將探討 Springwolf 用於記錄事件驅動的 Spring Boot 服務。Springwolf 實現了 AsyncAPI 規範,它是 OpenAPI 規範的適應版本,用於事件驅動的 API。
Springwolf 協議無關性,涵蓋 Spring Kafka、RabbitMQ、JMS、AWS SNS & SQS、STOMP (WebSocket) 和 CloudStream 實現。
使用 Spring Kafka 作為我們的事件驅動系統,Springwolf 自動為我們生成 AsyncAPI 文檔。 某些消費者會自動檢測。 其他信息由我們提供。
2. 設置 Springwolf
要開始使用 Springwolf,我們需要添加依賴並進行配置。
2.1. 添加依賴
假設我們已經有一個運行的 Spring 應用,並且使用了 Spring Kafka,則需要在 Maven 項目的 pom.xml 文件中將 springwolf-kafka 添加為依賴:
<dependency>
<groupId>io.github.springwolf</groupId>
<artifactId>springwolf-kafka</artifactId>
<version>1.7.0</version>
</dependency>
最新版本可以在 Maven Central 找到,關於除了 Spring Kafka 之外的其他綁定支持,請參考項目網站。
2.2. application.properties 配置
在最基本的形式下,我們向我們的 application.properties 添加以下 Springwolf 配置:
# Springwolf Configuration
springwolf.docket.base-package=com.baeldung.boot.documentation.springwolf.adapter
springwolf.docket.info.title=${spring.application.name}
springwolf.docket.info.version=1.0.0
springwolf.docket.info.description=Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf
# Springwolf Kafka Configuration
springwolf.docket.servers.kafka-server.protocol=kafka
springwolf.docket.servers.kafka-server.host=${spring.kafka.bootstrap-servers}第一個塊設置了通用的 Springwolf 配置。 這包括 base-package,Springwolf 使用它來自動檢測監聽器。 此外,我們還設置了在 docket 配置鍵下的通用信息,該信息出現在 AsyncAPI 文檔中。
然後,我們設置了 springwolf-kafka 相關的配置。 同樣,這出現在 AsyncAPI 文檔中。
2.3. 驗證
現在,我們準備好運行我們的應用程序。應用程序啓動後,AsyncAPI 文檔默認在 /springwolf/docs 路徑下可用:
http://localhost:8080/springwolf/docs3. AsyncAPI 文檔
AsyncAPI 文檔遵循與OpenAPI文檔相似的結構 (參考鏈接)。首先,我們只關注關鍵部分。 規範(規範)可在AsyncAPI網站上找到。為了簡便起見,我們只關注一部分屬性。
在後續子章節中,我們將逐步分析AsyncAPI文檔的JSON格式。我們從以下結構開始:
{
"asyncapi": "3.0.0",
"info": { ... },
"servers": { ... },
"channels": { ... },
"components": {
"messages": { ... },
"schemas": { ... }
},
"operations": { ... }
}3.1. info 部分
info 部分包含有關應用程序本身的資料。 這包括至少以下字段:標題、應用程序版本 和 描述。
根據我們添加到配置中的信息,創建了以下結構:
"info": {
"title": "Baeldung Tutorial Springwolf Application",
"version": "1.0.0",
"description": "Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf"
}3.2 服務器部分
同樣,服務器部分包含有關我們的 Kafka 代理的信息,並基於上述的 application.properties 配置:
"servers": {
"kafka-server": {
"host": "127.0.0.1:9092",
"protocol": "kafka"
}
}3.3. 通道部分
本部分目前為空,因為我們尚未配置應用程序中的任何消費者或生產者。在稍後部分配置它們之後,我們將看到以下結構:
"channels": {
"incoming-topic": {
"address": "incoming-topic",
"messages": {
"IncomingPayloadDto": {
"$ref": "#/components/messages/IncomingPayloadDto"
}
}
}
}通用術語 channel 指的是 Kafka 術語中的 topic。
為了避免在多個渠道和操作中重複相同的 payload 信息,AsyncAPI 使用 $ref 符號來指示 components 部分的引用。
3.4. 組件 章節
再次説明,此時該章節為空,但將具有以下結構:
"components": {
"messages": {
"IncomingPayloadDto": {
...
"payload": {
"schema": {
"$ref": "#/components/schemas/IncomingPayloadDto"
}
},
"headers": ...
}
},
"schemas": {
"IncomingPayloadDto": {
"type": "object",
"properties": {
...
"someString": {
"type": "string"
}
},
"example": {
"someEnum": "FOO2",
"someLong": 1,
"someString": "string"
}
}
}
}組件部分包含所有 $ref 引用詳情,包括 #/components/messages/IncomingPayloadDto 和 #/components/schemas/IncomingPayloadDto。
除了消息之外,payload 模式也位於 components 部分。模式字段包括數據的 type、payload 的 properties 以及 payload 的可選 (JSON) example。
3.5. 操作 模塊
一個 操作 對象與一個 通道 (3.3) 以及一個 消息 (3.4) 關聯,該消息可以被髮送或接收。它可能包含額外的信息,例如 描述。
"operations": {
"incoming-topic_receive_consume": {
"action": "receive",
"channel": {
"$ref": "#/channels/incoming-topic"
},
"title": "incoming-topic_receive",
"description": "More details for the incoming topic",
"messages": [
{
"$ref": "#/channels/incoming-topic/messages/IncomingPayloadDto"
}
]
}
}類似於 通道,操作對象是一個鍵值對映射,其中鍵是操作 ID,值是 操作對象。在此,incoming-topic_receive_consume 是 Springwolf 生成的操作 ID。
4. 文檔消費者
Springwolf 會自動檢測所有@KafkaListener註解,如下所示。 此外,我們使用@AsyncListener註解手動提供更多詳細信息。
4.1. 自動檢測 @KafkaListener 標註
通過在方法上使用 Spring-Kafka 的 @KafkaListener 標註,Springwolf 會自動在 base-package 中找到消費者:
@KafkaListener(topics = TOPIC_NAME)
public void consume(IncomingPayloadDto payload) {
// ...
}現在,AsyncAPI 文檔確實包含名為 TOPIC_NAME 的通道,該通道具有 publish 操作和 IncomingPayloadDto 模式,正如我們之前所見。
4.2. 手動記錄消費者 via <em @AsyncListener@ 註解
使用自動檢測和 結合,可能導致重複記錄。為了能夠手動添加更多信息,我們完全禁用 的自動檢測,並將其添加到 文件中:
springwolf.plugin.kafka.scanner.kafka-listener.enabled=false接下來,我們添加 Springwolf @AsyncListener 註解到同一方法中,併為 AsyncAPI 文檔提供額外信息:
@KafkaListener(topics = TOPIC_NAME)
@AsyncListener(
operation = @AsyncOperation(
channelName = TOPIC_NAME,
description = "More details for the incoming topic"
)
)
@KafkaAsyncOperationBinding
public void consume(IncomingPayloadDto payload) {
// ...
}此外,我們添加了 @KafkaAsyncOperationBinding 註解,以將通用的 @AsyncOperation 註解與 Kafka 代理連接,在 servers 部分進行設置。 通過此註解,還設置了 Kafka 協議特定的信息。
更改後,AsyncAPI 文檔包含更新後的文檔。
5. 記錄生產者
生產者手動記錄使用 Springwolf 的 @AsyncPublisher 註解。
5.1. 手動記錄生產者,通過 <em @AsyncPublisher/> 註解
類似於 <em @AsyncListener/> 註解,我們添加 <em @AsyncPublisher/> 註解,並添加 <em @KafkaAsyncOperationBinding/> 註解:
@AsyncPublisher(
operation = @AsyncOperation(
channelName = TOPIC_NAME,
description = "More details for the outgoing topic"
)
)
@KafkaAsyncOperationBinding
public void publish(OutgoingPayloadDto payload) {
kafkaTemplate.send(TOPIC_NAME, payload);
}根據以上信息,Springwolf 為 TOPIC_NAME 頻道添加了 subscribe 操作到 channels 部分,利用上述信息。 payload type 從方法簽名中提取,類似於 @AsyncListener 的提取方式。
6. 提升文檔質量
AsyncAPI 規範涵蓋了我們之前所討論的更多功能。接下來,我們對 Spring Kafka 默認的 __TypeId__ 頭部進行文檔記錄,並改進了有效負載的文檔。
6.1. 添加 Kafka 頭部
當運行原生 Spring Kafka 應用程序時,Spring Kafka 會自動添加 TypeId 頭部,以協助消費者的消息負載解序列化。
我們通過在 @AsyncOperation 字段上設置 headers 字段,將 TypeId 頭部添加到文檔中,該字段位於 @AsyncListener (或 @AsyncPublisher) 註解中。
@AsyncListener(
operation = @AsyncOperation(
...,
headers = @AsyncOperation.Headers(
schemaName = "SpringKafkaDefaultHeadersIncomingPayloadDto",
values = {
// this header is generated by Spring by default
@AsyncOperation.Headers.Header(
name = DEFAULT_CLASSID_FIELD_NAME,
description = "Spring Type Id Header",
value = "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
),
}
)
)
)現在,AsyncAPI 文檔包含一個新的字段 headers 作為 message 對象的一部分。
6.2. 添加 Payload 詳情
我們使用 Swagger@Schema 標註,以提供關於 Payload 的額外信息。在下面的代碼片段中,我們設置了 description、一個 example 值,以及該字段是否是 required:
@Schema(description = "Outgoing payload model")
public class OutgoingPayloadDto {
@Schema(description = "Foo field", example = "bar", requiredMode = NOT_REQUIRED)
private String foo;
@Schema(description = "IncomingPayload field", requiredMode = REQUIRED)
private IncomingPayloadDto incomingWrapped;
}基於此,我們看到 AsyncAPI 文檔中增強的 OutgoingPayloadDto 模式:
"OutgoingPayloadDto": {
"type": "object",
"description": "Outgoing payload model",
"properties": {
"incomingWrapped": {
"$ref": "#/components/schemas/IncomingPayloadDto"
},
"foo": {
"type": "string",
"description": "Foo field",
"example": "bar"
}
},
"required": [
"incomingWrapped"
],
"example": {
"incomingWrapped": {
"someEnum": "FOO2",
"someLong": 5,
"someString": "some string value"
},
"foo": "bar"
}
}我們的應用程序的完整 AsyncAPI 文檔可在鏈接的示例項目中使用。
7. 驗證 API 契約變更
當 AsyncAPI 文檔被提交到代碼倉庫時,我們可以將其與當前代碼生成的 AsyncAPI 文檔進行比較。這使得我們能夠驗證 (a) 添加或遷移的 事件類 與我們預期的 示例 相匹配; (b) 無害的重構確實保持了 API 契約不變。
一個簡單的測試就足夠了:
@Test
public void asyncApiArtifactTest() {
InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).trim();
String actual = restTemplate.getForObject("/springwolf/docs", String.class);
JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
}8. 使用 Springwolf UI
Springwolf 擁有自己的 UI,儘管任何符合 AsyncAPI 規範的文檔渲染器都可以使用。
8.1. 添加 springwolf-ui 依賴
要使用 springwolf-ui,我們需要將 依賴 添加到我們的 pom.xml 文件中,然後重新構建並重啓應用程序:
<dependency>
<groupId>io.github.springwolf</groupId>
<artifactId>springwolf-ui</artifactId
<version>1.7.0</version>
</dependency>8.2. 查看 AsyncAPI 文檔
現在,通過訪問 http://localhost:8080/springwolf/asyncapi-ui.html,在瀏覽器中打開文檔。
該網頁的結構與 AsyncAPI 文檔類似,並顯示關於 應用程序、服務器、通道和模式的信息:
8.3. 發佈消息
Springwolf 允許我們直接從瀏覽器發佈消息。點擊 發佈 按鈕會將消息直接發送到 Kafka。 消息頭和消息內容可以根據需要進行調整:
由於安全考慮,此功能默認已禁用。要啓用發佈功能,請將以下行添加到我們的 application.properties 文件中:
springwolf.plugin.kafka.publishing.enabled=true9. 結論
在本文中,我們已在現有的 Spring Boot Kafka 應用中設置了 Springwolf。
利用消費者自動檢測功能,自動生成符合 AsyncAPI 文檔。我們進一步通過手動配置來增強文檔。
除了通過提供的 REST 端點下載 AsyncAPI 文檔外,我們還使用 springwolf-ui 在瀏覽器中查看文檔。