OpenAPIの活用について

弊社ではこれまでにOpenAPIをサーバーサイドのAPIのテスト、フロントエンドのクライアントコードの自動生成などに活用してきました。

現状の課題

自動生成されたクライアントコードがあったとしても返すデータがなければマークアップや組み込みを進めるには少し不便です。
せっかくスキーマから型まで生成しているのに、データがないので仮組みしてあとから実データにあわせて修正では無駄が多いように思います。

また、プロジェクトの初期段階などにバックエンドとフロントエンドの担当者がそれぞれがローカル環境で開発を行っている状態もあり、この時にフロントエンドの担当者に「APIサーバーをローカルで動かしてください」というのはハードルが高いこともあります。
ローカルでプログラミング言語やフレームワークのセットアップをしたりDockerを使ったりすることはできますが、作業の度に確認が発生するのは好ましくありません。

このようにAPIの実装はどうしても組み込みをブロックしてしまうので、OpenAPIのスキーマを定義した時点でバックエンドとフロントエンドが同時に開発をスタートできる状態になるのがよいと考えています。

OpenAPI → モックサーバー

バックエンドとフロントエンドの実装を切り分けるときにモックサーバーを使うという方法が考えられます。

前述の通り初期のマークアップや組み込みの段階でAPIサーバーをローカルにたてつつテストデータを用意するのは負担が大きく、モックサーバーのために別のデータを準備するのもあまり効率がよいとはいえません。
できれば複雑なセットアップなしでOpenAPIの定義ファイルとコマンド一発でできるぐらいが理想的です。

前提として「OpenAPIのスキーマを正とする」という条件をおけば、バックエンドはスキーマを満たすよう実装を進め、フロントエンドはスキーマに基づくデータを受け取る前提で組み込みを進めることができます。

これを実現できるのがStoplightの Prism というツールです。

※StoplightはAPI開発を効率化できるツールなどを開発しておりStoplight StudioはOpenAPIのスキーマをGUIで編集できるツールでこちらもおすすめです

Prismを使うとOpenAPIのスキーマをもとにリクエストのバリデーションやダミーデータのレスポンスを返すモックサーバーを簡単にたてることができます。

Prismを活用すればOpenAPIのスキーマができた時点でフロントエンドはとりあえずPrismでモックサーバーを立てつつ実装を進めてください、とできるのはないかと思います。


Prismを試す

インスール

Prismはいくつかインストール方法があるのですが、フロントエンドエンジニアが作業する場合npmまたはyarnがわかりやすいかと思います。

# インストール方法はお好みで
$ npm install -g @stoplight/prism-cli
# 動作確認したバージョンは4.1.0
$ prism mock --version
4.1.0

モックサーバー起動

まずはリポジトリに用意されているOpenAPIスキーマのサンプルファイルを使ってPrismを起動してみるとエンドポイントの一覧が表示され、モックサーバーが起動しました。
ポート番号などはオプションで変更可能です。

$ prism mock https://raw.githubusercontent.com/stoplightio/prism/master/examples/petstore.oas3.yaml
[18:26:51] › [CLI] …  awaiting  Starting Prism…
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets?name=saepe
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/no_auth/pets
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/findByStatus?status=sold&status=available&status=pending&status=pending&status=sold&status=available&status=available&status=available&status=pending&status=pending&status=sold&status=sold&status=available&status=available&status=sold&status=pending&status=available&status=pending&status=pending&status=sold
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/findByTags?tags=aut&tags=id&tags=ut&tags=sapiente&tags=molestiae&tags=nostrum&tags=magni&tags=facere&tags=enim&tags=vero&tags=sit&tags=beatae&tags=consequatur&tags=facere&tags=unde&tags=eveniet&tags=aut&tags=et&tags=rem&tags=sint
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/no_auth/pets/421
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/no_auth/pets/801
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets
[18:26:52] › [CLI] ℹ  info      PUT        http://127.0.0.1:4010/pets
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/findByStatus?status=available&status=available&status=available&status=sold&status=pending&status=pending&status=available&status=available&status=pending&status=available&status=available&status=sold&status=pending&status=sold&status=pending&status=available&status=available&status=available&status=sold&status=available
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/findByTags?tags=modi&tags=laudantium&tags=ea&tags=nihil&tags=ab&tags=quod&tags=quis&tags=voluptatem&tags=quia&tags=et&tags=cum&tags=et&tags=dolor&tags=odit&tags=deleniti&tags=est&tags=voluptate&tags=molestias&tags=illo&tags=id
[18:26:52] › [CLI] ℹ  info      GET        http://127.0.0.1:4010/pets/785
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets/863
[18:26:52] › [CLI] ℹ  info      DELETE     http://127.0.0.1:4010/pets/973
[18:26:52] › [CLI] ℹ  info      POST       http://127.0.0.1:4010/pets/939/uploadImage
# 一部略
[18:26:52] › [CLI] ▶  start     Prism is listening on http://127.0.0.1:4010

いくつかエンドポイントにアクセスして挙動を確認してみましょう。

  • GET http://127.0.0.1:4010/no_auth/pets?name=saepe

このエンドポイントにアクセスすると、スキーマのexampleで定義されたレスポンスが返ってきました。

$ curl -i 'http://127.0.0.1:4010/no_auth/pets?name=saepe'
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 34
Date: Fri, 23 Oct 2020 09:39:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5

[{"name":"a_name","photoUrls":[]}]

ちなみにこのエンドポイントはクエリパラメーター name が必須となっているので、パラメータが不足しているとバリデーションエラーで 422 を返します。

$ curl -i 'http://127.0.0.1:4010/no_auth/pets'
HTTP/1.1 422 Unprocessable Entity
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
content-type: application/problem+json
Content-Length: 376
Date: Fri, 23 Oct 2020 09:41:15 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"type":"https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY","title":"Invalid request body payload","status":422,"detail":"Your request is not valid and no HTTP validation response was found in the spec, so Prism is generating this error for you.","validation":[{"location":["query"],"severity":"Error","code":"required","message":"should have required property 'name'"}]}
  • GET http://127.0.0.1:4010/pets/785

前の例は認証不要なエンドポイントでしたが、このエンドポイントは認証情報がないアクセスは 401 となりました。

$ curl -i 'http://127.0.0.1:4010/pets/458'                                                                                                                   18:49:12
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
sl-violations: [{"location":["request"],"severity":"Error","code":401,"message":"Invalid security scheme used"}]
Content-type: application/json
Content-Length: 39
Date: Fri, 23 Oct 2020 09:49:46 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"code":-2147483648,"message":"string"}

securityで定義されているようにヘッダに api_key を追加すると正常なレスポンスが返ってきました。
api_key の値は問わず、存在しているかどうかが検証されるようです。

$ curl -i 'http://127.0.0.1:4010/pets/458' -H 'api_key: key'                                                                                                 18:51:54
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 126
Date: Fri, 23 Oct 2020 09:52:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"id":2,"category":{"id":1,"name":"Felis"},"tags":[{"id":1,"name":"pet"}],"name":"Fluffy","status":"available","photoUrls":[]}
  • POST http://127.0.0.1:4010/no_auth/pets

POSTの場合でも同じ要領で、リクエストボディの必須項目を定義している場合はバリデーションをかけてくれます。

サンプルのスキーマではこのエンドポイントに200番台のレスポンス定義がないためエラーになっています。
レスポンスのステータスや内容を決定するロジックはこちらに詳しく書かれています。

$ curl -i -XPOST 'http://127.0.0.1:4010/no_auth/pets' -H 'content-type: application/json' -d '{"name": "cat", "photoUrls": []}'
HTTP/1.1 500 Internal Server Error
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
content-type: application/problem+json
Content-Length: 148
Date: Fri, 23 Oct 2020 10:05:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"type":"https://stoplight.io/prism/errors#NO_SUCCESS_RESPONSE_DEFINED","title":"No response in the range 200-299 defined","status":500,"detail":""}
  • レスポンスの内容を動的に変更する

Prismには dynamic というオプションがあり、これを指定することで毎回違う内容のレスポンスを返してくれるようになります。
値はスキーマに定義されたプロパティの型に応じてFaker.jsで生成しているようです。

$ prism mock -d https://raw.githubusercontent.com/stoplightio/prism/master/examples/petstore.oas3.yaml
$ curl 'http://127.0.0.1:4010/no_auth/pets?name=saepe'
[
  {
    "name": "sin",
    "photoUrls": [
      "et nostrud labore veniam ut",
      "laboris enim",
      "pariatur cupidatat ad est ullamco",
      "exercitatio",
      "non eiusmod laboris",
      "aute in",
# 長いので省略

動的というかランダムなので細かい挙動の制御をするのは難しいかもしれません。
データの型を詳細に定義していればそれに応じた内容を返してくれるので、可能な範囲でスキーマのData Typesを書くようにしておくとよさそうです。
メールアドレスであれば type: string に加えて format: email 、テーブルのIDなら type: integer に加えて minimum: 1 などなど。

今後オプションでレスポンスデータを生成する挙動を細かく設定できるようになるとより使いやすくなりそうだと思いました。


まとめ

Prismを試したみた感じでは実行するのは簡単ですが、レスポンスのデータを細かく制御するのは難しそうです。
それでもOpenAPIのスキーマに基づいた挙動が保証されたモックサーバーを使うことで、APIの実装によりフロントエンドの作業がブロックされる状態は少なからず解消できるように思いました

まだ構想段階ではあるのでフロントエンドのエンジニアとも探りつつ、よりよい開発フローを作り上げていきたいと思います。