Android + JSON-RPC shibuya.apk #10
Android + JSON-RPC
shibuya.apk #10
About Me
Shinobu Okano
@operandoOS
Mercari, Inc.
Souzoh, Inc.
shinobu.apk #3 やります!!
Androidについて語る!
shinobu.apk #1 を開催しました!
http://tech.mercari.com/entry/2016/02/12/122918
shinobu.apk #2 のパネルディスカッション
録音データとShow Notesを公開しました!
http://hack-it-iron.hatenablog.com/entry/2016/04/20/124823
http://shinobu-apk.connpass.com/event/40202/
Join!!shinobu.apk #3
JSON-RPC??
JSON-RPC
“JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol.”
JSON-RPC spec
http://www.jsonrpc.org/specification
RPC
• gRPC
• http://www.grpc.io/
• Go Conference 2016 SpringでgRPCの現状について発表してきました
• http://tech.mercari.com/entry/2016/04/27/145227
JSON-RPC 2.0
JSON-RPC
HTTP bodyのJSONでリクエストを記述する
JSON-RPC Request
{ "jsonrpc": "2.0", "method": "subtract", "params": [ 42, 23 ], "id": 1}
JSON-RPC Response
{ "jsonrpc": "2.0", "result": 19, "id": 1}
JSON-RPC Batch
1回の通信で複数のリクエストを送れる
JSON-RPC Request
[ { "id": 1, "jsonrpc": "2.0", "method": "shibuya.apk", "params": { ... } }, { "id": 2, "jsonrpc": "2.0", "method": "shinobu.apk", "params": { ... } }]
JSON-RPC Response
[ { "id": 1, "jsonrpc": "2.0", "result": { ... } }, { "id": 2, "jsonrpc": "2.0", "result": { ... } }]
何に使うの??
何に使うの??
何に使うの??
• タイムラインの投稿を取得
• 現在地の座標を住所に変換する
• バナーを取得する
以下のリクエストが1回のリクエストでできる
GET /home??
複数の処理をまとめたAPIは
仕様が壊れやすい
JSON-RPC Batch
• APIは個々の処理をメソッドとして定義
• クライアント側で個々の処理を1つのリクエストにまとめて呼び出す
JSON-RPC Request
[ { "id": 1, "jsonrpc": "2.0", "method": "GetTimeline", "params": { ... } }, { "id": 2, "jsonrpc": "2.0", "method": "GetBanner", "params": { ... } }]
JSON-RPC Response
[ { "id": 1, "jsonrpc": "2.0", "result": { ... } }, { "id": 2, "jsonrpc": "2.0", "result": { ... } }]
よさそう!!
じゃAndroidで実装するか!!
OkHttp使って書いてみる Request
OkHttpClient client;
JSONObject json = new JSONObject();JsonArray params = new JsonArray();
json.put("method","subtract");params.put(42);params.put(23);json.put("params",params);
RequestBody requestBody = RequestBody.create(MEDIA_TYPE, json.toString().getBytes());
Request request = new Request.Builder() .url("https://....") .post(requestBody) .build();Response response = client.newCall(request).execute();
OkHttp使って書いてみる Response
Response response = client.newCall(request).execute();// ... isSuccessfulのチェックとか...ResponseBody value = response.body();
JSONObject j = new JSONObject(value.string());int result = j.getInt("result");// return 19
これイケてますかね??
イケてないですよね...
いい感じのLibraryあるんじゃね… 検索検索
あまりない??
• jsonrpc4j
• https://github.com/briandilley/jsonrpc4j
• Batchに対応してない??
• retrofit-jsonrpc
• https://github.com/segmentio/retrofit-jsonrpc
• Batchに対応してない??
途方に暮れる日々...
あーなんでこんなAPIに なってるんだ...
うぅ…僕はわるくない... (。-ω-)
そうも言ってられない...
そうだ! 自分で作ればいいんだ!
作ってみた
※当日までにコードが公開できませんでしたすいません🙇
作る前に考えたこと
• 毎回同じ処理は書きたくない
• リクエストの型が決まればレスポンスの型が決まる
• リクエストとレスポンスを型で定義
• Batch リクエストをいい感じに受け取りたい
• Rxとか使っちゃう??
• 特定の通信ライブラリに依存しない
JSON-RPCのクライアント実装で大変だったこと
• Batch リクエストの受け取り方
• エラーハンドリング
• Retrofitの恩恵が受けられない…
レスポンスのパターン
• OK 1つのレスポンス
• OK 複数のレスポンス(2,3つ)
• NG 全レスポンスエラー
• NG 一部のレスポンスエラー
Batch リクエストの受け取り方
• 複数のリクエストを作る
• 作ったリクエストをまとめてAPIに投げる
• 複数のレスポンスが返ってくる
• ハンドリングしてレスポンスを通知する
複数のレスポンス...??
複数のレスポンス...??
• List<Object> ??
• List<?> ??
• List<JSONObject> ??
どれもイケてない
そうだ...Tuple💪だ!!
💪
筋肉 2つ 💪💪
@Getterpublic class Pair<F, S> { private final F first; private final S second;
private Pair(F first, S second) { this.first = first; this.second = second; }
public static <F, S> Pair<F, S> create(F first, S second) { return new Pair<>(first, second); }}
筋肉 3つ 💪💪💪
@Getterpublic class Triplet<F, S, T> { private final F first; private final S second; private final T thread;
private Triplet(F first, S second, T thread) { this.first = first; this.second = second; this.thread = thread; }
public static <F, S, T> Triplet<F, S, T> create(F first, S second, T thread) { return new Triplet<>(first, second, thread); }}
複数のリクエストを作る
// リクエストとレスポンスをクラスとして定義// リクエスト : GetBanner レスポンス : GetBannerclass GetBannerResponse { String url;}
// リクエストの型が決まればレスポンスの型が決まる = RequestType<レスポンスの型>class GetBanner extends RequestType<GetBannerResponse> { @Override public String getMethod() { return "GetBanner"; }
@Override protected Class<GetBannerResponse> getResponseType() { return GetBannerResponse.class; }}
複数のリクエストを作る// 同じように別APIのリクエストとレスポンスをクラスとして定義// リクエスト : GetTimeline レスポンス : GetTimelineResponseclass GetTimelineResponse { List<Item> items;}
class GetTimeline extends RequestType<GetTimelineResponse> { private final long timelineId;
GetTimeline(long timelineId) { this.timelineId = timelineId; }
@Override public String getMethod() { return "GetTimeline"; }
@Override protected Class<GetTimelineResponse> getResponseType() { return GetTimelineResponse.class; }}
作ったリクエストをまとめてAPIに投げる
GetTimeline getTimeline = new GetTimeline(1);GetBanner getBanner = new GetBanner();
RxApiClient rxApiClient = new RxApiClient();
// Observable<Pair<GetBannerResponse, GetTimelineResponse>>が返ってくるrxApiClient.responseFrom(getTimeline,getBanner)....
リクエストのクラスはJSONになってHTTP bodyに設定される
[ { "params": { "timelineId": 1 }, "method": "GetTimeline", "jsonrpc": "2.0", "id": "1" }, { "params": {}, "method": "GetBanner", "jsonrpc": "2.0", "id": "2" }]
複数のレスポンスが返ってくる + ハンドリングしてレスポンスを通知する
// 複数のレスポンスTupleに包んでRxに流す// responseFrom内でTupleに包む処理をしてるrxApiClient.responseFrom(getTimeline, getBanner) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<Pair<GetTimelineResponse, GetBannerResponse>>() { @Override public void call(Pair<GetTimelineResponse, GetBannerResponse> pair) { // Tupleからレスポンスを取り出す pair.getFirst(); // return GetTimelineResponse pair.getSecond(); // retrun GetBannerResponse } });
Thanks!