進化的アーキテクチャ ―絶え間ない変化を支える|要約・感想|進化可能性を高める3つの考え方【書籍から学ぶ】

はじめに

システム開発をする中で、変化が早い現代において「既存のプロダクトを安全かつ迅速に変更し続けられるようにしたい」と考えている方も多いのではないでしょうか。

進化的アーキテクチャ ―絶え間ない変化を支える」という本は、そのような悩みに対する一定の方向性を示してくれる一冊です。

この本の中から、「現在の私」にとって特に興味深いと感じた内容を、3点整理しました。

私と同じような悩みを持っている方のみならず、新規のプロダクトを継続的に進化させたいという方にとっても、参考になれば幸いです。

結論:重要な3ポイント

  1. 適応度関数を構築する。
  2. 既存プロダクトを安全に進化させる。
  3. アンチパターンを避ける。

適応度関数を構築する

詳細は第2章で解説されていますが、要点を以下にまとめました。

適応度関数とは

書籍内での適応度関数の定義は、以下の通りです。

アーキテクチャの適応度関数は、あるアーキテクチャ特性がどの程度満たされているかを評価する客観的な指標を与える。

アーキテクチャ特性とは、パフォーマンスや信頼性などの非機能要件に近い概念です。

そして、適応度関数は、アーキテクチャ特性が満たされているかを継続的に評価する仕組みを指します。そのため、ユニットテスト等の全てが適応度関数に該当するのではなく、アーキテクチャ特性を検証できるもののみが該当します。

適応度関数の具体例を示します。

  • パフォーマンス要件を満たすための、主要なサービス呼び出しが100msで完了するようなパフォーマンステスト
  • コーディング標準に準拠するための、ソースコードの循環的複雑度を監視するツール

適応度関数のメリット

  • 早い段階で適応度関数を特定することで、プロダクトが持つリスクを早めに洗い出せるうえに、アーキテクチャや機能の変更時にアーキテクチャ特性の基準を満たせているかを検証しやすくなる。
  • 「個々の適応度関数」に加えて「システム全体の適応度関数」を導入することで、複数のアーキテクチャ特性間のトレードオフを調整しやすくなる。
  • 基準が不明瞭で曖昧な評価や、担当者の主観的な評価を避けることができる。

適応度関数の注意点

  • 全てのアーキテクチャ特性を検証することは難しいので、優先順位を付ける必要がある。
  • 全ての適応度関数の実行を必ず自動化できるとは限らないので、必要に応じてデプロイメントパイプラインの手動のステージで実行する必要がある。
  • ビジネス環境の変化や顧客の成長や要望に追従するために、定期的(少なくとも1年に1回)見直す必要がある。

個人的な活用案

現在のプロダクトでは

  • 機能要件に対するテストは充実していて、自動化もされている。
  • 非機能要件に対する検証はあまり実施しておらず、自動化もあまり進んでいない。
  • 非機能要件は、システムの成長に伴って基準を満たせなくなっている可能性がある。

という状態です。

そして課題の一例として、現在携わっているプロダクトで最も重視しているパフォーマンスが挙げられます。
具体的には、詳細なレスポンスタイムを継続的かつ自動的に計測しているわけではないので、プロダクトの進化や外部サービス側の改修などでパフォーマンス低下し、その都度調査することがあります。

そのため、「リリース前のテストとリリース後の監視にレスポンスタイムの適応度関数を導入すること」を早速検討したいです!

既存プロダクトを安全に進化させる

ここでは、特に以下2点に焦点を当てています。

  1. 既存のDBを改良する際の移行状態の維持
  2. 既存のアーキテクチャを改良する際のアーキテクチャ特性の保護

DBの改良に関しては第5章で、アーキテクチャの改良に関しては第6章で解説されていますが、要点を以下にまとめました。

ただし、「マイクロサービスになっており、他のサービスと疎結合になっていること」を前提としています。

DBを改良する際の移行状態の維持

並列変更の一種であるexpand/contractパターンを活用します。

並列変更に関しては、下記の記事も参考にしてください(英語 & アプリケーションの移行状態を例にした解説です)。
https://martinfowler.com/bliki/ParallelChange.html

概要

古い状態と新しい状態の両方を維持する移行期間を設ける。

手順

  1. 開始状態
    • DB改良前のカラムだけを持つ。
  2. 展開状態
    • DB改良後のカラムを追加する。
    • DB改良前のカラムを残す。
    • DB改良前のカラムのデータを、DB改良後のカラムに加工して登録する。
    • DBのトリガーを利用して、DB改良前後どちらに対応しているアプリケーションからも、DB改良前後両方のカラムにデータを登録する。
    • アプリケーションをDB改良後に対応できるように順次切り替える。
  3. 最終状態
    • 全てのアプリケーションの切り替えが完了したら、DB改良前のカラムを削除する。

アーキテクチャを改良する際のアーキテクチャ特性の保護

書籍内では、既存アーキテクチャの改良と、アーキテクチャの移行の両方が解説されていますが、個人的に関わることが多い前者に焦点を当てています。

概要

予測可能性よりも進化可能性を重要視し、腐敗防止層を構築して早過ぎる複雑さの導入を防ぐ、というような方針を取ります。

その背景は、未知の未知(予期できず誰もが発生することが分からなかった問題)が存在することを前提にしているためです。

手順

  1. アーキテクチャの進化とともに保護したい次元を特定する。
    • 次元とは、アーキテクチャ特性や、ソフトウェアの構築や運用に寄与する観点を指す。
  2. 各次元に対して適応度関数を定義する。
  3. デプロイメントパイプラインを利用して適応度関数を自動化する。

考慮するべき事項

  • コンポーネント結合や機能的凝集が適切であるか。
    • アーキテクチャ量子のサイズが大きすぎても小さすぎても扱いづらい。
      • アーキテクチャ量子とは、高度な機能的凝集を持つ、独立してデプロイ可能なコンポーネントのことである。
  • 開発プラクティスが成熟しているか。
    • サイクルタイムが十分に短いか
    • 扱いやすい運用か
  • 適応度関数が十分か。
    • アーキテクチャにおける全ての検証を適応度関数として扱えるとベスト。

個人的な活用案

私が感じるexpand/contractパターンに関する課題は、人によっては展開状態を経由せずに一度に対応したいと考える方もいる、ということです。

対象機能の要件や特性、SLAなどによっては一括移行で問題ない場合もあるので、皆が納得する方針を柔軟に採用できればと思いました。

アーキテクチャ特性に関する適応度関数に加え、主要な機能に関するテストに対しても、以下の課題を感じています。

  • 根本的に適応度関数とテストが不足している。
  • 一部の適応度関数とテストは、不定期かつ手動実行になっている。

すぐに理想状態へ移行することは難しいので、サービスの修正や検討に合わせて、適応度関数やテストの追加と自動化を進めたいです。

また、大掛かりな修正の場合は、サービスのエンドポイントを一時的にプロキシに変更して、プロキシが要求されたサービスのバージョンを確認して、要求されたバージョンに転送するなどの方針も検討したいです。

アンチパターンを避ける

第7章には様々なアンチパターンが解説されているのですが、私が特に気になった3点を解説します。

  1. ベンダーキング
  2. コード再利用の乱用
  3. 均一化に価値を置くガバナンス

ベンダーキング

概要

  • アーキテクチャがベンダー製品を中心に構築されること。
  • ベンダー製品には、ソフトウェアに加えて、外部ツールやフレームワークなども含まれる。

問題点

  • ベンダー製品による技術的な制約を受ける。
  • ラスト10%の罠(ビジネス観点での要求を完全には満たせない)が発生する。
  • 結果として、ツールではなくビジネスプロセスを変更せざるを得なくなる。

対策

  • ベンダー製品ありきにはせず、ベンダー製品を単なる統合点として扱う。
  • ベンダー製品との統合点の間に腐敗防止層を設ける。

コード再利用の乱用

概要

  • 可能な限りコードの共通点を見つけて再利用する。
  • そもそもコードが再利用可能なモノだとみなす。

問題点

  • 再利用可能にするために様々なオプションや判定ロジックを組み込まざるを得なくなる。
  • 結果として、使いづらいコードになる。

対策

  • マイクロサービスでは、再利用ではなく重複を採択する。

均一化に価値を置くガバナンス

概要

  • 最も複雑なサービスに対応できる技術スタックやアーキテクチャで、組織内のサービスを均一化する

問題点

  • ほとんどのサービスにとってはオーバースペックである。
  • シンプルなサービスでも複雑な技術やアーキテクチャに従う必要がある

対策

  • サービスの規模や複雑さに応じた技術スタックやアーキテクチャを採用する。
  • 7.2.2章にサンプルが載っている。

個人的な活用案

「進化可能性」という観点が欠落すると、どれも陥りそうなアンチパターンだと感じます。

そのため、変化が早い時代であることをメンバー全員で理解した上で、進化可能性を高められる方法であるかという観点で、プロダクトの開発を進めたいです。

ちなみに、第7章では技術者側がビジネス側にどのように働きかけて巻き込んでいくか、という観点の解説もあるので非常に興味深かったです。

まとめ

予測可能性ではなく進化可能性に焦点を当てるという方針が、変化の早い現代では非常に重要であり有用であると感じました。

上記に解説した内容以外にも、色々と役に立つ考え方や手法が解説されていますので、システム開発に携わる全ての方に一度は目を通しておいてほしいと感じました。

文末の読書メモに簡単にまとめていますので、興味のある方は軽く目を通してみてください。

また、書籍内で頻繁に紹介されていたリファクタリングはまだ読んでいないので、これを機に購入してみようかなと思います。

読書メモ
  • 書籍内の部品販売業者の例が、わかりやすく理解を助ける。
  • 適応度関数は初めて聞いた単語であり、概念を理解したい。
  • 2018年に発行された書籍。他の(気になる)書籍からの引用もある。
  • 本編が210ページに満たない分量なので、手に取りやすいと感じます。
  • ケーススタディが理解の促進に役立ちます!
  • 第8章には、技術者側がビジネス側にどのように働きかけて巻き込んでいくか、という観点も解説されていて面白い。

序文

  • 進化的アーキテクチャとは、変化する要件に加えて実装からのフィードバックを、アジャイル的な方法で受け入れられるアーキテクチャを指す。
  • 適応度関数とは、アーキテクチャの状態を監視するための概念である。
  • コンウェイの法則が、書籍内の議論の大部分を占める。

1章 ソフトウェアアーキテクチャ

  • 進化可能性は、他のすべてのアーキテクチャ特性を保護・維持しつつシステムを進化させられる特性を指す。
  • 技術的もしくはドメインの変化がいつ起こり、どの変化が持続するかは予測できないが、変化自体は避けられない。
  • 進化的アーキテクチャの定義は「複数の次元にわたる漸進的誘導的な変更を支援するもの」である。
    • 漸進的は変更では、ソフトウェアの構築とデプロイの両面を考慮する。
    • 誘導的という言葉は、最終的に到達したいアーキテクチャを反映している。
    • アーキテクチャの変更を誘導するために、適応度関数を導入する。
      • 適応度関数とは、アルゴリズムが時間経過とともに改善されたかどうかを評価するものである。メトリックスやテストをはじめとする、各種検証ツールが挙げられる。
    • 次元とは、アーキテクチャの特定の観点を支持する部分を意図的に分割したもので、技術・データ・セキュリティ・運用系、などが代表例である。
  • コンウェイの法則:組織が生み出す設計は、その組織のコミュニケーション構造に倣った構造になる。
    • 逆コンウェイ戦略:サービス境界を中心にチームを組織する。

2章 適応度関数

  • 適応度関数
    • 概要
      • あるアーキテクチャ特性がどの程度満たされているかを評価する客観的な指標を与えるもの
        • 特性の間でトレードオフが発生する。
      • すべてのテストが適応度関数になるのではなく、アーキテクチャ特性を検証できるテストが適応度関数に分類される。
        • パフォーマンス要件を担保するためのパフォーマンステスト、ソースコードの循環的複雑度の評価や監視、など
        • 規制上の理由などで、ソフトウェアとして実装できず、手作業になる場合もある。
      • システム全体の適応度関数は、アーキテクチャが進化するためには欠かせないものである。
        • これにより、複数のアーキテクチャ特性を比較して評価することができ、特性間のトレードオフ解消に役立つ。
        • 個々の適応度関数と、システム全体の適応度関数の双方で評価する。
      • 様々なリスクを低減するために、設計の早い段階で特定して優先順位をつけるべきである。しかし、システムを開発している最中に出現するものもある。
      • 1年に1回は見直す。
    • 分類例
      • アトミック vs ホリスティック → 単一次元 vs 複合次元
      • トリガー式 vs 継続的 → 特定のイベント vs 本番環境での監視
      • 静的 vs 動的 → 閾値が固定 vs 閾値が変動
      • 自動 vs 手動

3章 漸進的な変更を支える技術

  • 図3-1〜3-3に、新バージョンのサービスへの移行時の運用に関する例が載っていて、イメージしやすい。
  • アジャイルさのためにアーキテクチャレベルで求められる構成要素
    • テスト可能性
      • 自動テストでアーキテクチャ特性の正しさを検証する。
      • 様々なアーキテクチャ特性をテストするツールが存在する。
    • デプロイメントパイプライン
      • 変更に応じて実行され、アーキテクチャ特性を担保する。
      • ビルドを複数ステージに分割するという考え方が含まれており、手動の検証が含まれることもある。それがCIとの違いになる。
    • 適応度関数の分類の組み合わせ
  • 仮設駆動開発:検証可能な仮説(機能Xを追加すると売り上げが5%上がる、など)を立ててA/Bテストを実施して、結果を検証して方向性を決定する。

4章 アーキテクチャ上の結合

  • モジュールは論理的なグループ化、コンポーネントは物理的なグループ化、を意味する。
  • アーキテクチャ量子とは、高度な機能的凝集を持つ、独立してデプロイ可能なコンポーネントのことである。システムとして正常に機能するために必要な機能とデータをすべて含む。図4-2がイメージしやすい。
  • アーキテクチャパターンごとの進化可能性
    1. 巨大な泥団子
      • 変更が困難なアンチパターン
    2. モノリス(モノリシックアーキテクチャ)
      1. 非構造化モノリス
        • 量子サイズが大きく、漸進的な変更が難しい。
      2. レイヤー化アーキテクチャ
        • レイヤーを跨ぐ変更は調整が大変である。
      3. モジュール式モノリス
        • 量子サイズが大きいと、漸進的な変更が大変である。
      4. マイクロカーネル
        • プラグイン間に依存や結合があると、量子サイズが大きくなる。
    3. イベント駆動アーキテクチャ
      1. Broker
        • 部品が疎結合なので変更しやすいが、部品同士の挙動の調整が大変。
      2. Mediator
        • 調整役がいるが、結合度も上がり量子サイズが大きくなる。
    4. サービス指向アーキテクチャ
      1. ESB駆動SOA(Enterprise Service Bus)
        • アジャイルさが低く、運用が難しい。
      2. マイクロサービス
        • サービス(ビジネスドメイン)単位で疎結合であり、進化的アーキテクチャの優れた例である。
      3. サービスベースアーキテクチャ
        • マイクロサービスの利点を持っているものの、量子サイズが大きくなることによる難易度も上がる。
    5. サーバレスアーキテクチャ
      • いくつかの次元を外部に委譲できるというメリットはあるが、適応度関数の重要性が高い。
  • アーキテクチャ量子が小さいほど、アーキテクチャの進化可能性が高くなる。

5章 進化的データ

  • スキーマ(データベース構造)も進化させる必要がある。
    • 現実世界を抽象化して表現した状態と乖離しない状態を維持する必要がある。
    • 修正、テスト、バージョン管理などをして、漸進的に進める。
    • マイグレーションツールを活用すると良い。
    • 元のマイグレーションを修正するのではなく、変更する差分用に新たにマイグレーションを作成する。
  • expand/contractパターン(図5-2〜3)
    • 古い状態と新しい状態の両方を維持する移行期間を設ける。
  • トランザクションは機能を強く結合するものなので、トランザクションコンテキストを可能な限り制限したい。

6章 進化可能なアーキテクチャの構築

  • 構築手順
    1. アーキテクチャの進化とともに保護したい次元を特定する。
    2. 各次元に対して適応度関数を定義する。
    3. デプロイメントパイプラインで適応度関数を自動化する。
  • 既存のアーキテクチャの改良・移行
    • コンポーネントの結合度と機能的凝集を適切に保つ。
    • チームの開発プラクティスを成熟させていく。
    • 適応度関数を作りやすい状態にする。
      • アーキテクチャにおける全ての検証を適応度関数として扱えるとベスト。
    • 商用のパッケージソフトウェアなどを利用していると、適応度関数の定義や漸進的な変更が難しくなりやすいので、注意が必要である。
    • サービスの境界を特定した上で進める。
      • コンウェイの法則、トランザクション境界、デプロイメントの目標、など
  • 進め方ガイド
    • 不要な変数を特定して、削除したり、定数にしたりする。
    • 意思決定は可能な限り可逆にする。
    • 未知の未知に対しては、予測可能性ではなく進化可能性で対応する。
    • DDDの腐敗防止層を活用する。
    • サービステンプレートは、必要とする結合のレベルを正確に理解した上で構築する。
    • MVP(Minimum Viable Product)のための犠牲的アーキテクチャを構築する。
    • ライブラリのバージョンは必要なときに更新する方針でも問題は少ないが、フレームワークの更新は早めに対応しておくべきである。
      • どちらも継続的デリバリーを活用すると対応しやすい。

7章 進化的アーキテクチャの落とし穴とアンチパターン

  • 導入しているベンダー製品ありきになってはいけない。
    • 色々な面で制限を受けるリスクがある。
  • 抽象化の欠如
    • 原始抽象の浸出:低レベルの抽象が壊れることで予期しない大惨事を引き起こすこと。
  • ラスト10%の罠。無限後退問題により、全ての特性を細かい粒度で実現できない。
  • コードを再利用可能にしようとするほど、様々な用途に対応するための機能や処理を追加する必要があり、結果的に単一の目的に対しては使いづらくなってしまう。
    • マイクロサービスでは、このようになりそうな場合は、コードの重複を選択する。
  • アーキテクチャから出発してはいけない。
    • 問題領域から出発して考えること。
  • 単一の開発環境や技術スタックに均質化してはいけない。
    • 多くのサービスや機能で過剰性能かつ複雑になってしまう。
  • リリース速度の欠如。サイクルタイムが長くなること、とも言い換えられる。

8章 進化的アーキテクチャの実践

  • 組織の観点
    • ドメイン中心のチームが良い
      • チーム内にビジネスアナリストや運用担当などの機能横断的にメンバーがいる状態
    • ソフトウェアをプロジェクトではなくプロダクトと考える。
    • 実験の文化
      • スパイク、セットベース開発
    • 企業規模に合わせた、適切な量子サイズの特定と、適応度関数の構築をする。
  • 最初に着手するものは、以下の中から選んでみると良い
    • 最も簡単なこと
    • 最も価値があること
    • テストコード
    • インフラ整備

以上

タイトルとURLをコピーしました