🦔

LIFTに基づいてAngularのアーキテクチャーを考えてみた

2021/12/14に公開

Angular Advent Calendar 2021 14日目の記事です。

普段はフロントエンドエンジニアとしてAngularを使用して開発しております。今回は配属されたチームがAngularのアーキテクチャーを考える段階であったので、Angularの公式ドキュメントのコーディングスタイルガイドの一部にあるLIFTをできる限り参考にしてアーキテクチャー設計をしました。。
今回は実際にどのような設計をしたのか具体的な例を記載しながら説明していきます

前置き

Angularもコンポーネント思想です。これは最近流行りのフロントエンドフレームワークと同じです。
しかしページ内で使用するComponentの配置先は開発者のこれまでのバックグラウンドによってそれぞれ自由なディレクトリ構成を作ることが可能になってしまいます。なのですぐに別々の設計思想が混在してしまう可能性が多いにあります。

そのためプロジェクト、チーム内である程度アーキテクチャーの決まりを意識して、共有しておかなければ
機能追加でも、改修でもソースコードの検索のために開発者に余計な混乱や余計なコミュニケーションを発生させてしまいます。

そうならようにするためにアーキテクチャー設計をしっかりと行い、開発者間で認識があった状態でいることが開発者にとって重要であり、それは各開発者が開発を不安なく進められると考えております。

しかしなにも指針がない状態でアーキテクチャー設計を行うと、決めるのに開発者同士内でコミュニケーションコストがかかってしまい大事な開発にすぐに入ることができません。
そこでAngular公式ドキュメントに記載しているLIFTに基づいて、設計をすることで、公式ドキュメントに記載してあるという説得力を持った状態で意思決定をしていくことが可能です。

LIFTとはなんぞや

  • L:Locate
    • すぐに期待するコードを見つけられるようにするべき
  • I:Identify
    • 一目でコードを識別できるようにするべき
  • F:Flattest
    • できる限りフラットな構造を維持しするべき
  • T:Try to DRY
    • できる限りDRY原則を目指すべき

それぞれの頭文字をとって命名しているのがLIFTです。

実際に意識したこと

  1. Feature moduleごとでディレクトリはまとめよう
  2. Feature moduleで使用するページ、コンポーネント、型定義、サービスのディレクトリは一箇所にまとめよう
  3. ルーティングする画面とルーティングしない画面はディレクトリを分けよう
  4. どんな機能を持ったファイルか、ファイル名には必ずプレフィックスとタイプをつけて分かるようにしよう

具体的な例

Feature moduleごとでディレクトリはまとめよう

Locate, Identify

ここで意識したLIFTの考え型は すぐに期待するコードを見つけられるようにするべき という考え方と一目でコードを識別できるようにするべき です。

公式ドキュメントにはFeature moduleごとで整理するといいと書いてあります。
これはAngularがFeature moduleごとで遅延ローディングすることに関係しております。そのためFeature Moduleごとでディレクトリを切っていくことにしました。

src/app
├── features # ここ
│   ├── {feature名} # feature moduleごと
│   ├── {feature名}
│   └── {feature名}

Feature moduleで使用するページ、コンポーネント、型定義、サービスのディレクトリは一箇所にまとめよう

Locate, Identify, Flattest

ここで意識したLIFTの考え型は すぐに期待するコードを見つけられるようにするべき という考え方と一目でコードを識別できるようにするべき, できる限りフラットな構造を維持しするべき です。

設計を考える上で、コンポーネント、サービス、型のファイルをどこに置くかというお話が必ず出てきます。
ルート直下(src/app)に置くという話しもチーム内で会話しました。しかし上で決めた通りにfeature内で使用するそれぞれのディレクトリはfeature配下に置こうということに決めました。こうすることで開発しているfeatureのフォルダを検索すれば済むようになり、使用しているファイルの配置先をすぐに推論することが可能となると考えたためです。

src/app
├── features
│   ├── {feature名} 
│   │   ├── {feature名}.module.ts
│   │   ├── pages
│   │   ├── components
│   │   ├── models
│   │   └── services

ルーティングする画面とルーティングしない画面はディレクトリを分けよう

Locate, Identify

ここで意識したLIFTの考え型は すぐに期待するコードを見つけられるようにするべき という考え方と一目でコードを識別できるようにするべき です。

開発当初はモーダル、ダイアログをページ扱いをして、features/{feature名}/pagesに配置していました。でもそうしていくことで pagesディレクトリ内がごちゃごちゃになっていきました。これは LIFTの Locate の考え方から離れてしまいます。
なので モーダルをfeatures/{feature}/componentsに配置しました。そうすることで features/{feature}名/pages配下は必ずルーティングするファイルだけになり一目でコードを識別することが可能になります。

src/app
├── features
│   ├── {feature名} 
│   │   ├── pages
│   │   │   └── {page}
│   │   │        └── {page}.component.html | ts | spec.ts | scss
│   │   └── components # modalは componentsに配置することにした
│   │   │   └── modal-{component名}
│   │   │        └── {component名}.component.html | ts | spec.ts | scss

どんな機能を持ったファイルか、ファイル名には必ずプレフィックスとタイプをつけて分かるようにしよう

Identify, Try to DRY

ここで意識した考え方は一目でコードを識別できるようにするべきできる限りDRY原則を目指すべき という考え方です

Angularはそれぞれの役割毎でファイルを作成していくことになります。そのため開発を通じてVSCodeで開くファイルがいっぱいになっていきVSCodeのタブやVSCodeのナビゲーションに表示されているファイルはどんなファイルであるのか分からなくなってしまいます。
それだけでも開発に対してすごくストレスになっていきます。
そこでファイルに対してプレフィックスと、Typeをつけることにしました。これはプログラミング実装で重要だと言われているDRY原則から外れてしまうケースもあります。
しかしLIFTは可読性が落ちるのであればDRY原則にする必要はないと記載されております。だからTry to DRYなのです。

モーダル、ダイアログコンポーネントであれば、ディレクトリとファイルのプレフィックスに modal-*とつけました。
また型定義ファイルであれば、ファイルに*.model.ts とタイプを付与することにしました。

src/app
├── features
│   ├── {feature名}
│   │   ├── components
│   │   │   ├── fuga-fuga
│   │   │   └── modal-hoge-hoge # modalであるため modal-* とした
│   │   ├── models
│   │   │   └── hogefuga-hoge.model.ts # 型定義であるため *.modal.tsとした
│   │   ├── pages
│   │   │   └── task.component.ts | spec.ts
│   │   ├── services
│   │   │   └── fuga.service.ts | spec.ts

今後

Angular公式ドキュメントには

小さく始めますが、アプリケーションがどこに向かっているのかは意識しておきます。
短期的な実装の視点と長期的なビジョンを持ちます。

と記載されております。そのため、今回持続可能なサービスとなるように最低限度のアーキテクチャー設計をいたしました。なのでまだ機能追加などを通じて上のアーキテクチャーでは対応できなくなっていく可能性もあります。そのため上記で記載した以外にもやりたいことはあります。それを下記に記載いたします。

これからやりたいこと

pageにもタイプを付与したい

今のアーキテクチャー内で足りない箇所は ファイルの役割がページであるにも関わらず*.component.tsとつけていることです。 ディレクトリには pagesとつけているが、ファイルのタイプはcomponentなので、これは *.page.tsで運用をしたいと考えております。

components配下にさらに1階層をプラスで役割ごとのディレクトリを切って、プレフィックスに役割名を付与したい

心理学的に同じような役割を持ったファイルが9つを超えると人間は探せなくなってしまうみたいです[1]。そのためにこまめに見直しつつ、UIの役割ごとでディレクトリを切っていく必要も出てくるのかと思っております。

src/app/features/{feature名}
├── components
│   ├── sidenavbar # Sidenavbarのコンポーネントであること
│   │   ├── sidenavbar-{component1}
│   │   │    └── {component1}.component.html | ts | spec.ts | scss
│   │     └── sidenavbar-{component2}
│   │        └── {component2}.component.html | ts | spec.ts | scss
│   └── modal # modalのコンポーネントであること
│   │   ├── modal-{component3}
│   │   │    └── modal-{component3}.component.html | ts | spec.ts | scss
│   │   └── modal-{component4}
│   │        └── modal-{component4}.component.html | ts | spec.ts | scss

現状はコンポーネント層、サービス層だけであるが、ビジネスロジック層も入れたい

現状は、Serviceファイルでサーバーから値を取得し、Serviceファイルで値の整形を行い、Componentに値を渡しております。
AngularはMVCであります。そのため値の設計はビジネスロジック層を作成し、その中で行いたいと考えております。
その理由の一番は 一目でコードを識別できるようにするべき があります。サービスに記載しているメソッドはサーバーとの通信なのか、整形のためのメソッドなのか判断が出来ないためです。
またLIFTの考えからは少し外れてしまいますが、1ファイルは400行までの方がいいとAngularの公式ドキュメントに記載されていることもあります[2]。どうしても業務システムの行数は多くなっていきがちです。
なので今後はリファクタリングなどを通じてビジネスロジックを作って、保守性の高いソースを目指していきたいです。

Schematicsをカスタマイズして、実装者すべてがなにも考えずに同等のディレクトリ構成を作成したい

今は現状はREADMEなどに記載し、各開発者が上記のアーキテクチャー通りで実装していき、レビューなどを通じて第三者目線からアーキテクチャーを保っている現状があります。
これが出来ているのは、経験が豊富な開発者たちであることと、このアーキテクチャーを一緒に会話しながら考え決めたからだと思っています。しかし持続可能なサービス開発を考えた上ではこの運用ではいつかはだめになってしまう可能性が高いです。
持続可能なサービス開発を目指すためにはできる限り人の知識と努力に頼ることをしてはいけないと考えております。自動でなにも考えずにこのアーキテクチャーを作ることが一番いいことだと考えております。
そこでAngularのSchematicsをカスタマイズを行い、ドメイン名、コンポーネントの役割などを入力することで自動的に適切なディレクトリ構成を作成してくれるならば、LIFTのLの文字も知らない人でも綺麗なディレクトリ構成を作っていけると考えております。

最後に

こうゆうアーキテクチャー設計を開発者同士であーでもないこうでもないと会話をすることは楽しいことである反面、これまで経験してきた言語の違いなどの色々なバックグラウンドのため話が発散しがちです。
もしくはなかなか決めきれない場合も往々にしてあります。そのときに重要なことは公式がなんて言っているかどうかが重要であるかと思っております。
独自路線で開発していくことは、そのときはいいかもしれないのですが、必ず壁にぶち当たります。説明コストも多くかかってしまいます。
なのでこうゆう共通のルールを決めるときは必ず公式がなんて言っているのかを把握し、それを共有することが一番いいと思っております。
今回、このようにしっかりとアーキテクチャーを決めた理由は、開発しているサービスが長い年月をかけて運用されるものであることを約束されているからです。
そしてこれから大きくなっていくサービスであるためしっかりとアーキテクチャー設計をしなくてはいけないと考えたためです。
一方で開発者としてサービスをいち早くリリースすることも重要な要素であり、そのためには少しアーキテクチャーがむちゃくちゃでもリリースするのが一番だとは開発者として考えております。しかし大きくなってことに比例して改修コストも大きくなっていきます。そこのコストをどれだけを抑えられることも重要であり、それは最初のアーキテクチャー設計が一番重要であると考えております。

脚注
  1. マジカルナンバー7 ↩︎

  2. 単一のルール Style01-01 ↩︎

Discussion