のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

perlでWEB APIを複雑に扱いたい人向けのSpicaというモジュールを書いている話

インターネット、便利ですよね。TwitterFacebookニコニコ動画とか、みんないろんなWEBサービス使ってますよね。

一般的に使ってる人や、中にはYoutubeの動画をNoPasteみたくペタペタできる自作のWEBサービスを作ったり、IRCからニコ動の動画検索できるようにしたり。
エンジニアなら日頃の些細な面倒くさいを簡単にしちゃいたくなりますよね。

そう、WEBのエンジニアなら様々なWEB APIを使いこなしてこそです。
自分でAPI書いたり、人が作ったAPIを使ったり。もはや日常茶飯事。息をするようにAPIを叩いているはずです。


さて、PerlにはLWP::UserAgentやFurlといったAPIを叩くためのライブラリがありますね。
とあるAPIをGETで叩いてJSONを受け取りたいなんて時も、次のようにすれば一瞬です。

use Furl;
use JSON;
use Log::Minimal;

my $client = Furl->new(agent => 'Sample');
my $response = $client->get('http://example.com/json');

my $data = JSON->new->utf8->decode($response->body);

infof ddf $data;

ちょっぱや便利!


・・・しかしですね。大規模なプロジェクトやいくつものAPIを一つのプロジェクトで使いわけようとすると、さすがに生でLWPやFurlを叩くことにも限界を感じるようになるんです。
APIから取得したデータを再利用するために加工なんてしようものなら、JSONだったりXMLだったりするデータを解析してDateTimeに変換したり正規表現で書き換えたり。。。
だんだん管理する方も「これなにやってんの?」ってなってくるんですよ。たとえ自分が書いたコードでも。
ちなみに僕はこの一年の間で、8種類くらいのWEB APIをそれぞれ実装したわけです。FurlだったりLWPだったりを使って。そのたびに、独自のラッパーを用意したり、プロジェクト毎に違う書き方だったり、僕が手をいれる前の人の歴史的経緯に従属してたりして、もはやカオスなんです。
管理しきれるかこんなの\(^o^)/


ところで、みなさんORMapper使ってますか? 使っていないのであれば、Tengをおすすめします。TengはDBIx::Skinnyの良いところを残しつつ、よりシンプルにしかし柔軟に利用できるORMapperです。TengのRowクラスは非常に拡張しやすく、しかし所在が明確なので(よほど奇特なことをしない限り)直感的に実装できるスグレモノなんです。


僕はこのTengさんを愛用しながら、「あーAPIもTeng::Rowみたいに拡張しやすければ使いやすいしこんな複雑なクラスを毎度毎度毎度毎度書かなくて済むのにー」と思ったわけです。


・・・それだ(゚∀゚)


というわけで、書きました↓


p5-Spica


いや正しくは、『書いてます』。まだ開発段階で煮詰め切れてないところが多々ありますので気になる方はこっそり使ってみてもいいけど動作は保証しないよ。テスト書いてないから←


Tengをモロパクリさせてもらって、TengみたいにAPIを扱えるライブラリ、を目指そうとしたんですが、とあるかるぱちゅぱちゅぱ先生のアドバイスもあって、ちょっと方向転換中です。

基幹にMouseを使ってますが、とりあえず手っ取り早く動かしたかったから大好きなMouseを使っているだけで、それ以上に意図は無かったので、MooにするかMouse辞めるかするかもしれません。


使い方としては、READMEにある通り、適度にSchemaを書いてそいつをSpicaに食べさせて上げるとお手軽にfetchしてくれて、かつRowオブジェクトを生成してくれます。IteratorとかRowクラスはまじパクリです。

package MySchema;
use Spica::Schema::Declare;

client {
  name 'profile';
  endpoint 'single', '/profile', [qw(user_id)];
  endpoint 'search', '/users', [];
  columns qw(user_id name text);
  inflate text => \&inflate_text;
};

package main;
use Spica;

my $spica = Spica->new(
  host         => 'example.com',
  schema_class => 'MySchema',
  parser       => Spica::Parser::JSON->new,
);

my $profile = $spica->fetch('profile', 'single', +{user_id => $user_id});
isa_ok $profile => 'Row::Profile';

can $profile => qw(user_id name text);

という感じで書けます。
`endpoint`は第一引数にendpoint名、第二引数に対象APIのパス、第三引数に必須パラメータのキーを指定できます。第一引数は省略可能で、

    endpoint '/profile', [qw(user_id)]

という書き方もできます。この場合は最後に実行されたendpointしか保持されないので注意が必要です。

最近はやりのRESTFulなURLにも対応したいという場合には以下の様にしていするとよいです。

    endpoint '/profile/{user_id}', [qw(user_id)];

こう書くと、

$spica->fetch('profile', +{user => 'hogehoge'});

と実行したタイミングで、↓のようにパスが設定されます

http://example.com/profile/hogehoge

`{}`でくくったワードをfetchメソッドのパラメータのキーで置換しているのです。便利でしょ!(自画自賛


というわけで、複雑にWEB APIを扱うためのSpicaというモジュールを書いているというお話でした。意見どしどしお待ちしてます!←