AngularJSを使ったWebアプリのアーキテクチャ設計

AngularJSは公式で分かりやすいチュートリアルが用意されているし、日本語の記事も増えてきたし、けっこう簡単に使い始めることができるんじゃないかと思います。

でも、チュートリアルやサンプルはクライアントサイドオンリーなことが多くて、サーバーサイドも含めたWebアプリを作ろうと思うと、どういう構成にすればいいのか迷うのではないでしょうか？（僕がそうでした）

最初は試行錯誤していたのですが、書籍やネットの記事を読んだりGitHubで見つけたアプリを真似たりしているうちに、どういう構成にすればいいのかだんだん見えてきたので、解説してみたいと思います。

# SPA
最近、SPA（Single Page Applicationまたは Single Page Web Application）という言葉をよく耳にするようになりました。

SPAとは、最初のページだけ通常のWebアプリと同じようにサーバーからHTMLを取得して表示し、それ以降は、AJAXを利用してデータやテンプレートを取得しつつ、クライアントサイドで画面を書き換えたりページ遷移を行うような構成のWebアプリケーションのことです。

AngularJSを始めとするクライアントMVCのフレームワークを使ってアプリケーションを作る場合、SPAの構成にするのが一般的なようです。（もちろん、AngularJSを使ってSPAじゃない構成にすることも可能です）

では、なぜSPAを採用するのでしょうか？
それは、よりネイティブアプリケーションに近いUIを提供するためです。
ユーザーが操作するたびにサーバーに問い合わせてHTMLを生成するよりも、クライアントサイドでできることはできるだけクライアントサイドで行ったほうが早いですし、より細かなインタラクションが実現できます。

一方、静的コンテンツが多くて動きの少ないアプリケーションの場合、SPAにするメリットは少ないです。
また、クローラー対策が面倒だったりするので、SEOを重視する場合は向いていないかもしれません。

# サーバーサイドとクライアントサイドの役割

さて、SPAにすることが決まったら、サーバーサイドとクライアントサイドの役割分担は明確になります。

SPAの構成では、今までサーバーサイドで行っていた処理の多くをクライアントサイドに移譲することになります。
よって、サーバーサイドは以下のようにシンプルになります。

 * MVCパターンのViewにあたるものが不要となる
 * 主な役割はJSONを返すRESTfulなAPI
 * 基本的にステートレス。状態を持つとしても認証情報などの限られたものだけ

一方のクライアントサイドは複雑で規模も大きくなります。

 * 保守性向上のためMVCパターンを適用し、プレゼンテーション層とドメイン層を分離する
 * ユーザーの操作に応じてインタラクティブに動くリッチクライアント
 * ステートフル

クライアントMVCについて説明するとちょっと長くなりそうなので、詳細については次回の記事で解説したいと思います。

# ビルドツール

Play FrameworkやRuby on Railsのようなサーバーサイドのフレームワークには、JavaScriptやCSSを結合・圧縮してくれたり、altJSをコンパイルしてくれたりするような仕組みがあります。
僕も最初はこの機能を使ってAngularJSアプリの開発を行なっていました。
だけど、サーバーサイドのフレームワークに付属するこれらの機能はあくまでもオマケであって、ちょっと込み入ったことをしようとすると機能不足を感じてしまいます。

そこで、餅は餅屋、クライアントサイドにはクライアントサイド用のツールを使うのがよいと判断しました。
ビルドプロセスはGruntで記述し、パッケージ管理はBower、ひな形の自動生成はYeoman、テストランナーはKarmaなどなど、便利なツールがたくさんあります。最初は覚えるのが大変ですが、使いこなせば捗ります。
最近よく使われるツールについては、下記のスライドがまとまっていて分かりやすいです。

 * [JavaScriptフロントエンド開発の昨今](https://speakerdeck.com/naoya/javascripthurontoendokai-fa-falsezuo-jin)

# ディレクトリ構成

Play FrameworkやRuby on Railsでは、サーバーサイドのソースコードを`app`に、クライアントサイドのソースコードを`app/assets`格納し、静的コンテンツを`public`に格納する構成となっています。
しかし、今回はGruntなどクライアントサイドのツールを使うことにしたので、`app/assets`ディレクトリは使いません。
代わりに`ui`ディレクトリ（名前は何でもよいです）を追加し、クライアント用のソースコードやGruntfileなどをこの中に配置します。
そして、`ui`ディレクトリ内でGruntを実行すると、コンパイル結果のJavaScriptやCSSを`public`ディレクトリに配置するようにします。

ディレクトリ構成例を以下に示します。（説明に不要なディレクトリは省略しています）

~~~
Project
 ├ app      サーバーサイドのソースコード
 ├ public   静的コンテンツ
 └ ui       クライアントサイドのソースコード
~~~ 

サーバーサイドにPlay Frameworkを使っている場合は下記のソースコードが参考になります。

 * [Play Grunt / Angular Prototype](https://github.com/leon/play-grunt-angular-prototype)

Ruby on Railsを使っている場合は下記の記事が分かりやすいです。

 * [理想的な Rails, AngularJS 環境の構築](http://bokukoko.hatenablog.com/entry/20130825/1377415299)

# ルーティング

"シングルページ"と言われると、ページごとにURLが振られないから個別にブックマークもできないし、戻るボタンや進むボタンも使えなくて不便じゃないの？と思われるかもしれませんがそんなことはありません。
AngularJSでは、HTML5のpushState/popStateを利用することにより、クライアントサイドでページの切り替えを行いつつ、それに合わせてURLの変更を行うことができます。（HTML5モードを有効にした場合。参考：[AngularJS の $locationProvider.html5Mode について](http://qiita.com/shogogg/items/542bd6d18f777bcc24bc)）

僕は最初このあたりの仕組みをちゃんと理解できていなかったので、サーバーサイドとクライアントサイドのルーティングをどう使い分ければいいのか少し悩みました。
分かってしまえば全然難しくないんですけどね。
というわけで、[先ほどで紹介したPlay+AngularJSのサンプル](https://github.com/leon/play-grunt-angular-prototype)のルーティング設定を図示してみました。

![routing.png](https://qiita-image-store.s3.amazonaws.com/0/3489/6b9a239a-780e-ca36-f0be-94781198708f.png)

初回のページ取得はサーバーに問い合わせ、それ以降のユーザー操作はクライアントサイドで捌き、裏でAJAXを使ってサーバーからデータやテンプレートを取得する構成となっています。

# まとめ

アーキテクチャ設計は、システムの特性に応じて考えなければならないものなので、今回解説した内容がどんなアプリにでも適用できるというわけではありませんが、少しでも考え方の参考になればうれしいです。

# 参考情報

* [Single page apps in depth](http://singlepageappbook.com/)
* [Mastering Web Application Development with AngularJS](http://www.amazon.co.jp/Mastering-Web-Application-Development-AngularJS-ebook/dp/B00EQ67J30)
* [GUIアーキテクチャパターンの基礎からMVVMパターンへ](https://www.slideboom.com/presentations/591514/GUI%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AE%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89MVVM%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%B8)
* [ダブルMVCの意味するところ](http://wazanova.jp/post/64057743910/mvc-gogaruco-2013)
* [エンタープライズ アプリケーションアーキテクチャパターン](http://www.amazon.co.jp/dp/4798105538)
* [サービスデザインパターン](http://www.amazon.co.jp/dp/4048865366)
* [Backbone.jsを利用したクライアントサイドMVCの導入についてそろそろ書いておくか](http://d.hatena.ne.jp/kazuk_i/20110407/1302130947)