SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

実践!SPAでのリビジョンのズレ対策

スマートキャンプのエンジニア入山です。

近年、ユーザ体験(UX)の優位性からSPA(Single Page Application)を採用しているWebアプリケーションを多く目にするようになりました。

弊社が8月1日にリリースした、インサイドセールスに特化したCRM Biscuet(ビスケット) も、Vue.jsを使ったSPAで構成されたサービスです。

SPAを採用することで多くのメリットがありますが、従来のMPA(Multiple Page Application)とは異なる運用ノウハウが必要になると思います。

今回はSPAをプロダクション運用する上で避けては通れない、リビジョンアップ時のクライアント側の対応をご紹介します!

SPAにおけるリビジョンアップ時の課題

SPAのWebアプリケーションでは、必要なコード(HTML、JavaScript、CSS)を最初にまとめてブラウザに読み込み、ブラウザでできる処理はJavaScriptで完結させることで、サーバとのAPI通信を必要最小限に抑えることができます。

MPAのWebアプリケーションとは異なり、サーバからは必要最小限のデータのみを取得するため、クライアントとサーバが疎結合になることが特徴です。

しかし、クライアントとサーバが疎結合であることにより、リビジョンアップの際には、クライアントが意図的にリロードしない限りブラウザに保持されたJavaScriptファイルが最初に読み込まれたリビジョンのままとなり、クライアント(旧)とサーバ(新)でリビジョンの不整合が発生する可能性があります。

特に大きなシステム修正(DBのカラム変更など)の場合には、リビジョンを合わせないと予期せぬ障害につながる可能性もあるため、運用者が新しいリビジョンをリリースする際には、何らかの方法によりクライアントにブラウザのリロードを促す手段が必要となります。

リビジョン確認機能の実装方針

今回は、CDN(AWSのCloudFront + S3など)でのクライアント配信を例として、以下の手順でリビジョン確認を行うことにしました。
尚、今回は強制リロードではなく画面へアラートを表示させることで、ブラウザのリロードをユーザーに促す仕様にしています。

  • リビジョン確認機能の実装内容

    1. アプリケーションにリビジョンIDを埋め込む
    2. リビジョン管理用JSONファイル(revision.json)をS3に配置
    3. JSONファイルから最新のリビジョンIDを取得し、アプリケーションのリビジョンIDと比較
    4. リビジョンIDに差異があればアラートを表示

リビジョン確認を実施するための実装案は他にもありましたが、以下メリットを考慮して今回のような実装方針にしました。

  • クライアント側だけでリビジョン確認が完結
  • リビジョン確認処理にかかる負荷が少ない

リビジョン確認機能の実装

アプリケーションにリビジョンIDを埋め込む

アプリケーションのリビジョンIDは、柔軟性を持たせるためにビルド時の環境変数(APP_REVISION_ID)から取得し、process.envで参照します。尚、クライアントビルドは、webpackにて実施しています。

クライアントビルド時

export APP_REVISION_ID="1.0.0"
yarn install
yarn build

アプリケーション参照時

process.env.APP_REVISION_ID

リビジョン管理用JSONファイル(revision.json)をS3に配置

最新のリビジョンIDを含んだJSONファイルをS3へ配置するようにします。

revision.json

{"revisionId": "1.0.0"}

JSONファイルから最新のリビジョンIDを取得し、アプリケーションのリビジョンIDと比較

S3に配置したリビジョン管理用JSONファイルを取得し、アプリケーションのリビジョンIDと比較する処理を実装します。

axiosライブラリを利用してAPI通信を行います。(Vueを普段使っているのでVueっぽいサンプルです)

src/revisionCheck.vue

<script>
import axios from 'axios'

export default {
  data () {
    return {
      revisionId: null
    }
  },
  created () {
    this.getRevisionId()
  },
  computed: {
    revisionCheck () {
      // JSONファイルとアプリケーションのリビジョンIDを比較した結果を返す
      return this.revisionId === process.env.VUE_APP_REVISION_ID
    }
  },
  methods: {
    async getRevisionId () {
      await axios.get('/revision.json').then(res => {
        this.revisionId = res.data.revisionId
      })
    }
  }
}
</script>

リビジョンIDに差異があればアラートを表示

リビジョンIDの確認結果がfalseとなった場合に、アラートのメッセージが表示されるようにします。

src/revisionCheck.vue

<template>
  <div class="revision-check" v-if="!revisionCheck">
    <div>新機能がリリースされています。ページを更新してください。</div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      revisionId: null
    }
  },
  created () {
    // setInterval関数で定期実行する
    setInterval(() => {
      this.getRevisionId()
    }, 60000)
  },
  computed: {
    revisionCheck () {
      // JSONファイルとアプリケーションのリビジョンIDを比較した結果を返す
      return this.revisionId === process.env.VUE_APP_REVISION_ID
    }
  },
  methods: {
    async getRevisionId () {
      await axios.get('/revision.json).then(res => {
        this.revisionId = res.data.revisionId
      })
    }
  }
}
</script>

プロダクトでは最終的に以下のように表示するようにしています!

f:id:tkgwy:20190906162125p:plain

※ setInterval関数については、弊社エンジニアの瀧川が以前記事を書いているので、ぜひ参考にしていただければと思います!

tech.smartcamp.co.jp

最後に

今回は、SPAにおけるリビジョンアップ対策について紹介しました。 弊社では、CircleCIでデプロイ作業を自動化しており、リビジョンIDの更新も含めて自動で行われるようにしています!

SPAには多くのメリットがあり、近年SPAのWebアプリケーションが増えていますが、実際に構築・運用する場合の情報がまだまだ少ないのが現状です。

リビジョンアップ対策についても実装方法はいくつかあると思いますが、今回紹介した内容が参考になれば嬉しいです!