OpenAPIスキーマ互換なAsyncAPIとは？


みらい翻訳 Advent Calendar 2021の記事です。

## はじめに

みなさんご存知だと思いますが、OpenAPIはREST APIを記述するデファクトな仕様記述言語と言っていいと考えています。では、AsyncAPIとは何でしょうか？

結論から言うと、Event駆動アーキテクチャのAPIのインターフェイスを定義するためのものです。Eventは通常その処理結果を待つことなく任意のタイミングで発生しうるので、当然Event駆動アーキテクチャでは、Async(非同期)であることが要求されます。(AsyncAPIは検索しづらい名前ではあるけど)
AsyncAPIはこのようにEvent駆動アーキテクチャで非同期APIを定義するためのOpenAPIのスキーマに対応した仕様を持ちます。仕様面だけでなく、OpenAPIのエコシステムが提供するのと同じように仕様のドキュメント化、コード生成、コードからの仕様化までも提供しています。

https://www.asyncapi.com/


## マイクロサービスアーキテクチャとEvent駆動アーキテクチャ

さて、何故私がAsyncAPIを取り上げるかですが、マイクロサービスアーキテクチャに関連します。もちろんシンプルに非同期APIのよい仕様の記述の仕方を模索していたのもありますが、なによりマイクロサービスアーキテクチャの文脈では、開発チーム体制としても各マイクロサービス間はAPI仕様で会話を成り立たせることで、開発の並行度を上げスケールさせることもメリットになると考えています。そのためには、統一されたAPI仕様の書き方とというものが必要になってきます。


まずは、マイクロサービスアーキテクチャとEvent駆動アーキテクチャの設定について見てみます。

ファイルストレージサービスを考えます。とある会社組織に属するAliceは、社外のBobとファイル共有したいと考えました。その組織ではその時点ではファイル共有が認められていたとします。

![ファイル共有の仕組み](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/0156b42e-01ad-459d-09e2-eadb3caa5cae.jpeg)

動作としては、クライアントのためのBFF層が、組織設定を管理するマイクロサービスとユーザのファイルを管理するマイクロサービスを使ってよしなにAliceの要求に応える振る舞いをしました。ところがある日、会社のルールが変わって、このファイルストレージサービスでは社外の人とファイル共有が禁止されました。当然、管理者のcharlieは過去に共有されたファイルも共有禁止にすることを望みます。システムで組織の設定を変えるだけで管理者の仕事としては終わりにしたいと望むでしょう。

![ファイル共有の禁止](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/a3966854-ccfe-616e-d2ce-4c01fe071ed1.jpeg)


あなたらなら、どう設計するでしょうか？もちろんBFF層で設定変更のシーケンス内でなんとか仕様とするでしょうか？ 
対象ファイルがいくつあるか分からないので、禁止にするシーケンス内で処理を完了させようとするのはよくない戦略だと考えると思います。レスポンスを管理者に返した後、BFFが頑張ってもらうというのも考えられますが、BFFが責務を持ちすぎことになるので好ましくはないですね。
このようなケースは組織設定で起こったファイル共有可否設定の変更というEventをそのことに関心があるユーザーファイル管理が **検知できる** ようにするという考え方で整理するのがよいです。つまりEvent駆動ですね。
注意： **検知する** という表現を使っているのは理由があります。組織設定がユーザーファイル管理に伝えるという表現を使うと組織設定がユーザーファイル管理を知っていると理解される可能性があります。このような依存関係はよくありません。Event発生源がそのEventに関心がある先を知る必要があるということは、利用先が増える度にEvent発生源に何らかの影響(例えば、実装)が必要になる可能性を示唆しています。


<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/f9035398-e6d3-d912-95a3-0e2fd64b7dd6.jpeg" />

Event発生源、関心がある箇所、その間をBrokerと表現をしていますが、例えば、それぞれPublisher, Subscriber, メッセージキューなどとするとよりイメージしやすいかと思います。
このとき、EventはEvent発生源からどのように情報が流されるか、あるEventに関心がある箇所では実際どの情報に関心があるか、これらを表現するためのAPI仕様が AsyncAPIになります。

OpenAPIを利用して、API利用者への仕様を提示することの有用性はすでに理解されている人も多いと思います。マイクロサービスアーキテクチャを利用すると、Event駆動が必要になり、それらがマイクロサービス間でAPI仕様として定義する場面は多くなると思います。そのためのスキーマ言語としてOpenAPIと同様に有用なのが、AsyncAPIであると考えています。

![OpenAPIとAsyncAPI](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/de9c22d0-aaa0-8b9c-8299-770293ea4eb5.jpeg)


## AsyncAPIの紹介

さて、ここからやっとAsyncAPIの説明です。AsyncAPIは、はじめに 言及したようにOpenAPIのスキーマに対応していいます。(2021/12/21 現在2.2.0)
本家の図を引用すると以下の様な対応になっています。

<img width="480" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/0d013654-9b39-f357-0dd3-0da4c8c1596d.png" />

最初に注目すべきは、REST APIでPATHによって機能が分かれているということに対して、AsyncAPIではChannelとして関心のあるEventを分類すると考えるのがよいでしょう。Channelをトピックとした方が理解しやすい人もいるかもしれません。

それともう一つ、 `Servers Object` こちらは、OpenAPIではHTTP前提でしたが、AsyncAPIでは Channelの実装となる `protocol` の定義がなされます。 `protocol` は、 `http` , `amqp` , `kafka` などが定義されています。さらに仕様上 `protocol` は自ら定期もできるようです。(定義する `protocol` に必要な情報の定義を各種 `Bindings Object` として定義する必要がありそうです)

## AsyncAPIの書き方

AsyncAPIのチュートリアルを見てみましょう。

```yaml
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

```yaml
servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
```

上図で示したいわゆる `broker` にあたるサーバーの情報を記載します。ネストされた `mosquitto` は任意の名前を付けることができます。 他のExampleには `production` など記載されていますので、環境毎の定義もできそうです。 

`url` と  `protocol` が必須項目になります。特に `protocol` はメッセージのプロトコルを表します。

### Channels Object

```yaml
channels:
  light/measured:
```

こちらもServers Objectと同じく任意の名前 (`light/meaured`) を定義しますが、名前はRFC6570のURIテンプレート形式でなければならないようです。クエリパラメータなどはChannel Bindings Objectで定義するようです。

### Operation Object

```yaml
    publish:
      summary: Inform about environmental lighting conditions for a particular streetlight.
      operationId: onLightMeasured
      message:
```

Operation Objectの `publish` または、 `subscribe` で送信側か受信側かの定義を示しています。

### Message Object

実際のやりとりされるメッセージの定義をします。

```yaml
      message:
        name: LightMeasured
        payload:
          type: object
          properties:
```

メッセージの名前を表す `name` はmachine-friendly, 似たようなフィールドで `title` はhuman-friendly な名前をつけるという仕様のようです。使い分けは仕様上は読み取れませんが、コード生成などで `name` が利用されるのではと想像しています。

メッセージ本体は `payload` で定義しますが、このフィールドの方は `any` となっていますので、型を表す `type` でこの後を定義します。

なお、この例では一つの `message` オブジェクトですが、 `oneOf` を利用して複数のメッセージの送受信を定義も以下の様にできます。

```yaml
message:
        oneOf:
          - $ref: '#/components/messages/hello'
          - $ref: '#/components/messages/connectionError'
          - $ref: '#/components/messages/accountsChanged'
```

## ツール

Swagger Editorに相当するブラウザ上でEditとViewができるツールは本家からもでています。

[AsyncAPI Studio](https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/asyncapi/asyncapi/v2.2.0/examples/simple.yml)

AsyncAPI Studioはβ版のようですが、記載したAPIのPublishとSubscribeする各仕様について図で視覚的にわかりやすく出力してくれる機能もあり、なかなかの優れものです。

![スクリーンショット 2021-12-20 22.44.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6547/f392dbac-6300-20ef-943c-f997372be7b9.png)


その他サードパーティーからもCode Generatorやドキュメント生成、Validator、それらをGitHub Actionsのワークフローで実行しているものまででています。

[Tooling | AsyncAPI Initiative for event-driven APIs](https://www.asyncapi.com/docs/community/tooling)

すでにツールが多くてどれを見ればいいんだ？状態なので、公式のこちらの言葉も理解できますね。

> Please, before you decide to create a new tool, consider contributing to the existing ones. Thanks!
> 

## 最後に

まだ、AsyncAPIの仕様を確認しはじめたところなので、今後、利用しもっと分かってきたら追加情報をかけたらなと思います。