リクルートテクノロジーズアドカレ14日目です!先日mod_mruby/ngx_mrubyのアドカレで記事を書かせていただきましたが,その勢いでngx_mrubyを使って実験的な試みをしてみました.
ProxyでバックエンドAPIを結合する
今年の前半ぐらいに盛り上がった話題で周回遅れ感があり若干の気恥ずかしさがあるのですが,サーバサイドでAPI実装が既に完了してしまっている/改修不能な状態で,クライアントアプリが要求するAPIの仕様がドラスティックに変わるというずれがあるケースで,クライアントサイドのエンジニアがサーバサイド側でAPIをある程度整形できる緩衝地帯的な仕組みがあるは面白いなと,kawasimaさんのdarzanaやnetflixの記事や同僚の話を聞いて考えておりました.SOA,SOAPやWDSLのことが思い浮かばれますが,最近覚えたproxyのリクエスト制御の仕組みを使って同じようにOracle Service busのようなものが作れるんじゃないか思いつきでちょっと作ってみました.
構成の概要
nginx/ngx_mrubyでレスポンス制御を行いますが,制御のロジックをRedisに格納して,それをngx_mrubyのハンドラで取得してevalするという大胆な仕組みで実現しています.redisへのロジック格納を外部から楽に行えるようにするため,管理用のwebアプリも付けています.
ハンドラの制御より抜粋
r = Nginx::Request.new
#redisから取得したロジックをeval
orchestrate = lambda{eval(score)}
r.content_type = "application/json"
Nginx.rputs orchestrate.call
デモ
プロトタイプを公開していて,一応デモを動かすことができます.一式構成できるDockerfileを用意したので,コンテナを立ち上げればすぐ動きます.
git clone https://github.com/prevs-io/Karajan.git && cd Karajan
docker build -t karajan .
docker run -it --rm -p 49080:80 -p 49081:8080 -p 49082:3000 --name karajan karajan /data/scripts/start.sh
デモは次のようなケースを想定して作っています.
- クライアント側アプリはある画面の描画のために
http://0.0.0.0:8080/api/contents
andhttp://0.0.0.0:8080/api/badge
の2つのAPIからデータを取る必要がある. - しかしアプリ側はできることなら1回のhttpリクエストだけで必要な情報を取得したいので,2つのリクエストを1つのリクエストにまとめたい.
このケースにしたがって,2つのリクエストをまとめるロジックをngx_mrubyのフックスクリプトとして書くと次のようになります.
api_endp_1 ||= WebAPI.new "http://0.0.0.0:8080"
api_endp_2 ||= WebAPI.new "http://0.0.0.0:8080"
contents = JSON.parse(api_endp_1.get('/api/contents').body)
badges = JSON.parse(api_endp_2.get('/api/badges').body)
JSON.generate(contents.merge(badges))
上記のロジックをngx_mrubyから使うために,管理アプリ経由でRedisに格納します.POSTリクエストでリクエストを受けるURIとbase64エンコードしたロジックの組を登録します.
#!/usr/bin/env ruby
require 'httparty'
require 'json'
require 'base64'
path = File.dirname(__FILE__)
score=File.open(File.join(path, 'score.rb')).readlines.join('')
url = 'http://0.0.0.0:49082/write_score'
#{<context_path> => <client logic base64 encoded>}
body = {'/' => Base64.encode64(score)}.to_json
# write logic
puts HTTParty.post(url, :body=>body)
ロジックの格納が完了したら,動作を確認してみます./orchestrate
以下にロジックが登録されます.
➜ karajan git:(master) ✗ curl http://0.0.0.0:49080/api/badges
{"badges":[{"badge1":"badge1"},{"badge2":"badge2"},{"badge3":"badge3"},{"badge4":"badge4"},{"badge5":"badge5"},{"badge6":"badge6"}]}
➜ karajan git:(master) ✗ curl http://0.0.0.0:49080/api/contents
{"contents":[{"item1":"description1"},{"item2":"description2"},{"item3":"description3"}]}
#2つのリクエストが1つにマージされた
➜ karajan git:(master) ✗ curl http://0.0.0.0:49080/orchestrate/
{"contents":[{"item1":"description1"},{"item2":"description2"},{"item3":"description3"}],"badges":[{"badge1":"badge1"},{"badge2":"badge2"},{"badge3":"badge3"},{"badge4":"badge4"},{"badge5":"badge5"},{"badge6":"badge6"}]}
まとめ
前段の話に加え,AWS Lambdaの発表などもあってああいう仕組みを作ってみたいなぁと思っていたんですが,Lambdaもそうであるようにロジックはクライアント側が触るものなのでやっぱりjsにした方がいいのかなと思いつつ,そうなるとngx_mrubyでなくnodeで実装したほうがよいな...と迷いましたが気づいたら作り終わってました.Orchestration Layerに絡む問題を把握しきれていないこともあり,proxyレイヤでorchestrationすることに対して現状作ったコンセプトでメリットを出すことができませんでしたが,関心のある分野なので今後もウォッチしていきたいと思っています.