Firebaseの技術情報サイト

Firebaseの技術情報サイト

Firebase活用の為の技術情報サイトです

FirebaseとSendGridでメルマガサービスのバックエンドAPI実装の注意!

FirebaseとSendGridでメルマガサービスのバックエンドAPI実装の注意!

FirebaseとSendGridでメルマガ(ニュースレター)サービスを作る場合、メッセージの送信には、通常のE-Mailベースのメッセージ送信より、マーケティングキャンペーンを利用する方が便利です。

マーケティングキャンペーンの処理をバックエンドで実装する際には幾つかの注意点があります。 今日は、FirebaseとSendGridでメルマガ(ニュースレター)サービスで利用するバックエンドをexpressを利用して、実装する場合の注意点について解説しています。

バックエンドのAPIの決め方

WebサービスやWebアプリでバックエンドのインターフェースを決める場合には幾つか注意点があります。

  • 機能的なインターフェース
  • セキュリティ上のインターフェース

セキュリティ上のインターフェースは、バックエンドのサービスを利用できる人を制限したりする場合には重要な要素になります。セキュリティ対策にはいろいろありますが、(セッション)クッキーを利用したり、ログイン情報(UIDなど)を添付したり、CORSで利用できるドメインを限定したりして、必要以外の人のアクセスを制限するためのインターフェースです。

今回はセキュリティ上のインターフェースではなく、機能的なインターフェースを中心に解説をします。

機能的なインターフェースは、フロントエンドから必要な機能のバックエンド処理をどのように呼び出すかという仕様です。フロントエンドとバックエンドのやり取りは基本的にはカスタムのインターフェースなので、自由に設計する事ができます。

しかし、今回のSendGridのサービスを利用したバックエンドを実装する場合、SendGridのAPIに合わせた設計をした方が、わかりやすい場合が多いので、この記事ではSendGridのAPIを元にフロントエンドとバックエンドのインターフェースを決めるという例を紹介します。

サンプルはマーケティングキャンペーンのAPIです!

前回の記事ではマーケティングキャンペーンの作成フォームの例を挙げています。

このフォームでマーケティングキャンペーンを作成した後に、各キャンペーンに対して色々な処理を行う必要があることは記事内で紹介しました。

SendGridのレガシーマーケティングキャンペーンのAPIは以下のような操作が定義されています。

  • マーケティングキャンペーンの作成
  • 全てのマーケティングキャンペーン(の一覧)を取得
  • 個々のマーケティングキャンペーンの詳細を取得
  • マーケティングキャンペーンの更新
  • マーケティングキャンペーンの削除
  • マーケティングキャンペーンの送信
  • マーケティングキャンペーンの送信予約
  • マーケティングキャンペーンの送信予約の変更
  • マーケティングキャンペーンの送信予約の取り消し
  • マーケティングキャンペーンの送信予約時間の取得
  • マーケティングキャンペーンのテスト送信

SendGridのAPIは、基本は「/campaigns」を使います。 この中でHTTPの「GET」メソッドを使うAPIは全部で3つほどあります。

  • 全てのマーケティングキャンペーン(の一覧)を取得
  • 個々のマーケティングキャンペーンの詳細を取得
  • マーケティングキャンペーンの送信予約時間の取得

全てのマーケティングキャンペーン(の一覧)を取得する場合には

GET /campaigns

個々のマーケティングキャンペーンの詳細を取得する場合は

GET /campaings/{campaign_id}

スケジュールしたマーケティングキャンペーンの送信予約時間を取得する場合は

GET /campaings/{campaign_id}/schedules

になっています。POST/PATCH/DELETEも似たような感じでURLにIDなどの情報を加えて別の機能を呼び出せるようになっています。

コンセプトは同じなので、この記事では「GET」を使用する場合について詳しく見ていきます。

フロントエンドとバックエンドのカスタムのAPIもSendGridと同じAPIを採用するとすると

上の3つのAPIと同じになります。違うのは、SendGridのサーバーのURLに送るか、WebホスティングのサーバーのURLに送るかの違いだけになります。

フロントエンド側はシンプル

フロントエンド側はシンプルに実装できます。

マーケティングキャンペーンの一覧を取得する場合はシンプルに「/campaign」のみを送るだけです。他の2つは、操作対象のキャンペーンを一覧から選択して、キャンペーンのIDを取得して、URLにキャンペーンのIDを埋め込んで送ればよいだけです。 ローカルホストの場合は、

http://localhost:5000/campaings
http://localhost:5000/campaings/12345
http://localhost:5000/campaigns/12345/schedule

のように送ればよいことになります。

バックエンドはちょっと厄介です!

バックエンドの実装は少し面倒です。バックエンドの場合は、違うIDが送られてくることに対応する必要があります。マーケティングキャンペーンの一覧の取得の場合はシンプルで、IDは入っていないので簡単です。

以下の例では定数(CONSTANT.API_LEGACY_CAMPAIGN)でURLをチェックしていますが、固定のURLを見ればよいのでシンプルです。

this.app.get(
      CONSTANT.API_LEGACY_CAMPAIGN,
      (req: express.Request, res: express.Response) => {
        console.log("GET");
        const url = CONSTANT.SG_URL + CONSTANT.API_LEGACY_CAMPAIGN;
        const options: any = {
          method: "GET",
          url: url,
          headers: { AUthorization: "Bearer " + CONSTANT.SG_API_KEY },
        };
        axios(options)
          .then((response: AxiosResponse) => {
            console.log(response.data.result);
            res.end(
              JSON.stringify({
                success: true,
                data: response.data.result,
              })
            );
          })
          .then((error: any) => {
            console.log("Error:Campaign Get");
            console.log(error);
            res.end(
              JSON.stringify({
                success: false,
                data: error,
              })
            );
          });
      }
    );

問題は、IDが入る個々のキャンペーンの取得と、送信予約時間の取得の2つです。

これは、正規表現を利用してチェックします。バックエンドではURLのパス情報は「req.path」で取得できるので、パス情報からID情報を抜き出します。

送信予約時間を取得する例は以下の様になります。

this.app.get(
      CONSTANT.API_LEGACY_CAMPAIGN + "(/*)?" + "/schedules",
      (req: express.Request, res: express.Response) => {
        const route: Array<string> = req.path.split("/");
        const id = route[route.length - 2];
        const options: any = {
          method: "GET",
          url:
            CONSTANT.SG_URL +
            CONSTANT.API_LEGACY_CAMPAIGN +
            "/" +
            id +
            "/schedules",
          headers: { AUthorization: "Bearer " + CONSTANT.SG_API_KEY },
        };
        axios(options)
          .then((response: AxiosResponse) => {
            console.log(response.data);
            res.end(JSON.stringify({ success: true, data: response.data }));
          })
          .catch((error: any) => {
            console.log(error);
            res.end(JSON.stringify({ success: false, data: error }));
          });
      }
    );

個々のメッセージを取得する例は以下の様になります。 要領は同じです。

this.app.get(
      CONSTANT.API_LEGACY_CAMPAIGN + "(/*)?",
      (req: express.Request, res: express.Response) => {
        const route: Array<string> = req.path.split("/");
        const id = route[route.length - 1];
        const options: any = {
          method: "GET",
          url: CONSTANT.SG_URL + CONSTANT.API_LEGACY_CAMPAIGN + "/" + id,
          headers: { AUthorization: "Bearer " + CONSTANT.SG_API_KEY },
        };
        axios(options)
          .then((response: AxiosResponse) => {
            console.log(response.data);
            res.end(JSON.stringify({ success: true, data: response.data }));
          })
          .catch((error: any) => {
            console.log(error);
            res.end(JSON.stringify({ success: false, data: error }));
          });
      }
    );

記述の順番に気を付けよう!

これでSendGridと同じAPIをフロントエンドとバックエンドで利用できます。 この時気を付けることが一つあります。expressのルーティングの評価は上から順番に行われます。正規表現でワイルドカードを使っている場合には各順序に気を付けないと誤った処理をしてしまうことがあります。例えば、個々のマーケティングキャンペーンを取得する処理は、通常のマーケティングキャンペーンのAPIにIDを加えていますが、この正規表現は、マーケティングキャンペーンを一括取得するときのURLにもマッチしてしまいます。

この問題を避けるためには、マーケティングキャンペーンを一括取得する処理を先に書いておく必要があります。

まとめ

バックエンドのルーティング処理では、正規表現を使ったマッチングも利用できます。 正規表現でワイルドカードを使用する場合は、処理の順番は重要です。

正しい処理を行えるように、評価する順番を慎重に選ぶ必要があります。

SendGridのAPIでは、同じようなURLを使って、HTTPのメソッドと追加情報で少し違った処理を呼び出せるように工夫されています。APIをわかりやすく体系的に割り当てるには便利な手法ですが、バックエンドなどでルーティングの処理を行う場合には慎重に評価順番を決める必要があります。

同じAPIを使うことで、バックエンドの記述をシンプルにすることも可能なのでSendGridを使った実装の場合は、この方針でフロントエンドとバックエンドの実装を決めるとわかりやすいコードにすることができます。