水曜日, 8月 27, 2014

RESTとJSON、スキーマ定義について思うところ このエントリーを含むはてなブックマーク


 mozaic.fm #7 REST#mozaicfm REST を聴いての感想、それから「Web+DB vol82のWebAPIデザインの鉄則」に触発されたので書こうと思う。

REST設計について


WebAPIを設計するうえでRESTが重要であることは周知のとおりである。

“Constraints are liberating”「制約は自由をもたらす」

@t_wadaさんがおっしゃっているように、RESTを前提にすれば、「アーキテクチャとしてもそうだし、アプリケーションフレームワークも「適切な制約」を設けることで設計のコストが下がる」という大きなメリットが生まれる。

しかし、相変わらずリソース設計やらインターフェース設計やらで悩んでおられる方も多いと聞く。 その一方で個人的には適切なフレームワークを使えばREST設計で悩まなくてもよいはず(※3)という思いもある。

インターフェース設計などの基本設計をフレームワーク側で用意してあげれば、普通に設計していくだけで自然とRESTfulになっていくような開発が可能になる。これはReflexWorksが目指しているところでもある。

そういう意味で、ちゃんとしたフレームワークさえあれば、今は開発者がRESTの基本をしっかり学んですべて設計する時代じゃないかもしれないと個人的には思っている。

URIの制約をどうするか


ではどのような制約を設ければ自然とRESTfulに設計できるのか。
まず私が一番大事だと思っているのはURIの制約である。

URIはリソースを指し示す重要な表現であるが、これは単にリソースの場所を示すものであって、表現形式や検索条件など他の情報を含めるべきではないと考えている。(ただし、クエリパラメータは別)

例えば、以下のURIはよくあるものだが、このなかにはバージョン番号や「API」で「datastore」であるということ、また階層によってコレクション、またはエントリであることを説明する情報がURIに含まれてしまっている。

/v1/api/datastore/{コレクションID}/{オブジェクトID}

私はこれをURIの「お仕着せ」と呼んでいる。

一方、単にリソースの場所を示すURIは以下のようになる。

/d/{リソースID1}/・・/{リソースIDn}

これにはバージョンやAPIなどを示す情報はなくコレクションやエントリの区別もない。
階層が/によって多段に表現されているが単にリソースがあることを示すだけである。
例えば、{リソースID1}/{リソースID2} は、リソースID1の配下にID2があることを示しているが、ちょうどファイルシステムのフォルダをイメージするとわかりやすいかもしれない。

/d/app/index.html とすれば、/appフォルダの下にindex.htmlが入っているというように直感的にわかる。これを、リソースにおいても/d/invoice/master/・・ などと階層で表現したいと考えている。

ちなみに、/v1/api/datastoreも階層表現されているように見えるがこの階層には意味はない。その証拠に、/v1_api_datastoreとしても同じことである。またURIの設計上だけで実際にエントリがあるわけでもない。
これはURLが単に固定されているだけの話であって制約というより不自由なだけである。

繰り返しになるが、URIにリソースのアドレス以外の特別の意味を持たせてはいけない。
そういう固定的でお仕着せ的な設計がAPIの齟齬を生む。そして最終的には管理できなくなりバージョニングが必要という話になってしまう。

URIが単にリソースを示すだけであれば、APIの仕様を見ながらコーディングする必要もなく、バージョニングも必要ない。(どうしても必要なら?v1とかクエリーパラメータで区別すべきというのが私の意見。<関連> You Need Not API verson2

ただ、先日ある開発者に、ReflexWorksにはGoogle Drive APIのような一覧ってないのですか?って聞かれたときには、世の中はそういうものなのかなあとは思った。(その開発者にとってみれば制約よりも仕様の方がわかりやすいということだった。(※1)

オブジェクトの中の項目をURIに含めるべきか


JSONオブジェクトの構造中の特定の位置を指し示すJSON Pointerというのがある。
例えば、以下のオブジェクトが/d/masterに入っていたとする。


{
  "customer" :[
    { "name":"foo" ,"id": "1"},
    { "name":"bar" ,"id": "2"},
    ・・・
  ],・・・
}

JSONPointerでアドレスを/customer/0/idとすれば、customer配列の0番目のidという意味になる。これを、URI(/d/master)にアドレスを直結して、/d/master/customer/0/id と表現するのはあまりうまくない。

なぜなら、先ほどのリソースの定義では、customerの下に0というリソースがあり、その下にidというリソースがあることになってしまうからだ。

HTMLの場合は同一文書内のフラグメントには#を使うが、リソースも同様であり、あくまでmasterの中なので、私なら/d/master#customer[0].id と表現する。この方がJSONっぽくて自然だと思う。

クエリパラメータによる表現の区別


では、リソースの階層表現においてコレクションやエントリをどのように区別すればいいだろうか。

私はクエリパラメータを使って区別すればいいと考えている。
実際にReflexWorksでは、?f(feed)でコレクション、?e(entry)でエントリを意味する。

また、リソースというのはデータの集合であり、その表現はJSONやXMLやその他どの様なフォーマットであっても同様に扱えるのが理想である。ReflexWorksでは、デフォルトはJSONだが、?xでxml、?mでMessagePackのフォーマットで表現できる。

もっといえば、HTMLコンテンツであっても画像であっても同じリソースと考えることができるわけで、GET /d/app/index.html でも、 GET /d/masterでも区別する必要はない。
フレームワーク実装の難易度は上がるかもしれないが、こうすることで全てのリソースに対してRESTで統一できる。


クエリパラメータを使っての条件絞り込みは直感的でよい実装だと思う。ReflexWorksでも、?の後ろに絞り込み条件を複数指定することができる。そのときのパラメータは以下のようにエントリの中の項目(subInfo.favorite.foodやpriceなど)となる。-eq-(=)や-lt-(<)は条件式である。
例) 
http://xxx.xxx/{URI}?f&subInfo.favorite.food-eq-egg&price-lt-5000&l=20
リソースを直列分割して扱う「?fields」というパラメータをよく見かけるが、個人的には好きにはなれない。これは、指定されたフィールドのみを対象としたリソースを得るために使われるものだが、リソースの最小単位=エントリの括りがなくなってしまい曖昧になってしまう。

上記の絞り込みの条件で得る結果はフィード(コレクション)もしくは、エントリであるべきだ。

リンクがない問題と語彙の導入


XML全盛の頃はATOM FeedのセマンティクスとRESTによってルールはうまく整理されていたように思う。(結局流行らなかったが)


ATOMの語彙は以下のようなシンプルなものであった。

title
id
link
 title
 rel
 href
content
・・

しかし、JSONによってXMLが駆逐されてしまった。それに伴い、SOAPもWSDLもATOMも無くなった。JSONがもたらした不毛の荒野にはXMLやATOMが培ってきた財産(スキーマやリンク)は跡形無く見当たらない。

リンクのないデータは扱いにくい。PrimaryKeyのないデータと同じようなものだ。
JSONにはメディアタイプとLINKヘッダという2つの解決策があるらしいが、エントリに入らないと扱いにくいし、特にコレクションをどう表現するかが課題だと思う。


JSON Schemaでは以下のようにidやlinksという項目が導入されている。

title
id
type
links
 title
 rel
 href
 mehod
 mediaType
 targetSchema

リンクについていえば、AtomよりJSON Schemaの方がむしろ語彙数が多くなっているのは皮肉である。Atomに比べてJSON Schemaはむしろ冗長になっている。
リソースは自由に変換されるべきということは述べたが、私はリソース定義にはmethodやmediaType、targetSchemaという項目は必要ないと思う。

結局、ATOMの語彙でよかったのではないか。ATOMのJSON表現で。

JSONでスキーマを書くべきか


JSONもちゃんと設計したいということで構造を含むスキーマ設計がやりたくなるのはわかる。それに、スキーマレスだから
設計しないでもOK(ヒャッハー)というわけにはいかない。特に業務システムであれば。

きちんと設計したうえで必要なときにいつでもスキーマを変更できるような柔軟なソフトスキーマが望まれていると思う。

ただAtomやXMLを削ってJSONを拡充するという発想は何か間違っている気がする。


今はJSON全盛とはいえ、JSON SchemaはWSDLと同じ匂いがする。
ATOMに比べても語彙数が増えている。JSONに語彙を追加するのはHTML5で語彙を追加するのとはわけが違うと思うのだが、それでもなお、似たようなものを導入するつもりなのだろうか。

たしかに、JSONはXMLよりシンプルで、基本的な値などをやりとりするだけであれば、XMLよりもJSONの方がずっと簡単だし、JavaScriptを使っているのであればJSON形式は自然な形式である。

しかしスキーマは別の話だ。何でスキーマをJSONで定義するの?という素朴な疑問は湧いてくる。


構造を定義するのにお世辞にも読みやすいとはいえないJSONを使う意味は本当にあるのだろうか。

XMLはHTMLに似て宣言的なのだしマークアップに適している。スキーマ定義も宣言的なのだからXML使う方がまだましではないか。

リソース定義のためのシンプルなスキーマ言語の必要性


そして、そもそも考える順序が逆ではないかということに気づく。

先に述べたように、JSONはリソースの一つの表現にすぎないわけだから、リソースはJSON前提で考える必要はなく、逆にJSONに縛られて考えてもいけない。

JSONを拡充する方向で上がっていってもだめで、つまりは、「JSONやXMLなどの表現にとらわれない汎用的な」リソース構造を定義するスキーマ言語があればよいという考えに至る。(※2)

例えば、以下のように、項目名や型、バリデーションルールなどを1項目につき1行で記述できれば便利である。ルールは、()に型、省略でString、{}に値の範囲、=の右辺に正規表現、!で必須項目などとする。



master
 customer
  name=^.{0,50}$
  id(int){0~999999}!
  ・・・


きっとEXCELの定義書でも1行1項目で管理しているだろうから、そこから書き起こしたテンプレートに簡単にルールを記述していける。

JSONは可読性においてXMLに及ばず、利便性においてEXCELに及ばない。
JSON Schemaで管理するのはいいが結局はEXCELを捨てきれないだろう。
プロジェクトの共有文書が相変わらずEXCELのままであれば、JSONSchemaで管理すると言った人が2重メンテするハメになるのは目に見えている。

ReflexWorksでも上記のようなシンプルなスキーマ定義を採用している。そして、スキーマからエンティティオブジェクトを自動生成している。


テンプレートに項目名を記述することで自由にスキーマを定義でき、運用中に項目の追加変更が可能である。ソフトスキーマであり更新すると直ぐにシステムに反映する。

また、型の指定、親子関係や繰り返しといった構造の定義、必須チェックや最大/最小チェック、バリデーション、Index、暗号化などを指定できる。

(詳しくは、仕様の「テンプレートによるスキーマ定義」を参照してください)


(追記)

(※1) このように感じる方は意外と周りに多い。特にサービス(ビジネスロジック)を含めて考えると可読性や振る舞いの予想という意味でURI固定の方がわかりやすいという。ここではあくまでサービスを含めない静的なリソースのことを主に想定している。(ReflexWorksの/dは静的なdataの意味で、サービスは/pのプロバイダというように使い分けている。)

 (※2) JSONとXMLは等価ではないし互いに可換でもないので、1つのスキーマで両対応は無理。サブセットしかサポートできないというのはおっしゃるとおり。https://twitter.com/makotokuwata/status/504954681085263872 ただ、構造や型、バリデーションなどを汎用的に定義することは可能だし、ReflexWorksで実際にやっていることでもある。


(※3) 悩まなくなったらものづくりの仕事つまんなくない?との意見をいただいた。

2 件のコメント:

kuwata さんのコメント...

> JSON SchemaはWSDLと同じ匂いがする
気持ちはわかりますが、比較する対象が違うように思います。
JSON Schemaと比較すべきはXML SchemaやRELAXINGであり、WSDLと比較すべきは API Blueprint や RAML や Swagger や Slate ではないでしょうか。

文章を拝見しても、「スキーマやAPI仕様を定義することの是非」と「スキーマのフォーマットにJSONを採用することの是非」が混じっているため、どっちの話をしているのか把握しづらいと感じました。

> XMLはHTMLに似て宣言的なのだしマークアップに適している。スキーマ定義も宣言的なのだからXML使う方がまだましではないか。
前半と後半が繋がってないような。「マークアップに適しているから宣言的な記述に向いている」という主張だと推測しますが、その理由が書かれてないし、説得力が弱いと感じました。

> 何でスキーマをJSONで定義するの?という素朴な疑問
XML SchemaやRELAXINGがXMLで書かれてあるのと同じようなものでしょう。スキーマ定義そのものをスキーマで検証したいということ。

またスキーマを専用の記法で書くと、表現力と拡張性が低いです。たとえば ttp://reflexworks.jp/documentation.html の記法だと、以下のようなことはありませんか?
・enumが表現できない(とりうる値を列挙できない)
・(RDBMSでいうところの)unique制約が表現できない
・文字列や数値の長さを直接的に表現できない(正規表現の'{0,30}'がいいとは思えない)
・「X以上Y以下」は書けても「Xより大きくかつY未満」は書けない

仕様を**より正確に**表現しようとすると、どうしても表現力を高める必要があり、そのためには専用記法よりJSONのほうが拡張性が高いと思います(高い表現力が必要ない人もいると思いますが、それはまた別の話)。

あと、Excelの使用を前提にするならプルダウンやチェックボックスを使えばよく、それこそ独自記法はいらないと思いました。

takezaki さんのコメント...

コメントありがとうございます。
前半はごもっともな部分なので省略します。

プロジェクトで実際に数百ぐらいエンティティを記述してみるとわかると思いますが、JSON Schemaのような複雑な記法では正直やってられません。記述に膨大な工数がかかってしまいます。もっと簡易なものにしないと実用的ではないと思います。

私は完全なスキーマ検証や高度な記法より求められるのは、モデルを(8割〜9割ぐらいで)それなりに記述できることだと思っています。それでも実際のプロジェクトでは相当な効果を発揮します。

スキーマで記述できない部分は別途チェック用のロジックを書くことでフォローすればいいと思っています。

というか、たとえ完璧で高度なスキーマがあったとしても、結局はロジックを書かないといけない部分が残るわけで、スキーマ表現の完璧さを追求する意味はないように思います。

 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。