一休.com Developers Blog

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

tsqllint & Appveyor & AWS CodeDeployで実現するDDL適用自動化

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

qiita.com


データベースに対するDDLの適用、みなさんはどのように運用していますか。
一休では長らく担当者が手動適用をしていました。が、開発者全員の依頼をまとめて、定期的にDDL適用を行うのはかなりの作業負荷です。 そこで、アプリケーションのソースコードと同じようにGitHubとCI/CDのパイプラインを構築して、適用したい開発者が自分で適用できる仕組みを構築しました。

この記事では、その概略を紹介したいと思います。
※当社はMicrosoft SQL Serverを使っているので、その前提の記事になります。

デプロイフロー

f:id:s-tokutake:20181221184621p:plain

  • CIにはAppveyorを、CDにはAWS CodeDeployを利用
    • CodeDeployは後述の通り、外部アクセス用のプロキシサーバも利用
  • GitHubにDDLを管理するリポジトリを作成
  • 開発者はこのリポジトリで新規に適用したいDDLのPull Requestを作成
  • PRをmasterブランチに適用するとAppveyor経由でCodeDeployが起動される。
  • CodeDeploy Agentが動いているddl-controllerマシンでDDLを実行し、SQL Server に適用
    • 適用履歴もSQL Serverのテーブルで管理して同じDDLが2重適用されないように制御
    • 適用結果(実行ログ)はAmazon S3にアップロード
    • Lambdaでログの中身をチェックして、適用結果をSlackに通知

工夫点、注意点

tsqllintでDDLの構文チェック

データベースの定義変更なので、注意深く実施する必要があります。また、実際に定義を適用してみたらエラーになった、となると手戻りが発生して面倒です。
そこで、linterを導入しようと考えました。調べてみるとSQL ServerのT-SQL (Transact-SQL)の構文がチェックできる tsqllintというツールがありました。 動作確認してみたところ、きちんと動作するし、lintのルールのカスタマイズもできるのでこれを採用しました。
そして、これを利用した簡単なNode.jsのスクリプトを書き、Appveyor上で動かして、構文チェックを実行するようにしました。チェックが通らなければ、Pull Requestをマージできないようにします。

※ちなみに、tsqllintはVS Codeの拡張もあるようです。SQL Serverの管理などでT-SQLに触れる機会が多い方には便利かもしれません。

linterでチェックできない点はレビューでチェック

ファイルグループの指定が正しいか、などlinterのチェックだけでは検出できない重要な点があります。 このような点についてはPull Requestのテンプレートにチェック項目を書き出し、Pull Requestの作成者とレビューワーの双方がチェックする、という運用ルールにしました。もちろん、レビューワーのApproveがなければ、Pull Requestはマージできません。 機械的なチェックではないので、多少の不安はありますが、今のところうまくいっています。

インターネットに出れないEC2インスタンスでCodeDeploy Agentを動かすにはプロキシが必要

これは、注意点なのですが、データベースが置かれているネットワークは、インターネットには接続できないようになっているのが一般的です。
一方でCodeDeploy Agentが正常に動作するためには、外向きHTTPSの通信ができることが必須です。
CodeDeploy Agentが外部のAPIを定期的にコールしてデプロイを実行する必要があるかどうか確認しているからです。
この場合、CodeDeploy Agentの構成ファイルに外向き通信をプロキシするためのプロキシサーバのURLを設定する必要があります。
当社では、外向きの通信ができるネットワークにApacheでプロキシサーバを立て、そのURLをCodeDeploy Agentの構成ファイルに設定しました。

Apacheの設定は以下の通りです。

Listen *:8088
<VirtualHost *:8088>
  ProxyRequests on
  AddDefaultCharset off
  AllowCONNECT 443
  <Proxy codedeploy-commands.ap-northeast-1.amazonaws.com>
      <LimitExcept CONNECT>
         Order deny,allow
         Deny from all
        # ↓ CodeDeploy Agent が動いているマシンのIP
         Allow from xx.xx.xx.xx 
      </LimitExcept>
  </Proxy>
</VirtualHost>

CodeDeploy側は、conf.ymlを次のように修正します。
Windowsの場合、 C:\ProgramData\Amazon\CodeDeploy\conf.yml にあります。

---
:log_dir: 'Amazon/CodeDeploy/log'
:root_dir: 'Amazon/CodeDeploy'
:verbose: true
:wait_between_runs: 1
:wait_after_error: 1
:bundle_name: 'artifact_bundle.tar'
:proxy_uri: http://xx.xx.xx.xx:8088 # ここにプロキシサーバのIPを記述

これで、CodeDeploy Agentを再起動すれば正常に動くようになります。

※ 環境によってはセキュリティグループなどネットワークレイヤの調整も必要です。

終わりに

長年、手動でやっていたものを自動化して、果たしてうまく運用が回るか、心配はありましたが、しっかりと運用に乗りました。
データベースをクラウドに移行したという前提条件とアプリケーションのCI/CDの構築経験の応用が実現のキーポイントだったと思います。

※データベースのクラウド移行については、当ブログに詳細な記事がありますので、ご覧ください。

user-first.ikyu.co.jp

user-first.ikyu.co.jp

この記事の筆者について

  • システム本部CTO室所属の 徳武 です。
  • サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。

一休のWebデザイナーとUIデザイナーの違い

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

qiita.com


はじめまして、宿泊サービスのUIデザインを担当しています河村です。

一休のデザイナーは部署ごとに在籍チームが異なります。私は長い間、営業企画部デザイナーとして働いていましたが、今年4月よりプロダクト開発部UIデザイナーとして働いています。(本ブログでは、前者をWebデザイナーとします)

WebデザイナーとUIデザイナーをやってみて、多くのことが「違った」ので、どんな違いなのかをお話させていただきます。

目次
1) 仕事内容の違い
2) 必要なスキルの違い
3) 仕事の進め方の違い
4) まとめ

仕事内容の違い

ビジュアル表現に特化

一休のWebデザイナーの主な業務内容は、トップページなどの更新作業、施設紹介ページ、特集・企画販促ページ、メールマガジン等の作成です。ページ全体のビジュアルを管理しているので、一つ一つの画像の選定などサイト全体の雰囲気作りに欠かせない役割です。

私は、施設の魅力をユーザーに届けることをゴールにして、施設の世界感を感じさせるページ作りを意識して行っていました。

f:id:kawamuram:20181219114006p:plain

操作性の向上に特化

一方、UIデザイナーの主な業務内容は、ユーザーが「使いやすい」と思うデザインを実現することに特化しています。宿泊プランがすっきり整理された構図、見やすいカラーリング、最適な文字の大きさ、使いやすい検索機能などを意識して、ユーザーにとって使い勝手のいいサイトを目指しています。

基本的には予約のコンバージョン率向上を目的として改善を繰り返しますが、予約には関係ない部分でも使いやすさや見やすさを良くする為の改善もあります。具体例としましては、宿泊料金表示を即時ポイント利用での割引後の料金表示に切り替えるられる機能を追加したり、フォトギャラリーのサムネイル表示の追加などを行いました。

f:id:kawamuram:20181219114156p:plain

必要なスキルの違い

意匠力

Webデザイナーの必要なスキルは「意匠力」です。施設の魅力をユーザーに届けるページ作り、時としてインパクトを残せるページ作りなどを行いますが、オシャレだったり、かっこいいといった見た目の印象、装飾的考案が大切です。

設計力

一方、UIデザイナーの必要なスキルは「設計力」です。ユーザーの目線に立ってユーザーが抱える問題の本質を考え、それらを解決するための設計をし、表現や形を作っていくことが重要です。

Webデザイナーは、多くの場合、施設側や営業担当から指示書があるので、何を見せたいかの意図をくみ取りデザインで表現する流れですが、UIデザイナーは、今何が問題で、こうやったら使いやすくなるのではと仮説を立てるなど、デザインをするまでの過程がとても重要になります。

仕事の進め方の違い

ビジュアル的なクオリティの追求を自分だけで行える

Webデザイナーは営業担当や施設との調整はありますが、作業の進行自体は一人で行います。納期内であればビジュアル的なクオリティを突き詰めることに時間を割くことができます。

共同作業ならではのルールがある

一方、UIデザイナーはマーケティングやエンジニアとの共同作業なので、作業の進行は自分だけの判断では進められません。また、エンジニアと認識合わせなどを文章に残すのも重要で、やりたいこと、なぜやるかなどを案件ごとに言語化します。

個人プレーからチームプレーとなったことで、自分のタイミングでリリースできないことにストレスを感じたり、言語化する作業が煩わしく感じたりすることもありました。 しかし、リリース前に必ずレビューが入ることでミスを防げたり、なぜやるかというのを文章で残すことで、自分自身も思考の整理ができたり、過去のリリースを振り返る際にも役立ってます。

まとめ

f:id:kawamuram:20181221092253p:plain

一休においての各デザイナーの違いについて、簡単にまとめてみました。あくまでも私が個人的に感じたことなので、一般的な「WebデザイナーとUIデザイナーの違い」とは異なるかもしれません。

目的別チームで各々作業している一休のデザイナーですが、「一休らしい綺麗なサイト」や「高級感を感じるデザイン」を作りたい、というのは共通して目指していることです。そのために私たちデザイナーは、デザインの力(意匠力、設計力)を駆使して、「美しく機能的なサイトで宿泊先を選んでいる」という、ユーザーの心地よい体験を叶えるべく、改善を繰り返します。

一休では、ともに良いサービスをつくっていく仲間(デザイナー/エンジニア/マーケティング)を積極募集中です。応募前にカジュアルに面談をすることも可能ですので、お気軽にご連絡ください。

明日は @hayatoise さんによる「読書合宿のお話」です。お楽しみに!

Hangfireで実現する.NETアプリのバックグランドジョブ

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

qiita.com


ある程度の規模のウェブアプリケーションであれば、応答性能を損なうことなく複雑な業務処理を完遂させたい場面が出てきます。 このような場合、処理をある程度の粒度で切り出して、応答を返すプロセスとは別のプロセスで処理する、という方法が考えられます。 例えば、予約完了処理の中でメール送信部分だけを別のプロセスで処理する、といった具合です。 ASP.NETでこのようなバックグランドジョブ処理を実現するには、どのような方法があり得るでしょうか。

この記事では、.NET環境で利用できるバックグラウンドジョブのライブラリであるHangfireについて、当社での利用事例を簡単に紹介します。

Hangfireを導入した経緯

当社のサービスにも当然、上述したようなバックグラウンドジョブのニーズがありました。

ASP.NETでバックグラウンドジョブの処理を実現する場合、公式に提供されているQueueBackgroundWorkItemを使うという手段があります。 これなら導入は非常に簡便です。しかし、大事な処理をバックグラウンドジョブで実行することを考えると、ジョブのステータス(キューイング、処理中、処理正常完了、処理失敗)やジョブの実行履歴の管理も行いたい、と考えたました。
この場合、QueueBackgroundWorkItemを採用するなら、その部分は自前で作らなければなりません。
また、クラウドが提供するキュー処理のサービスを使う方法もあり得ますが、この場合も、ジョブのステータスやジョブの実行履歴の管理は自前で作らなければなりません。 Hangfireは、ジョブストレージとジョブの管理機構が組み込まれており、かつ、既存の.NETのコードを大きく変えることなく利用できるという利点があります。 作者によれば

Hangfire is a .NET Framework alternative to Resque, Sidekiq, delayed_job, Celery.

だそうです。 以上の点を総合的に加味して、Hangfireを活用することに決めました。

構成

f:id:s-tokutake:20181219123844p:plain

Hangfireを使う場合、アプリケーションの構成は大きく以下のふたつがあり得ます。

  • バックグラウンドジョブ処理を行いたいウェブアプリケーションの内部にバックグラウンドジョブサーバを動作させる。
  • バックグラウンドジョブ処理を行いたいウェブアプリケーションとバックグラウンドジョブサーバを完全に分離する。

当社では、完全に分離する構成にしました。内部にバックグラウンドジョブサーバを動作させる場合、ウェブアプリのプロセスの再起動とジョブの処理の状態を気にする必要があるため、完全に分離したほうが運用が簡単だと判断しました。

上記の図の通り、ジョブキューのストレージにはElasticache Redisを使っています。Hangfireは標準ストレージとしてSQL Serverを使いますが、Redisのほうがより高性能であるというベンチマーク結果を考慮し、Redisにしました。

また、ジョブのステータスや各種管理ができるDashboardは、Hangfireを使うウェブアプリともバックグラウンドジョブサーバとも別のサーバに構築しました。

実装例

実装自体はとても簡単です。

namespace Sample.Service.JobQueue
{

    /// <summary>
    /// バックグラウンドジョブ導入のためのサンプルクラス
    /// </summary>
    public class JobQueueSampleService 
    {

        public void DoSomething(string param1)
        { 
            JobQueueClient.Enqueue(() => DoAsBackgroundJob(param1));  
        }

        /// <summary>
        /// このメソッドをキューに詰める。
        /// </summary>
        /// <param name="param1">
        /// Hangfireは、パラメータをJsonでシリアライズしてストレージに詰める。
        /// </param>
        [Hangfire.AutomaticRetry(Attempts = 3)] // <== リトライ回数をキューに詰めるメソッドの属性で回数を指定する。指定しないと10回リトライ。リトライしないなら0回にしておく。
        public void DoAsBackgroundJob(string param1)
        {
             // --- do something  
        }
    }

    public class JobQueueClient
    {

        private static readonly Lazy<IBackgroundJobClient> _cachedClient = new Lazy<IBackgroundJobClient>(() => new BackgroundJobClient());

        public static string Enqueue(Expression<Action> methodCall)
        {
            var client = _cachedClient.Value;

            return client.Create(methodCall, new EnqueuedState(QueueName));
        }
    }
}

Hangfireは、メソッドをジョブキューにエンキューする、という仕組みになっています。↑のコードでは、 JobQueueClient.Enqueue(() => DoAsBackgroundJob(param1)); で、 DoAsBackgroundJobメソッドでジョブとしてジョブストレージに保存しています。
エンキューすることで、メソッドのシグネチャやパラメータ、そのメソッドが属するアセンブリ名などがジョブストレージに保存されます。バックグラウンドジョブサーバは、この情報をデキューして、リフレクションを使って、保存されたメソッドを呼び出します。
リフレクションを使うので、Hangfireのクライアントとなるウェブアプリとバックグラウンドジョブサーバは同じアセンブリを参照する必要があります。

※Hangfireには、 BackgroundJob.Enqueue というエンキュー処理のためのわかりやすいインターフェースがあるのですが、これは後述する理由により、使いませんでした。

工夫点

前述した通り、Hangfireのクライアントとなるウェブアプリとバックグラウンドジョブサーバは同じアセンブリを参照する必要があります。 実運用でこれを実現しようとすると、次の2点を考える必要があります。

  • 毎日複数回デプロイされるウェブアプリケーションとバックグラウンドジョブサーバのアセンブリを一致させる必要がある。
  • ウェブアプリケーションのデプロイサイクルよりもLong Runningなジョブでもきちんと処理を完遂させる必要がある。
    • それも、エンキューしたウェブアプリケーションと同じアセンブリを参照しているバックグラウンドジョブサーバで処理を完遂させる必要がある。

このふたつを実現する実現するために、以下のふたつの手段を採用しました。

  • Hangfireの名前付きキューを利用する。
  • バックグラウンドジョブサーバはWindows Serviceとして稼働させ、同じアセンブリを参照しているウェブアプリケーションが古いバージョンになっても動かし続ける。

Hangfireの名前付きキューを利用する。

Hangfireは、キューに名前を付けることができます。ある名前のキューに詰められたジョブはその名前のキューを処理するように指定されたバックグラウンドジョブサーバでしか処理されません。キュー名は通常、次のようにキューに詰めるメソッドの属性で指定します。

[Queue("testqueue")]
public void SomeMethod() { }

そして、バックグラウンドジョブサーバ側ではサーバを初期化するときに、キューの名前を指定します。以下のように。

var options = new BackgroundJobServerOptions
{
    Queues = new[] { "testqueue" }
};

app.UseHangfireServer(options);
// or
using (new BackgroundJobServer(options)) { /* ... */ }

これで、testqueue というキューに詰めたメソッドは、↑のサーバでしか処理されなくなります。

さて、あるバージョンのアセンブリを参照しているウェブサーバがキューに詰めたメソッドは同じバージョンのアセンブリを参照しているバックグラウンドジョブサーバで処理させたい という要件は、以下を実現すれば満たせそうです。

  • 同じバージョンのアセンブリを参照しているウェブサーバとバックグラウンドジョブサーバは同じキュー名を利用する。そのキュー名はビルド単位で生成され完全にユニークである。

ビルド単位で生成され完全にユニークな値として、ビルドバージョンの番号がありました。そこでこの番号をキュー名にすることでこの要件を満たすことができました。

Untitled (1).png

ただし、Hangfireのサンプルなどで一番よく見かける BackgroundJob.Enqueue では、キュー名をメソッドの属性としてしか指定できません。つまり、動的に設定できないのです。調べてみると、BackgroundJobClient クラスのCreateメソッドを直接使うことでキュー名を動的に指定できることがわかりましたので、以下のようなBackgroundJobClient をラップしたクラスを作りEnqueueメソッドを実装しました。

  public class JobQueueClient
    {

        private static readonly Lazy<IBackgroundJobClient> _cachedClient = new Lazy<IBackgroundJobClient>(() => new BackgroundJobClient());

        public static string Enqueue(Expression<Action> methodCall)
        {
            var client = _cachedClient.Value;

            return client.Create(methodCall, new EnqueuedState(QueueName));
        }
    }
}

バックグラウンドジョブサーバはWindows Serviceとして稼働させ動かし続ける。

新しいウェブアプリがデプロイされても古いバックグラウンドジョブサーバが動作していれば、デプロイサイクルよりもLong Runningなジョブもきちんと処理できます。 これは、シンプルに バックグラウンドジョブサーバはWindows Serviceとして実装し、新しいバージョンのバックグラウンドジョブサーバをデプロイをしても、古いバージョンのバックグラウンドジョブサーバは停止しないようにしました。 バックグラウンドジョブサーバをWindows Service として動かす方法は、公式サイトにも開設されています。しかし、このやり方には従わずに、Topshelfを使ってServiceにしました。Windows Serviceのインストール、アンインストール、開始、停止が簡単に制御できるためです。

バックグラウンドジョブサーバのデプロイはAWS Codedeployを使います。 Codedeployのafter installのhookで、バックグラウンドジョブサーバをWindows Serviceとしてインストールし、開始をしています。 このとき、配置するフォルダをデプロイバージョンごとに変えることで、既存のServiceを動かし続けながら、新しいバージョンのServiceを稼働させることができます。

appspec.ymlは↓のようにします。

version: 0.0
os: windows
files: 
  - source: /
    destination: d:/latest
hooks:
  AfterInstall:
    - location: /after-install.bat

after-install.batは、以下の通り。

rem $TARGET_FOLDER を ビルド時にビルドバージョンに書き換える。
xcopy D:\latest D:\$TARGET_FOLDER\ /E /Y /I /Q
D:\$TARGET_FOLDER\bin\BackgroundJob.ProcessingServer.exe install
D:\$TARGET_FOLDER\bin\BackgroundJob.ProcessingServer.exe start

ただし、このままだと永遠にWindows Serviceが増え続けてしまいます。対策として、日次で1日以上古くなったバージョンのバックグラウンドジョブサーバを停止するようにしました。

まとめ

当社ではHangfireをプロダクション環境の業務処理で活用し始めてまだ日が浅いです。しかし、現時点では、問題なく動作しています。 今後は、短い間隔で動かしているバッチ処理をウェブアプリのバックグラウンドジョブに切り替えていく、という使い方も想定しています。

この記事の筆者について

  • システム本部CTO室所属の 徳武 です。
  • サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。

「ちょっとしたことを検索できる」Slack botを作った

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

qiita.com


こんにちは。 社内情報システム部の下村です。
一休ではOfficeITに関する全ての業務、改善を担当しています。いわゆる情シスです。

本日は、一休の情シスが行ってきた活動のうち、開発者ブログらしく社内向けのSlackツールを開発(?)したことについて記載したいと思います。

どんなツールを作ったのか?

一休ではコミュニケーションツールとして、非エンジニアであってもSlackが活用されています。 そのSlackを使って、 「ちょっとしたことを検索できるbotがあれば便利なんじゃないか?」 と思い 社内向けに提供しました。 具体的には下記のようなことがSlack上で検索できるようにしています。

  1. 内線番号や、メールアドレスなどの社員情報を検索。
  2. 「座席表」や「無線LANのキー(パスワード)」などのURLを検索。
  3. 会議室の空き状況を確認。(予定が空いていればbot上から予約も可能にしています。)

デモ

「百聞は一見にしかず」ということで実際の動作デモです。 雰囲気が伝われば幸いです。 ちなみに「ぽ」とはbotの起動トリガーです。 ぽ<なんちゃら>と書くとbotが反応するようにしています。

f:id:undersooon:20181217141737g:plain

余談:なんで「ぽ」なの?

起動トリガーは下記の条件を満たす必要がありました。 下記条件を全て満たすのが「ぽ」でした。

  • 覚えやすい。
    • 「ぽ」ってなんかインパクトありますよね。
  • 打ちやすい。
    • ご自身のキーボード配列見てみてください。「p」と「o」が近くて打ちやすくないですか。
  • かぶらない。
    • かぶりやすいキーワードを起動トリガーにするとbotが誤検知しちゃうので。
    • 「ぽ」で始まるキーワードってなかなかないですよね。

構成

このツールの構成は下記のようになっています。 ツールが検索/回答する順序として、

  1. 投稿されたキーワードが「会議室状況確認」を含む場合、会議室予約情報を回答。
  2. 上記キーワードではない場合「Google SpreadSheet」に記載があるかを確認。あればSpreadSheetの内容を回答。
  3. SpreadSheetにもキーワードが無い場合、ADに登録されている社員情報かどうか確認し回答。

という順序で検索/回答をかけるようにしています。 (botの起動トリガーを無理やり一つで完結するようにしたので若干無理があります。。)

■詳細

  1. 社員が特定のチャンネルに投稿する。
  2. SlackのOutgoing Webhookで投稿された内容を検知し、GAS(Google Apps Script)に投稿内容をPostする。 ※1
  3. 投稿内容が「会議室状況確認」なのか確認。
    • 会議室予約の場合:会議室空き情報を返して終了。 ※2
    • そうではない場合:次のステップへ。
  4. Google SpreadSheetの内容をGASにて確認。
    • 記載がある場合:検索結果を返して終了。 ※3
    • 記載がない場合:次のステップへ。
  5. 検索内容をhubot用のSlackチャンネルに投稿。ADやSlackのユーザ一覧に検索情報が含まれているか確認し、結果を返す。
    • ADからは電話番号や内線番号、部署名などの情報を抽出。 ※4
    • SlackAPIの「users.list」を使って、EmailAddressをキーにSlackUserIDを抽出。 ※5

f:id:undersooon:20181217004143p:plain

※1 参考URL
※2 参考URL
地味にハマったのが対象のリソース(会議室)を閲覧状態にしないと予定が取得できないので、 おまじないとして、 CalendarApp.subscribeToCalendar(\<resourceIDを入力>); と事前に定義しておくと良いです。
※3 こちらのURL を参考に行番号を取得して、こちらを参考に対象のセルを取得してます。
※4 WindowsServerでhubotを起動しています。「Get-AdUser -Filter 'enabled -eq $true' -Properties *」して、AD登録情報を内線番号など必要なものを引っ張ってます。
※5 参考URL

課題

メンテナンス性を考えずに適当に作ってしまったので構成がとても複雑になってしまいました。。
ADについてはいずれ、AWS Directory Serviceに移行予定なので、AD情報などはhubotを使わずに
Lambdaで参照するようにしたら多少はシンプルな構成になるのではないかと考えています。

botをリリースしてから起きたこと

内線番号とか、座席表って参照する機会はあるものの「いざ」という時に 「どこに書いてあったっけ状態」に陥りますよね。

お陰様で「ぽ」は社内でよく利用されていますので、 結果として、「どこに書いてあったっけ状態」を解消でき、 社員が「何かを探す」時間を少しは減らせたのかなと思います。

余談ですが、人が「探す時間」に時間を費やすのは年間150時間もあるそうですよ。
その時間を少しでも減らせる手助けができたかなと思います。

最後に

これからも、一休情シスは今回紹介したツールのようなものなどを提供して、
社員が 「本来やるべき業務に集中して時間を割けるよう」 サポートを続けていきます。
そして、この記事が同じ志を持つ誰かの参考になれば嬉しいです。

id:undersooon でした。

明日は@s-tokutake の 「Hangfire [導入編]」 です。お楽しみに!

普段MacやLinuxでWeb開発している方向けに知ってもらいたいC#とWindows

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

qiita.com


宿泊事業部のいがにんこと山口です。
UIUXチームでフロントエンド、バックエンドのアプリケーション開発を担当しています。

一休では宿泊事業とレストラン事業があります。
私が所属する宿泊事業では開発言語にC#とVB.NETを使用しています。
その背景から開発にはWindowsを使っています。
普段MacやLinuxでWeb開発しているWebエンジニアにとってはWindows、C#を使ったWeb開発はあまり馴染みがないかもしれません。
そこでそんな方向けにWindows、C#を使ったWeb開発についてお話したいと思います。

C#について

まずはC#について。
C#に触れたことのない方はどんなイメージを持っていますかね?
Microsoft製?Javaみたい?使っている企業は?

色々なイメージがあるかと思います。
ここではC#を企業、開発環境、記述といった点から説明します。

使っている企業

C#を使っている企業ってどんな業界なんでしょう?
主にゲーム系、金融系で使われているイメージです。
ゲーム系はコンシューマー、ソシャゲに限らないですが、Unityを使っている企業はその流れでサーバーサイドもC#という企業が多いようです。
金融系はMicrosoftのサポートを期待しての導入でしょうか。
サポート期間の長さも特徴の一つで、それも採用の理由の一つかもしれません。 https://support.microsoft.com/ja-jp/lifecycle/search?alpha=Microsoft%20.NET%20Framework%203.5 を見ると2008年に出た.Net Framework 3.5(C#の実行環境)が2028年までサポートされることになっているので、20年ものサポート期間があることになります。 自社のWebサービス、アプリ(ゲームを除く)をやっている会社ではまだまだ採用している企業が多いとは言えません。
余談ですがC#erに特化した会社なんてのもできましたがゲーム向けですね。

開発環境

弊社ではVisual StudioというIDEを使用しています。
Visual Studioと聞いて思い浮かべるのはVisual Studio Codeでしょうか。
Visual StudioとVisual Studio Codeは全くの別物です。
Visual Studio Codeはクロスプラットフォームで動作するのに対して、Visual StudioはWindowsでのみ動作します。
C#の開発に使用するIDEとしてはVisual Studioが一強です。
他にはVisual Studio for MacやJetBrainsから出ているRiderというIDEもありますが一休では使用していません。

一休は.Net FrameworkというWindowsでのみ動作するC#の実行環境をメインに使用しています。
そのためWindowsにロックインされていますが、クロスプラットフォームである.Net Coreの登場に加え、上記のIDEもあるためMac、Linuxでの開発も可能です。
まだ.Net Coreの採用事例は少ないですがC#はWindowsだけのものではなくなってきています。

Windows内部でのWebサーバー

ローカル開発環境はIISというものを使用します。
いわゆるApacheやnginxのようなWebサーバーです。
画像のようにGUIからの操作が可能であり、もちろんCUIからの操作も可能です。
一休では開発環境の初期構築をしてくれるバッチファイルがあり、それを実行するとこのIISのサイトを構築、設定してくれるようになっています。

f:id:igatea:20181214181051p:plain

Docker

実はWindowsもDockerを動かすことができます。
Macで使われているDocker for MacのWindows版、Docker for Windowsがあります。
内部ではHyper-VでMobyLinuxを立ててその上にDockerコンテナがホスティングされるようになっています。

f:id:igatea:20181217035420p:plain

それだけでなく今やVirualboxなどの仮想環境構築ソフトに頼らずUbuntuも使うこともできるWSL(Windows Subsystem for Linux)というものもあります。
VagrantやDockerとは使い勝手が違うので躓くこともありますが、使用用途を限定すれば今のところ問題ありません。 www.atmarkit.co.jp

Webフレームワーク

一休ではC#でWebを開発するときにASP.NET Web FormsとASP.NET MVC、ASP.NET Web APIを使用しています。
ASP.NET Web FormsはイベントドリブンモデルでWindowsアプリケーションの知識をWebでも活用できるように作られたフレームワークです。
単体テストを行いにくかったり、固有の概念やビューとなるHTMLの制御が難しいなど、今のWeb開発には合っておらず新規で採用するメリットはありません。
そのためこれから新規プロジェクトを作る場合はMVCかWeb APIで作成するかと思います。

ASP.NET MVCについては特筆する点はありませんが、一般的なMVCフレームワークの機能が備わっており、薄すぎず厚すぎずでちょうどいい塩梅のフレームワークとなっています。
ASP.NET MVCはMVCという名前はついていますが実はモデルに相当する機能はありません。
そこでロックインされていないのでモデルの作成は柔軟に行えるようになっているところもメリットです。

記述型式

ここからはC#の記述を紹介します。
C#は強い静的型付けの言語です。
そしてオブジェクト指向言語でもあります。

実際のコードを見てみましょう。

public class Child : Parent
{
    private readonly string str;

    private readonly int num;

    public Child(string str, int num)
    {
        this.str = str;
        this.num = num;
    }

    public string GetContent()
    {
        return $"str = {str}, num = {num}";
    }
}

var child = new Child("test", 1);
child.GetContent();
// str = test, num = 1

オブジェクト指向言語をやってきた方なら細かい差異はあれど理解しやすいのではないでしょうか。
このコードだけ見るとかなりJavaに近いですね。
名前空間、パッケージによるアクセシビリティレベルが違ったりEnumにメソッドが生やせないといった違いはあります。
加えて型推論、getter setter、async awaitなど便利な機能に加えてLINQという便利なコレクションライブラリもあります。

型推論

// int型に
var num = 1;

// List<string>型に
var strList = new List<string>() { };

getter, setter, メンバ

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName
    {
        get => $"{FirstName}{LastName}";
    }
}


var Person = new Person();
Person.FirstName = "Taro";
Person.LastName = "Tanaka";
Console.WriteLine(Person.FullName);

プロパティごとにアクセス修飾子を変えることもできます。

public int Age
{
    get;
    private set;
}

readonlyというものも。

public class Person
{
    private readonly DateTime birthday;

    public Person(DateTime birthday)
    {
        this.birthday = birthday;
    }
}

インスタンスフィールドであればコンストラクタ内でのみ割り当て可能という制限をつけることができます。

初期化

オブジェクト初期化子
public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public Person() { }
}

var Person = new Person()
{
    FirstName = "Taro",
    LastName = "Tanaka"
};
名前付き引数
public class Person
{
    public Person(int age, DateTime birthday)
    {
        // 処理
    }
}

var Person = new Person(age: 5, birthday: DateTime.Now);

async await

単数のタスク
public async Task<string> GetSingleAsync()
{
    var result = await GetStringAsync();
    return $"結果={result}";
}

private Task<string> GetStringAsync()
{
    var task = Task<string>.Run(() =>
    {
        // 何か重い処理
        return "async!";
    });
    return task;
}

var single = new SampleAsync().GetSingleAsync().Result;
複数のタスク
public async Task<string> GetMultiAsync()
{
    var task1 = GetStringAsync();
    var task2 = GetStringAsync();
    var task3 = GetStringAsync();
    var task4 = GetIntAsync();

    var results = await Task.WhenAll(task1, task2, task3);

    return string.Join(",", results);
}

var multi = new SampleAsync().GetMultiAsync().Result;

LINQ

コレクションを便利に扱える拡張メソッドです。
これがとても強力でこれがあるからこそC#は良いと言えるほどです。

var arr = new string[] { "a", "bb", "ccc" };

// 含んでいるか
arr.Any(s => s == "a");
// True

// 絞り込み
arr.Where(s => s.Length >= 2);
// IEnumerable<string> { "bb", "ccc" }

// 射影
arr.Select(s => s + s);
// IEnumerable<string> { "aa", "bbbb", "cccccc" }

// 他にも色々

IEnumerableな値(簡単に言うとforeachでループ可能)が返されるためメソッドチェーンで書くことが可能です。

arr.Where(s => s.Length >= 2)
    .Select(s => s + s)
    .ToArray();

他にも

  • 拡張メソッド
  • ジェネリクス
  • 属性定義、Attribute
  • null条件演算子
  • 例外フィルター
  • タプル、分解

といったものがあります。
記述だけ見るとモダンな言語に劣らず便利なものがそろっていますよね。
この記事で少しでもWindows、C#でのWeb開発に興味を持ってもらえたら幸いです。

明日はid:undersooon 「ちょっとしたことを検索できる」Slack botを作った、です。

一休.comにおけるAMP導入と今について

本記事は、一休.com Advent Calendar 2018の16日目の記事です。

qiita.com


デジタルマーケティング部で主に宿泊サイトを担当している田中(id:yakisoba6318)です。

今回は今年2月に導入したAMPについて導入時と今について紹介したいと思います。 内容に関しては主に宿泊サイトの話となります。

AMPとは?

AMPとは、Accelerated Mobile Pages (アクセラレイティッド・モバイル・ページ)と言い、主にGoogleが提唱しているモバイルウェブ高速化を目的としたプロジェクトです。

ここではそのAMPプロジェクトのオープンソースフレームワーク(AMP HTML)を指してAMPと言います。

www.ampproject.org

一休.comにおけるAMP導入動機

  • ランディングページのスピード改善
  • 速度改善による流入の増加
  • ユーザ体験向上

などいろいろありますが

簡潔に言うと ランディングページの最適化 です。

Choose a structured data feature  |  Search  |  Google Developers

一休.comのAMPページの紹介

現在一休で公開しているAMP対応ページとポイントを紹介します。

f:id:yakisoba6318:20181213171024p:plain
左からレストラン店舗ページ、宿泊施設ページ、宿泊観光ページ、宿泊特集ページ

レストラン店舗ページAMP

  • リッチカード x AMPで流入増
  • amp-list でプラン情報、割引率を動的表示

ランチ - ザ・ロビー - ザ・ペニンシュラ東京/コンチネンタルダイニング [一休.comレストラン]
*スマホからのみ確認できます

宿泊施設ページAMP

  • amp-list プラン情報・クチコミ表示
  • amp-access でクーポン表示

ザ・プリンス パークタワー東京 - 宿泊予約は[一休.com]
*スマホからのみ確認できます

宿泊観光ページAMP

  • レスポンシブ(Canonical / Responsive AMP )
  • amp-toolbox-optimizerを使ったページの最適化
  • まだまだ試験的なページで今は京都、金沢、箱根や一部温泉地のみですがこれからエリアを拡大予定

京都観光で行きたい名所!京都旅行におすすめ人気スポットランキング 19選【2018年】 - [一休.com]
*レスポンシブ対応のためPC・スマホどちらでも確認できます

宿泊特集ページ

  • amp-story実験的に公開中
  • 高級宿の素材をリッチに表示できるページ
  • ただ、日本では検索でAMPViewとしての表示はまだ出来てない
  • 今後の展開を期待

ジ・ウザテラス ビーチクラブヴィラズ 新規開業[一休.com]
*amp-storyがPC表示に対応しているためPC・スマホどちらでも確認できます

AMPコンポーネントの活用例

AMPページを作るにあたって欠かせないのが公開されてるコンポーネントです。 日々いろんなコンポートの開発が進んでいてAMPで出来ることが増えてきています。 今回はその一部を使った利用例を紹介します。

www.ampproject.org

動的なカルーセルを出したい

<amp-list 
    src="フォトギャラリーAPI"
    height=”254”
    layout=”fixed-height”
    single-item>
    <template type="amp-mustache">
      <amp-carousel
                    controls
                    loop
                    height="254"
                    layout="fixed-height"
                    type="slides">
        {{#photos}}
        <div>
          <amp-img src="{{ImageUrl}}"
                   layout="fill"
                   alt="{{ImageAlt}}"></amp-img>
        </div> 
        {{/photos}}
      </amp-carousel>
    </template>
</amp-list>

amp-listはamp上でxhrできるのでAPIからリスト取得、受け取ったリストをmustache形式でループさせて画像を動的に生成することができます。

https://www.ampproject.org/docs/reference/components/amp-list https://www.ampproject.org/docs/reference/components/amp-mustache

カルーセルの画像枚数を表示させたい

<amp-state id="GuideTopPhotoUrl" src="フォトギャラリーAPI"></amp-state>
<amp-carousel
    controls
    loop
    height="254"
    layout="fixed-height"
    type="slides"
    on="slideChange:AMP.setState({
      GuideState: {
        header: {
          selectedSlide:event.index,
          Is_slide:true
        }
      }
    })">
<!-- 略 -->
</amp-carousel>
<div>
    <span
    [text]="!GuideState.header.is_slide ? '' : 
(GuideState.header.selectedSlide + 1)  +
 '/' +
(GuideTopPhotoUrl.items.photos.length + 1) ">
    </span>
</div>

on属性にslideChangeをトリガーを設定してsetStateを更新、更新した情報をspanにbindすることで動的に枚数を更新することができます。

https://www.ampproject.org/docs/reference/components/amp-carousel https://github.com/ampproject/amphtml/blob/14153fe212a80aeb6f1e7a7f14e4849cc228eba9/spec/amp-actions-and-events.md#L177

結局AMPはどうなの?

紹介したように既に多くのページをAMP化してきましたが効果どうだったのか、 いろいろ工夫が必要な点もあったのでそれを踏まえて紹介したいと思います。

サイトパフォーマンス

f:id:yakisoba6318:20181213183156p:plain
Before: AMP Visually Complete 4G
上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ

こちらは少し古いレポートですが、始めはAMPコンポーネントの使い過ぎやamp-stateの不要な更新が多かったため AMPの要件は満たしていたがユーザ体験が悪化していました。

なので対応として

  • Googleキャッシュからの配信の恩恵を受けるためリアルタイム性の不要なコンテンツはxhrで取得しない
  • amp-stateの更新をbindするのに時間がかかるためUI上での不要なstate管理を辞める

具体的な例として

  • フォトギャラリーのamp-listを辞める
  • 開閉のUIをamp-state + hidden属性からamp-accordionに変更

結果として

変更
After: AMP Visually Complete 4G
上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ

AMPがoriginのスマホページよりVisually Completeの面では早い状態まで持っていくことができました。

ちなみにTTFBはoriginもほぼ同じでしたがDOMContentLoadで大きく遅れを取った結果このような数字になっていました(web page test)

ユーザ体験

amp-listで施設のプランを取得していますが、導入当初は読み込み後に展開するようにしていました。

変更前

ただ、これだとページ下部のコンテンツ(アクセスや施設情報など)閲覧中にリフローによって画面がずれ込み体験が悪くなっていました。

そこであらかじめスケルトンスクリーンを用意して高さを確保することでユーザ体験を損なわないように対応しました。(AMP開発者の方からのアドバイス)

変更後

これで表示が遅れてもユーザ体験に影響が少ないページになりました。

今後の方針

一休.comでの今後のAMP展開については以下の検討をしています。

  • amp-storyの活用事例を増やす
  • AMP上でjsが動くamp-scriptを使ったコンテンツのリッチ化
  • ITP2.0によるトラッキング対策(Safari対応)としてamppackagerやamp-toolbox-optimizerなどの使用による対応

digitalidentity.co.jp

まとめ

AMPのコンポーネントは容量用法を意識して使うのが良さそうだと感じました。

AMPページとしてのサイトの役割を考えつつ良質なユーザ体験と流入を獲得できるサービスをこれからも作っていきたいと思います。

AMPと聞くとスマホだけの印象のありますが、 PCページとしてのAMPやコンポーネントを個別にshadowDOMとしてページに取り入れることができたりと活用の幅はとても広いです。 今後の進化からも目が離せないですね!

明日は id:IganinTea さんの「 C#とWindows」です!

Ikyu Frontend Meetupを開催しました

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

qiita.com


こんにちは。 id:kentana20 です。一休で宿泊サービスの開発をしています。 今日は一昨日の夜に実施したイベント「Ikyu Frontend Meetup」の様子をレポートしたいと思います。イベントページはこちら。

ikyu.connpass.com

年末の忙しい時期にもかかわらず、多くの方にご応募・ご参加いただきました。

f:id:kentana20:20181212191052j:plain

今回のイベントのきっかけ

過去に2度、一休ではテック系イベントをやっていましたが、「ぼちぼちまたイベントやりたいな〜」と思っていたときに、 id:supercalifragilisticexpiali が書いた

user-first.ikyu.co.jp

を見た他社のエンジニアの方から「情報交換しましょう」と複数問い合わせや依頼があったので、イベントにしてしまおう、と思って今回のMeetup開催に至りました。

セッションの内容

今回のイベントでは「一休.com / 一休レストランでのフロントエンド開発」をテーマに3本のセッションを行いました。

セッション1: 「JavaScript/Vue.js アプリケーションのパフォーマンスチューニング」

1本目のセッションは宿泊サービスのエンジニアである id:ryo-utsunomiya がJavaScript/Vue.jsのパフォーマンスチューニングについてお話しました。

f:id:kentana20:20181212191727j:plain
ryo-utsunomiya によるJavaScript/Vue.jsのパフォーマンスチューニングの事例

一休.comモバイルWebのホテルページを高速化した事例をもとに

  • Lighthouse, Calibreを使ったパフォーマンスの計測
  • 同業他社のページを参考にした目標設定
  • JavaScriptのチューニングのポイント

などをお話しました。

f:id:kentana20:20181212193014j:plain

セッション2: 「imgix導入で画像最適化とサイトスピード改善」

2本目のセッションでは、画像の最適化によるチューニングの事例を id:akasakas がお話しました。

f:id:kentana20:20181212194216j:plain
akasakasによる画像最適化事例のセッション

もともと自社(Image Magick)で画像のリサイズや切り抜きなどの加工処理をしていたところを、画像最適化/配信のSaaSであるimgixを導入して最適化したお話を

  • 導入前の課題整理
  • 技術選定の観点とimgixに意思決定したポイント
  • imgixの優れている点
  • 導入後の効果

などの流れでお話しました。課題 → 解決のための手法 → 解決後の成果が順を追って語られていて、とてもわかりやすいセッションでした。セッションの中でも語られていましたが、一休が提供するサービスにおいて画像はとても重要で、ユーザ体験に大きな影響を与える部分なので、最適化によってユーザ体験を向上できたことは本当に良い成果につながったと思っています。

f:id:kentana20:20181212195231j:plain

セッション3: 「一休.comレストランのスマートフォン検索ページがSPAになりました」

3本目のセッションでは、イベントのきっかけになったブログエントリをもとに id:supercalifragilisticexpiali が一休レストランモバイルWebのレストラン検索ページをSPAにした事例ををお話しました。

  • 「もっと使いやすく」「サクサク動くように」というプロダクトのニーズ
  • SEOを落とさないようにという集客面のニーズ
  • Atomic Designを意識したコンポーネント指向設計を取り入れて生産性を上げたいという技術面でのニーズ

といった多角的な観点から Vue.js, Nuxt.js, ITCSS を導入した事例を細部まで丁寧にお話しました。

f:id:kentana20:20181212195441j:plain

内容が濃く、参加者のみなさんも真剣に聞いてくださっていて、とても良いセッションでした。

パネルディスカッション / 懇親会

3本のセッション終了後はビール片手にパネルディスカッションと懇親会を行いました。

事前に参加者の方からいただいていた質問をベースにスピーカーと話したり、参加者から当日出た質問に答えるQ&A形式で進行しました。

f:id:kentana20:20181212204134j:plain f:id:kentana20:20181212205745j:plain f:id:kentana20:20181212215210j:plain

編集後記

過去2回は他社と合同で実施したMeetupイベントでしたが、今回は一休単独での開催だったので、参加者が集まるか、イベントに満足いただけるか、など不安な点はありましたが、まずまず満足いただけたようで、開催してよかったです。

一休.com / 一休レストランともに、まだまだフロントエンド開発でやりたいことはたくさんあるので、継続的にコツコツ改善を続けてユーザにとって使いやすいサービスを提供していきたいと思います。

明日はアドベントカレンダーはお休み、明後日16日は id:yakisoba6318 による「 一休.comにおけるAMP導入について」です。お楽しみに!

一休レストランの店舗ページを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アプリケーションを指す。