サーバーレス・SPAによるモダンなWebアプリ開発

## はじめに

サーバーレスアーキテクチャとSPAを使ってWebアプリを開発しました。忘れないうちに知見をまとめます。[GitHubでコードを公開](https://github.com/raviqqe/code2d)しているので、何かの参考になればと思います。

作ったのは[code2d](https://code2d.net)というエンジニア向けのTodo管理アプリです。普通のTodoタスクだけでなく、後で読みたい記事、動画、本を一括管理できます。

![articles.png](https://qiita-image-store.s3.amazonaws.com/0/80296/52c9299e-a264-bb7c-76e9-a48691939b54.png)

## 使ったツール・プラットフォーム

ざっと並べると、

- フロントエンド
    - TypeScript
    - React（SPAフレームワーク）
    - Redux（アプリ状態管理）
    - Redux-Saga (非同期処理）
- バックエンド
    - TypeScript
    - Node.js
    - Firebase
        - Hosting (CDN)
        - Functions (FaaS)
        - Storage（リモートストレージ）
    - AWS Route 53 (DNS)

こんな感じです。雑多なモジュール・API等は省略しています。

フロンドエンドはVue.js等の他のフレームワークも検討しましたが、堅実にReact・Reduxの構成にしました。バックエンドはBaaSとしてFirebaseを利用し、Node.jsで書いたファンクションをFirebase Functionsにデプロイしています。

## TypeScriptについて

みなさんご存知（大好き）Microsoft謹製の静的型付けAltJSです。フロントエンドとバックエンドの両方に用い、ドメインロジック等一部のコードは共有させました。TypeScriptを使うとJavaScriptへのコンパイル時に型エラーを発見できるので大変便利です。

JavaScriptでうまくいっている人からすると型チェック用テストが要らなくなる程度の事かもしれません。しかし、TypeScriptの真のメリットは開発環境と統合されたときに発揮されます。エディタやIDEでTypeScriptのコンパイル・リントエラーを常に表示しておけば、開発者はそもそも型エラーになるようなコードを書かなくなります。型システムによって言語の自由度を低くすることで、余計なテストを書かなくて済みます。より重要なロジックやテストを書くことにリソースを集中できます。

Facebook派のあなたには[Flow](https://github.com/facebook/flow)をどうぞ。ReactとFlowが共にFacebook製なこともあり、Reactとの相性はこちらの方が良いです。また、少し前に話題になったREST APIツール[GraphQL](http://graphql.org/)の[Relay](https://facebook.github.io/relay)というReact用ライブラリもFlowをサポートしています。Facebook製ツールで固めるならFlowが断然おすすめです。ただ、以前React Nativeで利用したときはJavaScriptのモジュール間で対応しているFlowのバージョンが異なり少し苦労しました。

## SPA (Single Page App)について

### 速い

アプリの使用感として、いちいちページ更新が入るようなWebアプリと比べれば速いです。ただ、ユーザの行動毎にXHR飛ばしてレンダリングしてというようなことをすると同様に遅くなるので、事前にデータを取得しておく等の工夫が必要です。また、JavaScriptはバンドルして一ファイルにまとめているのですが、現在1.4Mあります。私の貧弱なラップトップでは、こいつのダウンロードや読み込みが中々遅く困っています。デスクトップではそれほど気になりませんでした。

### ネイティブアプリっぽいUIを作りやすい

SPAでは、ページ遷移のアニメーションを付けたり、ページ間でコンポーネントを共有することができます。これによってSPAでない複数ページからなるサイトにはないUIが実現できます。例えば、今回開発したアプリでは一時的なダイアログはページ間で共有されており、エラーメッセージの表示中にユーザがページを切り替えてもダイアログは消えません。

## FaaS (Function as a Service)について

バックエンドの抽象化の手段としてかなりいい感じです。前職でもモバイルアプリのバックエンドとして使っていたのですが特に不便はなかったため、今回も採用しました。

### メリット

#### 運用が楽

Firebase Functionsの場合、

```shell-session
> firebase deploy --only functions
```

のコマンド一発で指定されたディレクトリ内のファンクションがデプロイされます。FaaSではファンクションがHTTPリクエスト等のイベントに対して実行されます。従来のようにアプリケーションサーバの状態を考えなくても良くなるため、運用が格段に楽になります。ただし、APIのバージョン管理の手間は従来のままです。

個人開発のサービスでは運用の簡便さは特に重要なので、おすすめです。

#### 安い

FaaSは完全な従量課金制でファンクションの実行回数やCPU処理時間、メモリ使用量に応じて料金が計算されます。GCPのApp EngineもFlexible Environmentでオートスケールできそこそこ運用が楽でよいのですが、コストの面ではFaaSの圧倒的勝利です。

#### 永続化すべきデータが明確になる

プロセスとして動くようなアプリを開発・運用していると、アプリケーションサーバの状態に依存したコードを書きがちです。これに対し、FaaSを利用するとアプリケーションサーバが状態を持たないことを強制されます。これによって、アプリケーションの中でどのデータを永続化するかが明確になります。また、サーバが落ちて中間データが消えたといったトラブルを減らせます。

### デメリット

#### WebSocket等の長期間コネクションを保持するプロトコルが使えない

サーバー側からプッシュ通知を送りたい、ローカルのデータをリアルタイムに更新・同期したいという要件はよくあると思います。そういった場合、例えばアプリ起動時にWebSocketでサーバと双方向通信できるようにし、データを後から送るということを行います。FaaSでは一つの関数処理に時間制限があるため、このようなプロトコルが使用できません。プッシュ通知等を実現するには他の方法を取る必要があります。

今回開発したアプリでは、Firebase Realtime Databaseを利用してこれを実現しています。Realtime Databaseのイベント（レコードの更新）をWebアプリ側でフックし、Firebase Storage上の対応するデータを取りに行くようにしています。Realtime Databaseに直接データを保存してもよいのですが、[Firebase Storageを用いた方が200分の1程安い](https://firebase.google.com/pricing/)です。Firebase Realtime Storage。

#### コールドスタート

FaaSではしばらくファンクションにアクセスが無いとコンテナの起動やに時間がかかり、レイテンシーが増加する問題が指摘されています。対策としては、定期的にファンクションにをアクセスすることで緩和できるようです。調べてみると[Google Cloud Platformでは無いらしい](https://www.quora.com/Does-Google-Cloud-Functions-have-similar-cold-start-issue-in-AWS-Lambda)のですが、実測していないので分かりません。少なくともAWS Lambdaを使うときには要注意です。

## 今後の課題

### ルーティング

React用のルーティングモジュールに[react-router](https://github.com/ReactTraining/react-router)があります。色々あって入れてないのですが、また導入するかもしれません。

### UI/UX

継続的に改善すべきですがリソースが足りない。まず、KPIとしてDAUぐらいはまともに取れるようにしたい。

## まとめ

- サーバーレスとSPAでWebアプリ作った
- SPA
    - 速い（[dev.to](https://dev.to)とか話題になってるのでなんともですが）
    - ネイティブっぽいUIを作りやすい
- FaaS便利
    - 運用が楽
    - 安い
    - デメリットはアプリによっては致命的なので他の手段を使う

### おまけ

なんかそれぞれ別記事にした方がいい気がしてきました。まとまってない。

## 参考

- [Serverless FrameworkでLambdaのコールドスタート対策を行う](https://qiita.com/keita-nishimoto/items/d1c524a97dc844cb02cc)