Flutterで使うAPI関連のDartコードをSwagger/OpenAPIで自動生成する
はじめに
Flutterの開発の中でSwagger/OpenAPIのYamlからコードを自動生成して使うことがあったのでまとめておきます。
Swagger/OpenAPIとは
OpenAPIはREST APIの仕様を記述するフォーマットで、yaml形式で仕様を定義することができます。
2.0系と3.0系があって、3.0が出てからしばらく立ちましたが、API Gatewayはまだ2.0系のみサポートしているところが多い印象です。
SwaggerとOpenAPIの関係は少しややこしいようで、加えて2.0系と3.0系で仕様に差分があるため、ここでは2.0系の形式を扱うこととします。
Swagger/OpenAPIには以下の3つのツールがあります。
今回はSwagger/OpenAPIのyamlを使ってAPI仕様書を見る手順と、Flutterで使うDartのコードを生成する手順をまとめます。
Swagger UIでAPI仕様書を見る
Swagger UIはDocker Imageが用意されているので、yamlを連携してあげれば簡単に起動することができます。
API_URL
でデフォルトで読み込むyamlを指定できるので、マウントした領域を読み込んだり、ネットワーク越しにしていすればよいでしょう。
$ docker run -d -p 81:8080 -v $HOME/git/swagger-dart-client/:/usr/share/nginx/html/yaml \ -e API_URL=yaml/petstore.yaml swaggerapi/swagger-ui
http://localhost:81
にアクセスすれば確認できます。
Swagger CodegenでDartのコードを自動生成する
FlutterでAPIアクセスをして画面を描画する時、APIのモデルデータの一式を定義するのは大変です。
PHPやJavaScriptであれば単なるArrayのMapで処理するだけなのですが、JavaやDartではきっちり型定義が必要なのでList型や入れ子のJSONを含む複雑なレスポンスを返すAPIの場合は定義がとても面倒です。
加えて、特にフロントエンドとバックエンドが並行して新規開発するケースでは、やっとの思いでモデルクラスを定義しきれたとしてもAPIの仕様が流動的なため頻繁な変更が入ります。Typoでミスが入り、バックエンドと繋ぐときにIFの不整合が多発する要因にもなります。
そこでSwagger/OpenAPIのyamlで仕様を定義し、クライアントコードを自動生成するという戦略をとることでその手間やリスクを減らすことがことできます。
ツールをインストールする
コードの自動生成ツールをは、Swagger/OpenAPIそれぞれ用意されているのですが今回はOpenAPIの方を利用します。
インストールは公式のガイドに従っていけばよいです。
npm、homebrew、docker、jar、bashの5種類用意されています。
それぞれが利用しやすいものを選べばよいと思いますが、今回はjarを利用することにします。
API定義のYamlを記述する
まず YamlはAPIの仕様の肝となり、プロダクションで利用するコードの自動生成にも利用するものなので、フロントエンドとバックエンドをつなぐ重要な設計成果物であることをお互いに認識しましょう。
これが非常に重要です。
「ファイルサーバのExcelの方が最新です」ということにならないように、Gitのレポジトリで管理され、かつバージョニングされていることが望ましいです。
Yamlの記述についてはSwagger Editorを使えばSyntaxを確認できますが、yamlが巨大になってくるとブラウザが悲鳴を上げてくるのでcliで実行するとよいでしょう。
$ java -jar openapi-generator-cli.jar validate -i petstore.yaml Validating spec (petstore.yaml) No validation issues detected.
Yamlを手動で記述するのはなかなか大変なので、記述しやすい別の設計ドキュメントから自動生成するようにしてもいいかもしれませんね(何とはいいません🙊🙊🙊)。
個人的にはバックエンドのソースコードからYamlを生成してもらえると実装と設計の一意性がより担保できると思ってます。
YamlからDartのコードを自動生成する
Yamlが出来上がったらあとはクライアントコードの自動生成をするだけです。
Flutterはdartで記述するのでdartのクライアントコードを生成するのですが、GeneratorのREADMEを読むとdart
とdart2
とあります。
Flutterが利用するdartのバージョンは2.x系なのでdart2を利用すればいいのねと思ってコマンドを実行したら
$ java -jar openapi-generator-cli.jar generate -i petstore.yaml -g dart2 -o build Can't load config class with name 'dart2' ... [error] Check the spelling of the generator's name and try again.
となってエラーで失敗しました。まだコマンドラインツール上ではdart2は対応していないようです。
githubを見に行くとdart2用のテンプレートは用意されているようなのでテンプレートを指定する形で利用すればよさそうです。
テンプレートは-t
オプションで指定することができます。
$ java -jar openapi-generator-cli.jar generate -i petstore.yaml -g dart -t template/dart2 -o build [main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false [main] INFO o.o.c.ignore.CodegenIgnoreProcessor - No .openapi-generator-ignore file found. [main] INFO o.o.codegen.DefaultGenerator - OpenAPI Generator: dart (client) [main] INFO o.o.codegen.DefaultGenerator - Generator 'dart' is considered stable. [main] INFO o.o.c.languages.DartClientCodegen - Environment variable DART_POST_PROCESS_FILE not defined so the Dart code may not be properly formatted. To define it, try `export DART_POST_PROCESS_FILE="/usr/local/bin/dartfmt -w"` (Linux/Mac) [main] INFO o.o.c.languages.DartClientCodegen - NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI). [main] INFO o.o.c.languages.DartClientCodegen - Dart version: 2.x [main] INFO o.o.codegen.DefaultGenerator - Model Pets not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default) [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/lib/model/error.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/test/error_test.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/doc/Error.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/lib/model/pet.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/test/pet_test.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/doc/Pet.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/lib/api/pets_api.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/test/pets_api_test.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/doc/PetsApi.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/pubspec.yaml [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/api_client.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/api_exception.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/api_helper.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/api.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/auth/authentication.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/auth/http_basic_auth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/auth/api_key_auth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build//lib/auth/oauth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/git_push.sh [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/.gitignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/README.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/.travis.yml [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/.openapi-generator-ignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/takami228/git/swagger-dart-client/build/.openapi-generator/VERSION
生成コードをカスタマイズする
OpenAPIを使ったコード生成では何も指定しないとAPIクライアント、ドキュメント、テストコードなどが一式生成されます。
モデルクラスのみあればよいというケースでは、オプションを設定することで自動生成をスキップできます。
$ java -jar openapi-generator-cli.jar -DbrowserClient=false -DapiTests=false -DmodelTests=false \ generate -i petstore.yaml -g dart -t template/dart2 -o build
詳細は Selective Generation を見るとよいでしょう。
また自動生成されたコードがDartのLintルールに沿ってない場合は、テンプレートのmustacheファイルをいじってルールに沿うように修正するとよいです。もしテンプレートをいじる場合は忘れずにテンプレートファイルをバージョン管理しましょう。
一式の生成の流れがまとまったらレポジトリにまとめたり、API仕様書が変更されたときにFlutterのプロジェクトへどう修正を取り込むのかのパイプラインを考えるとよいでしょう。
Flutterの場合はpubspec.yaml経由でバージョンを指定してimportすることもできるようです。やや古いですが、こちらの記事が参考になります。
- OpenAPI Generator + golang + Flutter でアプリ開発 - ryuichi111stdの技術日記
- flutter + golang + openAPIで雑アプリ開発 - Qiita
まとめ
以上がSwagger/OpenAPIのYamlからコードを生成する流れになります。githubにサンプルもまとめておきました。
頻繁に変更されるAPI定義のモデルクラスの修正は、単純でありつつもミスがゆるされない作業になるため、自動生成の仕組みを上手く利用して効率化するとよいでしょう。
おまけ
Swaggerを使ったコードやドキュメントの自動生成の話はint128先生のJavaのSpringを使った例も参考になるので見てみてください。
Swaggerのテンプレートを魔改造した話 / Customize Swagger Templates - Speaker Deck