はじめまして! 技術開発室 運用技術開発担当の辰已です。
現在新規プロダクトを開発中ですが、その中でフロントエンドとサーバーサイドのAPI呼び出し元を自動生成するため、OpenAPI Generatorを利用してみました。そこで今回は、OpenAPI Generatorの簡単な使い方をサンプルコードと共に紹介します。
運用技術開発担当では日々業務効率改善・ハートビーツの価値向上につながる社内のさまざまなプロダクトの開発・保守運用を行っています。ハートビーツはITインフラの会社というイメージがあるかもしれませんが、運用技術開発担当はITインフラ・フロントエンド・バックエンドなど、さまざまな技術に触れながら技術開発や情報発信をしています。いろいろやりたいエンジニアにとっては非常におもしろい環境です。
フロントエンドのサンプル
https://github.com/heartbeatsjp/openapi_front_sample
バックエンドのサンプル
OpenAPI Generatorでできること
フロントエンドとバックエンドを分離するとAPIの調整が面倒になりがちです。 バックエンドで定義した型と同じ型をフロントエンドでも同じように定義したり、APIの仕様が変わったときにフロントエンドとバックエンドを漏れなく不具合なく変更したりするのは、なかなか骨の折れる作業です。
今回のシステム開発では、そのような状況を避けるためにOpenAPI Generatorを利用してみました。結果としては、面倒なコードの作成が自動化されていい感じです。
OpenAPIとは
OpenAPIは言語に依存しないRESTful APIのインターフェイス定義方法です。APIのリクエストとレスポンスがどのようなデータで構成されているのか、型が何かといったインターフェイスのフォーマットを言語に依存せず定義できます。 この定義ファイル(以降スキーマファイルと呼びます)はYAML形式あるいはJSON形式で記述します。今回のサンプルコードではJSONを利用しています。
OpenAPI Generatorとは
OpenAPIのスキーマファイルを読み込み、さまざまな言語のAPIクライアント(※1)を自動生成してくれるツールです。 スキーマファイルの内容に沿ったリクエストとレスポンスの型定義や、リクエスト実行の関数定義などを自動生成してくれます。
※1)この「クライアント」は「フロントエンド」の意味ではなく「APIの呼び出し元」という意味です。バックエンドのコードでもAPIを呼び出すコードのことを「APIクライアント」と呼びます。OpenAPI Generator関連で「クライアント」と記載されていた場合は「フロントエンド」を意図していない場合があるのでご注意ください。
サンプルの構成
今回のサンプルコードの構成です。 現在開発中のプロダクトも同じような構成で作成しています。
よくあるフロントエンドとバックエンドを分離する構成です。 フロントエンドはTypeScriptでNext.jsを利用しています。 バックエンドはPythonでFastAPIを利用しています。
最近、運用技術開発担当ではフロントエンドはTypeScript、フレームワークとしてNext.jsを選択することが多いです。 また、バックエンドではPythonやGoを利用することが多いですが、性能が求められるような場所ではRustを利用することもあります。
コード自動生成までの流れ
OpenAPI Generatorを利用した開発のフローには「スキーマファースト」「コードファースト」の2種類が存在します。
スキーマファーストの開発フローでは以下の流れで開発します。
- スキーマファイルを作成
- その情報を元にフロントエンドとバックエンドのクライアントを自動生成
- 自動生成したクライアントを利用して、フロントエンドとバックエンドを作成
スキーマファイルさえ定義してしまえば、フロントエンドとバックエンドを並行で開発できるのがメリットです。しかし、スキーマファイルを書くための学習コストがかかってしまうのがデメリットです。
コードファーストの開発フロー(※2)では以下の流れで開発します。
- バックエンドのコードを作成
- 作成したコードからスキーマファイルを自動生成
- スキーマファイルを元にフロントエンドのクライアントを自動生成
- 自動生成したクライアントを利用して、フロントエンドを作成
コードからスキーマファイルを生成するので、OpenAPIのことをあまり意識することなくスキーマファイルを生成できるのがメリットです。しかし、コードが作成できないとスキーマファイルを生成できないので、スキーマファーストのように並列で開発しにくいことがデメリットです。
今回サーバーで利用しているFastAPIはOpenAPIに準拠して設計されているため、ほぼOpenAPIの学習なしでスキーマファイルを生成できます。 また、今回の開発は少人数で融通が効きやすかったので、コード作成が進まずにスキーマファイルが出力できない、といったリスクもありませんでした。 そのため、今回はコードファーストのフローで開発を進めることにしました。
※2)フロントエンドを先に開発し、そこからスキーマファイルを出力する流れも可能ですが、あまりすることはないかもしれません。
バックエンドの作成
FastAPIを用いてバックエンドを実装するだけです。
スキーマの定義
定義といってもPythonを実装するだけです。以下はItemのデータを返すエンドポイントのサンプルコードです。
# openapi_server_sample/openapi_server_sample/main.py class Item(BaseModel): name: str description: str | None price: float tax: float | None ... @app.get("/item") async def read_item() -> Item: return Item(name="test", description="description", price=10, tax=1)
OpenAPIの記法をまったく意識せず作成できるのはすごいですね。 注意点として、エンドポイントの引数と返り値の型は明記する必要があります。 型を記載しなかった場合には、コードを正しく出力できないことがあります。
スキーマファイルの出力
バックエンドのアプリケーションを起動し、openapi.jsonのURLにアクセスすればスキーマファイルを取得できます。 今回のサンプルコードであればURLはhttp://127.0.0.1:8080/openapi.jsonになります。 ただ、毎回バックエンド起動・URLにアクセス・コピペは面倒です。そこで、以下のようなpythonコードを作りました。コマンド一発でスキーマファイルの内容を出力できるので快適です。
# openapi_server_sample/openapi_server_sample/generate.py from main import app import json j = json.dumps(app.openapi()) print(j)
フロントエンドの作成
フロントエンドはスキーマファイルから自動生成したクライアントを利用します。 本来ならAPIの定義に沿った型を実装したり、バックエンドアクセス用の関数を実装したりといった作業が必要ですが、ここをOpenAPI Generatorが自動生成してくれます。
クライアントの生成
OpenAPI Generatorのコマンドにスキーマファイルを渡すだけでコードが自動生成されます。 以下はOpenAPI Generatorでtypescript-axiosを利用し、コードを自動生成する例です。
openapi-generator generate -i [スキーマファイルパス] -g typescript-axios -o [出力先ディレクトリパス]
ただし、OpenAPI GeneratorはJava製のため実行するためにはJavaをインストールする必要があります。 フロントエンドの開発コンテナにJavaを入れることも可能ですが、ツールのために環境を変更するのはちょっと気が引けます。OpenAPI Generator用のDockerイメージが公式から配布されていたので、こちらを利用することにしました。
openapitools/openapi-generator-cli
今回のサンプルコードではフロントエンドも開発コンテナを利用しています。 そのため、DooD(Docker outside of Docker)で開発コンテナからOpenAPI Generatorコンテナを操作できるようにしました。 以下のスクリプトを実行することでtypescript-axiosの形式でコードを自動生成できます。 ディレクトリやコンテナ名は今回のサンプルに合わせて設定しています。
# openapi_front_sample/gen-openapi-cli.sh #!/bin/bash CONTAINER_NAME=openapi_front_sample SRC_OPENAPI_FILE=/workspace/openapi.json DST_DIR=/workspace/src/openapi/typescript-axios # 開発コンテナ内からdocker run でボリュームをマウントしようとすると、ホストのディレクトリ名が必要になる # ホストのディレクトリ名を取得するため、docker inspectでコンテナ名を指定してホストのディレクトリ名を取得 HOST_DIR=`docker inspect ${CONTAINER_NAME} | jq -r '.[0].Mounts[] | select(.Destination == "/workspaces") | .Source'` # SRC_OPENAPI_FILEに配置したopenapi定義を元にDST_DIRにopenapi-clientを出力 rm -rf src/openapi && docker run --rm -v ${HOST_DIR}:/workspace -it openapitools/openapi-generator-cli java -jar /opt/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i ${SRC_OPENAPI_FILE} -g typescript-axios -o ${DST_DIR}
ホストコンテナのボリュームをマウントするため少しごちゃごちゃしてしまいましたが、フロントエンドの開発コンテナ内外からOpenAPI Generatorを利用できるようになりました。
クライアントの利用
クライアントの生成で出力したコードをフロントエンドのコードから利用します。 今回、自動生成されたコードはopenapi_front_sample/src/openapi/typescript-axiosに出力しています。 いろいろなファイルが自動生成されますが、主に利用するのはapi.tsです。 このファイルの中にはスキーマファイルで定義していた型定義やリクエスト用関数などが出力されます。 今回のサンプルでは5つの定義がexportされています。
- Item
- DefaultApiAxiosParamCreator
- DefaultApiFp
- DefaultApiFactory
- DefaultApi
ItemはFastAPI側で定義していたレスポンスのモデルの型です。 DefaultApiAxiosParamCreatorはリクエスト実行時のパラメーター生成用関数です。 DefaultApiFp、DefaultApiFactory、DefaultApiはリクエスト実行用のインターフェイスです。インターフェイスは3種類ありますが、処理内容はどれも同じで書き方が異なるだけです。使う側のフォーマットに合わせて選択すればOKです。 今回のサンプルではDefaultApiFactoryを利用しました。
// openapi_front_sample/src/pages/index.tsx const fetcher = async (): Promise- => { const res = await DefaultApiFactory(config).readItemItemGet(); if (res.status !== 200) { throw new Error("アイテムの取得に失敗しました"); } const data = await res.data; return data; }; const { data, error } = useSWR
- ("/item", fetcher); ...
APIから取得したデータはuseSWRを用いて処理しています。 レスポンスのデータ型やエンドポイント用関数が自動生成されているので、それらを呼び出すだけで型に守られたAPI呼び出しが可能です。
動作確認
フロントエンドとバックエンドのサンプルリポジトリにあるREADMEに従い、それぞれのアプリケーションを起動してみてください。
起動後にブラウザからhttp://127.0.0.1:8080
にアクセスすると、フロントエンドからバックエンドのAPIを呼び出した結果が表示されます。
- バックエンドでAPIのエンドポイントを作成
- バックエンドでスキーマファイルを自動生成
- フロントエンドのクライアントを自動生成
- 自動生成したクライアントをフロントエンドから利用
これだけで簡単にコードが作れてしまいました。
インターフェイスに仕様変更が発生した場合は、再度スキーマファイルを出力してコードを自動生成すればよいだけです。 さらに、今回の例ではフロントエンドはTypeScriptを利用しているので型に守られています。インターフェイスの形式が変わった場合、もし変更漏れがあればビルド時にエラーが発生するので安心です。
おわりに
今回はOpenAPI Generatorを用いたクライアントコードの自動生成について紹介しました。 コード生成の自動化により作業の効率化や不具合の発生を抑止できるので、開発を効率化したい方にはメリットの多いツールではないかと思います。
他にも運用技術開発担当では、さまざまな技術やツールを活用して日々の業務の効率化や技術開発を行っています。 ITインフラだけでなくこのような部門もあるんだなー、ということが少しでも伝われば幸いです。