中安拓也のブログ

プログラミングについて書くブログ。 Twitterやってます @l08084

【Angular(Ionic)】ダークカナリアリリースのモバイルアプリ側対応

はじめに

最近、仕事でダークカナリアリリース対応を少しだけお手伝いしたので、その時のメモになります。

本記事では、モバイルアプリ側の対応だけにしか触れていません。具体的には、ダークカナリアリリースのアプリかどうか判別するときに使用するHTTPヘッダの追加方法についてのみ説明します。

ダークカナリアリリースとは

ダークカナリアリリースとは、本番環境下で新機能を検証することを目的としたリリース手法を指します。

  • カナリアリリース
    • 一部のユーザー・開発者にのみ、新バージョンのアプリケーションをリリースする手法。これにより、新バージョンのアプリで何か問題が発生しても、影響範囲を抑えることができる。
  • ダークカナリアリリース
    • 本番環境で実施するカナリアリリースを指す。対象者は開発者で、新機能を本番環境下で検証することが目的となる。

ダークカナリアリリースのイメージ

今回実装するダークカナリアリリースのイメージ図です。ダークカナリアリリースの実装形式は多種多様だと思うので、下記の図はあくまで一例として捉えてください。

f:id:l08084:20210306175118p:plain

新バージョンのモバイルアプリのリクエストからは、 x-relase-model: dcrというカスタムのHTTPヘッダー(x-relase-model)と値(dcr)を渡すようにします。

HTTPヘッダーx-relase-model: dcrがリクエストに含まれている場合は、新バージョンのAPIにリクエストを割り振ります。逆にHTTPヘッダーx-relase-model: dcrがリクエストに含まれていない場合は、現バージョンのAPIに割り振ることによってダークカナリアリリースを実現します。

HTTPヘッダーの追加方針

ビルド時にカスタムの環境変数RELEASE_MODEL=dcrがセットされている時のみ、x-relase-model: dcrをHTTPヘッダーに追加するようにします。なお、モバイルアプリのリリースはJenkins経由で実施する想定です。

モバイルアプリの環境

今回、ダークカナリア対応を実施するモバイルアプリのフレームワーク・ライブラリのバージョンは下記となります。

ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1101.4
   @angular-devkit/schematics    : 11.1.4
   @angular/cli                  : 11.1.4
   @ionic/angular-toolkit        : 3.1.0

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

ビルドスクリプトの作成

該当Ionicプロジェクトのscripts/配下にdcr.jsというスクリプトを作成します。

scripts/dcr.js

'use strict';

/*
 * ダークカナリアリリース用のHTTPヘッダーを追加する。
 */
const env = process.env,
  releaseModel = env.RELEASE_MODEL || '',
  fs = require('fs');

const addReleaseModelHeader = (releaseModel) => {
  if (releaseModel === '') {
    return;
  }
  const httpInterceptorFilePath =
    './src/app/http-interceptors/http-header-interceptor.ts';
  let httpInterceptorFile = fs.readFileSync(httpInterceptorFilePath, 'utf-8');
  httpInterceptorFile = httpInterceptorFile.replace(
    /\/\* \{RELEASE_MODEL\} \*\//,
    `.set('x-relase-model', '${releaseModel}')`
  );
  fs.writeFileSync(httpInterceptorFilePath, httpInterceptorFile);
};

addReleaseModelHeader(releaseModel);

上記のスクリプト(dcr.js)は、環境変数(RELEASE_MODEL)に値が設定されていた場合、TypeScriptファイル(http-header-interceptor.ts)の/* {RELEASE_MODEL} */というコメント文を.set('x-relase-model', [RELEASE_MODELの値])に置き換える処理を行います。

package.jsonに上記のスクリプトを実行するnpm-scriptsを追加します。

package.json

  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "dcr": "node ./scripts/dcr.js"
  },

追加した行は、"dcr": "node ./scripts/dcr.js"の部分になります。こうすることで、npm run dcrコマンドでdcr.jsを実行することができます。

インターセプターの作成

続いて、HttpClientによるHTTPリクエスト送信とHTTPレスポンス受信の直前に呼び出されるAngularのインターセプターという機能を追加します。

src/app/http-interceptors/http-header-interceptor.ts

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class HttpHeaderInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
      const dcrReq = req.clone({
        headers: req.headers /* {RELEASE_MODEL} */
      });
      return next.handle(dcrReq);
  }
}

上記のインターセプターでは、HTTPヘッダーを設定している箇所に/* {RELEASE_MODEL} */というコメントを挿入しているため、コメント/* {RELEASE_MODEL} */dcr.js.set('x-relase-model', [RELEASE_MODELの値])に置き換えることによって、x-relase-model: [RELEASE_MODELの値]をHTTPヘッダーに追加することができます。

動作確認

RELEASE_MODEL='dcr' npm run dcrコマンドを実行すると、http-header-interceptor.tsの内容が下記画像のように変更され、HTTPヘッダーx-relase-model: dcrが追加されることがわかります。

f:id:l08084:20210314184358p:plain
インターセプターの内容がnpm-scriptsによって変更されていることがわかる

実際の運用では上記で作成したソースコードを使って、JenkinsなどのCIでモバイルビルド前に毎回npm run dcrコマンドを実行し、Jenkinsの実行時に環境変数RELEASE_MODEL=dcrがセットされた場合のみ、ダークカナリアリリース用のアプリとしてリリースする、といったことを実施するイメージです。

参考サイト

Angular 日本語ドキュメンテーション

インターセプターによる介入 - Angular After Tutorial