RevenueCat WebhookをAWS API Gateway、Rust Lambda、DynamoDBで処理する

RevenueCatは、iOS、Android、Webなど様々なプラットフォームでアプリ内サブスクリプションを簡素化する強力なサブスクリプション管理サービスです。その主要機能の1つは、購入、更新、キャンセルなどのサブスクリプションイベントについてバックエンドに通知するWebhookを送信する能力です。この記事では、AWS API Gateway、AWS Lambda（Rustで実装）、およびストレージ層としてDynamoDBを使用して、RevenueCat Webhookを処理する方法を探ります。

![RevenueCat-Webhooks with AWS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887819/8af63fd5-705c-983d-48e9-b334c03a9315.jpeg)

## アーキテクチャ概要

アーキテクチャは以下の主要コンポーネントで構成されています：

1. **RevenueCat Webhook**：RevenueCatはWebhookイベント（サブスクリプションの更新など）をAWS API Gatewayに送信します。
2. **AWS API Gateway**：API Gatewayは、Webhookリクエストのエントリーポイントとして機能し、それらをLambda関数にルーティングします。
3. **AWS Lambda（Rust）**：RustでUnixmplemented Implementation of 関を処理し、保存に適した構造に変換します。
4. **DynamoDB**：この NoSQL データベースは、将来の参照と分析のためにサブスクリプション関連データを保存します。

### 前提条件

- **Rust**がインストールされ、AWS Lambda環境へのクロスコンパイルの準備ができていること。
- **AWS CLI**がインストールされ、設定されていること。
- インフラストラクチャ・アズ・コードのデプロイメントのための**Terraform**（オプションですが、強く推奨されます）。

---

## ステップバイステップガイド

### ステップ1：API Gatewayのセットアップ

API GatewayはRevenueCatがサブスクリプションイベントが発生したときに呼び出すパブリックエンドポイントとして機能します。以下は設定方法です：

1. **API Gatewayの作成**：
AWSコンソール、CLI、またはTerraformなどのインフラストラクチャ・アズ・コードツールを使用してHTTP APIを作成できます。このガイドでは、API Gatewayをセットアップするためのterraformコードを示します。

    ```
    resource "aws_api_gateway_rest_api" "revenue_cat_webhook" {
      name = "revenue-cat-webhook"
    }
    
    resource "aws_api_gateway_resource" "webhook_resource" {
      rest_api_id = aws_api_gateway_rest_api.revenue_cat_webhook.id
      parent_id   = aws_api_gateway_rest_api.revenue_cat_webhook.root_resource_id
      path_part   = "webhook"
    }
    
    resource "aws_api_gateway_method" "webhook_post" {
      rest_api_id   = aws_api_gateway_rest_api.revenue_cat_webhook.id
      resource_id   = aws_api_gateway_resource.webhook_resource.id
      http_method   = "POST"
      authorization = "NONE"
    }
    ```

2. **Lambdaとの統合**：
POSTメソッドは、後で設定するLambda関数をトリガーする必要があります。

    ```
    resource "aws_api_gateway_integration" "lambda_integration" {
      rest_api_id             = aws_api_gateway_rest_api.revenue_cat_webhook.id
      resource_id             = aws_api_gateway_resource.webhook_resource.id
      http_method             = aws_api_gateway_method.webhook_post.http_method
      integration_http_method = "POST"
      type                    = "AWS_PROXY"
      uri                     = aws_lambda_function.revenue_cat_lambda.invoke_arn
    }
    ```

### ステップ2：Rust Lambda関数の実装

Webhookを処理し、関連データを抽出してDynamoDBに保存するRustベースのAWS Lambda関数を構築します。

1. **AWS Lambda用のRustセットアップ**：
Lambdaの環境へのクロスコンパイルに必要なツールを含むRustがインストールされていることを確認してください：

    ```bash
    rustup target add x86_64-unknown-linux-musl
    ```

2. **Lambda関数**：
Lambda関数はRevenueCatからの着信JSONを解析し、処理して、DynamoDBに保存します。

    ```rust
    use lambda_http::{service_fn, Error, IntoResponse, Request};
    use serde::{Deserialize, Serialize};
    use aws_sdk_dynamodb::{Client, model::PutItemInput};
    use std::collections::HashMap;
    
    #[derive(Deserialize)]
    struct WebhookEvent {
        event: String,
        product_id: String,
        app_user_id: String,
        purchase_date: String,
    }
    
    #[derive(Serialize, Deserialize)]
    struct SubscriptionData {
        app_user_id: String,
        product_id: String,
        event: String,
        timestamp: String,
    }
    
    async fn handle_request(event: WebhookEvent, client: &Client) -> Result<impl IntoResponse, Error> {
        // サブスクリプションデータを保存するためのDynamoDBアイテムを作成
        let item = SubscriptionData {
            app_user_id: event.app_user_id.clone(),
            product_id: event.product_id.clone(),
            event: event.event.clone(),
            timestamp: event.purchase_date.clone(),
        };
    
        // アイテムをDynamoDBに適した形式に変換
        let mut item_map = HashMap::new();
        item_map.insert("app_user_id", item.app_user_id.into());
        item_map.insert("product_id", item.product_id.into());
        item_map.insert("event", item.event.into());
        item_map.insert("timestamp", item.timestamp.into());
    
        // アイテムをDynamoDBに書き込む
        client
            .put_item(PutItemInput {
                table_name: "SubscriptionEvents".to_string(),
                item: item_map,
                ..Default::default()
            })
            .await?;
    
        Ok("Success".into_response())
    }
    
    #[tokio::main]
    async fn main() -> Result<(), Error> {
        let config = aws_config::load_from_env().await;
        let client = Client::new(&config);
    
        lambda_http::run(service_fn(|request: Request| async move {
            let event: WebhookEvent = serde_json::from_slice(request.body())?;
            handle_request(event, &client).await
        }))
        .await
    }
    ```

3. **Lambda関数のデプロイ**：
Lambda ランタイム用に関数をコンパイルします：

    ```bash
    cargo build --release --target x86_64-unknown-linux-musl
    ```

    バイナリを zip 化し、Lambda にデプロイします：

    ```bash
    zip lambda.zip ./target/x86_64-unknown-linux-musl/release/bootstrap
    aws lambda update-function-code --function-name revenue_cat_lambda --zip-file fileb://lambda.zip
    ```

### ステップ3：イベント保存用のDynamoDBテーブル

RevenueCatからのサブスクリプションイベントを保存するためのDynamoDBテーブルが必要です。以下は、TerraformでDynamoDBテーブルを定義する方法です：

```
resource "aws_dynamodb_table" "subscription_events" {
  name           = "SubscriptionEvents"
  hash_key       = "app_user_id"
  range_key      = "timestamp"
  billing_mode   = "PAY_PER_REQUEST"

  attribute {
    name = "app_user_id"
    type = "S"
  }

  attribute {
    name = "timestamp"
    type = "S"
  }
}
```

### ステップ4：RevenueCat Webhookの設定

API GatewayとLambda関数をデプロイしたら、RevenueCatがWebhookイベントをAPI Gatewayエンドポイントに送信するように設定する必要があります。

1. **RevenueCat**ダッシュボードで、**Webhook**設定に移動します。
2. API Gatewayエンドポイントを指す新しいWebhook URLを追加します。URL例：

    ```
    https://your-api-id.execute-api.region.amazonaws.com/prod/webhook
    ```

### ステップ5：Webhookのテスト

Postmanやcurlなどのツールを使用して統合をテストできます。例：

```bash
curl -X POST https://your-api-id.execute-api.region.amazonaws.com/prod/webhook \
    -H "Content-Type: application/json" \
    -d '{
        "event": "RENEWAL",
        "product_id": "com.example.premium",
        "app_user_id": "user123",
        "purchase_date": "2023-09-13T00:00:00Z"
    }'
```

このエンドポイントに正常にヒットすると、Lambdaはイベントをログに記録し、サブスクリプションデータをDynamoDBテーブルに保存します。

---

## 結論

AWS Lambda（Rustで実装）、API Gateway、およびDynamoDBを使用することで、RevenueCat Webhookを処理するためのスケーラブルでコスト効果の高いソリューションを構築できます。このセットアップにより、サブスクリプションイベントをリアルタイムで処理し、さらなる分析やサブスクリプション管理のためにそれらを永続化することができます。このアーキテクチャにより、サーバーレスコンピューティングと強力なサブスクリプション管理プラットフォームの最良の部分を活用しています。