はじめに
この記事はHow to Learn Software Design and Architecture | The Full-stack Software Design & Architecture Mapを翻訳したものです。
翻訳がおかしい箇所などあればご指摘頂けるとありがたいです。
元記事の著者: Khalil Stemmler(@stemmlerjs)
設計、アーキテクチャ、フロントエンド、ブロックチェーンに興味ある方是非Twitter(@show_clements)フォローしていただけると嬉しいです!
設計に関する記事
ソフトウェア設計・アーキテクチャの学び方
ソフトウェアデザインとアーキテクチャは、DevOpsやUXデザインのように、コンピューティングの領域の中でも独自の研究分野となっています。ここでは、クリーンコードからマイクロカーネルまで、ソフトウェアデザインとアーキテクチャの幅広さを説明するマップを紹介します。
Uber、YouTube、Facebook、Githubなどの企業で、世界で最も才能のある開発者たちがシステム構築の方法を学ぶために何が必要だったか考えたことはありますか?
かつては誰かのコンピュータ上の空のテキストファイルだったFacebookが、今ではあらゆることに手を出し、世界中で15億9千万人以上の人々に個人的な影響を与えている巨大企業になっているという事実は信じ難いです。
独学で開発を学んだジュニアや中級者にとって、クリーンでスケーラブルなシステムを設計する方法を学ぶためのロードマップは少し難しいでしょう。
多くの人々は、プロジェクトを1回か2回繰り返しただけでコードがメンテナンス不能な状態になってしまいます。
では、設計を改善するためには何から始めればよいのでしょうか?
ソフトウェアデザインとアーキテクチャが大きなテーマであるのは事実です。
・ ユーザーのニーズに応えるシステムを構築する
・ 変更が容易なコードを書く
・ メンテナンスが容易なコードを書く
・ テストがしやすいコードを書く
これらを理解するのはとても難しいです。必要とされる学習の幅があまりにも大きいのです。
そして、少なくとも一度は物事を動かすためのコードの書き方を知っていたとしても、より大きな課題は、現在の要求に追いつくために変更を容易にするコードの書き方を考えることです。
でもどこから手をつけたらいいのか...。
複雑な問題に直面したときは、最初の原理原則に立ち返ります。
第一原理
第一原理は、問題を解決するための最も効果的な方法です。
第一原理とは、問題を分解できない原子レベルまで分解し、絶対に正しいと確信できる部分から解決策を再構築する方法です。
では、これをソフトウェアに応用するために、まずゴールを明確にしましょう。
ソフトウェアの第一の目標は何ですか?
ソフトウェアの目標は、ユーザーのニーズを満たすものを、そのために必要な労力を最小限に抑えながら継続的に作り出すことです。
私は長い間、最適な定義を導き出すのに苦労しましたが、なぜそれが正確だと思うのか議論する準備はできています。
ユーザーのニーズに応えられないソフトウェアは、単純に良いソフトウェアとは言えません。
ユーザーのニーズは頻繁に変わるので、ソフトウェアが変更されることを前提に設計されているかどうかを確認することが重要です。
もしソフトウェアが(簡単に)変更できなければ、それはユーザーの現在のニーズを満たすことができないため、悪いソフトウェアとなってしまいます。
デザインが重要であること、デザイン性の高いソフトウェアを作る方法を学ぶことが重要であることはわかったが、長い道のりになるだろう。
今回の記事では、私が考えるソフトウェアデザインとアーキテクチャの具体的な柱を紹介したいと思います。
スタック
地図をお見せする前に、スタックをお見せします。
OSI参照モデルのように、各レイヤーは下のレイヤーの基礎の上に成り立ちます。
スタックには、そのレイヤーで最も重要なコンセプトの例をいくつか載せていますが、すべてではありません(あまりにも数が多いため)。
では、地図を見てみましょう。スタックは全体像を把握するのに適していると思いますが、マップはもう少し詳細で、結果的にはこちらの方が役に立つと思います。
マップ
以下は、ソフトウェアデザインとアーキテクチャのマップ1です。
ステージ1: クリーンコード
長く使えるソフトウェアを作るための最初のステップは、クリーンなコードの書き方を理解することです。
「クリーンなコードとは何だと思いますか」と尋ねると、おそらく毎回異なる答えが返ってくるでしょう。多くの場合、クリーンなコードとは、理解しやすく、変更しやすいコードであると言われています。低いレベルでは、以下のようなデザイン上の選択があります。
- 一貫性がある
- コメントを書くよりも、意味のある変数名、メソッド名、クラス名を使うこと
- コードが適切にインデントされ、スペースが確保されていること
- すべてのテストが実行できること
- 副作用のない純粋な関数を書く
- nullを渡さない
これらは小さなことのように思えるかもしれませんが、ジェンガゲームのようなものだと考えてください。プロジェクトの構造を長期的に安定させるためには、インデント、小さなクラスやメソッド、意味のある名前などが、長期的には大きな効果を発揮します。
どういうことかというと、クリーンコードは、優れたコーディング規約を持ち、それに従うということです。
これ、クリーンなコードを書くための一面に過ぎないと思います。
私が考えるクリーンコードとは、次のようなものです。
- 🧠 あなたの開発者マインドセット(共感、職人気質、成長思考、デザイン思考)
- ⚙️ あなたのコーディング規約(名前の付け方、リファクタリング、テストなど)
- 🤹🏼 あなたのスキルと知識(パターン、原則、コードの臭いやアンチパターンを回避する方法など)
クリーンなコードを書くためには、正しい考え方をすることが非常に重要です。ひとつの条件は、コードを書いているビジネスについて学ぶほどの関心を持つことです。ドメインを理解するだけの関心がなければ、ドメインの概念を表すのに良い名前を使っているかどうかを確認することはできません。また、機能要件を正確に把握しているかどうかを確認することもできません。
自分が書いているコードに関心がなければ、必要なコーディング規約を実行したり、意味のある議論をしたり、解決策に対するフィードバックを求めたりする可能性はかなり低くなります。
コードはエンドユーザーのニーズに応えるためだけに書かれていると思われがちですが、私たちがコードを書いているのは、私たち自身やチームメイト、そしてプロジェクトの将来のメンテナなど、他の人々のためであることを忘れています。設計の原則を理解し、人間の心理がどのように設計の良し悪しを決めるのかを理解することで、より良いコードを書くことができます。
つまり、本質的にあなたのこの旅のステップを表現する最高の言葉は?
「共感」です。
これができたら、ソフトウェア開発に欠かせないパターンや原則についての知識を深めることで、商売のコツを学び、時間をかけて改善していきましょう。
学習リソース
『クリーンコード (日本語版)』 (Robert C. Martin著)
『リファクタリング (日本語版)』 (Martin Fowler著 (2nd edition))
『The Pragmatic Programmer』(Andy Hunt and Dave Thomas著)
『The Design of Everyday Things』(Don Norman著)
クリーンなコードの書き方を学ぶのに最適なリソースは、ボブおじさんの著書『Clean Code』です。
ステージ2: プログラミングパラダイム
私たちは保守しやすい読みやすいコードを書いていますが、3つの主要なプログラミングパラダイムと、それらがコードの書き方に与える影響をよく理解しておくとよいでしょう。
ボブおじさんの著書『Clean Architecture』では、次のような点に注目しています。
- オブジェクト指向プログラミングは、ポリモーフィズムやプラグインを使ってアーキテクチャの境界を越える方法を定義するのに最も適したツールです。
- 関数型プログラミングは、データをアプリケーションの境界に押し出すために使用するツールです。
- 構造化プログラミングは、アルゴリズムを記述するためのツールです。
これは、良いソフトウェアは3つのプログラミングパラダイムのスタイルをハイブリッドで使用していることを意味しています。
厳密に機能的なコードを書くことも、厳密にオブジェクト指向のコードを書くこともできますが、それぞれが得意とする分野を理解することで、設計の質が向上します。
ハンマーしか持っていないと、すべてが釘のように見えてしまう。
学習リソース
- 『クリーンアーキテクチャ (日本語版)』(Robert C. Martin著)
- 『ドメインモデリングの機能化 (Domain Modeling Made Functional)』(Scott Wlaschin著)
- 『プログラミング言語の概念 (Concepts of Programming Languages)』(Robert W. Sebesta著 (10th edition))
ステージ3: オブジェクト指向プログラミング
それぞれのパラダイムがどのように機能し、どのようにコードを構成するように促しているかを知ることは重要ですが、アーキテクチャに関しては、オブジェクト指向プログラミングが明確に適しているツールです。
オブジェクト指向プログラミングは、プラグインアーキテクチャを作成し、プロジェクトに柔軟性を持たせることができるだけでなく、OOPには4つの原則(カプセル化、継承、ポリモーリズム、抽象化)があり、リッチなドメインモデルを作成するのに役立ちます。
多くのオブジェクト指向プログラミングを学ぶ開発者はここまでたどり着きません:
問題領域のソフトウェアを実装し、それをレイヤー化されたWebアプリケーションの中心に配置する方法を学ぶのです。
このシナリオでは、関数型プログラミングはすべての目的を達成するための手段のように見えますが、オブジェクトモデラーがどのようにしてビジネス全体をゼロ依存のドメインモデルにカプセル化することができるのか、その全体像を理解するためには、モデル駆動設計やドメイン駆動設計を知ることをお勧めします。
なぜそれが大事なのか?
ビジネスのメンタルモデルを作ることができれば、そのビジネスのソフトウェアを実装できるので、これはとても大きいことです。
学習リソース
- 『Object-Design Style Guide』(Matthias Noback著)
- 『クリーンアーキテクチャ (日本語版)』(Robert C. Martin著)
- 『ドメイン駆動設計 (日本語版)』(Eric Evans著)
ステージ4: 設計原則
この時点で、オブジェクト指向プログラミングがリッチなドメインモデルのカプセル化にとても有効であること、そして「難しいソフトウェアの問題」のタイプ3の"複雑なドメイン"を解決することを理解できたと思います。
しかし、OOPには設計上の課題もあります。
コンポジションはどのような場合に使うの?
継承はどのような場合に使うの?
抽象クラスはどのような使うの?
設計原理とは、オブジェクト指向のよく知られ、実運用で試されたレールガードとして使用するベストプラクティスとして確立されたものです。
一般的によく知られている設計原則の例として、以下のようなものがあります。
- 継承よりも構成
- 変化するものをカプセル化する
- 具象ではなく抽象的なプログラム
- ハリウッドの原則:「呼び出すな、俺たちが呼ぶから」
- SOLIDの原則、特に単一責任の原則
- DRY (Do Not Repeat Yourself: 繰り返すな)
- YAGNI (You Aren't Gonna Need It: それ必要ないでしょ→必要になってから実装しろ)
ただし、結論は自分で出すようにしましょう。誰かが言ったことに従うのではなく、自分が納得できるかどうか。自分が納得できるかどうかを確認してください。
学習リソース
- 『Head First Design Patterns』(たくさんの著者)
) - 『GoF Design Patterns』(たくさんの著者)
ステージ5: デザインパターン
ソフトウェアの問題のほとんどは、すでに分類されて解決されています。これらのパターンをデザインパターンと呼んでいます。
デザインパターンには「創造的」「構造的」「行動的」の3つのカテゴリーがあります。
創造的
創造的パターンとは、オブジェクトの作成方法を制御するパターンのことです。
創造的パターンの例:
- Singletonパターン:クラスのインスタンスが1つしか存在しないようにするためのパターン
- Abstract Factoryパターン:複数のクラス群のインスタンスを作成するためのパターン
- Prototypeパターン: 既存のクラスからクローンされたインスタンスから始めるためのパターン
構造的
構造的パターンは、コンポーネント間の関係を定義する方法を単純化したパターンです。
構造的デザインパターンの例:
- Adapterパターン: 通常は連携できないクラスを連携させるためのインターフェイスを作成するためのパターンです。
- Bridgeパターン: 実際には1つまたは複数であるはずのクラスを、階層に属する一連のクラスに分割し、それぞれの実装を独立して開発できるようにするためのパターン
- Decoratorパターン: オブジェクトに責任を動的に追加するためのパターン
行動的
行動的パターンは、オブジェクト間のエレガントなコミュニケーションを促進するための共通パターンです。
行動的パターンの例:
- Templateパターン: アルゴリズムの正確なステップをサブクラスに委ねるためのパターン
- Mediatorパターン: クラス間で許可される正確な通信チャネルを定義するためのパターン
- Observerパターン: クラスが興味のあるものを購読したり、変更が発生したときに通知を受けることができるようにするためのパターン
デザインパターン批判
デザインパターンは素晴らしいものですが、時にそれがデザインを複雑にしてしまうことがあります。そのため、YAGNIを意識して、できるだけシンプルなデザインを心がけることが大切です。デザインパターンを使うのは、本当に必要だと確信したときだけにしましょう。必要になったときには、きっとわかるはずです。
これらのパターンがどのようなものか、どのような場合に使うのか、そしてどのような場合には使わないのかを知っていれば、より大きなシステムをどのように構築すればよいのかを理解することができます。
なぜなら、アーキテクチャパターンはデザインパターンをハイレベルにスケールアップさせただけのものであり、デザインパターンはローレベルな実装(クラスや関数に近いもの)だからです。
学習リソース
- 『Head First Design Patterns』 (たくさんの著者)
ステップ6: アーキテクチャ原則
私たちはクラスレベルを超えた思考ができます。
私たちは、ハイレベルとローレベルのコンポーネント間の関係を整理・構築するために行う決定が重要であることを理解しています。これは私たちのプロジェクトのメンテナンス性、柔軟性、テスト性に大きな影響を与えるでしょう。
新しい機能や要件に対応するためにコードベースが必要とする柔軟性を、可能な限り少ない労力で構築するための指針を学びます。
すぐにでも身につけておきたいこと:
- コンポーネント設計の原則: 「安定した抽象化の原則」、「安定した依存性の原則」、「非循環的な依存性の原則」では、コンポーネントの構成方法、依存関係、それらを結合するタイミング、そして誤って依存性のサイクルを作り、不安定なコンポーネントに依存することの意味を説明しています。
- 「方針 vs. 詳細」では、アプリケーションのルールと実装の詳細を分離する方法について説明しています。
- 境界: アプリケーションの機能が属するサブドメインを特定する方法を説明しています。
ボブおじさんはこれらのたくさんの原則を発見し、最初に文書化したので、これを知るための最良の資料はやはりClean Architectureです。
学習リソース
- 『クリーンアーキテクチャ (日本語版)』(Robert C. Martin著)
ステージ7: アーキテクチャスタイル
アーキテクチャは重要です。
それは、システムを成功させるために必要なものを特定し、その要件に最も適したアーキテクチャを選択することで、成功の確率を高めることです。
例えば、ビジネスロジックが複雑なシステムでは、その複雑さをカプセル化するためにレイヤードアーキテクチャを使用することが有効です。
Uberのようなシステムでは、一度に多くのリアルタイムイベントを処理し、ドライバーの位置を更新する必要があるため、パブリッシュ・サブスクライブ形式のアーキテクチャが最も効果的かもしれません。
繰り返しになりますが、アーキテクチャスタイルの3つのカテゴリーは、デザインパターンの3つのカテゴリーに似ています。なぜなら、アーキテクチャスタイルは高レベルのデザインパターンだからです。
構造的
さまざまなレベルのコンポーネントや幅広い機能を持つプロジェクトでは、構造的なアーキテクチャーを採用することで、恩恵を受けることもあれば、苦しむこともあります。
例:
- コンポーネントベースのアーキテクチャは、システム内の個々のコンポーネント間の関心事の分離を重視しています。Googleを考えてみましょう。企業内にどれだけのアプリケーションがあるか考えてみてください(Google Docs、Google Drive、Google Mapsなど)。多くの機能を持つプラットフォームでは、コンポーネントベースのアーキテクチャーによって、懸念事項が疎結合の独立したコンポーネントに分割されます。これは水平方向の分離です。
- モノリシックとは、アプリケーションが1つのプラットフォームやプログラムにまとめられ、デプロイされることを意味します。 注: アプリケーションを適切に分離し、すべてを1つのピースとしてデプロイすれば、コンポーネントベースのアーキテクチャとモノリシックなアーキテクチャを両立させることができます。
-
レイヤード・アーキテクチャは、ソフトウェアをインフラストラクチャ、アプリケーション、ドメインの各層に分割することで、関心事を垂直方向に分離します。
レイヤード・アーキテクチャを使用することで、アプリケーションの関心事を垂直方向に削減する例。方法の詳細については、こちらをご覧ください。
メッセージング
プロジェクトによっては、メッセージングがシステムの成功にとって本当に重要な要素となる場合があります。このようなプロジェクトでは、メッセージベースのアーキテクチャは、機能的プログラミングの原則や、オブザーバーパターンのような行動的デザインパターンの上に構築されます。
メッセージベースのアーキテクチャスタイルの例:
- イベント駆動型のアーキテクチャでは、状態に対する重要な変化をすべてイベントとして捉えます。例えば、レコード取引アプリでは、両者が取引に合意すると、オファーの状態が「pending」から「accepted」に変わります。
- パブリッシュ・サスクライブアーキテクチャは、Observerデザインパターンの上に構築され、システム自体、エンドユーザー/クライアント、および他のシステムやコンポーネント間の主な通信方法となります。
分散型
分散型アーキテクチャとは、簡単に言えば、システムのコンポーネントが別々に配置され、ネットワークプロトコルを介して通信することで動作することを意味します。分散システムは、スループットの拡大、チームの拡大、(潜在的にコストのかかるタスクや)責任の他のコンポーネントへの委譲などに非常に効果的です。
分散型アーキテクチャの例:
- クライアント・サーバーアーキテクチャ。最も一般的なアーキテクチャの1つで、クライアント(プレゼンテーション)とサーバー(ビジネスロジック)の間で行うべき作業を分割します。
- ピアツーピアアーキテクチャは、ピアツーピア・ネットワークを形成して、アプリケーション層のタスクを等しい権利を持った参加者間で分配するものです。
学習リソース
- 『クリーンアーキテクチャ (日本語版)』(Robert C. Martin著)
- 『Software Architect's Handbook』(Joseph Ingeno著)
ステージ8: アーキテクチャパターン
アーキテクチャパターンは、それらのアーキテクチャスタイルの一つを実際に実装する方法を、より戦術的に詳しく説明するものです。
ここでは、アーキテクチャーパターンとそれを継承したスタイルの例をいくつかご紹介します。
- ドメイン駆動設計は、非常に複雑な問題領域に対するソフトウェア開発のアプローチです。DDDを成功させるためには、ドメインモデルの関心事を、データベース、ウェブサーバ、キャッシュなど、アプリケーションを実際に動作させるインフラの詳細から分離するために、レイヤードアーキテクチャを実装する必要があります。
- モデル・ビュー・コントローラは、ユーザーインターフェースベースのアプリケーションを開発するための最も有名なアーキテクチャパターンです。MVCは、アプリケーションをモデル、ビュー、コントローラの3つのコンポーネントに分けることで機能します。MVCは、開発を始めたばかりの頃は非常に便利で、他のアーキテクチャへの移行にも役立ちますが、ビジネスロジック多い問題に対しては、MVCでは不十分だと気づく時があります。
- イベントソーシングは、トランザクションのみを保存し、状態を保存しないという機能的なアプローチです。状態が必要になった場合は、最初からすべてのトランザクションを適用することができます。
学習リソース
- 『ドメイン駆動設計 (日本語版)』(Eric Evans著)
- 『実践ドメイン駆動設計 (日本語版)』(Vaughn Vernon著)
ステージ9: エンタープライズパターン
どのようなアーキテクチャパターンを選択しても、いくつかの構造や技術的な専門用語が出てくるので、それらを理解した上で、使用する価値があるかどうかを判断する必要があります。
多くの人が知っている例を挙げると、MVCでは、ビューはすべてのプレゼンテーション層のコードを保持し、コントローラはビューからのコマンドやクエリをリクエストに変換し、モデルが処理してコントローラが返すという仕組みになっています。
これらをモデル(M)のどこで処理するのか?:
- 検証ロジック
- 不変のルール
- ドメインイベント
- ユースケース
- 複雑なクエリ
- ビジネスロジック
単純にモデルとしてSequelizeやTypeORMのようなORM(object-relational mapper)を使用した場合、重要なものがどこに書かれるかはそれぞれの解釈に任され、(リッチであるべき)モデルとコントローラの間の不特定の層に入ってしまいます。
solidbook.ioの「3.1 - Slim (Logic-less) models」から引用。
MVCを超える旅の中でこれまでに学んだことがあるとすれば、それは「すべてのものには構造がある」ということです。
MVCが解決できなかったことに対して、ドメイン駆動設計では具体的に、それを解決するためのエンタープライズパターンがいくつか存在します。例えば、以下のようなものです:
- エンティティは、アイデンティティを持つモデルを表します。
- 値オブジェクトは、アイデンティティを持たないモデルで、検証ロジックをカプセル化するために使用できます。
- ドメインイベントは、関連するビジネスイベントの発生を示すイベントであり、他のコンポーネントから受信することができます。
選択したアーキテクチャスタイルに応じて、そのパターンを最大限に実装するために学ばなければならないエンタープライズパターンが他にもたくさんあります。
学習リソース
これらは、主にドメイン駆動設計とエンタープライズ・アプリケーション・アーキテクチャに焦点を当てた、いくつかの異なる学習リソースに過ぎません。しかし、これまで学んできたことの上に構築されているため、最もたくさんのことを学ぶことができ、最も深く学習することができる場所でもあります。
- 『Patterns of Enterprise Application Architecture』(Martin Fowler著)
- 『Enterprise Integration Patterns』(Gregor Hohpe著)
- 『ドメイン駆動設計 (日本語版)』(Eric Evans著)
- 『実践ドメイン駆動設計』(Vaughn Vernon著)
リソースと結論
このブログではドメイン駆動設計についてよく取り上げていますが、TypeScriptでリッチなドメインモデルを構築する前に、まず読者が知っておくと役に立つことがたくさんあります(レイヤードアーキテクチャ、OOP、モデル駆動設計、設計原則やパターンなど)。
もしあなたが第一リソースを探しているなら、私はsolidbook.io - The Software Design & Architecture Handbookをプレローンチしました。この記事で紹介したような良いソフトウェアを作るために、このマップの各段階で本当に必要だと思われることを読者に教えています。この本は現在、完結編まで販売されていますが、私がソフトウェアデザインとアーキテクチャを学んでいたときに個人的に使っていたその他の良いリソースもいくつかお勧めします。