├── .gitignore ├── .textlintrc ├── .travis.yml ├── README.md ├── docs ├── README.md ├── component.md ├── css.md ├── domain.md ├── img │ └── workflow.png ├── infra.md ├── store.md └── use-case.md ├── misc ├── .babelrc ├── postcss.config.js └── z-index.css ├── package.json ├── prh.yml ├── prh ├── .gitignore ├── .travis.yml ├── README.md ├── css.yml ├── ja │ ├── jser-info.yml │ ├── kanji-open.yml │ ├── spoken.yml │ ├── typo.yml │ └── web+db.yml ├── javascript.yml ├── package.json ├── software.yml └── unique-noun.yml ├── refs.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/workspace.xml 8 | .idea/tasks.xml 9 | .idea/dictionaries 10 | .idea/vcs.xml 11 | .idea/jsLibraryMappings.xml 12 | 13 | # Sensitive or high-churn files: 14 | .idea/dataSources.ids 15 | .idea/dataSources.xml 16 | .idea/dataSources.local.xml 17 | .idea/sqlDataSources.xml 18 | .idea/dynamic.xml 19 | .idea/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/gradle.xml 23 | .idea/libraries 24 | 25 | # Mongo Explorer plugin: 26 | .idea/mongoSettings.xml 27 | 28 | ## File-based project format: 29 | *.iws 30 | 31 | ## Plugin-specific files: 32 | 33 | # IntelliJ 34 | /out/ 35 | 36 | # mpeltonen/sbt-idea plugin 37 | .idea_modules/ 38 | 39 | # JIRA plugin 40 | atlassian-ide-plugin.xml 41 | 42 | # Crashlytics plugin (for Android Studio and IntelliJ) 43 | com_crashlytics_export_strings.xml 44 | crashlytics.properties 45 | crashlytics-build.properties 46 | fabric.properties 47 | ### Node template 48 | # Logs 49 | logs 50 | *.log 51 | npm-debug.log* 52 | 53 | # Runtime data 54 | pids 55 | *.pid 56 | *.seed 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # node-waf configuration 71 | .lock-wscript 72 | 73 | # Compiled binary addons (http://nodejs.org/api/addons.html) 74 | build/Release 75 | 76 | # Dependency directories 77 | node_modules 78 | jspm_packages 79 | 80 | # Optional npm cache directory 81 | .npm 82 | 83 | # Optional REPL history 84 | .node_repl_history 85 | 86 | -------------------------------------------------------------------------------- /.textlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "filters": { 3 | "comments": true 4 | }, 5 | "rules": { 6 | "preset-ja-spacing": true, 7 | "preset-ja-technical-writing": { 8 | "ja-no-weak-phrase": false, 9 | "max-kanji-continuous-len": { 10 | "allow": [ 11 | "依存関係逆転" 12 | ] 13 | }, 14 | "no-exclamation-question-mark": { 15 | // allow to use ? 16 | "allowFullWidthQuestion": true, 17 | // allow to use ! 18 | "allowFullWidthExclamation": true 19 | } 20 | }, 21 | "prh": { 22 | "rulePaths": [ 23 | "./prh.yml" 24 | ] 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: "stable" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 複雑なJavaScriptアプリケーションを作るために考えること 2 | > Patterns For Large-Scale JavaScript Application Architecture [![Build Status](https://travis-ci.org/azu/large-scale-javascript.svg?branch=master)](https://travis-ci.org/azu/large-scale-javascript) 3 | 4 | クライアントサイドJavaScriptで複雑なアプリケーションを作るにおける議論した内容をまとめたものです。 5 | 6 | [同名のスライド](http://azu.github.io/slide/2016/react-meetup/large-scale-javascript.html)をベースに、実際のものに近い開発ガイドや雑多な内容を追加しています。 7 | 8 | 作成するアプリケーションによって必要な構造は異なるため、この構成がよいということを主張するものではありませんが、 9 | 何か参考になるものがあれば幸いです。 10 | 11 | Written by azu [![github][github]][@azu] [![twitter][twitter]][@azu_re] 12 | 13 | [@azu]: https://github.com/azu 14 | [@azu_re]: https://twitter.com/azu_re 15 | [twitter]: http://i.imgur.com/wWzX9uB.png "twitter icon" 16 | [github]: http://i.imgur.com/9I6NRUm.png "github icon" 17 | 18 | ## 140文字でOK 19 | 20 | 140文字しか表示されない環境向けのサマリです。 21 | 22 | > JavaScriptで複雑なアプリケーションを作る構成と実践ガイド。 23 | > ドメインモデルをどのように考えて作っていくかについて。 24 | > Babel、React、Almin、PostCSSがベース。 25 | 26 | ## 目的 27 | 28 | - 難しいものを簡単に作れないため、難しいものは考えて作るしかない 29 | - 考えて作るためには、議論できる言語化されたもの(コード)が必要 30 | - 長期的にメンテナンスするならこの傾向はより必要 31 | - ルールは明確に、でも最初から明確なワケではない 32 | - 議論できるベースをどのように作っていくかについて 33 | 34 | ## 複雑なものってどんなもの? 35 | 36 | ここでは、ライブラリ抜きで数万LOC([lines of code](https://ja.wikipedia.org/wiki/LOC "lines of code"))以上ぐらいを目安に考えている。 37 | 38 | ## 基本構成 39 | 40 | - [Babel](http://babeljs.io/ "Babel") 41 | - [.babelrcの設定例](./misc/.babelrc) 42 | - [React](https://facebook.github.io/react/ "React") 43 | - [Almin](https://github.com/almin/almin "Almin") 44 | - [PostCSS](https://github.com/postcss/postcss "PostCSS") 45 | - [PostCSSの設定例](./misc/postcss.config.js) 46 | 47 | ## アーキテクチャ解説 48 | 49 | - [Almin.js | JavaScriptアーキテクチャ](http://azu.github.io/slide/2016/child_process_sushi/almin-javascript-architecture.html "Almin.js | JavaScriptアーキテクチャ") 50 | - [Almin](https://github.com/almin/almin "Almin")をどのように考えて実装していったかのスライド 51 | - 構造化の考え方などについて 52 | - [複雑なJavaScriptアプリケーションを考えながら作る話](http://azu.github.io/slide/2016/react-meetup/large-scale-javascript.html "複雑なJavaScriptアプリケーションを考えながら作る話") 53 | - Fluxに慣れている人向けのスライド 54 | - Fluxでドメインモデルを扱うにあたりどこが曖昧に感じるかという点をベースにしている 55 | - CQRSを参考に[Almin](https://github.com/almin/almin "Almin")を実装するまでの話 56 | - どのような設計思想で作られているかについて 57 | - [参考資料](./refs.md) 58 | - その他の参考資料まとめ 59 | - 書籍や記事、スライドなど 60 | - [アーキテクチャをめぐるたび | Web Scratch](http://efcl.info/2016/09/30/architecture-refs/ "アーキテクチャをめぐるたび | Web Scratch") 別視点でのまとめ 61 | 62 | ## 開発ガイド 63 | 64 | 具体的なコーディングルールなどの開発ガイドのドキュメントは次を読む。 65 | 66 | - [docs](./docs) - 目次や全体像について 67 | - [css.md](./docs/css.md) 68 | - [component.md](./docs/component.md) 69 | - [domain.md](./docs/domain.md) 70 | - [infra.md](./docs/infra.md) 71 | - [store.md](./docs/store.md) 72 | - [use-case.md](./docs/use-case.md) 73 | 74 | ## 実装例 75 | 76 | - [azu/presentation-annotator](https://github.com/azu/presentation-annotator "azu/presentation-annotator: viewing presentation and annotate.") 77 | - 開発ガイドをできるだけ適用した参考実装 78 | 79 | ## 意見や疑問点 80 | 81 | Create [New Issue](https://github.com/azu/large-scale-javascript/issues/new). 82 | 83 | Issue作ってそこに書くか、[Twitter][@azu_re]とか適当に聞いてください。 84 | 85 | ----- 86 | 87 | 88 | 以下は考え始めたときに「こういう情報を教えてくれる人がいれば助かったのにな」というのを書いたものです。 89 | 90 | Inspired by [https://github.com/tokuhirom/java-handbook](https://github.com/tokuhirom/java-handbook) 91 | 92 | ## 無理なく理解 93 | 94 | [Almin](https://github.com/almin/almin "Almin")はできるだけクラスで書けるようにした。 95 | 色々な言語のバックグラウンドをもつ人にとってクラスで書けたほうが直感的に理解ができるため。 96 | 97 | Reduxのように関数を主軸した方が柔軟性やImmutabilityとの相性がいい。 98 | しかし、プロジェクトに新しく入る人が読みやすいコードとはまた異なる印象のものができあがる。 99 | また素のJavaScriptだとPayloadオブジェクトなどに型を付けにくいという問題がある。 100 | 101 | JSDocでは、オブジェクトに対して型を付けるよりも、クラスのインスタンスとした方が型を扱いやすい。(JSDocのtypedefの使い勝手の問題も大きい) 102 | 103 | これはTypeScriptやFlowなどを使えば解決し易いが、あくまでJavaScriptとして書いたときの理解を優先している。 104 | 105 | ## 小さくより小さく 106 | 107 | 問題が複雑化していくほど1つのモデルでは線形的に複雑度が上がっていく。 108 | モデルを小さく分けていくことは、線形的に上昇する複雑度を軽減するためのパターン。 109 | 110 | ## 役割分担 - ドメイン、API、View、デザイン、人 111 | 112 | 小さなプロジェクトなら分割の単位は大きくてもあまり問題は起きない。 113 | プロジェクトが大きくなり、人が増えてきた場合に色々問題が起きやすい。 114 | 単純にファイルのコンフリクトする可能性が上昇する。 115 | 116 | 無意味に分ける必要はないが、分けられるなら積極的に分けた方がよい。 117 | 役割が分担され、複数人で並行的に開発がしやすくなる。 118 | 119 | たとえば、コンポーネントは小さく作り、そのコンポーネントをレイアウトするコンポーネントを作って配置する。 120 | UseCaseはUseCaseごとにファイルとして分け、むやみにUseCaseをまとめないようにするなど。 121 | 122 | また、ファイルという単位ではなくレイヤーという大きな単位で分けることは、変更の影響範囲を限定しリファクタリングをしやすくする。 123 | 124 | > アーキテクチャで重要なものとしてレイヤー化があるが、優れたレイヤー化とは何か? 125 | > レイヤーに変更を加えても他のレイヤーに影響しないこと 126 | 127 | ## UseCase 128 | 129 | ## 1UseCase1ファイル 130 | 131 | - use-case/ ディレクトリを見たときに、誰(アクター)が何をしたいのかが把握しやすい 132 | - 1つファイルに複数のことが書かれていると読むときのノイズとなりやすい 133 | 134 | ## UseCaseの名前は能動的 135 | > 参考: [オブジェクト開発の神髄](http://bpstore.nikkeibp.co.jp/item/books/P82370.html "オブジェクト開発の神髄") P189 136 | 137 | UseCaseはアクターから見た能動的な名前にし、受動的な名前を避ける。 138 | 139 | - ✗「ゼミは学生に指定される」 140 | - ◯「学生はゼミを指定する」 141 | 142 | UseCaseの目的は、アクターがシステムをどうしたいかを理解するため。 143 | なので、受動的よりも能動的な表現で理解した方が望ましいため。 144 | 145 | この問題はDOMの状態をシステムへ反映するときによく考える必要がある。 146 | システムが変更されたからDOMに反映されるではなく、DOMのイベントを起因としてシステムを変更するという形のとき、 147 | UseCaseを受動的にしたくなってしまう。 148 | そこを堪えて能動的な名前にした方がよいと気がしている。(混ざってしまうのを避けたい) 149 | 150 | ## UseCaseはシナリオを書く 151 | > 参考: [オブジェクト開発の神髄](http://bpstore.nikkeibp.co.jp/item/books/P82370.html "オブジェクト開発の神髄") P189 152 | 153 | UseCaseにはそのユースケースにおけるアクションの一連の流れを書く。 154 | 機能をUseCaseに書くのではなく、あくまでどのような流れなのかを書く。 155 | 156 | 実際の機能と呼ばれるロジックはドメインモデルに書き、 157 | UseCaseはそのドメインモデルを使った流れを記述する場所です。 158 | 159 | 160 | ### UseCaseの事前条件 161 | 162 | UseCaseの事前条件はできるだけクリアにしておく。 163 | そうすることでUseCase自体はスッキリと実装できる。 164 | 165 | そうでない場合は、UseCase内容として無駄なチェックが増えてしまい本質として何がしたいのかがわからなくなる。 166 | 167 | たとえば、アプリの初期化が済めば必ず存在するデータをUseCaseで触るケースについて考えてみる。 168 | このときに、そのデータがあるかの判定をUseCaseに含めるとif文の記述が増えてしまいます。 169 | そのため、そのUseCaseの事前条件として、そのデータは存在しているとして書いたほうがシンプルになる。 170 | 171 | もちろんそのUseCaseにそのような事前条件があるということは、コメントや仕組みとして書いた方がいいです。 172 | 173 | ## ビジネスロジックはどこへ? 174 | 175 | ドメインへ。 176 | 177 | ## ドメインを考えられることができる 178 | 179 | ### ドメインを作るのは難しい 180 | すごい難しい。 181 | そのドメインのしごと、ライフサイクル、複雑度合いなどを考慮してドメインを分ける。 182 | チームメンバーと相談し、都度判断する。 183 | 184 | 逆にいえば、相談して作れない作りになっていたらおかしい。 185 | 186 | ## Plain Old JavaScript Object 187 | 188 | ドメインはできる限りPlain Old JavaScript Object - ただのJavaScriptで書く。 189 | これはドメインの独立性を維持し、コアドメインに集中するためでもある。 190 | 191 | Dateやデータ構造的な問題でJavaScriptに足りない機能がある場合はこの限りではない。 192 | 193 | > フレームワーク独立 194 | 195 | - [The Clean Architecture | 8th Light](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html "The Clean Architecture | 8th Light") 196 | - [クリーンアーキテクチャ(The Clean Architecture翻訳)](http://blog.tai2.net/the_clean_architecture.html "クリーンアーキテクチャ(The Clean Architecture翻訳)") 197 | - [持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処理 + FRP - Qiita](http://qiita.com/kondei/items/41c28674c1bfd4156186 "持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処理 + FRP - Qiita") 198 | 199 | Alminというフレームワーク?を作って導入しているが、そのフレームワークはドメインに対して影響は与えない。 200 | フレームワークとビジネスドメインは独立してないといけないため。 201 | それをかんたんに推し量る目安として、ドメインはPlain Old JavaScript Objectで書けるというものがある(ライブラリ依存してない) 202 | 203 | ## システムの状態(ドメイン)とViewの状態(State) 204 | 205 | ドメインとStateを分けたことで、アプリケーションの状態(ドメイン)とViewの状態(State)を分けて考えられます。 206 | この場合に、Stateは一時的な状態として扱い、[信頼的できる唯一の情報](https://en.wikipedia.org/wiki/Single_source_of_truth)としてはドメインを利用します。Stateをドメインから作成できるようにしておくと、Stateを作り直すことが簡単になります。 207 | 208 | たとえば、`history.pushState`でページ切り替えを行う際に、Stateをリセットしたいことが多いはずです。 209 | 210 | 逆に、Viewでしか利用しない状態などはStateだけで管理しておきます。 211 | (アニメーションやメニューを開いているなどの状態) 212 | 213 | この問題は実際のアプリケーションを作る際に置いて選択を迫られることが多いです。 214 | 215 | - `