はじめに
最近のウェブアプリではバックエンドをREST-APIとして用意し、
フロントエンドはREST-APIから引っ張ってきたデータをReactやVueといったフレームワークで描画することが多いと思います。
このようなウェブアプリを開発する際に問題となるのはバックエンドとフロントエンドを並行して開発しにくいということです。バックエンドができなければフロントエンドはどんなデータが来るのかわからず描画できませんし、バックエンドもフロントエンドからどのようなリクエストが来るか決まらないと実装ができません。
そのため、最初にREST-APIの仕様を定めて、その仕様に沿ったモックサーバを作成し、
フロントエンドはバックエンドが完成するまでそれを用いて開発を進めるということが行われます。
さてそのREST-APIの仕様とはどのように記述されるのでしょうか?
1. 自然言語で記述する
一番よくある場合だと思います。自然言語では非常に柔軟な仕様を記述することができます。
ただし、その解釈は一通りとは限らないので、認識のズレから不整合が起こることもあります。
2. gRPC等で記述する
gRPC
の時点でREST-APIとは正確には異なりますが、機械が読める形で仕様を記述できるので認識のズレは起こりにくいです。
ただ、記述できる仕様はデータの基本的な型のみでそこまで柔軟ではありません。
また、どちらの場合であっても、モックサーバを実装するのにも多少の時間がかかります。
また、モックサーバの入出力が本当に仕様を守っているのかは検証しないことの方が多いでしょう。
私はここでの第3の選択肢となるツールSmopeckを開発しました。
https://github.com/autotaker/smopeck
ツールはまだプロトタイプですが、それなりに使えるようになってきたので紹介していきます。
Smopeck
Smopeckの特長は以下の通りです
- 仕様を型で書くとその仕様のREST-APIをモックするサーバができます。
- 依存型や正規表現をサポートすることで柔軟な仕様が記述できます。
デモ
Hello World!
試しにHello Worldを作成してみましょう。
endpoint "/hello" GET {
response : JsonResponse {
body: String [ . = 'hello world!' ]
}
}
これを入力として渡してサーバを立ち上げます。
cabal new-exec smopeck mock --host localhost --port 8888 hello.spec
これだけでREST-APIができています。試しにアクセスしてみるとちゃんとレスポンスが返ってきます。
$ curl -s http://localhost:8888/hello | jq .
"hello world!"
Profile
次は /profile
でユーザのIDとnameを取得するAPIを作ってみましょう。
ただし、次の仕様を満たす必要があるとします。
- ユーザIDは正の整数である
- ユーザ名は英数字のみからなり1文字以上10文字以下である。
endpoint "/profile" GET {
response: JsonResponse {
body: Object {
id : Int [ . > 0 ],
name : String [ . =~ r'[a-zA-Z0-9]{1,10}' ]
}
}
}
これだけです。[ ]
の内側にデータが満たすべき述語がかけるので非常に直感的に書くことができます。
現在サポートしている述語演算子は 等号, 比較演算子(整数型のみ), =~
(文字列型のみ)です。
サーバを再起動するとREST-APIができます。
$ curl http://localhost:8888/profile -s | jq .
{
"name": "nNgDWJho",
"id": 93
}
$ curl http://localhost:8888/profile -s | jq .
{
"name": "aaKCZgzOLD",
"id": 9
}
$ curl http://localhost:8888/profile -s | jq .
{
"name": "x",
"id": 86
}
$ curl http://localhost:8888/profile -s | jq .
{
"name": "xbk",
"id": 61
}
smopeckでは仕様を満たす値が複数ある場合はそれらをランダムに生成します。
Request Parameter
次は検索API /search
を作ってみましょう。
仕様
- GETパラメータ
key
で検索文字列を受け取る - 返り値はオブジェクトのリストで長さは10未満
- リストの各オブジェクトは 整数型フィールド
id
と 文字列型フィールドcontent
を持つ -
id
フィールドの値は1以上 -
content
フィールドの値はkey
を部分文字列として含む。
この仕様をsmopeckで記述するとこうなります。
endpoint "/search" GET {
parameter: Parameter {
query : Object {
key : String
}
},
response: JsonResponse {
body: Array {
length: Int [ . >= 0, . < 10 ],
get(i): Object {
id: Int [ . > 0 ],
content: String [ . =~ r'.*' + parameter.query.key + r'.*' ]
}
}
}
}
リストはArray
型で表現できます。Array
型では length
で長さの指定が
get(i)
で、i
番目の要素の指定ができます。
parameter
部でGETパラメータの仕様を書くとparameter.query.key
で利用できるのがポイントです。
実行してみましょう。
$ curl http://localhost:8888/search?key=hoge -s | jq .
[
{
"content": "hhohh?o%hohogeho",
"id": 69
},
{
"content": "thogehhhhoh9",
"id": 9
},
{
"content": "hi_4\\hhah hohhoghoge",
"id": 75
},
{
"content": "hhoge",
"id": 39
},
{
"content": "hhshoghhoh]h,hov7hLhog>hohoge",
"id": 15
},
{
"content": "|/hhhhho3nhhogeh",
"id": 73
},
{
"content": "hohhho\"hoge",
"id": 47
}
]
生成される文字列に検索文字列が含まれていることが確認できると思います。1
Form Validation
今度はPOSTを行うAPIを作ってみましょう。
Emailをアドレスを登録するAPIを作ってみることにします。
仕様
- パスは
/user/${id}/profile/email
ここで${id}
はユーザのIDを表す1以上の整数 - 送信データはJSONオブジェクトで、文字列型の
email
フィールドを持つ -
email
フィールドの値はemailアドレスでなければならない。
endpoint "/user/:id/profile/email" POST {
parameter : Parameter {
path: Object {
id : Int [ . > 0]
}
},
request: JsonRequest {
body: Object {
email: String [ . =~ r'[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*' ]
}
},
response: JsonResponse
}
parameter
部で path
パラメータを設定すると、RESTパスの:id
をInt
型としてパーズしてくれます。emailアドレスは簡単な正規表現でチェックしています。参考
smopeckは実際に来たリクエストがrequest
部に書かれた仕様を満たしているか検査します。
検査に通ればレスポンスを返しますが、通らなければ400エラーを返します。
$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@example.com" }' -L -s -w '%{http_code}\n' -o /dev/null
200
$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@exam@ple.com" }' -L -s -w '%{http_code}\n' -o /dev/null
400
smopeckの使い道
「はじめに」でも述べましたが、smopeckはWEBアプリケーションのフロントエンドを開発する際のモックサーバとして使うことを想定していますが以下の目的でも使えるでしょう。
- 外部APIを利用するアプリケーションのテスト用のモックサーバ
- テスト用データの自動生成
今後
smopeckでは今後、以下の機能を実装していく予定です。
- フロントエンドとバックエンドの通信の間に入って立ち、バックエンドで実装されていないAPIのみをモックするProxy機能
- リクエストの仕様に従った値を自動生成してバックエンドサーバに送信し、結果がレスポンスの仕様にあっているかを自動テストする機能
-
.spec
ファイルの構文およびサポートする演算子の拡充
-
生成される文字列に
h
が多いのは現在の生成アルゴリズムの分布に偏りがあるためです。 ↩