By

JSON SchemaでAPI開発を自動化する

このエントリは弊社の英語ブログのAutomating your API with JSON Schema — Commerce Hack の翻訳です。

APIのドキュメントとクライアントライブラリの保守には苦労します。時間もかかるし、ドキュメントの更新をついつい忘れてしまうこともよくあります。私たちは、こういう作業をするのにいいツールはないものか、ずっと探していました。

そして見つけたのが JSON Schema です。これは本当にクールな技術で、私たちはこれを、APIのドキュメント生成、クライアントライブラリ内のロジック、そして自動化テストの中で活用しています。ここではその活用法を紹介したいと思います。

JSON Schema とは何か?

JSON Schema とは、JSON object の記述と検証のための標準で、概略はこの ドラフト にあります。 XML Schema をご存知の方は、同じようなものだと思ってください。JSON Schema 自身もひとつの JSON documentで、JSON のデータをエラーチェックするための方法が書かれています。ひとつ例を見てください。

 1{
 2  "$schema": "http://json-schema.org/draft-04/schema#",
 3  "type": "object",
 4  "properties": {
 5    "name": {
 6      "description": "Name of the customer",
 7      "type": "string",
 8      "pattern": "[a-zA-Z\s]+"
 9    }
10  },
11  "required": [
12    "name"
13  ]
14}

このスキーマを見ると、対象のオブジェクトは name というプロパティを持っていることを求められていて、その値は string (文字列) である必要があることがわかります。さらに、その文字列は [a-zA-Z\\s]+ というパターンにマッチする必要があります。これらのルールに従えば、このスキーマに合致したオブジェクトを生成することができます。たとえば、次の JSON オブジェクトは、上のスキーマに適合したオブジェクトです。

1{ "name" : "John Doe" }

この仕様を日本語で書くこともできますが、JSON Schema で書いておけば、プログラムからも使える仕様になります。自分たちのAPIに出てくるオブジェクトのために、このようなスキーマを書いておけば、必要な時にそれを読み出して使うことができます。たとえば、APIの処理の中の一部、データチェックの部分などを ライブラリにまかせることができます。

JSON Schema と Hyperschema

JSON Schema を補完するもうひとつのドラフト標準として、Hyperschemaがあります。これは、links というプロパティによって、APIを記述します。さきほどの例に、links を追加して、API のエンドポイントを定義してみましょう。

 1{
 2  "$schema": "http://json-schema.org/draft-04/hyper-schema",
 3  "type": "object",
 4  "properties": {
 5    "name": {
 6      "description": "Name of the customer",
 7      "type": "string",
 8      "pattern": "[a-zA-Z\\s]+"
 9    }
10  },
11  "required": [
12    "name"
13  ],
14  "links": [
15    {
16      "title": "Create",
17      "description": "Create a customer.",
18      "href": "/customers",
19      "method": "POST",
20      "rel": "create",
21      "schema": {
22        "$ref": "#"
23      },
24      "targetSchema": {
25        "$ref": "#"
26      }
27    }
28  ]
29}

links プロパティを見てください。/customers というエンドポイントが定義されています。schema プロパティは、このエンドポイントが受け付ける JSON オブジェクトの形式を定義しています。targetSchema は、その時返却される JSON オブジェクトの形式の定義です。どちらも、今定義しているスキーマを指しています。

スキーマの作成

実際の業務で使用すると、 JSON Schema は、おそらくずっと複雑になります。ですから、雛形生成や検証をツールを使用して行なうべきです。

私たちは Prmd という Heroku によって開発された Ruby のライブラリを使っています。これは、CLIベースで、簡単に API オブジェクトのための JSON Schema の雛形を生成してくれます。

# APIオブジェクトの雛形を生成
$ prmd init customer > schema/customer.json
$ prmd init store > schema/store.json

# ひとつのスキーマに結合する
$ prmd combine schema/ > schema.json

# スキーマを検証する
$ prmd verify schema.json

実際に、自分でスキーマを書きはじめる時には、JSON Schema について勉強する必要があるでしょう。私は Understanding JSON SchemaJSON Schema 公式サイト のサンプルをおすすめします。

実際の業務で使用されている例としては、 Heroku の JSON Schemaや 私たちが開発、運用しているペイメントゲートウェイの Komojuスキーマも公開しています

全てを同期し続ける

最初のバージョンのスキーマをリリースできても、バージョンアップの時は、関連するコードやドキュメントを全部間違いなく更新する必要があります。スキーマと稼動しているコードは常に一致していなくてはなりません。それを保証するツールが必要です。

最新の JSON Schema でコードをテストできれば、更新の時の作業モレがなくなります。そのためには、APIのリクエストとレスポンスを JSON Schema でチェックする必要があります。

レスポンスの検証

私たちは、json-schema という gem を使って、テストの中でAPIから返却するオブジェクトを検証しています。RSpec では、次のような簡単なマッチャーを追加することによって、オブジェクトがスキーマに準拠しているかどうかをテストできます。

1RSpec::Matchers.define :match_response_schema do |resource|
2  match do |response|
3    schema = File.read("schema/#{resource}.json")
4    data = JSON.parse(response.body)
5
6    JSON::Validator.validate!(schema, data)
7  end
8end

このマッチャーを使ったテストの例です。

1it "creates a customer" do
2  post "/api/v1/customers", {name: "John Doe"}
3  expect(response).to match_response_schema("customer")
4end

リクエストの検証

APIが外部から受け取ったリクエストを、JSON Schema を利用して検証するには、もう少し工夫が必要になります。私たちは、コントローラの内部ではなく、Concernとして共通の処理を書いています。これによって、links プロパティで定義されたスキーマを使った検証を共通化できます。

 1module ValidateWithJsonSchema
 2  extend ActiveSupport::Concern
 3
 4  included do
 5    before_action :validate_params
 6  end
 7
 8  def resource_name
 9    controller_name
10  end
11
12  def action_name
13    params[:action]
14  end
15
16  def validate_params
17    LinkSchema.new(resource_name, action_name, params).validate!
18  end
19end

LinkSchema#validate! は指定されたスキーマを読みこみ、json-schema gem を使って、action_name に対応するスキーマに対する検証を行います。APIに送られたパラメータがスキーマに対して Valid でない時には例外を投げます。

LinkSchemaのコードは、非常に複雑なものなのでここでは省略しました。

同じ処理を行うcommittee という gem もあります。これは、Rails コントローラの中ではなく、独立した Rack ミドルウエアとして、リクエストを検証します。このコードを参考にして、Concern のような共通処理を作ってもいいし、このミドルウエアをあなたの Rails アプリにマウントして使うこともできます。

実データの例

リクエストとレスポンスを項目の仕様だけで説明するより、実データの例を含めることで、APIを使用する開発者のためのドキュメントは、はるかにわかりやすくなります。しかし、バージョンアップ時には、そのサンプルデータも間違いなく更新しなくてはなりません。

RSpec と Rails を使っているなら、これを自動化するよい方法があります。RSpec の after hook に次のような処理を追加して、テストの中で使用されたリクエストとレスポンスをファイルに書き出す方法です。

 1RSpec.configure do |config|
 2  config.after(:each, :request) do
 3    file_path = Rails.root.join("schema/examples.json")
 4
 5    recording = {
 6      "verb" => request.method,
 7      "path" => request.path,
 8      "request_data" => request.request_parameters,
 9      "response_data" => JSON.parse(response.body),
10      "head" => response.status
11    }
12
13    key = "#{controller.controller_name}##{controller.action_name}"
14
15    output = {}
16    output = JSON.parse(File.read(file_path)) if File.file?(file_path)
17    output[key] ||= []
18    output[key] << recording
19
20    File.write(file_path, JSON.pretty_generate(output))
21  end
22end

hook の中では、 requestresponse が使えるので、これを JSON ファイルに書き出すことができます。上記の hook は、schema/examples.json というファイルに次のようなデータが書かれます。

 1{
 2  "customers#create": [
 3    {
 4      "verb": "POST",
 5      "path": "/customers",
 6      "request_data": {
 7        "name": "John Doe"
 8      },
 9      "response_data": {
10        "name": "John Doe"
11      },
12      "head": 200
13    }
14  ]
15}

このテストから抜き出したデータが、CIプロセスの中で、ドキュメントの中にサンプルデータとして挿入されます。これによって、更新漏れを無くし、コードとドキュメントを常に一致している状態に保つことができます。

ドキュメントの生成

JSON Schema と実データのサンプルがあれば、そこから API のドキュメントを生成することができます。JSON Schema はプログラムから読めるデータでもあるので、markdown のような形式のドキュメントを簡単に生成することができます。

 1<% @schema = JSON.parse(File.read("schema/customer.json")) %>
 2
 3<%= @schema["title"] %>
 4---
 5
 6## <%= @schema["description"] %>
 7
 8### Attributes
 9
10| name | type | description |
11| ---- | ---- | ----------- |
12<%= @schema["properties"].each do |key, value| %>
13| <%= key %> | <%= value["type"] %> | <%= value["description"] %> |
14<% end %>

Herokuと同じフォーマットの JSON Schema を使っていれば、Prmd によって ドキュメントを生成すること もできます。 私たちも Prmd と custom markdown templateによって、 Komoju API のドキュメントを生成しています。

API 利用者側のコードの自動生成

JSON Schema の利点の一つはコミュニティが活発なことで、その回りにたくさんのオープンソースのツール が開発されています。 現時点で、 ruby, nodejs, golang 用のクライアント生成ツールがあります。

JSON Schema はプログラムでの取り扱いが容易なので、この中にない言語を使っていても、同様のツールを作ることは簡単でしょう。

まとめ

JSON Schema を扱う一連のツールによって、自分でコードを記述する必要を劇的に減らすことができました。また、ドキュメントと API クライアントのコードを自動生成することで、 deploy した時に、全てが最新版であることを保証できるようになりました。初期設定には時間がかかるかもしれませんが、その努力は充分報われます。

ここでは、 JSON Schema についてほんのさわりだけを紹介しています。興味を持たれた方は、ぜひ、下記のリンクなどを参照してみてください。

参考リンク

  • Elegant API’s with JSON Schema - https://brandur.org/elegant-apis
  • Committee, a collection of ruby middleware for working with JSON Schema - https://github.com/interagent/committee
  • Heroku interagent repository - https://github.com/interagent
  • Understanding JSON Schema - https://spacetelescope.github.io/understanding-json-schema/
  • Official site - http://json-schema.org/

Written by Richard Ramsden, translated by Taku Nakajima.

一緒にユニークな決済サービスを作ってくれる Rails エンジニアを募集中です!
多国籍なメンバーと一緒に仕事をしてみませんか?詳細はこちらのリンクです:D