みらい翻訳 Advent Calendar 2021の記事です。
はじめに
みなさんご存知だと思いますが、OpenAPIはREST APIを記述するデファクトな仕様記述言語と言っていいと考えています。では、AsyncAPIとは何でしょうか?
結論から言うと、Event駆動アーキテクチャのAPIのインターフェイスを定義するためのものです。Eventは通常その処理結果を待つことなく任意のタイミングで発生しうるので、当然Event駆動アーキテクチャでは、Async(非同期)であることが要求されます。(AsyncAPIは検索しづらい名前ではあるけど)
AsyncAPIはこのようにEvent駆動アーキテクチャで非同期APIを定義するためのOpenAPIのスキーマに対応した仕様を持ちます。仕様面だけでなく、OpenAPIのエコシステムが提供するのと同じように仕様のドキュメント化、コード生成、コードからの仕様化までも提供しています。
マイクロサービスアーキテクチャとEvent駆動アーキテクチャ
さて、何故私がAsyncAPIを取り上げるかですが、マイクロサービスアーキテクチャに関連します。もちろんシンプルに非同期APIのよい仕様の記述の仕方を模索していたのもありますが、なによりマイクロサービスアーキテクチャの文脈では、開発チーム体制としても各マイクロサービス間はAPI仕様で会話を成り立たせることで、開発の並行度を上げスケールさせることもメリットになると考えています。そのためには、統一されたAPI仕様の書き方とというものが必要になってきます。
まずは、マイクロサービスアーキテクチャとEvent駆動アーキテクチャの設定について見てみます。
ファイルストレージサービスを考えます。とある会社組織に属するAliceは、社外のBobとファイル共有したいと考えました。その組織ではその時点ではファイル共有が認められていたとします。
動作としては、クライアントのためのBFF層が、組織設定を管理するマイクロサービスとユーザのファイルを管理するマイクロサービスを使ってよしなにAliceの要求に応える振る舞いをしました。ところがある日、会社のルールが変わって、このファイルストレージサービスでは社外の人とファイル共有が禁止されました。当然、管理者のcharlieは過去に共有されたファイルも共有禁止にすることを望みます。システムで組織の設定を変えるだけで管理者の仕事としては終わりにしたいと望むでしょう。
あなたらなら、どう設計するでしょうか?もちろんBFF層で設定変更のシーケンス内でなんとか仕様とするでしょうか?
対象ファイルがいくつあるか分からないので、禁止にするシーケンス内で処理を完了させようとするのはよくない戦略だと考えると思います。レスポンスを管理者に返した後、BFFが頑張ってもらうというのも考えられますが、BFFが責務を持ちすぎことになるので好ましくはないですね。
このようなケースは組織設定で起こったファイル共有可否設定の変更というEventをそのことに関心があるユーザーファイル管理が 検知できる ようにするという考え方で整理するのがよいです。つまりEvent駆動ですね。
注意: 検知する という表現を使っているのは理由があります。組織設定がユーザーファイル管理に伝えるという表現を使うと組織設定がユーザーファイル管理を知っていると理解される可能性があります。このような依存関係はよくありません。Event発生源がそのEventに関心がある先を知る必要があるということは、利用先が増える度にEvent発生源に何らかの影響(例えば、実装)が必要になる可能性を示唆しています。
Event発生源、関心がある箇所、その間をBrokerと表現をしていますが、例えば、それぞれPublisher, Subscriber, メッセージキューなどとするとよりイメージしやすいかと思います。
このとき、EventはEvent発生源からどのように情報が流されるか、あるEventに関心がある箇所では実際どの情報に関心があるか、これらを表現するためのAPI仕様が AsyncAPIになります。
OpenAPIを利用して、API利用者への仕様を提示することの有用性はすでに理解されている人も多いと思います。マイクロサービスアーキテクチャを利用すると、Event駆動が必要になり、それらがマイクロサービス間でAPI仕様として定義する場面は多くなると思います。そのためのスキーマ言語としてOpenAPIと同様に有用なのが、AsyncAPIであると考えています。
AsyncAPIの紹介
さて、ここからやっとAsyncAPIの説明です。AsyncAPIは、はじめに 言及したようにOpenAPIのスキーマに対応していいます。(2021/12/21 現在2.2.0)
本家の図を引用すると以下の様な対応になっています。
最初に注目すべきは、REST APIでPATHによって機能が分かれているということに対して、AsyncAPIではChannelとして関心のあるEventを分類すると考えるのがよいでしょう。Channelをトピックとした方が理解しやすい人もいるかもしれません。
それともう一つ、 Servers Object
こちらは、OpenAPIではHTTP前提でしたが、AsyncAPIでは Channelの実装となる protocol
の定義がなされます。 protocol
は、 http
, amqp
, kafka
などが定義されています。さらに仕様上 protocol
は自ら定期もできるようです。(定義する protocol
に必要な情報の定義を各種 Bindings Object
として定義する必要がありそうです)
AsyncAPIの書き方
AsyncAPIのチュートリアルを見てみましょう。
asyncapi: '2.2.0'
info:
title: Streetlights API
version: '1.0.0'
description: |
The Smartylighting Streetlights API allows you
to remotely manage the city lights.
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
servers:
mosquitto:
url: mqtt://test.mosquitto.org
protocol: mqtt
channels:
light/measured:
publish:
summary: Inform about environmental lighting conditions for a particular streetlight.
operationId: onLightMeasured
message:
name: LightMeasured
payload:
type: object
properties:
id:
type: integer
minimum: 0
description: Id of the streetlight.
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
info
はOpenAPIのInfo Objectと内容は同じなので、割愛します。
Servers Object
servers:
mosquitto:
url: mqtt://test.mosquitto.org
protocol: mqtt
上図で示したいわゆる broker
にあたるサーバーの情報を記載します。ネストされた mosquitto
は任意の名前を付けることができます。 他のExampleには production
など記載されていますので、環境毎の定義もできそうです。
url
と protocol
が必須項目になります。特に protocol
はメッセージのプロトコルを表します。
Channels Object
channels:
light/measured:
こちらもServers Objectと同じく任意の名前 (light/meaured
) を定義しますが、名前はRFC6570のURIテンプレート形式でなければならないようです。クエリパラメータなどはChannel Bindings Objectで定義するようです。
Operation Object
publish:
summary: Inform about environmental lighting conditions for a particular streetlight.
operationId: onLightMeasured
message:
Operation Objectの publish
または、 subscribe
で送信側か受信側かの定義を示しています。
Message Object
実際のやりとりされるメッセージの定義をします。
message:
name: LightMeasured
payload:
type: object
properties:
メッセージの名前を表す name
はmachine-friendly, 似たようなフィールドで title
はhuman-friendly な名前をつけるという仕様のようです。使い分けは仕様上は読み取れませんが、コード生成などで name
が利用されるのではと想像しています。
メッセージ本体は payload
で定義しますが、このフィールドの方は any
となっていますので、型を表す type
でこの後を定義します。
なお、この例では一つの message
オブジェクトですが、 oneOf
を利用して複数のメッセージの送受信を定義も以下の様にできます。
message:
oneOf:
- $ref: '#/components/messages/hello'
- $ref: '#/components/messages/connectionError'
- $ref: '#/components/messages/accountsChanged'
ツール
Swagger Editorに相当するブラウザ上でEditとViewができるツールは本家からもでています。
AsyncAPI Studioはβ版のようですが、記載したAPIのPublishとSubscribeする各仕様について図で視覚的にわかりやすく出力してくれる機能もあり、なかなかの優れものです。
その他サードパーティーからもCode Generatorやドキュメント生成、Validator、それらをGitHub Actionsのワークフローで実行しているものまででています。
Tooling | AsyncAPI Initiative for event-driven APIs
すでにツールが多くてどれを見ればいいんだ?状態なので、公式のこちらの言葉も理解できますね。
Please, before you decide to create a new tool, consider contributing to the existing ones. Thanks!
最後に
まだ、AsyncAPIの仕様を確認しはじめたところなので、今後、利用しもっと分かってきたら追加情報をかけたらなと思います。