Google Cloud Pub/Subを使ったPC-モバイルアプリ間のデータ連携

#はじめに

PC <-> モバイルアプリ間のデータ通信にPublish/Subscribeメッセージモデルを利用しています。今はPubNubを使っていますが、スケールや価格を考慮してGoogle Cloud Pub/Subへの移行を検討しています。今回はGoogle Cloud Pub/Subをとりあえず使ってみた時のメモです。

#やりたいこと

1. PC Webのある画面でモバイルアプリからのデータを待ち受ける。(Subscribe)
2. モバイルアプリからPC Webにデータ連携。(Publish)

※ 今回の内容は、1Publisherに対して、1Subscriberを想定しています。PC Webとモバイル間でTopic情報を連携する必要がありますがこの記事ではその部分は省略します。

#Google Cloud Pub/Subの構成

Cloud Pub/Subの構成は以下のようになっていて、Publishする時は、事前に作成したTopicに対してメッセージを送信します。

[What is Google Cloud Pub/Sub?](https://cloud.google.com/pubsub/overview)

![スクリーンショット 2016-01-30 04.41.16.png](https://qiita-image-store.s3.amazonaws.com/0/34078/1d6887ce-e81d-1a6e-1a02-abcaebb4a7d9.png "スクリーンショット 2016-01-30 04.41.16.png")

#とりあえず使ってみましょう

[GithubにあるGoogle Cloud Node.js Client](https://github.com/GoogleCloudPlatform/gcloud-node)を使います。

##1. 事前準備

1, [Google Developers Console](https://console.developers.google.com/project)を開く

2, プロジェクトを作成、または既存のプロジェクトを選択

3, メニューからAPI Managerを選択して、以下APIを有効にする

* Google Cloud Datastore API

* Google Cloud Storage

* Google Cloud Storage JSON API

4, 認証情報メニューからサービスアカウントキーを作成後、JSONファイルを保存

![スクリーンショット 2016-01-30 04.57.28.png](https://qiita-image-store.s3.amazonaws.com/0/34078/e0058ae9-b60a-d40c-8431-4c2c93146a71.png "スクリーンショット 2016-01-30 04.57.28.png")

![スクリーンショット 2016-01-30 04.58.25.png](https://qiita-image-store.s3.amazonaws.com/0/34078/802729b1-ec6d-8618-9f67-8c37563bed04.png "スクリーンショット 2016-01-30 04.58.25.png")

5, ローカルPCに適当なディレクトリを作成して、Google Cloud Node.js Clientをインストール

```
$ npm install @google-cloud/pubsub --save
``` 

6, 5のディレクトリに認証用JSONファイルを保存

7, 以下のようなディレクトリ構造になります。(subscribe.js, publish.jsについては後述)

```
pub-sub-sample
  ├── subscribe.js
  ├── publish.js
  ├── sample-project-1234567890.json
  ├── node_modules
       └── gcloud
```


##2. subscribe.jsを実装

subscribe.jsでは以下を処理します。

1. Topic作成
1. Subscribe
1. データ受信後ログに表示
1. Topic削除
1. データ受信リスナー削除

```subscribe.js
const Pubsub = require('@google-cloud/pubsub');

const pubsub = Pubsub({    
    projectId: 'sample-project',
    keyFilename: '/tmp/sample-project-1234567890.json'
});

const topicName = 'info'
const subscriptionName = 'info-subscription'

// Create Topic
pubsub.createTopic(topicName, function(err, topic, apiResponse) {

    if(err){
        if (err.code === 6) {
            // topic already exists.
            topic = pubsub.topic(topicName)
        }else{
            // Error check e.g. Authentication Check
            console.log('err ',err);
            return;
        }
    }

  // Create Subscription
  topic.createSubscription(subscriptionName, (err, sub) => {
    if (err) {
        console.error("create subscription error topic:", subscriptionName)
      return;
    }

    subscription = sub;

    subscription.on('error', function(err) {
        console.log('err');
      });

    // onMessage Callback
    function onMessage(message) {
        console.log(JSON.parse(message.data));
        console.log(message.attributes);

        // Remove Listener
        subscription.removeListener('message', onMessage);

        // Delete subscription
        subscription.delete(function(err, apiResponse) {
            if(err){
                console.error("delete subscription error topic:", topicName)
                return;
            }
            console.log(subscription.name + " is deleted");
        });

        // Delete topic
        topic.delete(function(err, apiResponse) {
            if(err){
                console.error("delete topic error topic:", topicName)
                return;
            }
            console.log(topic.name + " is deleted");
        });
    }

    // Add Listener
    subscription.on('message', onMessage);

  });

});

```

##3. publish.jsを実装

publish.jsでは以下を処理します。

1. Publish

```publish.js
const Pubsub = require('@google-cloud/pubsub');

const pubsub = Pubsub({    
  projectId: 'sample-project',
  keyFilename: '/tmp/sample-project-1234567890.json'
});

const registerMessage = {
    data: {
      userId: 3,
      name: 'Stephen',
      event: 'new user'
    },
    attributes: {
      key: 'value',
      hello: 'world'
    }
};

// Publish
const topic = pubsub.topic('info');
const publisher = topic.publisher();
publisher.publish(Buffer.from(JSON.stringify(registerMessage)), function(err) {
  if (err) {
    console.log(err);
  }
});
```

##4. 実行

Terminalの画面を2画面用意して実行します。

###4-1. Terminal1(PC Web)

```
$ node subscribe.js
```

###4-2. Terminal2(モバイルアプリ)

```
$ node publish.js
```

###4-3. Terminal1(PC Web)

Publishした結果が表示されます

```
$ node subscribe.js
{ userId: 3, name: 'Stephen', event: 'new user' }
{ hello: 'world', key: 'value' }
info is deleted
```

#まとめ

なんとなく動くことは確認しました。次回はPubNubで懸念だった以下の件を確認する予定です。

* モバイルアプリからのPublish (Swift)
* 3G回線などのコネクション状態が悪い場合のエラーハンドリング。

#参考

[What is Google Cloud Pub/Sub?](https://cloud.google.com/pubsub/overview)

[Google Cloud Client Library for Node.js](https://github.com/GoogleCloudPlatform/gcloud-node)

[gcloud-node document](https://googlecloudplatform.github.io/gcloud-node/#/docs/v0.27.0/pubsub)

[Google Cloud Pub/Sub Node.js Sample: TypeError: Cannot read property 'on' of null](http://stackoverflow.com/questions/35031349/google-cloud-pub-sub-node-js-sample-typeerror-cannot-read-property-on-of-nul)

#TipMe

<a class="indiesquare-tip-button" href="https://widget.indiesquare.me/tip/a7bdb82ba7272bd7" target="_blank" data-vid="a7bdb82ba7272bd7" data-domain="indiesquare.me"><img src="https://dl.dropboxusercontent.com/u/72730511/paywith1.png"></a><script src="https://widget.indiesquare.me/tip/buttonjs" async="" type="text/javascript"></script>