当サイトはInternet Explorerに対応しておりません。より性能のよい、下記のようなブラウザを使ってみませんか?

IE in the grave
記事一覧に戻る

デモシステムを作成しました

営業活動を行うにあたり、デモ用の Web サービスを作成しましたのでご紹介させてください。

概要

古い写真と新しい写真を比較できる「Travelr」というサービスです(Time Traveler 的なことです)。

RePhotos鎌倉今昔写真など、既存のサービスがいくつか存在するものの、SPA のアプリは存在しなかったので、勉強がてら作ってみました。

自由に操作していただいて構いません。なお、暫定的にダミーデータを 5,000 件ほど登録してあります。

Web サイト

Travelr

ソースコード

https://github.com/junkboy0315/travelr/

構成

フロントエンドで使っているもの

名称 用途
react / redux UI/State 管理
redux-thunk redux ミドルウェア
redux-observable (RxJS) redux ミドルウェア
material-ui マテリアルデザインの UI フレームワーク
enzyme React コンポーネント用のテストツール
webpack / webpack-serve モジュールバンドラー
Typescript 型を使うため
Babel トランスパイラ
Workbox PWA 対応
  • Typescript は、Babel@7 の @babel/preset-typescript を使ってトランスパイルしています。Webpack のローダーを使うよりもシンプルな構成になるのでおすすめです。

  • 型の利用に関して、当初はFlowを使っていたのですが、当方の環境では動作がとても不安定であったため、途中で Typescript に変更しました。

バックエンドで使っているもの

名称 用途
Firebase 認証・ストレージ
PostgreSQL + PostGIS データベース
pgAdmin4 データベース管理
Node.js API サーバ
Kubernetes コンテナ管理
  • バックエンドのプロセスは、全て Docker コンテナにして、GCP 上の Kubernetes にデプロイしています。
  • データベースも Firebase に統合したかったのですが、緯度経度検索が弱いようだったので PostgreSQL + PostGIS を選択しました。

バックエンド・フロントエンドのどちらでも使っているもの

名称 用途
GraphQL 問い合わせ言語
lerna monorepo
jest テストツール
  • 一部の処理(データの取得)に、試験的に GraphQL を使用しています。

  • ソースの管理は Lerna を用いて一つのリポジトリで管理しています。

  • フロントエンド・バックエンドとも、Jest を用いてユニットテストを記述しています。React Component のテストには enzyme を使用しました。

    jest

データの保存先

データの保存先は、それぞれ下記のようになっています。

種類 保存先
認証情報 Firebase (Auth)
画像データ Firebase (Storage)
ユーザ情報
投稿のメタ情報
コメント
いいね
PostgreSQL

権限管理の方法

API サーバ(Node.js)

クライアントから送られてくる Fireabse の JWT トークンを Node.js サーバが受け取り、Firebase に問い合わせることで有効性を検証しています。

ストレージ

Firebase の Storege セキュリティルールで管理しています。

主な機能

認証

Firebase Auth を活用し、ひと通りの機能を実装しています。

  • Google 認証
  • Facebook 認証
  • メール認証
  • メールのベリファイ
  • パスワードリセット
  • アカウント削除

グリッド表示

インスタ風なルックスです。インフィニティにスクロールします。

マップ表示

マーカーもしくはマーカークラスタをクリックすると、モーダルで写真を閲覧できます。

フィルタ

撮影日や閲覧数などを条件にして、フィルタをかけて検索することができます。

地図検索にも対応しており、例えば「出雲大社から 50km 以内」といった検索も可能です。これは、Google Geocoding API と PostGIS の機能を組み合わせています。

閲覧

投稿の詳細画面では、スライダを使って古い写真と新しい写真を見比べることができます。また、いいねやコメントをつけることができます。

投稿と編集

  • 写真を投稿したり、投稿した写真の情報をあとから編集できます。
  • スマホであればカメラで撮影して投稿することもできます。
  • 住所や建物名から検索することで、撮影位置を設定できます。ピンをドラッグすることで微調整もできます。

投稿の管理

自分の投稿に関する統計情報の閲覧や、投稿の削除ができます。

PWA 対応

  • App Shell をキャッシュしているため、2 回目以降の訪問時において、初期レンダリングが高速に完了します。また、通信が不安定な環境であっても、必要最低限の部分は問題なくレンダリングされます。
  • AppManifest によりスマホのホーム画面等への追加が可能となっており、スタンドアロンアプリ風に見せることができます。

その他

スライダの作成

スライダ

上記のようにスライダを使って写真を比較できる React コンポーネントが必要だったのですが、よいものがありませんでした。このため、react-compare-imageというライブラリを自作しました。このライブラリは npm で公開しています。

Cloud Function の利用

Firebase の Cloud Funtion を使っています。画像がストレージにアップロードされると、自動的に下記の処理が走ります。

  1. Cloud Vision APIで画像を分析し、暴力的な画像など、不適切な画像であると判定された場合は特定の処理を行う。
  2. オリジナルの画像から、三種類の異なるサイズの画像を生成し、ストレージに配置する。

Redux Middleware の選択

はじめは redux-thunk を使っていたものの、すぐに辛くなりました。

「一つのアクションが終わったら、別のアクションを実行する」という処理が、thunk ではうまく書けないからです。実際には書けないことはないですが、とても辛いし、一つのロジックが複数のアクションに散在して管理性も低くなります。

このため、途中で redux-observable を導入しました。特に、スナックバー通知、プログレスバーの表示、ページのリダイレクトなど、システム全体で利用するロジックは、redux-observable を活用することで劇的に書きやすくなりました。

例えば下記のコードはリダイレクトの処理をしている Epic の例です。リダイレクトの処理を書きたいとき or リダイレクトに問題が発生したときは、この一箇所を見るだけですみます。

export const redirectorEpic = (action$: ActionsObservable<any>) =>
  action$.pipe(
    ofType(
      types.GET_OR_CREATE_USER_INFO_SUCCESS,
      types.DELETE_USER_SUCCESS,
      types.SIGN_OUT_USER_SUCCESS,
      types.CREATE_POST_SUCCESS,
      types.EDIT_POST_SUCCESS,
      types.DELETE_POST_SUCCESS,
    ),
    map(action => {
      switch (action.type) {
        case types.GET_OR_CREATE_USER_INFO_SUCCESS:
          if (history.location.pathname === '/auth') history.push('/all-map');
          break;
        case types.DELETE_USER_SUCCESS:
          history.push('/');
          break;
        case types.SIGN_OUT_USER_SUCCESS:
          history.push('/');
          break;
        case types.CREATE_POST_SUCCESS:
          history.push(`/post/${action.payload}`);
          break;
        case types.EDIT_POST_SUCCESS:
          history.push(`/post/${action.payload}`);
          break;
        case types.DELETE_POST_SUCCESS:
          history.push('/account/posts');
          break;
      }

      return { type: types.USER_REDIRECTED };
    }),
  );

開発を終えて

  • 全てのコードに対して愚直にテストを書いていたら、かなり手間を取られました。どのテストを書かないか、ということも検討しなければならないですね。
  • この開発には、実働で 40 日程度かかりました。うち、バックエンドにかけた日数が 12 日、フロントエンドにかけた日数が 28 日です。既存のライブラリを最大限活用し、フロント部分をいかに早く終わらせるかがキモだと感じます。
  • ところで、良い写真をお持ちでしたらぜひ投稿してください 📸。写真がそこそこ集まったらダミーデータは削除するつもりです。
profile

田村 翔太

Yuuniworks 代表。島根県浜田市を拠点に主にフロントエンド開発のお手伝いをしているフリーランスエンジニアです。React/Vue.jsを用いたSPAの開発や、NodeJSを使ったAPI開発を得意としています。

記事一覧に戻る