巷で流行りのflutter
を触ってみたついでに、以前から気になっていたopen-api-generator
を使って、HTTP通信行う雑なiOSアプリを作ってみた。
flutter
はこの書籍を読みつつキャッチアップ、open-api-generator
は、この記事を参考にした。
今回作ったアプリの構成
ユーザを登録する機能を実装。照会の実装はUX考えるとちょっとしんどくなったので、画面にjson
を表示するだけというクソ仕様で実装。
- クライアント:
flutter
- ユーザ情報(名前,年齢)を入力して登録
- 登録した情報をサーバから取得して
json
を表示
- サーバ:
golang
- オンメモリに登録されたユーザ情報を保存しておく
アプリ実装
まずはREST
の仕様を定めたyml
ファイルを作成する。
ここからopen-api-generator
を使用して、クライアント・サーバのREST
の実装を自動生成する。
open-api-generator
で自動生成できる言語はここを確認。結構豊富にある。
以下が今回使用するRESTの仕様定義yml
設定値の詳細についてはこの記事を参考にした
openapi: "3.0.0"
info:
version: 1.0.0
title: go_flutter_exam User
license:
name: MIT
servers:
- url: http://localhost:8080/api/
paths:
/users:
get:
summary: get all user information
tags:
- users
parameters: []
responses:
'200':
description: return user information
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
/user/add:
post:
summary: Create a new User
tags:
- addUser
parameters: []
requestBody: # リクエストボディ
description: user to create
content:
application/json:
schema: # POSTするオブジェクト
$ref: '#/components/schemas/User'
example:
name: Michel Roe
age: 23
responses:
'201':
description: CREATED
components:
schemas:
User:
required:
- name
- age
properties:
id:
type: string
name:
type: string
age:
type: integer
format: int64
次にopen-api-generator
をインストール
brew install openapi-generator
作成したyml
の妥当性を検証
$ openapi-generator validate -i user-management-api.yml
Validating spec (user-management-api.yml)
No validation issues detected.
サーバ側のコードを作成
$ openapi-generator generate -i user-management-api.yml -g go-server -o ./server
サーバ側のコードを自動生成すると、
- HTTPメソッドごとの関数の
.go
ファイル - APIで使うDTOの
.go
ファイル - routing処理,webサーバ起動処理の
.go
ファイル main.go
が生成される。
で、HTTPメソッドごとの関数の.go
ファイルにリクエストを受け取ってどうハンドリングするかのロジックを書いていく。
- ユーザ登録POSTリクエストハンドリング処理実装
package openapi
import (
"net/http"
"github.com/mholt/binding"
"strconv"
)
// UserAddPost - Create a new User
func UserAddPost(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
user := new (User)
userMap := GetUserMap()
binding.Bind(r, user)
user.Id = strconv.Itoa(len(userMap))
userMap[int64(len(userMap))] = user
}
func (user *User) FieldMap(req *http.Request) binding.FieldMap {
return binding.FieldMap{
&user.Name: "name",
&user.Age: "age",
}
}
- 全ユーザ情報取得GETリクエストハンドリング処理実装
package openapi
import (
"encoding/json"
"net/http"
)
// UsersGet - get all user information
func UsersGet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
userMap := GetUserMap()
values := []*User{}
for _, v := range userMap {
values = append(values, v)
}
json.NewEncoder(w).Encode(values)
}
- ユーザ情報DTO。今回はここにインメモリMapとMapの取得処理も実装
package openapi
var inMemoryUserMap = map[int64]*User{}
type User struct {
Id string `json:"id,omitempty"`
Name string `json:"name"`
Age int64 `json:"age"`
}
func GetUserMap() map[int64]*User {
if inMemoryUserMap == nil {
inMemoryUserMap = map[int64]*User{}
}
return inMemoryUserMap
}
サーバ側はこれで完成。0から自分でルーティング書くよりずいぶん早く実装完了した。
好感触。
続いてclient側も自動生成。
$ openapi-generator generate -i user-management-api.yml -g dart -DbrowserClient=false -o ./client
クライアント側もサーバ側とリクエストを投げる/受け取るという部分に違いはあるものの、同じようなファイルが生成される。
あとはクライアント側のUI部分を実装するだけ。
とりあえずflutterのスターターアプリを作成。
flutter create flutter-golang-exam_app
自動生成されたflutterのパッケージをimportできるように上のコマンドで作成したプロジェクトのpubspec.yml
の依存に自動生成されたパッケージを追加。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
openapi: ## ←これが自動生成されたパッケージ
path: ../../client/
dev_dependencies:
flutter_test:
sdk: flutter
で、main.dart
を以下のように実装。
import 'package:flutter/material.dart';
import 'package:openapi/api.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _userName ='';
int _userAge = 0;
List<User> _displayUsers = new List();
void _addUser() {
var api = new AddUserApi();
User user = new User(this._userName,this._userAge);
api.userAddPost(user: user);
}
void _getUser() {
var api = new UsersApi();
Future<List<User>> users = api.usersGet();
users.then((content) => setState(() {
this._displayUsers = content;
}));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField(
decoration: new InputDecoration(labelText: "Enter register user name "),
onChanged: (v) => this._userName = v,
),
new TextField(
decoration: new InputDecoration(labelText: "Enter register user age"),
keyboardType: TextInputType.number,
onChanged: (v) => this._userAge = int.parse(v),
),
Text(
'$_displayUsers',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: Column(
verticalDirection: VerticalDirection.up, // childrenの先頭を下に配置
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton(
onPressed: _addUser,
tooltip: 'User Register',
child: Icon(Icons.add),
),
Container( // 余白のためContainerでラップ
margin: EdgeInsets.only(bottom: 16.0),
child: FloatingActionButton(
onPressed: _getUser,
tooltip: 'All User Get',
child: Icon(Icons.autorenew),
),
),
],
),
);
}
}
これでクライアントも実装完了。
サーバ/クライアントを起動して、下のような画面がシミュレータに表示されれば成功。
適当に名前、年齢を入力してaddボタンを押して、リロードボタンを押すと
クソアプリの完成。
flutter+golang+open-api-generator
の所感
- 実装はとても楽チンになった!
routing
部分とか自前でやるとそれなりに詰まったかもなあと思いながら自動生成されたコードを見ながら感じたので。(初心者だからかもしれないけど。。) - サーバ側は自動生成されたものを手で修正せざるを得ないのでは?と思うところも多々あったので、自動生成されたものを一切触らずにサーバ側実装を行う方法を考えねばなあと感じた。この点が一番引っかかった。
- 個人的には
java
やってるので、flutter
の方がswift
よりも読みやすいなと思った。UIはflutterStudio使えば、もっと楽にできるのかなと思って触ってみたけど、コード見ながらじゃないとどのパーツを置いていけば良いかわからなくなったので、コード書くほうが良いやって思った。
感想
openAPI
は自動テストコードをメンテコスト低く作成できるかもしれないなと思って、前から目を付けていたけどやはり良い。
でもやっぱ自動生成されたものを手動更新せずに実装する方法は考えないとちょっと使えない。
クライアント側の自動生成コードはモデルのコンストラクタくらいしか追加してないので、サーバ側の自動テストを作る目的ならすぐに導入できそう。
flutter
も学習コスト高い感じはしなかったので、今後も継続勉強していこうと思った。今度はマトモなアプリを掲載できるようにw