概要
良い建物には優れた設計図が不可欠なように、良いシステムには優れたアーキテクチャが必要です。そして、その設計を担うのがアーキテクトです。
アーキテクトには高度なスキル、知識、判断が求められますが、職種としてちゃんと定義されている会社は少なく、そのシステムに詳しい人が「なんとなくアーキテクト的な仕事を担っている」ような場合がほとんどではないでしょうか。私も右も左もわからずなんとなくアーキテクトの真似をやっている時代もありました。今思うと反省点だらけです。
この記事では、現在アーキテクトをやり始めた方、これからアーキテクトを志す方を対象に、アーキテクトが具体的に何をすべきなのか、どう考え判断していけばいいのかを解説していきます。
参考図書
アーキテクチャとは
まず最初に、アーキテクチャとはなんでしょうか。いくつかの書籍を参考にすると以下のように定義されています。
- アーキテクチャとは、システムを構築するための計画である。
- アーキテクチャの目的は、求められるシステムを構築・保守するために必要な人材を最小限に抑えることである。リリースのたびに労力が増えていくようでは、優れたアーキテクチャとは言えない。
では、そんなアーキテクチャを考えるアーキテクトとはどんなお仕事をする人でしょうか。
アーキテクトのお仕事とは
アーキテクトのお仕事は複雑かつ曖昧で、多岐に渡ります。明確な定義は難しいですが、以下のようなものがあります。
- プロジェクトマネージャーではないが、ソフトウェアをいつ届けるかを決定する
- プロダクトマネージャーではないが、ソフトウェアがビジネス目標を満たすことを確認する
- コードを書くこともあるが、コード以上のものを設計する
- 納期に間に合い、予算内でシステムを構築し、メンバーが疲弊しないことに気を配る
- アーキテクチャをチームに伝える。アーキテクチャとなぜそうしたのかの根拠を伝え、チーム内での有意義な議論を促進させる。
などです。
どれくらい設計に時間をかけるか
まず、アーキテクチャはシステムにとって必要な投資です。アーキテクチャに先行投資することで、将来システムを改修するのにかかる時間を削減できます。
私は、設計をまともにしなかったばかりに、システムを改修しようとすると膨大なコストがかかるようになってしまい、結果手出しできなくなっているサービスを数多く見てきました。そしてもうどうしようもできなくなると「1から作り直そう」とシステムの刷新プロジェクトを立ち上げ、長大なロードマップになり「永遠に終わらない刷新プロジェクト」を続けるシステムもありました。アーキテクチャ設計への投資は必須と言っても過言ではないのです。
とはいえ、大体のプロジェクトには時間と予算が限られています。「こんなに短時間では良い設計はできない・・・」と思うこともあれば、逆に「設計に時間をかけすぎて実装が進まない」といったチームも経験したことがあります。ではアーキテクチャ設計にかける適切な時間はどれくらいでしょうか。
これについては「Design It!」の書籍で詳しく解説されています。平均すると全体の開発スケジュールの約20%をアーキテクチャ設計に使うのが良いとされています。
- 設計に少ししか投資しないと後で強烈なしっぺ返しが来る
- 逆にそれ以上投資しても得られる効果はそれほど多くない
- システムが大きくなればなるほど、アーキテクチャへの先行投資から受ける恩恵は大きくなる。大規模なシステムでは全体スケジュールの37%をアーキテクチャ設計に使うのが良い。
- 逆に小規模のシステムではアーキテクチャへの先行投資から受ける恩恵はほとんどない。全体の5%くらいを設計に費やし、あとは実装したほうが早い。そして必要であれば書き直す。
設計の流れ
ではアーキテクトが実際に設計をしていく流れを紹介します。
- ビジネスコンテキストの理解
- ビジネス目標を発見する
- UXデザインから始める
- 要件を決める
- ロードマップを作る
- システム・チームを分割し、責務を割り当てる
- マクロアーキテクチャを設計する
- API を定義する
- 並行して困難な問題に取り組む
- 安定したシステムの構築
- アーキテクチャを伝える
あくまでこれは理想的な流れです。現実では全てが同時並行に走ったり、いきなり開発からスタートしたり、むちゃくちゃな状況も多々あります。しかし、それぞれを理解していれば対処していきやすいと思います。
1つ1つ見ていきましょう。
1. ビジネスコンテキストの理解
このステップはかなり上流の工程なので、現実では「要件が決まってから開発に降りてきた」「ビジネス目標が現場にちゃんと伝えられない」など、アーキテクトが関与しづらいことがあります。しかし後追いでも良いのでこの辺りの理解を深めておくことをおすすめします。
私も同じような経験が多々あります。
すでに要件がかたまった段階で私に相談が来て「◯月までに開発してほしい」と言ってくるパターンです。しかし蓋を開けてみると、ビジネス目標をみんなイマイチわかっていなかったり、UXデザインをこれから考える、みたいな段階だったりします。そこで一緒にみんなで考えを深めていきましょう。すると要件が変わったり、UXデザインが大きく変わったりします。
どの段階でもやれることはあるので、このステップもしっかりと抑えておきましょう。
ビジネス目標を発見する
まず、何を目的・目標にするのか、それはなぜなのか、というビジネス目標を明確にします。ビジネス目標がなければUXデザインもできませんし、要件を作ることも、システムを開発することもできません。また、ビジネス目標を明確にすることで、品質特性やトレードオフ、技術的負債などアーキテクチャに関する議論を生み出すきっかけになります。
ビジネス目標は測定可能で、明確な成功基準を持つものが理想的です。
例えば以下のようなテンプレートに当てはめてみると考えやすいと思います。
市長(ステークホルダー)は調達予算を30%減らす必要がある(ステークホルダーのニーズ)。なぜなら、彼は必要不可欠な部門の予算削減を避けたいと思っているからだ(コンテキスト)。
ほとんどのシステムには3〜5つのビジネス目標しかなく、逆にそれ以上あると混乱します。
また、そのビジネス目標は「必須」なのか「あるとよい」ものなのかも記述しておくとよいでしょう。
ビジネス目標を明確にしたら、それをプロジェクトメンバー全員が理解できるように共有するとその後の会話がスムーズです。
UXデザインから始める
次にアーキテクトが取り組むのはUXデザインです。といっても実際にアーキテクトがデザインするわけではありません。デザイナーやプロダクトマネージャーと協力し、優れたUXを考えていきます。
一般的には要件を先に決めることが多いですが、おすすめは先にUXデザインも始めることです。結局UXデザインを考えるうちに要件が変わることがありますし、プランナーもデザイナーもエンジニアも実際にシステムを体験してみないと、それが良いものかどうか判断できないのです。
また、アーキテクチャより先にUXをデザインすることで、実装を考慮に入れずデザインできるというのもあります。早期にUXデザインを掘り下げることで、あとで手戻りが発生するコストも避けられます。優れたUXはアーキテクチャの決定をスムーズにして、システムの成功確率を高められます。
そしてアーキテクトはUXを深く理解することで、重要な機能とそうでない機能を分けられます。
UXデザインの考え方
UXデザインや「デザイン思考」についてはそれだけで1冊の本ができるレベルのものですので、ここで詳細に解説をすることは避けます。
が、アーキテクトとして最低限以下を意識しながら会話をすると良いでしょう。
-
驚き最小の原則
「驚き最小の原則」とは、ユーザの期待を予測し、シームレスな体験を提供することです。ユーザの目的・目標、知識レベル、典型的なUXは何か、などを分析・考慮します。 -
複数のユーザがいることを理解する
システムには通常、異なる目的を持つ異なるユーザがいます。異なるユーザには異なるUXが必要ですが、全てのユーザをサポートするわけにはいきません。異なるユーザには手がかりを残しつつ、最適なUXのバランスをとる必要があります。 -
必要最小限のことをする
ユーザに提供するものは全て負債になると思ってください。例え悪いUXであったとしてもユーザはそれに慣れてしまい、あとで改善しようと思っても直せなくなることがあります。また、機能が増えるほどミスも増え、UXを適切に設計するのが難しくなります。 -
標準的なUXがあればそれを採用する
基本的には全てのデザインは既存のデザインの再設計になります。もし広く知られた標準的なUXがあるならそれを採用するといいでしょう。ユーザは親しみがあるため学習コストが少なくなります。また、UXデザインの手間も省けます。既存のデザインから学び、洗練させることに時間を使いましょう。 -
良いプロダクトにはマニュアルがいらない
ユーザはマニュアルを読みません。ユーザが何を求めているのか予測し、適切なレコメンドやヒントを残すことでユーザを誘導するのが理想です。 -
必要な情報を素早く見つけられるか
ユーザは必要な情報を素早く見つけられるほど満足感が高くなる傾向にあります。 -
求める情報を少なくする
ユーザに必要以上にいろいろな情報を求めるシステムがありますが、ユーザはストレスを感じ離脱率が上がります。初期設定を設ける、前回の情報を再利用する、同じことを2回聞かない、答えやすいようにする(日付をカレンダーで入れられる、郵便番号から住所を入れるなど)、などの工夫をしましょう。 -
シンプルにする
システムを使い始めたばかりのユーザは操作に迷ったり、確証が持てず操作が止まったりします。システムを最初に触るときのユーザの体験は、シンプルなものでなければなりません。 -
"初期設定"をさせない
システムを使い始めるのに、最初にユーザに初期設定を求める場合があります。しかし初期設定は煩わしいだけでなく、知識のないユーザは上手く設定できません。ユーザに明確な意思がある場合、必ず求めないといけないもの以外は、初期設定は避けましょう。
拡張のためのUXデザイン
システムに新たな機能を追加したい場合、「拡張機能」の考えが使えるかもしれません。例えばAPIが受けつけることのできるフォーマットを増やしたい場合は、そのための拡張機能を追加するといった考え方です。上手く設計された拡張機能はシステムの柔軟性を高めます。
- 優れた拡張機能は1つのことを上手くやります。
- 入力と出力はAPIや設定のようにユーザが理解しやすいものにします。
- 拡張機能の内部構造を決して外部に露呈させない。内部のデータ構造が変わると拡張機能が壊れてしまいます。
- ユースケースを深く理解するまでは拡張機能の実装を待ったほうが良いでしょう。
要件を決める
要件は多くの会社では企画者やプロジェクトマネージャーが決める話かと思いますが、アーキテクトがその内容を深く理解し、時には積極的に関与することで、最終的に良いアーキテクチャを設計することができます。
できればプロジェクトの最初からこの手の話には混ぜてもらいましょう。あとから入っていこうとすると「知らない奴が急に首を突っ込んできた」感が出てしまって仕事がやりづらくなります。
ユーザージャーニーを考える
ユーザージャーニーとは、ユーザがシステムを使って何ができ、何をするのかを定義するものです。以下のようなことを考えながら作ると良いです。
- ユーザーの目標: ユーザーは何を達成したいのか?
- ステップ: 目標達成のために、ユーザーはどのような行動をとるか?
- 感情や課題: 各ステップでユーザーはどのような気持ちになるか? どんな困難に直面するか?
ユーザジャーニーはそれぞれ異なる部分に関心のあるいくつかのグループに分類されます。そうしたグループの全てをサポートできないので、どのユーザを優先するのか、どのユーザを捨てるのかを判断する必要があります。アーキテクトはユーザジャーニーを詳細に理解し、重要なものをリストアップしておくと良いでしょう。
アーキテクチャ上重要な要求を掘り下げる
アーキテクチャ設計の選択に強く影響する影響がないかを掘り下げましょう。
- 制約: 変更できない設計判断。例えばプログラミング言語などの技術的なものから、チーム編成や予算などのビジネス上の制約もある。逆にあえて制約を設けることでその後の設計をシンプルに考えるという手段もある。
- 品質特性: 「非機能要件」とも言われる。求められる可用性、スケーラビリティ、パフォーマンスなどはあるか。
- 重要な機能: 決済システムなど、特別な注意を必要とする機能
- その他影響を及ぼすもの: 時間、知識、経験、スキル、社内政治、余計なバイアスなど
機能一覧を作って優先度をつける
ユーザージャーニーができたらそこから必要な機能を考えていきます。ユーザージャーニーは「ユーザが何をするか」を考えるものですが、それを「どうやってそれを実現するか」に変換していきます。例えば、ユーザージャーニーの「アカウント登録」というステップから、「新規アカウントを作成できる」「パスワードをリセットできる」といった機能が生まれます。
機能一覧ができたら、機能に優先度をつけます。ここで「必須な機能」と「あれば良い機能」がわけられます。優先度をつける上で以下のことを考慮すると良いです。
- 機能はできるだけ少なくする。多くの機能は使われないまま終わる。疑問がある機能は思い切って見送る。
- 最も多くのユーザをサポートする機能であること
- 最も収益をもたらすユーザをサポートする機能であること
- あるいはプロダクトの知名度を高めてくれるユーザをサポートすること
これらはプロダクトのフェーズによって異なることに注意してください。例えば、プロダクトの初期フェーズでは収益化よりも知名度を上げることを優先する、の決定がされても良いと思います。
また、競争優位性のあるもの、セキュリティ的な機能は最優先にすべきです。
簡単だからといって価値のない機能の優先度を上げるべきではありません。アーキテクチャの失敗を引き起こす最大の要因は、滅多に使われない機能です。そうした機能開発に費やされる時間やコストが無駄になります。ユーザージャーニーを理解し、その機能を実装することで得られるメリットと、見送ることで生じる損失の両方の観点から機能を評価するといいでしょう。
2. ロードマップを作る
設計プロセスの第一歩はロードマップを作ることです。
ここでいうロードマップとは、ただプロジェクトマネージャーと一緒にスケジュールを引くだけではなく、適切なサイズにチームを分割し、開発方式を考えることです。
チームを分割する
何を作るか決まったら、チームを適切なサイズに分割します。もしくは、すでにあるチームに仕事を割り当てるということも多いでしょう。大事なのは、それぞれのチームが独立して動ける、独立してデプロイできるように設計することです。
有名なものに「コンウェイの法則」というものがあります。
それは 「システムを設計する組織は、そのコミュニケーション構造をそっくりまねた構造の設計を生み出してしまう」 という法則です。やや極端な例ですが、「新規会員登録機能」をAチームが開発、「会員一覧機能」をBチームが開発するとします。すると同じデータベースを操作する機能を作ろうとしているにも関わらず、まったく別のコードベースになってしまう、ということが起こり得ます。つまり、ここで設計したチーム構造がそのままシステムの設計に反映されてしまうということです。組織構造が非効率だと、技術的負債や設計の歪みに直結してしまうのです。
これを逆手にとった「逆コンウェイの法則」があります。
これは 「目指すべきアーキテクチャ設計に合わせて組織構造を設計する」 というアプローチです。
イテレーティブな開発方式を用いる
ウォーターフォール開発では、設計 → 開発 → 結合 → テスト → リリース という工程で開発が進みます。しかし全てのパーツを作ってからシステムを結合すると、ほとんどの問題はシステムの結合段階で発覚し、その結果プロジェクトが数ヶ月遅延する、なんてことがザラにあります。
アジャイル開発のようにイテレーティブな開発方式が採用できれば、アーキテクチャ面でもメリットがあります。早期に結合を強制することで、設計に関する失敗が取り返しのつかないことになる前に修正できます。また、過剰にアーキテクチャを作り込むという失敗も防げます。最初はシンプルなアーキテクチャを選択しながら各段階でエンドツーエンドで結合していき、ボトルネックが特定できたらあとから複雑なアーキテクチャを選択する、という手段です。そうすることで不必要に複雑なアーキテクチャの開発に時間をかけなくて済みます。
また、スタートアップであれば「リーンスタートアップ」方式で開発するのをおすすめします。これについては書くと長くなるので割愛しますが、必要最小限のプロダクトでテストを繰り返し開発していく手法で、システム開発における無駄を省けます。
5つの質問
これから最良のアーキテクチャを設計するために以下の5つの質問を自問すると良いでしょう。
- 市場投入に最適なタイミングはいつか?
- チームのスキルレベルはどの程度か?
- システムパフォーマンスの感度はどれくらいか?
シンプルに実装にしたら秒間50〜100リクエストはさばける。これで大体のアプリケーションはなんとかなる。複雑な実装が必要になるくらい規模が大きくなったとき、書き換えるコストが払えられればいい。 - システムを書き直せるのはいつか?
最初から3年、5年先を見通して設計できるなんていうのは傲慢である。最初の数万人のユーザ向けにシステムを作り、そこから学び、適切なときがきたら書き直そすのがいい。 - 難しい問題はどこにあるか?
競合優位性はどこにあるか?難しい問題を特定し、早いうちから対処する必要がある。
何から優先して設計すべきか?
すべてを前もって設計しておけるのが理想ですが、現実ではそうもいきません。並行して開発がスタートしてしまうこともありますし、開発中に新たな問題が見つかって設計をやり直す、なんてこともあります。では何を優先して設計していくべきでしょうか。
機能の優先度順に設計する
最もシンプルなやり方です。機能に優先度がついているはずなので、その優先度順に設計するというやり方です。
しかし単純に優先度順でやっていくと、あとで「優先度3位の機能が実現できないことがわかった。全体方針を考え直す必要が出てきた。」みたいなケースが起き得たりします。
リスク駆動で設計する
もう1つの考え方でリスク駆動というものがあります。
プロジェクトにおいてリスクになることを洗い出し、リスクの大きいものから順に対処するという考え方です。リスクの本質は「条件(現実世界に起こること)」と「結果(結果として将来発生するよくないこと)」を書くと明確化できます。例えば「条件:短時間に集中して生放送が配信される」「結果:サーバ負荷が高まりサービスダウンする」などです。
また、このリスクを軽減する方法を考えることで、設計の道しるべにもなります。
例えば
- 確率を下げる
- 影響を軽減する
- リスクの時間的制約を取り除く
- 条件を取り除く
- 受け入れて何もしない
- など
3. マクロアーキテクチャを設計する
現代のシステムは、複数のシステムが相互作用して作られる分散システムです。マクロアーキテクチャとは、システムをコンポーネントとコンポーネントの相互作用として捉えて設計することです。では具体的にどうやって設計していくのか見ていきましょう。
設計するときの観点
マクロアーキテクチャには他でも再利用できる機能があります。例えばデータベース、ロードバランサー、ID管理、メッセージキューなど、こうした機能はオープンソースや既存のベンダー機能が使えます。適切に使うことができれば、時間とコストの両方を節約できます。
既存の機能では対応できないもの、機能間のギャップを補う必要がある場合は、それをサービスとして実装します。このサービスをどう設計し、配置し、接続するかを考える、これがマクロアーキテクチャの設計です。最適なマクロアーキテクチャはビジネスコンテキストで決まります。
マクロアーキテクチャは家を建てるようなものです。
多少であれば後からでも変更できますが、基礎ができた時点で選択肢のほとんどは決まってしまいます。そのため設計には慎重にならなくてはなりません。
しかし逆に「1人暮らしなのに3世代ニーズに対応できる家を建てよう」とするのは過剰です。ときには建て直しが必要になると思ってください。
典型的なソフトウェアアーキテクチャ
現代的で典型的なソフトウェアアーキテクチャは以下のようになっています。
- ユーザがWEBページ、モバイルアプリ、あるいはスケジュールされたタスクを介してリクエストを送信する
- ロードバランシング層: 高可用性、ルーティング、スケール、レート制限、セキュリティなどを担う。ミドルウェアサービスを利用してもいい。
- コーディネーション層: サービスを使用してユースケースを完了する。必要であればキャッシュもする。
- サービス層: ロジックをステートレスなサービスとして実装する。
- データベース層: データを格納する。
まずはこれをベースに設計して、各システムごとのビジネス要件に合わせて最適化していきましょう。
マイクロサービス
マイクロサービスはシンプルで軽量かつ疎結合なサービス群が連携して大きなシステムを構築するという考え方のアーキテクチャです。個々のサービスは独立して開発・リリースできるという特徴があります。サービスを設計する際は、マイクロサービスの考え方で設計するのが良いでしょう。
マイクロサービス分割の考え方
マイクロサービスの分割の仕方はこれと決まったものがありません。しかし考え方としてはいくつかあります。
- 単一責任原則: 変更する理由が同じものは集める、違うものは分ける。
- ビジネスコンテキストで分ける: ビジネスの境界に合わせて分ける。
- チームで分ける: チームメンバーが7〜9人を超えると、メンバー同士が効率的にコミュニケーションを取ることと、リーダーがチームを管理することが難しくなる。システムを小さなパーツに分割し、それぞれに異なるチームを割り当てることで、チーム間のコミュニケーションの必要性を減らすことができる。
- 技術的特徴で分ける: 例えば普通の WEB ページとチャットアプリでは、使う技術が異なります。そのような場合はサービスを分けるのが良いでしょう。
- 単独でデプロイできるか: 単独でデプロイ、変更できないものはマイクロサービスにしたところであまりメリットを得られない。
- 独立してスケーリングさせたいか: モノリスなシステムでは全てを一緒にスケーリングしなくてはいけない。マイクロサービスでは独立してスケーリングできる。
- 再利用できるか: いろいろなシステムが共通の機能を持っている場合、それをマイクロサービスとして切り出すことで再利用ができ、全体のコストを下げることができる。
マイクロサービスを設計する上で考慮すること
- ステートレスにする。ステートレスだとスケーリングが容易になる。どうしても状態を保持しなくてはならない場合は最小限にする。
- 時期尚早なマイクロサービス化をしない。ドメインの知識が浅いうちにマイクロサービス化を進めるとあとで修正コストが大きくなる。
マイクロサービスでよくある悩み
-
共有データベース: 絶対に避ける。各マイクロサービスは独自のデータベースを持つべき。データベースを共有すると、1つのサービスがデータベーススキーマを変更したときに2つ目のサービスが壊れてしまう。また、密にチームが連携する必要があり開発速度が大幅に低下する。
ただし、システムの一貫性を担保するためトランザクションを使用してデータを更新する必要がある場合にはデータベースを共有したいこともある。それにはいくつかのアプローチがある。- 解決策1: 特定のサービスだけがデータベースを更新する
特定のサービスだけでデータベースの更新が起きる場合には、もう一方のサービスに非同期でメッセージングする。これでデータベースを共有せずにデータを共有できる。 - 解決策2: 2つのサービスがデータベースを更新する
2つのサービスがデータベースを更新する場合は、2つのサービスの統合を検討するか、トランザクションを検討する。
- 解決策1: 特定のサービスだけがデータベースを更新する
- 共有ライブラリ: DRYの原則が染み付いていると、共通で使えるなコードはサービス間で共有したいことがある。しかしマイクロサービスでは注意しないとサービス間で変な依存ができてしまう。また、ライブラリを更新した際には全システムがデプロイし直さないといけないデメリットがある。ロギングのコードなど、ドメインロジックに影響しない部品の共有にとどめておこう。もしくは、DRYの原則を崩してコードをコピーするのも悪い手ではない。
マイクロサービスの代替としてリポジトリベースのチーム
マイクロサービスが採用できない場合でも、チームによってリポジトリを分割しマイクロサービスのように扱うことで、同じような疎結合の状態を再現できます。
- システムは、それぞれがリポジトリを所有する2つのチーム(各7〜9人)によって開発される
- リポジトリ内のコードをマイクロサービスであるかのように扱い、マイクロサービスアーキテクチャの原則を適用する。例えば、リポジトリ内のコードがデータベースを共有するのは問題ないが、異なるチームが所有するコードが同じデータベースにアクセスすることは禁止するなど。
- チームが成長したらチームを分割する。それに合わせてリポジトリを分割し、それぞれのチームにリポジトリを割り当てる。
コーディネーション層の設計
設計の肝になるのがコーディネーション層です。
現代のアーキテクチャはサービス、DB、API、ライブラリ、SaaSなど様々なものがコーディネーション層によって連動して機能します。
コーディネーション層の設計パターン
コーディネーション層の設計にはいくつかのパターンがあります。
まずは一般的に馴染みのある 1, 2 を検討し、必要に応じてより複雑な 3, 4 を検討していく流れが良いでしょう。
-
クライアントからフローを駆動する
コーディネーションロジックをユーザのクライアント(WEBブラウザ、モバイルアプリなど)に組み込む方法です。独立したコーディネーション層を作らずクライアントサイドで複数のサービスを呼び出すように設計します。コーディネーション層がなくなるためシンプルになりますが、攻撃者にフローが露呈してしまうリスク、ユーザに良いネットワーク状態であることが求められます。 -
別のサービスを作成する
クライアントのリクエストを受け取ってコーディネーションを実行するサービスを作成します。既存の言語やツールで実装でき、マイクロサービスアーキテクチャとの相性も良いです。
欠点は、サービスが多くのサービスを呼び出すため、単純なサーバアーキテクチャでは良いパフォーマンスが得られないところです。だからといって非同期処理を実装するのはそれはそれで複雑になります。 -
集中型ミドルウェアを使用する
ESBなどの集中型コーディネーションのミドルウェアを利用するアプローチです。主な利点は高度な非同期呼び出し機能が使えることです。並行処理をして優れたレイテンシー、パフォーマンスを提供できます。
デメリットは専用のDSLまたは言語を学ぶ必要があるところです。また、マイクロサービスアーキテクチャと相性が悪いというのもあります。(全てのサービスがESBに集まり、デプロイが複雑化、単一障害点になる) -
イベント駆動にする
イベント駆動のアーキテクチャを採用するパターンもあります。連携するサービスはイベントを待ち受け、各アクションは非同期イベントを生成し、後続の要素をトリガーします。
このアーキテクチャの利点は、システム同士が疎結合になるところです。多くのシステムを変更することなく、プロセスに新しい処理を追加することもでき、長時間実行でも耐えられます。
デメリットは、イベント駆動型は複雑で正しく実行するのが難しいところです。何度か予行演習みたいなことをしたほうがいいでしょう。また、進捗状況を追跡したり、エラーから回復したり、エラーを通知するための広範囲の監視が求められます。単純なユースケースで利用するには重すぎるアーキテクチャだと思います。
依存関係グラフ
あるマイクロサービスから他のマイクロサービスを呼び出したいというケースがあります。しかしこれは"スパゲッティアーキテクチャ"が形成されるため、注意して設計する必要があります。深い呼び出しはタイムアウトを引き起こしやすく、デバッグを難しくします。
極端な例として、マイクロサービスが他のマイクロサービスを呼び出すのを禁止し、全ての呼び出しをコーディネーション層を通じてのみ行うということもできます。これにすると1レベルの深さのツリーになりますが、コーディネーション層にビジネスロジックの大部分が寄ってしまうデメリットもあります。また、1レベルの依存関係グラフを実現しようと思うとパラメータの受け渡しが多くなりコードが複雑化する懸念もあります。
依存関係グラフを2〜3レベルの深さに保つのが、バランスの良い考え方だと思います。
トランザクション
データベースのトランザクションはデータの不整合を回避するのに役立つ仕組みです。データの整合性を担保するのに、複雑なコードを書かなくても、トランザクションを使用することで解決できることが多いです。
しかし、先ほど述べたコーディネーション層によって複数のサービスが連携している場合はどうでしょうか。1つのサービスで処理が失敗した場合、前段のサービスのデータをロールバックするのは非常に大変なことです。コーディネーション層でどうこれを解決するかを考えていきましょう。
-
損失を受け入れる
ロジックが失敗し何が起こったか判断できない場合は、損失を引き受けることも視野に入れます。例えば、商品の購入サイトで不整合が起きた場合、追加で発生したコストをユーザではなくシステム側が負担する、などです。
障害が稀にしか発生しない場合、複雑な分散トランザクションをサポートするシステムを構築するよりも、損失を受け入れたほうが安上がりな可能性があります。 -
一時的な不整合を受け入れる
例えばSNSの場合、ユーザは自分のアクティビティと他人のアクティビティに不整合があったとしても気づくことができません。このような場合は一時的な不整合を受け入れ、あとでデータの整合をかけるというパターンもあります。
最も簡単なアイデアは、ページが古くなっていることをユーザが判別でき、ユーザ自身がページを更新するボタンを押すことで解決させることです。 -
補償を使う
現実の世界ではトランザクションなしでも成り立っている業務が多いです。例えばUber Eatsは同時に複数の顧客を処理し、エラーが発生した場合は顧客に返金します。同じように、アクションが失敗した場合はそれを補償する考え方です。
次の3つの条件が当てはまる場合は、少ない労力で補償を実装できます。逆に当てはまらない場合は、むしろ補償の仕組みが複雑になるので分散トランザクションを使用することを検討してください。- 条件1: 個々の操作を検証できる
- 個々の操作はトランザクションとして実行される。
- 操作が冪等である。つまり同じデータを何度繰り返してもいい。
- 操作の状態を確認できる。(参照APIなどで)
- 条件2: システムには可変な値の読み取りに依存した書き込みや副作用がない
- 条件3: 障害に対処できる、あるいは補償操作を行える
- 条件1: 個々の操作を検証できる
-
トランザクションマネージャーを使う
複数のサービスにまたがるトランザクションを調整するために、トランザクションマネージャー(Atomikosなど)を使用することも検討できます。トランザクションマネージャーは2フェーズコミットを使用して分散トランザクションを実行します。
コーディネーションのオーバーヘッドを理解する
I/Oオーバーヘッド
同じデータベースに書き込む場合、複数のライターよりも単一のライターのほうが優れたパフォーマンスを発揮するときがあります。これは「単一ライターの原則」と呼ばれます。
複数のスレッドからのキューを貯めておき、単一のライターがデータベースに書き込むように設計するのは良いアプローチです。
メモリアクセスオーバーヘッド
I/Oがなくても、単一スレッドのほうがパフォーマンスが良いことがあります。例えば複数スレッドで何かの計算をさせる場合に、同じ変数を共有して頻繁に更新するようなケースでは、単一スレッドのほうが良いです。
OSレベルでは、各スレッドはメインメモリとは別に独自のキャッシュとプログラミングカウンターを持っています。まずキャッシュを更新する場合にロックをかけるので、ロックのオーバーヘッドが発生します。次に共有のキャッシュが更新された場合に、各スレッドが持っているキャッシュを無効化してメインメモリから再取得するため、実行速度が低下します。この性質はキャッシュ一貫性と呼びます。
- キャッシュ内のデータを最適化して、不必要なキャッシュ再取得がされないように設計する。
- Java 等では disruptor ライブラリを使うと、ロックのオーバーヘッドやキャッシュ一貫性の問題を解決してくれる
- Node.js ではシングルスレッドなのでこのような問題は発生しない。ただCPUメインの処理ではマルチスレッドのほうが高いパフォーマンスを発揮するだろう。
考慮するポイント
- 処理をするスレッドをできるだけブロックしないようにする。スレッドをブロックすると、スレッドはその間CPUを利用できなくなる。ときには非同期プログラミングが必要になるが、これは複雑でデバッグが難しくなるので注意。
- サービスが他のサービスやAPIをたくさん呼び出すとレイテンシーが増加する。その場合には外部サービスの呼び出しが並行してできるよう、ノンブロッキング呼び出しを使用する。
クラウドの利用
最近ではクラウドを利用することで、リリースまでの時間を短縮できたり、その後の保守運用コストを軽減できたりします。
クラウドのメリットとしては
- 市場投入までの時間が短縮される。
- コーディングや設定が少なくて済む
- セキュリティの処理を設定で行えるなど、定型的な実装を避けられる。
- 機能をAPI呼び出しに置き換えられる。
- 繋ぎこみの代わりにビジネスロジックに焦点を当てられる。
- 高可用性、スケーラビリティ、DevOpsをすぐに実現できる。
- 従量課金モデルによってアイドルタイムにかかるコストがなくなることで、プラットフォームコストを削減できる。
- DevOpsと監視のコストをクラウドに任せられる。
- 設備投資とシステム運用のリスクを軽減する。
一方でクラウドのデメリットもあります。
- ベンダーロックインしやすい。本番稼働するとそのクラウドから離れられなくなる。
- クラウドプラットフォームに対する学習コストがかかる。
- システムが日常的に大きな負荷を受ける場合には、クラウドは割高になる可能性がある。
まとめ
- ユーザリクエストごとに1, 2回サービス呼び出しをする程度であれば、コーディネーション層は設けなくても良いと思います
- パフォーマンスとスケールが要求されるケースでは、可能な限りトランザクションを限定的にしたほうがいいです。トランザクションを使わずに補償などで障害から回復できないか確認します。
- データの読み取りと書き込みは出来るだけ同じサービスにまとめます。考えなしにマイクロサービスにしないこと。複数のデータベース間で分散トランザクションを実行しなければならなくなります。時にはマイクロサービスのベストプラクティスに反することも考えることになるでしょう。
- サービス操作やAPI呼び出しは冪等にする。これによりトランザクションのようなシナリオの実装が簡単になります。
- できる限りミドルウェアやクラウドサービスを利用しましょう。例えば認証サービスをいちから実装すると大変ですが、IAMを使えば多くの機能を手軽に構築でき、その後のニーズにも対応しやすいです。
- 設計をシンプルにすること。例えばCQRS(データ更新と参照を分離する考え方)は良いアイデアですが、システムを複雑にしやすいです。サービス間の依存をシンプルに保ち、深さを少なくすることを考えましょう。
4. API を設計する
これまでシステム構成の大枠について話してきました。次は API の話ですが、抽象的な話からいきなり詳細な話に感じると思います。ここであえて API を取り上げているのは、API がサービスとサービスを繋ぐインターフェースだからです。
API の設計が悪いと、それがアーキテクチャ設計全体の歪みに繋がったり、不必要な改修コストが他のサービスに発生したりします。API 設計は軽視できない要素なのです。
API を設計する際にも、UXの考え方を適用できます
ユーザを理解し、マニュアルのいらない API を考える。優れた API は学びやすく誤用しづらく、次のステップが明確です。
- 広く使われている標準の仕様があればそれを採用しましょう。技術的な正しさよりも標準的な仕様を採用することをおすすめします。
- 良い API は1つのことをうまくやる API。もしも API に新たな機能を追加するか迷ったら、やめておくほうが良いです。
- リクエストとして要求する情報はなるべく少なくしましょう。
- APIは最初からバージョン管理しましょう。
- 基本は HTTP + JSON の API がおすすめです。やむを得ない理由が出てきたときに他の選択肢を検討しましょう。
- マイクロサービスにおける API は冪等になるよう設計しましょう。つまり2回同じリクエストを送っても結果が同じになるようにするということです。これが非常に重要で、冪等でないとリトライの仕組みが複雑になったり、データ整合のことを考えなくてはいけなくなってしまいます。
- 詳しくは 冪等性とは? の記事が良かったので参照してください。
ここでは API の基本的な設計について簡単に触れておきます。
URL の設計
外部に公開されている API を見ると、ほとんどが api.github.com のようにホスト名に api という名前を入れるのが主流のようです。ホスト名を WEB サービスと分離することでアクセスを DNS レベルで分離することができます。
もし小さいシステムなら WEB サービスと同じホスト名に API が相乗りすることも考えられます。その際はパスに /api/users/1 などと API とわかるパスを入れるのが良いでしょう。
パスの設計については、最近だと REST API で設計する人が多いと思います。REST でもそうでなくても、以下の考え方で設計すると良いでしょう。
- なるべく短く、読んで理解できるパス
- 大文字・小文字が混在していないパス
- 単語をつなげる必要がある場合はハイフンを利用するのがデファクトスタンダートになりつつあります
- 改造しやすいパス
- サーバ側のアーキテクチャが反映されていないパス
- ルールが統一されたパス
- リソースを表す場合は「集合」を表す複数形のほうが適切(例:
/users/1234) - できれば最初からバージョン管理する(例:
/v1/users/1234)
API レスポンスの設計
- API へのアクセス回数がなるべく少なくなるようにレスポンスを設計する
- 例えば、一覧取得APIに「説明文」が入っていなかった場合、詳細取得APIを追加で叩かなければならない。すると不必要に API へのアクセス数が増えてしまい、実装コストも高くなる。
- HTTP ステータスコードを正しく使う
-
200で応答するのに実際はエラーが発生している、みたいなのはエラーハンドリングのコストが高いのでやめましょう。エラーが起きたら適切に400系エラー、500系エラーを返してくれたほうが、標準的な HTTP クライアントも対応しやすく、バグの混入リスクも減らせます。
-
- エンベロープは使わない
エンベロープとは、以下のようにmetaにステータスやエラーコードが入っていて、実データはdata以下に入っているみたいな構造です。これだとレスポンスを受けた側は処理するのがやや面倒になります。正常系の場合はデータをラップせず直接返し、異常系の場合のみレスポンスボディにエラーコードやエラーの詳細を入れて返す、みたいな設計が好まれるパターンだと思います。{ "meta": { "status": "success", "errorCode": "A00001" }, "data": { ... }
5. 並行して困難な問題に取り組む
枠組みのアーキテクチャ設計が決まれば、あとは実際に開発が進んでいきます。ここでアーキテクトの役割はひと段落したように感じますが、実際には開発を進めていく中で明らかになるボトルネックや、残存する問題に並行して対処していくことになるでしょう。
困難な問題に早期に並行して取り組み、未知の要素を排除する
未知を早期に発見しシステム的に排除しましょう。
- 簡単なコーディングでわかるようなことは議論したり分析したりせず、まずコードを書いて検証しましょう。
- 早い段階でシステムに監視の仕組みを導入し、時間をかけて計測しましょう。
変更が難しいものは深く設計しゆっくり実装する
例えば広く公開されるAPI、共通的に使われる部品、データベーススキーマ、フレームワークなど、変更が難しいものは深く設計し、ゆっくりと理解しながら実装しましょう。
深く設計するといっても時間は有限なので、優先度の低いものは後回しでいいです。
決定を下し、リスクを負う
アーキテクトは、時には難しい決断を下してリスクを負う必要があります。
必要なデータを収集し、実験し、それでも残った不確実性(システムにどれだけ負荷がかかるか等)を理解し、リスクを負って判断します。
- プロジェクトの成果に重要でない決定は全て委譲すべき。成果にとって重要な決定とは、高い費用のかかる決定や重要度の大きい決定ではなく、重要で不可逆的な決定のことである。
- 全ての情報を知って判断できることはほとんどない。70%の情報を知った時点で、残された不確実性に責任を持って決断する。
アーキテクチャのトレードオフを理解する
時には最良のアーキテクチャを作るために、ソフトウェア設計の核となる原則を破らなければならないときもあります。これは柔軟性と凝集性のトレードオフです。
- 柔軟性:システムが変化に柔軟に対応できるかどうかの能力。柔軟性にもコストがかかり割高になることがある。
- 凝集性:理想的には同じ関心ごとは1つのシステムにまとめ、再利用したい。ただ、やりすぎには注意。複数のチームが連携しなければならないような場合は、チーム間の緊密なコミュニケーションを強いるため、絶対に必要なときしか行うべきではない。
6. 安定したシステムの構成
システムは動けばそれで良いというものではありません。アーキテクチャ設計する上で同時に「システムの安定性」も考えなくてはなりません。システムの安定性とは、単に「高可用性」を目指すことだけではありません。
- まずはシステムに継続してアクセスできる「高可用性」が重要。しかしそれだけではない。
- データが不可逆な状態や不整合な状態にならないこと。
- 重大な損害を与えないこと。
システムの安定性を高める方法は複雑で、かえって不安定にする可能性もあります。安定性を重視するためには、確率の低い失敗やすぐに回復できる失敗を受け入れることも大切です。このトレードオフを理解し、状況に合ったバランスを見つけましょう。
負荷への対処方法
- システム性能の限界ギリギリで運用する利点はあまりない。得られるコスト削減効果に対して、パフォーマンスの問題から回復するための複雑さと労力によって相殺される。リソースにはバッファを残しておくほうが良い。
- 負荷の問題への対処の1つとしてオートスケーリングがある。しかしオートスケーリングは無限ではない。レイテンシーが不可避的に増えた場合、リクエストの大部分をドロップするしかなくなる。
- 負荷のスパイクへの対処として「アドミッション制御」がある。例えばサブスクリプションのサービスにおいて、決済処理が追いつかない場合に初月無料にする処置である。これは顧客獲得コストが非常に高いため、初月無料にする損失をカバーできる理にかなった設計である。実装するにはキューのカウントをする、リクエストタイムアウトしたらアドミッション制御に切り替える、などが考えられる。
- 連続するサービスで後続のサービスの負荷が急増している場合、そのサービスは「バックプレッシャー」として前のサービスに503を伝播させる方法がある。
- 負荷への対処の最終手段として、重要でない機能をオフにすることも検討する。
障害からの回復
高速リカバリーを実現するには以下の条件を満たす必要があります。
- ステートレスであること
- 個々のサービスの起動時間が短いこと
- サービスは任意の順序で起動しシステムに参加できること
- 自動化されていること
ノードに障害が発生すると代替ノードに切り替える必要がありますが、自動化されていないと可用性が一気に低下します。Kubernetes やサーバレス、あるいは同等のアーキテクチャを使用すると良いでしょう。Kubernetes はやや学習コスト高めですが、今後の運用を考えるとおすすめの選択肢です。
依存関係への対処
マイクロサービスアーキテクチャでは、他システムに依存することが多くなりました。システムの可用性は、依存するシステムより高くなることはありません。例えば依存するシステムの可用性が98%なら、自システムの可用性は98%以上にはなりません。
クラウドのインフラなら大抵の場合「フォーナイン(9.999%)」〜「ファイブナイン(99.999%)」を実現するため、あまり気にする必要はないと思います。他システムと API 連携するような場合は、依存先の可用性にも注意しましょう。
また、依存先がダウンしたときに何が起こるか考えておく必要があります。顧客のリクエストを失ったり、最後の手順で失敗したり、システムを破損状態にしたりするよりは、リクエストを受け付けないようにするほうがマシなこともあります。
ヒューマンエラーへの対処
システムのダウンは残念ながらヒューマンエラーによるものが多いです。そうしたミスにはどう対処したら良いでしょうか。
最初の防衛壁はテストです。システムをテスト環境で徹底的にテストできればミスを未然に防ぐことができます。そのため、テスト環境はなるべく本番環境をシュミレートした環境であることが望ましいでしょう。
他にどんなことができるか考えていきます。
GitOps
GitOpsはシステムの起動に必要なすべての情報をGitでバージョン管理リポジトリに保存するやり方です。これをするとテスト環境と本番環境を同じコードを使って作るので、コード量とミスが減ります。また、何か不具合があればテスト環境で気付きやすくなります。
ただ、データベースがあると切替時にデータベースの整合性を担保するのが難しくなります。一般的には高可用性のデータベースを使用し、それ以外の部分についてGitOpsを使用するのがベストプラクティスでしょう。
ブルーグリーンデプロイメント
ブルーとグリーンと呼ばれる2つのシステムを稼働させ切り替えるデプロイ方法です。システムを変更する場合はコピーを作成し、変更内容を事前にテストしてからトラフィックを切り替えます。また、障害に備えてもう一方のシステムをバックアップとして保持できるメリットもあります。GitOps と同様に、2つのシステム間でデータを迅速に同期するのは現実的ではありません。両方のシステムが同じデータベースを共有する必要があるでしょう。
カナリヤデプロイはブルーグリーンデプロイの進化系です。
リクエストのうち少数のユーザだけ新しいシステムに送信し、変更の影響をテストするやり方です。問題がない場合に次第に新システムへの流量を増やしていき、完全に新システムへ切り替える方法です。影響範囲を少なくしてデプロイしたい場合は効果的な方法ですが、デプロイ完了まで数ヶ月かかる高コストなやり方です。
一般的なバグへの対処
リソースリーク
- メモリリーク: ガベージコレクションがあってもメモリリークは発生する可能性がある。長時間使用されるオブジェクト、接続プール、その他プールでも発生する。通常これはロングランテストでチェックする。ただ、再現が難しいリークもある。Apacheのように一定期間でプロセスを破棄する抜本的な方法もある。
- デッドロック: デッドロックは稀にしか発生しないため検知が難しい。多くの要素が発生する長時間のテストで検知できる可能性はある。デッドロックを防ぐベストプラクティスは、一方のリソースを保持している間に他方のリソースを取得しないこと。
- タイムアウト: タイムアウトはデッドロックの解消に役立つが、その前にシステムの応答速度が大幅に低下してしまう。また、低速のシステムではデッドロックに近い現象が起きることがある。例えばシステムのスレッドがほとんどないときにブロッキングI/Oを実行した場合など。
バグとテスト
まず、すべてのエラー条件を予測できないことを前提としましょう。
システムにメトリクスを仕込み、メトリクスから学ぶことが賢明です。レイテンシー、スループット、リソース使用率など、APMツールを利用すれば簡単に計測できます。最も重要なのは、障害が発生したときに問題を見つけて修正できる情報があることです。
基本的なプログラムは同じ入力に対して同じ出力をします。入力は「外部入力」「内部状態」「障害」の3つがあります。全ての入力をテストすればシステムを完全にテストしたことになりますが、入力は無限にあるため現実的ではありません。そのため入力をパターンに分類し、その中から重要なものだけテストすることになります。
テストに対してアーキテクトが心掛けることは以下です。
- ユニットテストを簡単に書けるよう整備すること。もしテストできない部分があればアーキテクトはそれを解消するために時間を割きましょう。この抜け道を塞ぎ、開発者にテストが書けない言い訳をさせないことです。
- アーキテクトは品質保証チームと協力して結合テストの内容を定義します。結合テストは「自動化が難しいシナリオ」「断続的なシナリオ」という難題があります。難題に直面したらアーキテクトはそれを解消するために時間を割きましょう。先ほどと同じ理由です。
- 未解決のバグをちゃんと潰しましょう。家の掃除と同じで放っておくとすぐに大掃除が必要になって、身動きができなくなります。
上記のテストでは「既知の未知」には対処し「既知のバグ」に変換できます。問題は「未知の未知」です。これを見つけるためのテクニックがあります。
- 驚きを予期する: 設計の仮定を外れたパターンを見つける
- カオステスト: ランダムな値と障害を注入する
- ブレインストーミング: 様々な人たちでシステムが失敗する可能性のあるパターンを洗い出す
- 生成AIを使う: 新しい入力と組み合わせを生成してもらう
Netflix は「カオスエンジニアリング」という大胆な手法を使っています。これは「本番環境に意図的な障害をランダムに発生させ、システムの耐障害性を検証・向上させる」手法です。複雑なマイクロサービスでは個々のサービスが正常稼働していても予期せぬ障害が起こることがあります。カオスエンジニアリングは、「障害は必ず起こる」という前提に立ち、システムがカオスな状態に陥ってもユーザーに影響を与えずにサービスを継続できるよう、弱点を発見するやりかたです。
7. アーキテクチャを伝える
アーキテクトは設計するだけで終わりではありません。システムのアーキテクチャをチームに伝え、理解してもらわなくては意味がありません。設計を伝えるのは難しいです。詳細なUML図や100ページ近いスライドでは伝わりません。というか誰も読みません。
おおまかな図と1〜2ページの説明で済ませるのが良いでしょう。
アーキテクチャの書き方
アーキテクチャを記録するのにスライドを使うのをやめましょう。スライドは誰かがプレゼンテーションしないと伝わらないことが多く、独立して成立するものが望ましいです。時間をかけて作られたアーキテクチャ図やアニメーションつきのスライドは、変更するのを躊躇するというのもあります。
以下のポイントを考慮して書きましょう。
-
読み手を意識する
まず誰が読む資料なのかを考えましょう。リポジトリのREADME.mdであれば「環境構築方法」や「設定方法」「デプロイ方法」などが知りたいはずです。もしくはもっと細かい「クラス、メソッド、インターフェイス」かもしれません。プロダクトの資料であれば、各システムがどう繋がっているかの構成図がほしいです。 -
ユビキタス言語を使う
下手な専門用語を使わず、ステークホルダーが使うユビキタス言語を使いましょう。 -
図を描く
- 全員がわかる図を描きましょう。例え UML であっても、わからない人がいます。
- 図には積極的に注釈や説明を入れましょう。簡素化された図だけでは意味が伝わりづらくなります。
- 全てを1つの図にまとめるのは難しいことがあります。無理に1つにせず分割しましょう。
- 余計な飾りや意味のない色付けはやめましょう。
-
「なぜそうしたか」「なぜそうしなかったか」を書く
設計判断とその根拠を記述しましょう。また、他にあった選択肢となぜそれを選ばなかったかも書くとあとから見た人が納得しやすいです。
「Design It!」ではアーキテクチャ図として以下のような例が挙げられていました。

プロジェクトの振り返り
最後に、プロジェクトの振り返りをすると今後のアーキテクチャ設計にも繋げることができます。
- ステークホルダーは誰だったのか
- 主要なビジネス目標は何だったのか
- ビジネス目標を満たすためにとられた全体のソリューションは何だったのか
- どのような技術に取り組んだか
- 最大のリスクはなんだったのか。またそれをどう克服したのか
- もう一度やり直すとしたらどうするか
まとめ
書いていたら長大な記事になってしまいましたが、これでもだいぶ詳細を省略しました。それくらいアーキテクトは複雑で多岐にわたる知識と経験が必要な職業だと思います。
改めて重要なポイントをおさらいしておきましょう。
-
ビジネスを常に意識する
プロジェクトに資金を投じるビジネス関係者との間に信頼関係を築きましょう。信頼を得るためには、会社の利益を理解し守ること、予想外のことが起きればそれを早めに伝え、説明することが大切です。 -
UXデザインを早めにする
UXデザインを早期に設計することで、不必要な修正コストを軽減できます。また、そのプロダクトを関係者自身が早めに体験できることで、必要な機能と不必要な機能を分類できます。 -
シンプルに設計する
アーキテクチャ設計はシンプルなものから始めて、必要に応じて複雑さを加えましょう。理解しやすく、学習しやすく、変更しやすいシステムを意識すると良いです。 -
過剰なエンジニアリングをしない
過剰な品質の向上、スケール、パフォーマンスは不要です。時間を無駄にせず、必要な作業にリソースを投資しましょう。また、チームの能力を超えたアーキテクチャは使いこなせないのでやめましょう。 -
アーキテクチャをチームに伝える
いくら良いアーキテクチャ設計をしても、伝わらなければ意味がありません。 -
詳細な設計はチームに任せる
「ソフトウェアアーキテクトのための意思決定術」には、アーキテクトは「指揮官のように振る舞うのではなく、庭師のように振る舞うべきだ」と書かれていました。具体的な指示を出すよりも、形を整え、選別し、雑草を取り除く作業です。

