RESTfulなAPIがつらくなってきた話

2015-12-13

この記事はCyberAgent エンジニア Advent Calendar 2015の13日目の記事です。

昨日は同期のmatsuokahさんのブログでした。明日はhuydxさんのブログです。

はじめに

新卒3年目の鈴木(@yudppp)です。 入社してからJavaやったりNode.jsやったりフロントかじったりしていました。 ここ1年はGolangでサーバーサイドのエンジニアをしていました。最近はNetflixを見てゆったりした休日を過ごしています。 会社関連のちゃんとしたブログを書くのはじめてなので柔らかめなマサカリください。

2年以上RESTを意識してAPIを作りつづけていました。 RESTful APIについては同期の鈴木が紹介していたのでこちらを参考にしてください。 また悩んだときはWEB+DB PRESS Vol.82の特集を読み返したりしてました。

ただ開発を進めていくうちに、だんだんとRESTが辛くなってきました。

single page application(SPA)の場合、完全にRESTなAPIになりすぎるとクライアント側で多くのAjaxリクエストが必要になってしまい、結局レンダリングするまでの時間が掛かってしまったり、 WebだけでなくiOSやandroidなどのクライアントがある場合、アプリではこのプロパティ使うけどWebからは使わないとか、このちょっとした情報をWebで使いたいから追加してとかで追加していくとレスポンスが肥大化していってしまったり、かっちりRESTにしたつもりがいろんなものに振り回されてしまいます。

Webとアプリでエンドポイント分けたり、UAで返す内容変えたり、どんと構えて別のAPIを叩いてもらえば済む話なのかもしれないのですが、どうしたって無駄が生じてしまいます。

またクライアント側からみても、同じ情報を再度取得しないようにクライアント側にstore層をもって、二回目からそこから取得するようにしたり書くのも飽きてきました。

それらのRESTの辛さを解決するものとしてFacebookの考案したGraphQL/relayNetflixFalcorなどがあります。 GraphQLについては以前弊社のブログで秋葉原ラボの鈴木さんが「GraphQLについて調べてみた」で書いています。

今回はFalcorのすばらしさとGraphQLとの違い、実際に触ってみた触感について書いていきたいと思います。

Falcorとは

A JavaScript library for efficient data fetching

効率よくデータを取得できるライブラリです。 サーバーサイドはNode.jsで実装されています。

サーバーの実装自体は他の言語でも可能でGithubを見る限り,Dart,PHPとかありそうです。(どのくらいちゃんと動くかは未確認です)

特徴

1. One Model Everywhere

すべてのバックエンドのデータを1つの仮想JSONオブジェクトとして扱うことができます。 クライアントはJSONの一部の要素をメモリーに乗っているJSONを取得するかのように取得することができます。

model.json?paths=["user.name", "user.surname", "user.address"]

// GET /model.json?paths=["user.name", "user.surname", "user.address"]
{
  user: {
    name: "Frank",
    surname: "Underwood",
    address: "1600 Pennsylvania Avenue, Washington, DC"
  }
}

サーバーのエンドポイントがひとつだけなので、まとめて様々なリソースのリクエストを送ることが可能になり、RESTのときに問題になっていた多くのAjaxリクエストが必要になってしまっていた件が解消されます。

2. The Data is the API

FalcorではクライアントでJSONデータを扱う際に直接JSONを取得することはできません。代わりに下記のようにgetValueすることによって値を取得できます。

// this data is
// user: {
//   name: "Frank",
//   surname: "Underwood",
//   address: "1600 Pennsylvania Avenue, Washington, DC"
// }

var model = new falcor.Model({
  source: new falcor.HttpDataSource(“/model.json”)
});

// prints “Underwood” eventually
model.
  getValue(“user.surname”).
  then(function(surname) {
    console.log(surname);
  });

JavaScriptでよくあるget,set,call等の操作を知っていれば簡単に扱うことができます。 データの構造さえ知っていればAPIから取得することができます。

3. Bind to the Cloud

キャッシュの機構があり、getしたときに一度も取得していないデータであればAPIを叩き取得し、取得したことのあるデータであればAPIリクエストは走らずクライアントのCacheから勝手に取得してきてくれます。 仮想DOMといい、仮想なんちゃらは人間様が考えるべきことを本質だけにしてくれて本当にありがたいです。

またバッチの機能もあり下記のようにバッチのモデルを定義するとこの3つの問い合わせをまとめて受け付けることができます。

var log = console.log.bind(console);
var httpDataSource = new falcor.HttpDataSource("/model.json");
var model = new falcor.Model({ source: httpDataSource });
var batchModel = model.batch();

batchModel.getValue("todos[0].name").then(log);
batchModel.getValue("todos[1].name").then(log);
batchModel.getValue("todos[2].name").then(log);

// The previous three model requests only send a single request
// to the httpDataSource: "todos[0..2].name"

上記のサンプルの場合キャッシュに乗っていなくてもAPIのリクエスト数は最大1回までとなります。

GraphQLとの違い(個人の感想)

GraphQLもFalcorもRESTが辛くなってきたときの解決法です。 GraphQL(relay)はQuery言語として色々な値を取得することができますが、複雑で学習コストが高いです。

// relay sample
var fragment = Relay.QL`
  fragment on User {
    name,
    surname,
    address
  }
`;

Falcorは比較すると簡単なことしかできないため、学習コストが低く見通しがよい。

// falcor sample
model.
  getValue('user.["name", "surname","address"]').
  then(function(val) {
    console.log(val);
  });

個人的にはよくわからないQueryを毎回書きたくなく、Falcorを使ってシンプルに書ける恩恵の方が大きいと思います。

サーバーの実装はGraphQLの方が言語の種類も多く、採用事例も多いような気がします。

まとめ

ちゃんとRESTにしようとすると色々と辛くなっていった。 解決策としてGraphQLやFalcorを使って、そのあたりを気にしないで実装できるようになりたいです。 プロダクト環境で使ったことがないので使うタイミングがあれば使ってみたいなーと思ってます。

参考文献

<< isucon6で人権を失いました。
net/httpでRedirectさせない >>
@yudppp
Web engineer.