Action CableのJavaクライアントを実装してみた

# TL;DR

* Rails5でリリースされたAction CableのJavaクライアントを実装した
* https://github.com/hosopy/actioncable-client-java

# Action Cable

[Rails 5.0がリリースされました](http://weblog.rubyonrails.org/2016/6/30/Rails-5-0-final/)が、目玉機能の一つに**Action Cable**があります。

詳細は[Action Cable Overview](http://railsguides.jp/action_cable_overview.html)に譲るとして、Action Cableの概要はこんな感じです。

* RailsとWebSocketの統合を容易にするフレームワーク
* WebSocketのサブプロトコル的なものを定義して、Pub/Subの仕組みを提供する
    * ただし、実際にはサブプロトコルとしては定義されていないようなので[^1]、[socket.io](http://socket.io/)のようなものだと捉えれば良いと思います。

[^1]: [WebSocket Protocol Registries](http://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name)


実は、まだ本家の開発がアルファ版だった半年以上前に、興味本位でJavaクライアントを実装していたのですが、当時は未確定な要素も多かったため、ある程度完成したところで放置していました。

今回、Rails 5.0が正式にリリースされたことを受け、せっかくなので色々整理して[0.1.0](https://github.com/hosopy/actioncable-client-java/tree/0.1.0)としてリリースすることにしました。

# 紹介

GitHubのリポジトリはこちらになります。

https://github.com/hosopy/actioncable-client-java

[README](https://github.com/hosopy/actioncable-client-java/blob/master/README.md)の焼き直しですが、使い方を簡単に紹介します。

## Gradleによる導入

[JitPack](https://jitpack.io/)経由で、Gradleによる導入が可能です。

```groovy
repositories {
    jcenter()
    maven { url "https://jitpack.io" }
}

dependencies {
    compile 'com.github.hosopy:actioncable-client-java:0.1.0'
}
```

## 基本的なインターフェース

`ActionCable`,`Channel`,`Subscription`など、基本的にはJavaScriptのインターフェースと世界観を合わせています。
パラメータの受け渡しについては、GSONに依存することにしました[^2]。

[^2]: イケてない気がするので考え直したい

```java
// 1. Setup
URI uri = new URI("ws://cable.example.com");
Consumer consumer = ActionCable.createConsumer(uri);

// 2. Create subscription
Channel appearanceChannel = new Channel("AppearanceChannel");
Subscription subscription = consumer.getSubscriptions().create(appearanceChannel);

subscription
    .onConnected(new Subscription.ConnectedCallback() {
        @Override
        public void call() {
            // Called when the subscription has been successfully completed
        }
    }).onRejected(new Subscription.RejectedCallback() {
        @Override
        public void call() {
            // Called when the subscription is rejected by the server
        }
    }).onReceived(new Subscription.ReceivedCallback() {
        @Override
        public void call(JsonElement data) {
            // Called when the subscription receives data from the server
        }
    }).onDisconnected(new Subscription.DisconnectedCallback() {
        @Override
        public void call() {
            // Called when the subscription has been closed
        }
    }).onFailed(new Subscription.FailedCallback() {
        @Override
        public void call(ActionCableException e) {
            // Called when the subscription encounters any error
        }
    });

// 3. Establish connection
consumer.connect();

// 4. Perform any action
subscription.perform("away");

// 5. Perform any action using JsonObject(GSON)
JsonObject params = new JsonObject();
params.addProperty("foo", "bar");
subscription.perform("appear", params);
```

## Channelへのパラメータ指定

先ほどの例では`Channel`にパラメータを指定していませんでしたが、パラメータを指定することもできます。

```java
Channel chatChannel = new Channel("ChatChannel");
chatChannel.addParam("room", "Best Room");
Subscription subscription = consumer.getSubscriptions().create(chatChannel);
```

パラメータに指定可能なデータ型としては、`Number`, `String`, `Boolean`,`JsonElement(GSON)`です。

```java
chatChannel.addParam("room_id", 1);
chatChannel.addParam("room", "Best Room");
chatChannel.addParam("private", true);
chatChannel.addParam("params", new JsonObject());
```

## カスタムSubscriptionの定義

基本的にメッセージの送信は`Subscription#perform("action_name", params)`で実行しますが、メッセージの種類に対応したメソッドを定義できたほうが便利な場合があります。

そこで、アノテーションを利用したカスタムSubscriptionの定義を行うことができます。

```java
public interface ChatSubscription extends Subscription {
    /*
     * Equivalent:
     *   perform("join")
     */
    @Perform("join")
    void join();

    /*
     * Equivalent:
     *   perform("send_message", JsonObjectFactory.fromJson("{body: \"...\", private: true}"))
     */
    @Perform("send_message")
    void sendMessage(@Data("body") String body, @Data("private") boolean isPrivate);
}
```

`Subscriptions#create()`の第2引数に、インターフェースを指定することで、インターフェースに対応した`Subscription`を生成することが出来ます。

```java
Channel chatChannel = new Channel("ChatChannel");
ChatSubscription subscription = consumer.getSubscriptions().create(appearanceChannel, ChatSubscription.class);

consumer.open();

subscription.join();
subscription.sendMessage("Hello", true);
```

## Option

その他、いくつかOptionがあるのですが、長くなりそうなので詳しくは[README#Options](https://github.com/hosopy/actioncable-client-java/blob/master/README.md#options)を参照して下さい。

# Swift版

Javaと一緒にSwiftのクライアントも開発していたのですが、~~途中で面倒になった~~既にGitHubに公開されているものがあったため、Swift版はお蔵入りとなる予定です。。

[danielrhodes/Swift-ActionCableClient](https://github.com/danielrhodes/Swift-ActionCableClient)

# 2017/09/04: Kotlin版を作りました

Kotlinの勉強ついでに...

https://github.com/hosopy/actioncable-client-kotlin