一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

一休レストランの店舗ページをSPA化して Fastly で段階的リリースした話

この記事は一休.comアドベントカレンダー2018の13日目の記事です。

qiita.com


こんにちは。
今年の7月に入社したレストラン事業部の渥美です。
一休.com レストランにてフロントエンドとバックエンドの開発を行なっております。

この記事の概要

  • 店舗ページをSPA化した背景
    • 店舗ページリニューアル
    • プラン詳細ページのSPA化
  • Vue.js によるモーダルの実装方針
    • 事前ロード
    • モーダルの開閉
    • URLを動的に生成する
  • Fastly での段階的リリース
    • Fastly について
    • VCL の設定
    • Fastly Fiddle によるテスト
  • 今後の展望

店舗ページをSPA化した背景

店舗ページリニューアル

私が一休に入社する1ヶ月前、店舗の情報は複数のページにまたがっていました。 具体的には、店舗のページはプラン一覧や店舗情報、アクセスマップなどの情報が別々のタブに分かれており、ページ遷移が必要でした。
しかし、restaurant2*1へのリニューアルと共に、SPAとして生まれ変わったのでした。

f:id:atsumim:20181213113140p:plain
Before: 旧店舗ページ

f:id:atsumim:20181213113445p:plain
After: 新店舗ページ

プラン詳細ページのSPA化

その後入社した私はユーザ体験をさらに向上させるために、 独立していたプランの詳細ページのモーダル化を担当しました。 これにより、店舗ページ内で完結できる範囲が広がりより予約がしやすいUIになりました。

※現在も下記のプラン詳細ページは動いていますが、後述するようにモーダルへ移行予定です。

f:id:atsumim:20181211173120p:plain
Before: プラン詳細ページ
f:id:atsumim:20181211172225p:plain
After: プラン詳細モーダル

今回はこのモーダル化の実装について簡単にお話するとともに、
段階的リリースの取り組みについてご紹介します。

Vue.js によるモーダルの実装方針

一休レストランのフロントエンド開発では Vue.js が使われています。 モーダルの実装もVue.jsで制御しており、今回はその基本的な実装方針を紹介します。

大まかな処理は以下のとおりです。

// 店舗ページの Vue ファイル
<template>
  <!-- 略 -->
  <object-plan-item
    @mouseenter="preloadPlanDetail"
    @click:plan="openPlanDetail"
  />
  <composition-plan-detail-modal
    :activated="showPlanDetail"
    @update:activated="onClose"
  />
</template>
export default {
  // 略
  data() {
    return { showPlanDetail: false };
  },
  methods: {
    openPlanDetail(plan) {
      // ①モーダルの開閉処理
      this.showPlanDetail = true;
      // ②URL の動的生成
      global.history.pushState(null, '', this.planDetailUrl(plan));
    },
    onClose() {
      // ①モーダルの開閉処理
      this.showPlanDetail = false;
      // ②URL の動的生成
      global.history.pushState(null, '', `/${this.restaurant.id}/`);
    },
    preloadPlanDetail(plan) {
     // ③プラン詳細情報の先読み
    },
    planDetailUrl(plan) {
      // URL を生成し返却する
    },
  }
}

<object-plan-item> が各プラン項目のコンポーネント、
<composition-plan-detail-modal> がモーダルのコンポーネントです。

①モーダルの開閉処理

モーダルの表示状態は showPlanDetail を定義して、この boolean 値で管理しています。 単純ですが、プランの「詳細・予約」ボタンがクリックされたときに showPlanDetail = true,
モーダルの背景がクリックされたときに showPlanDetail = false となり、表示が切り替わります。

②URL の動的生成

モーダルを開いたときに URL を動的に生成しています。
History API を利用して、

global.history.pushState(null, '', this.planDetailUrl({ plan, time }));

とすることでモーダルを開いたときに URL が更新され、履歴にも追加するようにしています。
モーダルを閉じるときは、

global.history.pushState(null, '', `/${this.restaurant.id}/`);

として店舗ページの URL に戻します。

③プランの詳細情報を先読み

プランの詳細情報を先に読み込む処理を書いています。 実際には <object-plan-item> から mouseenter のイベントが emit され、preloadPlanDetail が発火されます。
ユーザからすると、プラン一覧の項目をマウスオーバーすると同時にプリロードが走るので モーダルを開いたときにはシームレスにプラン詳細が表示される体験を提供できます。 (※ローディングが完了していない場合はローディング画像が表示されます)


勿論他にも処理はありますが、以上がモーダル化の大まかな実装となります。
Vue.js を使うことでモーダルの開閉状態や、先読みしたプランの情報をモーダルに受け渡せるのは便利な点でした。

Fastly での段階的リリース

今回モーダル化したページは予約導線に直結しているということもあり、 リリース当初は特定の店舗のみに限定公開しました。 この限定公開に Fastly を使いました。

Fastly について

一休ではリバースプロキシとして Fastly を利用しています。 Fastly は設定言語の VCL を書くことでリダイレクトの処理や、レスポンスの制御を簡単に行うことができます。

今回は、特定の店舗ページにアクセスしたときのみ、?modal_enabled=1というクエリパラメータを付与するように VCLの設定を行いました。 そして、このパラメータが付与されている場合はモーダルを開くようにアプリケーション側でハンドリングしました。

流れとしては下記のようなイメージです。

  • 特定店舗のURLにアクセスする
  • Fastly で ?modal_enabled=1 が付与されたURLにリダイレクトさせる
  • ?modal_enabled=1 が存在するときのみアプリケーション側でモーダルを開く

メリット

Fastly での段階的リリースのメリットとしては下記のようなものがあります。

  • 限定リリースができる
    • 今回の主目的である、URLに応じた限定リリースが可能です。
  • Fastly のデプロイが高速
    • VCL の本番環境への反映が、CIのテストを含めて2分ほどで完了します。
  • 切り戻しが容易
    • 上記のデプロイが高速であることと関係するのですが、なにかトラブルがあった場合の切り戻しが容易です。 アプリケーションのリリースは20分程かかるのでこれはありがたいポイントです。

それまで他の手法での段階的リリースは行われていたものの、
Fastly を使った段階的リリースは初めての試みでした。

VCL の設定

さて、実際に VCL で設定した内容をご紹介します。(必要に応じて変数名等を変えています)

table test_ids {
  "100000": "true",
}

sub vcl_recv {
#FASTLY recv
  if (req.url.qs !~ "modal_enabled=" &&
    req.url.path ~ "^\/(\d{6})" &&
    table.lookup(test_ids, re.group.1)
  ) {
    error 700; # 内部的にerror statusを飛ばすことでリダイレクトを行う
  }
}

sub vcl_error {
#FASTLY error
  if (obj.status == 700) {
    set obj.http.Location  = "https://" req.http.host req.url.path "?modal_enabled=1" if(req.url.qs == "", "", "&" req.url.qs);
    set obj.status = 307;
    set obj.response = "Temporary Redirect";
    return (deliver);
  }
}

table の項目で特定店舗のIDを指定しています。

ここで気になった方もいるかも知れませんが、 VCL の設定では内部的に error status を飛ばすことでリダイレクトを行います。

Fastly Fiddle によるテスト

この VCL の設定を行うにあたり、Fastly Fiddle というサービスを利用しました。これは VCL のコードをオンラインで簡単にテストできるサービスです。 Fastly Labsが実験的に運用しているようです。

f:id:atsumim:20181213141040p:plain
Fastly Fiddle の画面

Fastly Fiddle は実行したコードを他のユーザと共有することもできます。 上記のコードをサンプルとして用意しました。 ページにアクセスしたら、右上の[RUN]ボタンを押してみてください。 table test_ids に含まれるIDに対応するURL(/100000)に対してリクエストを送ると、リダイレクトが発生することがわかります。

f:id:atsumim:20181213140910p:plain
リダイレクト結果

反対に、test_idsに含まれないIDに対応するURL(例えば /200000)にリクエストを送ってもリダイレクトされません。 是非Send a requestの項目を/100000から変更して遊んでみてください。

このようにして、Fastly による特定店舗のみの段階的リリースができるようになりました。

今後の展望

今回のモーダル実装で店舗のページのSPA化が進みました。 しかし、モーダルのURLに直接アクセスしたときは、未だ以前のプラン詳細ページに遷移するようになっています。
今後はこのような導線にもモーダルが開くように対応していきます。

また、今回 Fastly での段階的リリースの仕組みも整ったので、今後の機能追加でどんどん活用していきたいと思います。

明日は id:kentana20 さんの 12/12 Ikyu Frontend Meetup 開催レポート です! お楽しみに!

*1:新しいアーキテクチャによる一休.com レストランのWebアプリケーションを指す。