Hatena::ブログ(Diary)

ぶろぐ。@はてな Twitter

Web関係の技術記事中心のblog

2014-08-28 (Thu)

JSON Hyper-Schemaのようなサービスディスクリプションがうまくいかない理由

RubyKaigiの発表のために考えていたのですが、発表本編に詳しく入れられなくなりそうなので、まとまりないですがブログに書いてみます。


SOAPでいうWSDL(Web Service Definition Language)のような、サービスのインタフェースを定義・記述するためのしくみを総称してIDL(Interface Description Language)と呼びます。

JSON Hyper-Schema*1もIDL、サービスディスクリプションの一種といえます。

WebのIDLは今までにもたくさん出てきて、しかしうまくいっていません。

なぜでしょう?

@yohei さん曰く、

RESTは統一インタフェースなんだから、そこにさらにインタフェースを定義するのはおかしい

API Meetup #1 質疑応答より)

これはどういうことかというと、

HTTP method + URL という統一インタフェース(Uniform Interface)が相互運用性(interoperability)の源であり、付け足すとuniformでなくなるため相互運用性が損なわれる、ということです。

「いや、それだけじゃどこのURLにどのメソッドでどういうパラメータ投げればいいかわからないでしょ。だから定義しなきゃ」この発想が間違いの元です。

「どこのURLにどのメソッドでどういうパラメータ投げればいいか」はインタフェースとして集約して記述されるのではなく、ハイパーメディアコントロール(リンクやフォーム)としてそれぞれの表現の中に自己記述されるのが適切です。

集約することが必ずしも悪いわけではなく、効率的な面もあります。

データの意味に関すること(application semantics)は集約して記述していいでしょう。

URL、メソッド、パラメータに関すること(protocol semantics)は集約してはいけません。(ただしメディアタイプで定義する場合を除く)

集約は自動生成の誘惑を生み、密結合が構成されます。すると全く変更できなくなってしまいます。

サービスディスクリプションがうまくいく道

IDL派がやろうとしていることは、独自の問題領域に固有なメディアタイプの定義であると考えれば筋は通っているし、参考にするべきところが多いです。しかしそれはいったん定義すると変更できなくなり、interoperabilityは低下し、閉じこもる道になってしまいます。

例:Heroku Platform APIはJSON Hyper-Schemaを使用してinterfaceを記述し、独自のメディアタイプ application/vnd.heroku+json を定義しました。

定義した独自メディアタイプがデファクトスタンダードになって広く普及すれば、ある意味でIDLの成功になるでしょう。変更できないものになりますが。

参考

追記 (8/29)

Twitterでコメントをいただきました。ありがとうございます。

全体の前提として「変化できるpublic Web APIをつくるには」ということを考えていたのですが、書き落としていました。すみません。

「だから定義しなきゃ」は「だからどこかにまとめて定義を記述しなきゃ」というほうが正確でした。

「自動生成」が問題なのではなく、「事前の静的な自動生成」が問題、というべきでした。

ランタイムに動的に処理(生成)するのが良い方法で、そうすればむしろ変化に強いといえますし、密結合にはなりません。

しかし傾向として、集約されたマシンリーダブルなサービスディスクリプションが存在すれば、そこから静的にコード生成したくなるのではないか、と思います。

「最初にAPIを呼び出すとき」は、「最初にWebページを見るとき」と同じ考え方でかまいません。どこかほかのサイト(API)からのリンクで来るかもしれませんし、「ホームページ(/)」を直接GETするかもしれません(これが現実的でしょう)。

エントリポイントは暗黙に決まっていて欲しいという需要もありますので、RFC5785では Well-Known URI というものを定義しています。これを採用してもいいと思います。

*1:JSON Hyper-SchemaはJSON Schemaの一部ですが、JSON Schema自体はバリデーションなどに便利に使えると思います。

2014-08-21 (Thu)

#mozaicfm REST を聴いての感想

mozaic.fmでRESTの回が企画されているということを、API Meetup #1 のときに yohei さんから直接聞いていたのですが、ついにそれが公開されたので、喜び勇んで聴きました。

断片的に感想をツイートしたので、そのまとめです。

RESTの何が重要なのか

さすがの t_wada さん。アーキテクチャとしてもそうだし、アプリケーションフレームワークも「適切な制約」を設けることで設計のコストが下がる、という話の流れでした。

“Constraints are liberating”「制約は自由をもたらす」は僕が好きな言葉ですが、これを知ったのはDHHのRubyKaigi 2006の講演からです。(初出はどこか別のところなのかも?)

RESTの流行

原理主義者的発言をするなら、「REST API」と謳って世に出たWeb APIはただのJSON/XML over HTTPばかりじゃないか、ともいえるわけです。Richardson Maturity ModelでいうLevel 1かLevel 2のものばかり。

流行り始めの頃はもっとひどくて、平気でLevel 0のがあったので、だいぶ良くなったわけですが。

これは話中にも出ていたリアルタイムWebにもあてはまることで、WebSocketやWebRTCなどが開発されたのは、まさに課題の解決にRESTアーキテクチャが合わなかったからだと思います。

ただ少し話がずれますが、たとえばWebSocketは制約がなさすぎるため、相互運用の需要が出てきたら今後問題になるんじゃないかな、と懸念しています。

初期の頃WebSocket上にRESTを持ち込もうという動きがありましたが、当然のごとくうまくいきませんでした。

Web APIのバージョニング

Golangのパッケージバージョニングの話は、 yohei さんの通りちょっとレイヤーが違う話だと思いましたが、 Jxckさんの「バージョンがないものに後からバージョンを入れるのは困難」というのは、わかる気がしました。

とはいえ、Web APIの文脈でバージョンを語るには「バージョンとは何か」「バージョンの切り替え方」などをクライアント理解させなければいけません。それがなければ、バージョンが変わったときにただクライアントは動かなくなるだけです。

非互換な変更がない(拡張など)なら、クライアントはバージョンを意識する必要はありません。そして、クライアントが壊れず正常に動き続けることが一番大事なことです。

バージョニング自体よりむしろ、やむを得ずバージョニングを行ったときに、API利用者誘導する移行プロセスのノウハウのほうが重要かもしれません。

今のJSON Web APIが抱える問題

これは「予告」で。

「Webを支える技術」を改訂するとしたら?

JSONにリンクがない問題、今のところメディアタイプとLINKヘッダという2つの解決策がありますが、どちらも決め手に欠けます。でもそれで行くしかないでしょう。ビッグプレイヤーがよいデファクトスタンダード提示してくれることを期待します。改訂のころにはコンセンサスが得られているかも?(いてほしい)

予告

このあたりのこと、特に「今のJSON Web APIが抱える問題」に関することを9月18日のRubyKaigi 2014で話します。よろしくお願いします。

2014-04-17 (Thu)

RESTful Meetup vol.3 を開催しました #RWABookja

http://restfulwebapis.com/

4/12(土)の夜に『RESTful Meetup vol.3』を開催しました。

昨年の記事の通り『RESTful Web APIs』の読書会を月2回ペースで開催してきましたが、その後、著者のMike Amundsen(@mamund)さんから、ワークショップのために東京へ行くという知らせがあったので、これはイベントをやるしかない!ということで企画しました。

vol.3ということで、実は過去2回開催しています。そのときは『RailsにおけるRESTfulなURL設計勉強会』というタイトルで、かなりターゲットを絞っていたのですが、今回は「REST」「Web API」というかなり広いテーマにしました。このことでRuby/Railsに限らず多様な言語の人に集まってもらえたのがとてもよかったです。

ビールビザをつまみながら、というビアバッシュ形式にしたのですが、有料イベントなので少ないだろうと思っていたら予想を上回る多くの参加者が集まり、当初15人程度の予定が30人以上になりました。

@tkawa による紹介

まず、イントロダクションとして、『RESTful Web APIs』がどのような内容の本かを簡単に説明しました。

これ、実はイベント前日までもっと専門用語だらけの意味不明で長い内容だったのですが、@ruziaさんのレビューのおかげでわかりやすくすることができました。ありがとうございました!

最後のほうに入っているRubyのコードの部分は、自分の構想としてLTしようと思っていたものです。そのスライドはこちら。

実はアイデアはmicrodata DOM APIそのままだったのでした。

@mamund さん「Reusable APIs」

APIでやってはいけない10の話。簡単な日本語字幕をつけたバージョンでした。

@t_wada さん「Reviewing RESTful Web Apps」

@zigorou さん「How to bake delicious cookie

@koriym さん「HTTP IS ONE THING」

http://koriym.github.io/http-is-one-thing/

@Jxck_ さん「PUT & DELETE why html form doesn't support?」

@josolennoso さん「Scale your Service with RESTful API」

(公開なし)


非常に熱くて内容の濃い発表ばかりですごかったです。

運営的にはこういううれしい悲鳴も。

イベント運営は実はSikachu Meetupを参考にさせていただきました。アドバイスをくれた@sanematさん、@machidaさんありがとうございました。

当日の様子はRESTful Meetup vol.3 - Togetterまとめ#RWABookjaにまとまっています(@NEKOGETさんまとめありがとうございます)。

参加者のみなさん、発表者のみなさん、そして@mamundさん、本当にありがとうございました。

RESTful Meetup was the great event, thanks to the attendees, speakers, and @mamund!

あまりに濃すぎたので、ライトなものをもう一度やろうかなと思っていますがいかがでしょう?

2013-10-11 (Fri)

RESTful Web APIs 読書会を開催しました&第2回募集 #RWABookja

http://restfulwebapis.com/

春ごろからずっと待っていた“RESTful Web APIs”がようやくリリースされたので、10/10に第1回の読書会を開催しました。

よく行くShibuya.rbや、主催のひとりであるSendagaya.rbで告知したので、Rubyを使っている方が中心でしたが、PHPPerlを使っている方もいらっしゃって、初回から10人で開催することができました。ありがとうございます。


なんと本の公式アカウントで紹介されて、著者のMike Amundsenにもメッセージをもらいました!


進め方は、僕がかいつまんで文章の訳を読んでいきつつ、随時みんなで気になったことや疑問点などを話し合いました。

予習必須にしないで、できるだけ気軽に参加してもらいたいという考えでこのようにしたのですが、僕の英語力が貧弱なせいで、ところどころ訳に詰まってまわりの方にサポートしてもらうことに…。すみません&ありがとうございました。もう少しがんばりますのでまたサポートいただければうれしいです。

とりあえず次回も同じスタイルでやろうと思いますが、セクション単位で参加者の方にお任せしようかな、とも考えています。

次回は10/24(木) 19:30から開催します。まだSection 1の途中なので入りやすいです。本文には特定のプログラミング言語の記述はほとんど出てこないので、言語は関係なく、RESTやWeb APIに興味のある方は下記のイベントページからどしどしご参加ください!

2013-08-12 (Mon)

Deviseのルーティングが微妙なのでgemを作ってみた

Devise便利ですよね。簡単にリッチな認証機能がつくれます。

# config/routes.rb
devise_for :users
        new_user_session GET    /users/sign_in(.:format)          devise/sessions#new
            user_session POST   /users/sign_in(.:format)          devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)         devise/sessions#destroy
           user_password POST   /users/password(.:format)         devise/passwords#create
       new_user_password GET    /users/password/new(.:format)     devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format)    devise/passwords#edit
                         PUT    /users/password(.:format)         devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)           devise/registrations#cancel
       user_registration POST   /users(.:format)                  devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)          devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)             devise/registrations#edit
                         PUT    /users(.:format)                  devise/registrations#update
                         DELETE /users(.:format)                  devise/registrations#destroy
       user_confirmation POST   /users/confirmation(.:format)     devise/confirmations#create
   new_user_confirmation GET    /users/confirmation/new(.:format) devise/confirmations#new
                         GET    /users/confirmation(.:format)     devise/confirmations#show

いまいちなところ

Devise::RegistrationsControllerの役割があいまい

/usersが Devise::RegistrationsController に取られてるのはひどい。普通 UsersController になるはずでしょう。

PUT /usersDELETE /usersも地味にひどい)

さらにここによく使うresources :usersを足すとカオスに…。

cancel_user_registration GET    /users/cancel(.:format)          devise/registrations#cancel
       user_registration POST   /users(.:format)                 devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)         devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)            devise/registrations#edit
                         PUT    /users(.:format)                 devise/registrations#update
                         DELETE /users(.:format)                 devise/registrations#destroy
                   users GET    /users(.:format)                 users#index
                         POST   /users(.:format)                 users#create
                new_user GET    /users/new(.:format)             users#new
               edit_user GET    /users/:id/edit(.:format)        users#edit
                    user GET    /users/:id(.:format)             users#show
                         PUT    /users/:id(.:format)             users#update
                         DELETE /users/:id(.:format)             users#destroy

何をどう使えばいいんでしょうか、これ…。


何が問題なのか

  • 複数形であるusersは普通「ユーザー全体」をさすはず(コレクションリソース)。なのに registrations#update は「自分自身を更新」つまり「自分自身」をさしている。 #cancel, #edit, #destroy も同様
  • そもそもURLにも出てこない“registrations”っていったい何?

「自分自身」をさすリソースは1つしかないので単数形で表現できます。

resource  :user  # (a)単数
resources :users # (b)複数
resource  :users # (c)まぎらわしいのでよくない

(a)と(b)は別のリソースなので区別しましょう。

解決策(案)

devise-better_routes gemを使うと、ルーティングはこのようになります。

# Gemfile
gem 'devise-better_routes'
              users POST   /users(.:format)               users#create
           new_user GET    /users/new(.:format)           users#new
cancel_current_user GET    /current_user/cancel(.:format) current_users#cancel
  edit_current_user GET    /current_user/edit(.:format)   current_users#edit
       current_user PATCH  /current_user(.:format)        current_users#update
                    PUT    /current_user(.:format)        current_users#update
                    DELETE /current_user(.:format)        current_users#destroy

謎のregistrationsは消えて、コントローラがusersとcurrent_usersに整理されました。

このコントローラは用意されていませんが、基本は単にこんな感じで作ってやればOKです。

# app/controllers/users_controller.rb
class UsersController < Devise::RegistrationsController
end

# app/controllers/current_users_controller.rb
class CurrentUsersController < Devise::RegistrationsController
end

同様に、passwordとsessionもパスが変更されます。

オプション

/current_userって名前はいまいち…、というときは、オプションで変更することができます。

# config/routes.rb
devise_for :users, path_names: {current_user: 'me'}
    users POST   /users(.:format)     users#create
 new_user GET    /users/new(.:format) users#new
cancel_me GET    /me/cancel(.:format) me#cancel
  edit_me GET    /me/edit(.:format)   me#edit
       me PATCH  /me(.:format)        me#update
          PUT    /me(.:format)        me#update
          DELETE /me(.:format)        me#destroy

いい感じじゃないですか?

Deviseへissueを投げてみた

Thanks for the proposal. Yes, it make sense, even though we have originally decided to not pollute two namespaces for the same Devise controller. Unfortunately, this is a very backwards incompatible change, so any change of this dimension would have to wait until Devise 4.0 (and we just released 3.0). Fortunately, you can customize it on your own, as you did. :)


So I am closing this but leaving a note to myself that we could possibly revisit this (in a year or two).

Registration routes are confusing ? Issue #2505 ? plataformatec/devise ? GitHub

ってことで、リップサービスかもしれないけどDevise 4.0に期待w