AppsFlyer Push APIからのJSONデータを、Cloud Functionsで受け取り、Cloud Firestoreに流し込むまで

## 自己紹介
AppsFlyerというモバイル広告計測ツールの会社のソリューションアーキテクトです。前職ではIBMでデータエンジニアをやっており、DWHの構築などをネットワークやOSレベルのインフラレベルのこともやっていましたが、基本的にはオンプレだけでしたので、Google CloudやAWSのことをキャチアップしたいと考えているところです。

参考: [AppsFlyerとそのアトリビューションの仕組み](https://qiita.com/maktag/items/0a6d9a3969393aa8dcb4)



AppsFlyerは計測ツールですので、そのローデータをお客様側に持ち出していただいて保管・分析いただくことは通常のユースケースになります。その連携方式の一つがPush APIという機能で、AppsFlyerから発生したイベントをリアルタイムで指定のエンドポイントにデータを送信できるというものなのです。

ただ、対面のお客様はエンジニアではなくマーケターの方だったりしますので「エンドポイントってそもそも何？」というような質問を多々受けることもあります。

「Google CloudやAWSならきっと簡単に作れるんだろうなあ」と思いつつ、自分ではまだ作ったことはなかったので、今回Push APIからの情報をGoogle Cloud側で受け取り、格納する処理を調べながら試しに実装してみることにしました。

## Push APIのエンドポイントの仕様

[Push APIのサーバー要件](https://support.appsflyer.com/hc/ja/articles/207034356#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E8%A6%81%E4%BB%B6%EF%BC%88%E3%81%8A%E5%AE%A2%E6%A7%98%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%EF%BC%89)を確認すると、成功時にステータスコード200を返すことのできるGoogle Cloud外からアクセス可能なエンドポイントが準備できれば良さそうです。

![Screenshot 2022-12-24 at 14.01.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/523013c3-0733-d217-2b94-fd773e909d08.png)

※本番で使うのなら、IPアドレスのホワイトリスト化や、余分なポートを閉じておくようなセキュリティ上の配慮も必要そうですが、今回はこの部分は端折ることにします。

# Cloud Functionsでエンドポイントを作る

調べてみたところ、Cloud FunctionsがHTTPリクエストを受け取って簡単に処理できそうなので、これで実装します。

手始めに、認証なしのHTTPSトリガーのCloud Functionsを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/c163bb05-3421-0483-83fe-7ae730b303db.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/b8d7ec2c-3d79-1994-4b48-4818eaa8a0b8.png)

このNode.jsのコードを書き換えて、ステータスコード200とRequest body内のJSONをテキスト化して返すようにしました。

```javascript
const functions = require('@google-cloud/functions-framework');

functions.http('helloHttp', (req, res) => {
  res.writeHead(200, {
    "Content-Type": "text/html"
  });
  const responseMessage = JSON.stringify(req.body);
    
  res.end(responseMessage);
  console.log(responseMessage);
});
```

これをデプロイすると数分後に、Cloud Functionsが立ち上がり、エンドポイントとのURLも確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/831e4aa3-52d9-1529-2d50-e4333fb1cdf4.png)

試しにcurlコマンドを叩いてみると、

```shell
curl -X POST https://function-1-qabhtikgmq-an.a.run.app  -H "Content-Type: application/json" -d '{"Id": 123456, "name": "Makoto"}'    
```

問題なくRequest BodyのJSONがそのまま返ってきました。
```
{"Id":123456,"name":"Makoto"}  
```
Cloud Functionsの画面上のログの方からも確認できます。
![Screenshot 2022-12-24 at 14.57.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/af34fcd6-ef74-ee42-c8a4-148d0b174864.png)


## AppsFlyerからテスト送信してみる

AppsFlyerの管理画面「APIアクセス」からPush APIのエンドポイントを設定し、POSTでテスト送信してみると、「テストが正常の送信されました」とのメッセージ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/b7ce098e-f176-1207-3077-05c0c4b1da91.png)

Cloud Functionsのログ見ると、データがRequest BodyのJSONとして確かに受け取れていそうです。
![Screenshot 2022-12-24 at 15.00.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/e03e6d5f-bdd5-4cf8-163f-bbdca6ca0dcb.png)

ちなみにGETでのテスト送信も試したところ、データはURLのパラメータとして送られてきました。
![Screenshot 2022-12-24 at 15.01.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/6e02274e-06e6-89e9-c940-b470c196607a.png)

ここまでで、Cloud FunctionsにてHTTPリクエストを受け取るためのエンドポイントを作り、実際にテスト送信でデータを受け取れることを確認しました。

# Cloud Functionsで受け取ったデータをFirestoreに保存する

せっかくJSON形式でデータを受け取っているので、そのまま格納したいので、適当なNoSQLデータベースサービスがないかを探したところ、Cloud Firestoreが良さそうだとわかりました。

Cloud Firestore には、異なるユースケース向けに最適化された 2 つのモード（Datastore モードとネイティブモード）がありますが、今回はネイティブモードを選択しました。

Cloud Functionsから利用するには、@google-cloud/firestore のパッケージを利用するようですので、package.jsonにこの依存関係を追加します。

```json:package.json
{
  "dependencies": {
    "@google-cloud/functions-framework": "^3.0.0",
    "@google-cloud/firestore": "^6.0.0"
  }
}
```

また、同じプロジェクト内でCloud Firestore を有効化していれば、あまり細かい接続情報などを意識せずに、Firestoreとの接続ができるようです。
あとは任意のコレクションIDとドキュメント名を指定し、JSONデータを入れていけば良さそう。

今回は受け取ったRequest bodyをそのまま新規のドキュメントとして作っていきたいので、ドキュメントのIDを自動付与する`firestore.collection('af_push_api_data').add(req.body);`でデータを格納することにしました。

```javascript:index.js
const functions = require('@google-cloud/functions-framework');
const {Firestore} = require('@google-cloud/firestore');

// Create a new client
const firestore = new Firestore();

functions.http('helloHttp', (req, res) => {
  // Add a new document with a generated ID
  firestore.collection('af_push_api_data').add(req.body); 
  
  res.sendStatus(200);
});
```

上記のコードをデプロイし、AppsFlyer側からテスト送信したところ、Firestoreに無事にドキュメントが作られて、フィールドも入ってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/911a7332-7e16-6a6c-3de9-92f9b7cedd4a.png)

クエリビルダーで簡単にデータもみれますね。
Push APIでのデータ送信はリアルタイムに各種のイベントもどんどん連携されてくるので、面白いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73759/012c0e03-094c-0fb2-1908-c1663711f498.png)

# まとめ

一部コーディングのデバッグなどでは苦労しましたが、環境構築はほとんど不要で、簡単にできてしまうのがクラウドのすごいところだと実感しました。Google Cloudはもっと触って理解していきたいです。

本番利用では、さらにCloud Load Balancingなどで受けるようにして、セキュリティ面での対応も入れると良さそうですね。