React + Reduxで作ったIsomorphic(Universal) JSなサービス開発の裏側

はじめまして、2016年新卒入社のいちきです。僕は現在、11/14にWeb版がリリースされたばかりのブッキングテーブルというサービスの開発チームでフロントエンドからバックエンドまで一貫して担当しています!

ブッキングテーブルとは?
https://bookingtable.jp/

icon.png

ブッキングテーブルは全国の飲食店の予約を全てネットで予約が出来る、予約特化アプリです。 既に2016年4月にiOS、Android版が同時にリリースされていましたが、遂に11/14に待望のWeb版がリリースされました!
ブッキングテーブルのWeb版は、Single Page ApplicationやServer Side Renderingなど、フロントエンド技術で様々な取り組みを行っています。
「予約特化アプリを目指す」という目標に対して、エンジニア側からもユーザにとっていかに良いUXを体験してもらえるかを考えて日頃開発をしています。 そんなWeb版が無事リリースされたこともあり、今回はWeb開発の裏側についてお話ができればと思います。

Web開発の構成

Webでは、NginxでUser-Agentを確認してAndroidか、iOSならSP版。それ以外ならPC版で振り分けています。
Node.jsではExpressフレームワークを利用し、Session管理、API Gatewayを行っています。
それに加えて、React.jsとReduxを利用することによってサーバサイドレンダリングを可能にしています。

blog.001.jpeg

利用したフロントエンド技術

カテゴリ 説明
React.js Framework Facebookが開発した画面のコンテンツを表示させるためのViewフレームワークです、画面のコンテンツ表示だけなので、イベントの実行などは特に既定されていません
JSX フロント React.jsの仮想DOM生成をXML風に記述出来る
Redux Framework Facebookが提言するFluxのアーキテクチャに基づいたフレームワークです。画面で実行されたイベントをstateと呼ばれる領域に保持し、「イベント発行 -> stateの更新 -> 画面レンダリング」のサイクルを管理します
SCSS Script style記述用スクリプト

各バージョン

ミドルウェア バージョン
Node.js v6系
npm v3系

Isomophic Javascriptの選定理由

今回はNode.jsを用いて、Isomorphic Javascript(最近だとUniversal Javascriptと言われている)のコンセプトに従って開発しています。
Isomorphic Javascriptとは、クライアントとサーバサイドで全く同じソースを共有する仕組みです。
そのため、例えばサーバサイドをLL言語で記述して、クライアントサイドをJavascriptで書いた場合、ブラウザ上でのバリデーションとサーバサイドのバリデーションは別々のコードを使用しなければなりません。
それに引き換えクライアントとサーバサイドをJavascriptで統一させると、同じロジックを複数の言語で再記述する必要性がなくなり、保守性が高まります。
また、サーバーサイドもJavascriptを利用することによって、React.js、Reduxでサーバーサイドレンダリングを可能としています。サーバーサイドレンダリングについては後述しております。

blog.007.jpeg

Signle Page Application (SPA)

ブッキングテーブルではReactを使い、Single Page Applicationでの開発を行っています。
Single Page Applicationはブラウザ側のJavascriptからDOMを組み立てる仕組みで、サーバーから返されるHTMLには空のdivだけがあってそこからJavascriptを読み込んでtemplateを描画する仕組みです。
それにより画面遷移する度に新しくhtmlのページを読み込むのではなく、変更されるDOMのみを操作させます。
そうすることでページ読み込みによるユーザのストレスを軽減させることが出来ます。

blog.003.jpeg blog.004.jpeg

Server Side Rendering (SSR)

Single Page Applicationには確かにUX上でのストレスを軽減させる効果がありますが、以下の問題点が考えられます。

  • First Viewの表示に時間がかかる

    • サーバーサイドからHTMLが返される場合と比べて時間がかかる。初期表示が遅いとユーザが直帰してしまう可能性を高めてしまう
  • SEOへの懸念

    • コンテンツを認識できるクローラは増えているが、従来のサーバでのレンダリングと同等かどうかは懸念がある。実際Page Speed InsightなどではSingle Page Applicationは点数を下げられる

サーバサイドレンダリングはこれらの課題を解決してくれます。
クローラからのアクセスは伝統的な画面のように画面遷移毎にサーバ側でレンダリングします。
ユーザからのアクセス時には、アクセスした最初のページのみサーバ側でレンダリングし、 以降はクライアント側でのDOMの切り替えのみでサーバ側でレンダリングすることはありません。
そうすることで、初期のロード時間やSEOでの対策が可能となります。

blog.004.jpeg blog.005.jpeg

ES2015

Web開発のメインとなっている言語がとなっていますが、Javascriptの今までの印象で言うと、「Classがない、prototypeが分かりにくい」「callBack地獄に陥る」などなど良く耳にする話だと思います。
ただ、最近のJavascriptで言うとES2015の登場により今までのJavascriptの書き方とは印象が大分変わっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//class
//before
function Dog(name) {
  this.name = name;
}
Dog.prototype.bark = function () {
  console.log(this.name + 'は吠えました');
}
var dog = new Dog('まる');
dog.bark();
//after
class Dog {
  constructor(name) {
    this.name = name;
  }
  bark() {
    console.log(this.name + 'は吠えました');
  }
}
const dog = new Dog('まる');
dog.bark();

ES2015の問題とBabelの利用

とても便利に感じるES2015ですが、フロントエンド環境(ブラウザ)上ではまだまだ直接ES2015を実行することが出来ない状態が続いています。(Node.jsでは実行可能)
そのため、ブッキングテーブルではES2015で書かれたJavascriptをブラウザで実行出来るようにES5形式へコンパイルさせるBabelというトランスパイラを採用しています。
これによりフロントエンドでもES2015を利用して開発を行うことが可能になります。

Atomic Design

ブッキングテーブルではAtomic Designという考え方を取り入れています。
Atomic Designはページ単位で考えるのではなく、コンポーネント単位同士での組み合わせでページを作ります。
ブッキングテーブルの場合、ページのデザイン自体はデザイナーの方が作ります。そこからエンジニア側でAtomic Designに沿ってページの中身をComponent単位で切り分けて実装を行います。
そうすることで、ボタンはもちろん、ダイアログなどをComponent単位で管理し、使いまわすことが出来ます。

blog.006.jpeg

ディレクトリ構成

ここで一番伝えたい点は、ディレクトリの分け方がclient,server,sharedという分け方をしている点です。
sharedはサーバーサイドとクライアントサイド両方で利用出来る記述が入っていて、最もコード量が多くなっています。
コードを共通化することによってコードの再利用が可能となっています。

ディレクトリ構成
bin express のアプリケーションサーバ処理
config 環境別の設定値
src client Client側だけで行う処理を記述。scrollBarの拡張、loggerなど
server services REST APIを呼び出すService
middlewares expressのミドルウェア
shared components atoms ボタン、ドロップダウンリストなどの全画面で扱う最小の部品単位のコンポーネント
molecules 店舗カセットなど複数の画面で通して扱う構造を持ったコンポーネント
organisms 各画面単位のコンポーネント
フッターコンポーネント
ヘッダーコンポーネント
templates 画面全体のレイアウト
サイト共通のスタイル定義
redux middleware reduxのミドルウェア
modules reducerとActionの置き場所

※注意: 一部省略をしております。

まとめ

開発全体を見ると最初のReact、Redux自体のスキル習得には少し時間が必要だったものの、それでもフロントエンドだった人がバックエンドも、バックエンドだった人がフロントエンドなどと、境界を減らすことで開発コストを下げられたと思います。
逆に困ったことは、会社で共通利用してきたツールでも少し工夫が必要なことでした。簡単な例だとライフスタイルではAdobe Analyticsというログ収集ツールを多くのサービスが利用していますが、ブッキングテーブルだとComponent単位で画面が設計されているため、単純な画面遷移時にログを取るのではなくて、stateが変わったタイミングでログを飛ばすなど、少し工夫が必要でした。
こういった簡単に見えていたが、実際には少し実装が必要なものも少なからずあったかなと思います。
フロントエンド界隈は技術の移り変わりが多いため、Documentが少ないこともありますが、自社に限らずフロントエンド界隈の情報発信を今後もしていけたらなと思います!