OpenAPI Generator + golang + Flutter でアプリ開発
1. はじめに
以下のような構成のスマホアプリを作ってみようと思います。
一般的な、HTTP経由でサーバと通信するタイプのスマホアプリです。
で、タイトルの通り「OpenAPI 3.0でWebAPIを定義」し「golangでWebAPIを実装」し「Flutterでスマホアプリを実装」する、ということをしたいと思います。
また、OpenAPI 3.0定義からサーバ及びクライアントコードを自動生成するために OpenAPI Generator を使用することとします。
今回利用した環境 macOS Mojave 10.14 Flutter 0.8.2 go 1.10.3 openapi-generator 3.3.2
2. 準備
Flutter / go 環境は構築済みの前提とします。
openapi-generatorは以下のbrewコマンドでインストールします。
$ brew install openapi-generator
3. 作る!
大まかな流れは以下になります。
- OpenAPI 3.0 specでAPI仕様を定義
- OpenAPI Generatorでgolangのserverコードを自動生成
- OpenAPI Generatorでdartのclientコードを自動生成
- Flutterアプリを作成(dart clientを利用してgolang serverを呼び出す)
では順番に進めていこうと思います。
まずは、アプリ開発用のディレクトリを作成します。
$ mkdir oas3_go_flutter_exam $ cd oas3_go_flutter_exam
3.1. OpenAPI 3.0 specを作成
OpenAPI定義yamlを作成します。
ファイル名は「employee_api.yml」とします。
idを指定してemployee情報を取得するWebAPIの定義になります。
Employeeは id / firstName / lastName / salary の属性を持ったオブジェクトとします。
openapi: "3.0.0" info: version: 1.0.0 title: oas3_go_flutter_exam Employee license: name: MIT servers: - url: http://localhost:8080/api/ paths: /employee: get: summary: get employee information operationId: getEmployee tags: - employee parameters: - name: employeeId in: query description: query target employee id required: true schema: type: string responses: '200': description: return employee information content: application/json: schema: $ref: "#/components/schemas/Employee" components: schemas: Employee: required: - id - firstName - lastName - salary properties: id: type: string firstName: type: string lastName: type: string salary: type: integer format: int64
3.2. yamlの妥当性をチェック
定義した employee_api.yml にエラーがないか、以下のコマンドでチェックします。
$ openapi-generator validate -i employee_api.yml Validating spec (employee_api.yml) No validation issues detected.
3.3. golangのserverコードを自動生成
以下のコマンドでgolangのserverコードを自動生成します。
$ openapi-generator generate -i employee_api.yml -g go-server -o ./server [main] WARN o.o.c.ignore.CodegenIgnoreProcessor - Output directory does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated. [main] INFO o.o.c.languages.AbstractGoCodegen - Environment variable GO_POST_PROCESS_FILE not defined so Go code may not be properly formatted. To define it, try `export GO_POST_PROCESS_FILE="/usr/local/bin/gofmt -w"` (Linux/Mac) [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/go/model_employee.go [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/go/api_employee.go [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/api/openapi.yaml [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/main.go [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/Dockerfile [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/go/routers.go [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/go/logger.go [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/go/README.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/.openapi-generator-ignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./server/.openapi-generator/VERSION
モデルクラス(model_employee.go)やルート定義(routers.go)、APIのカラ定義(api_employee.go)など一通りのgo実装が自動生成されました。
3.4. goのWebAPIを実装
自動生成されたコードにオリジナルの実装を追加します。
api_employee.goファイル の GetEmployee() に Employeeオブジェクト を返却する実装を以下のように追加します。
(本来はDBアクセスなどを行った結果を返すと思います)
package openapi import ( "encoding/json" "net/http" ) // GetEmployee - get employee information func GetEmployee(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) employeeID := r.URL.Query()["employeeId"] employee := Employee{ Id: employeeID[0], FirstName: "ryuichi " + employeeID[0], LastName: "daigo " + employeeID[0], Salary: 12000000} json.NewEncoder(w).Encode(employee) }
3.5. go serverを実行
WebAPIがうまく動作するか実行してみましょう。
$ go run main.go
curlでもブラウザでも何でも良いですが、以下のURLにGETを投げてみます。
http://localhost:8080/api/employee?employeeId=10 {"id":"10","firstName":"ryuichi 10","lastName":"daigo 10","salary":12000000}
golang serverのWebAPIが正しく動作していることを確認できました。
3.6. dartのclientコードを自動生成
以下のコマンドでdartのclientコードを自動生成します。
$ openapi-generator generate -i employee_api.yml -g dart -DbrowserClient=false -o ./client [main] WARN o.o.c.ignore.CodegenIgnoreProcessor - Output directory does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated. [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 - Dart version: 2.x [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/lib/model/employee.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/docs/Employee.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/lib/api/employee_api.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/docs/EmployeeApi.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/pubspec.yaml [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/api_client.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/api_exception.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/api_helper.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/api.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/auth/authentication.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/auth/http_basic_auth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/auth/api_key_auth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client//lib/auth/oauth.dart [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/git_push.sh [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/.gitignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/README.md [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/.openapi-generator-ignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/daigo/Projects/oas3_go_flutter_exam/./client/.openapi-generator/VERSION
※ 上記のように「-DbrowserClient=false」オプションを付けないとFlutterでコンパイルできない dart:html に依存したコードが生成されてしまうので注意。
dartによりWebAPI呼び出しを行うコード、また送受信するデータのモデルクラスなど一通りの実装が自動生成されました。
3.7. Flutterアプリを作成
以下のコマンドでFlutterアプリを作成します。
$ flutter create flutter_app
先程自動生成した dart client を利用して golang server にアクセスするために pubspec.yaml に dart client への依存定義を追加します。
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 openapi: path: ../client/
※ 上記定義の下2行
main.dartを修正します。
IDを入力し、ボタンを押すとWebAPI呼び出しが行われ、結果を画面にテキスト表示するようにします。
// main.dart import 'package:flutter/material.dart'; import 'package:openapi/api.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter with OpenAPI Generator'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { String _employeeName = ''; String _employeeId = ''; void _callWebApi() { var client = new EmployeeApi(); var result = client.getEmployee(this._employeeId); result.then( (employee) => setState(() { this._employeeName = employee.firstName + ' ' + employee.lastName; } ) ); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text('pleas input employee id'), new TextField( onChanged: (v) => this._employeeId = v, ), new RaisedButton( child: new Text('call WebAPI'), onPressed: _callWebApi, ), new Text( this._employeeName, style: Theme.of(context).textTheme.display1, ), ], ), ), ); } }
3.8. 実行
では実行してみます。
./serverディレクトリで以下のコマンドによりgolang serverを起動しておきます。
go run main.go
そしてFlutterアプリを起動します。
IDを入力し、call WebAPIボタンをタップします。
WebAPI呼び出しが行われEmployeeオブジェクトの取得&表示が行われました!
4. まとめ
OpenAPI Generator を使うとモデルを含めたテンプレート実装が一瞬のうちに生成されかなりいい感じに開発が進められるんじゃないかと思いました。サクサクっと必要な定形コードが自動生成されるので、ドメイン領域への集中力アップにも勿論効果的ですね。
OpenAPI 3.0によるAPI定義を明確に行った上で、サービスを実装していくスタイルも(まあSwagger時代から何年も行われていることではありますが)クリーンにプロジェクトが保たれていいと思います。
今回作ったコードは以下に置いておきました。