アーキテクチャドリフト検出:コードと設計の整合性を保つ - Archyl Blog

アーキテクチャドリフトとは、文書化された設計と実際のコードベースの間で静かに進行する乖離です。このガイドでは、その原因、検出方法、そしてアーキテクチャ図がフィクションになるのを防ぐ方法を解説します。

アーキテクチャドリフト検出:コードと設計の整合性を保つ

あなたの組織のどこかに、間違ったアーキテクチャ図があります。6ヶ月前に別のサービスに統合されたマイクロサービスがまだ表示されているかもしれません。本番障害時にチームがMemcachedに切り替えたのに、キャッシュレイヤーとしてRedisが記載されているかもしれません。ショートカットや回避策が蓄積してスパゲッティのようになっているサービスが、クリーンなヘキサゴナルアーキテクチャとして記述されているかもしれません。

これがアーキテクチャドリフトです。システムがどのように文書化されているかと、実際にどのように動作しているかの間で、徐々に静かに進行する乖離です。バグとは異なり、ドリフトはアラートを発生させません。パフォーマンスの低下とは異なり、モニタリングには表示されません。誰かが古いドキュメンテーションに基づいて意思決定を行い、その決定が間違っていたと判明するまで、静かに存在し続けます。

アーキテクチャドリフトは普遍的です。すべてのチームが経験します。問題は、ドキュメンテーションがドリフトするかどうかではなく、どれだけ早く検出し、それに対して何をするかです。

アーキテクチャドリフトとは何か?

アーキテクチャドリフトは、ソフトウェアシステムの実際の実装が、文書化または意図されたアーキテクチャから乖離する時に発生します。この用語はアカデミックなソフトウェアエンジニアリングコミュニティで造られましたが、その概念は実務エンジニアにとって痛いほど馴染み深いものです。

ドリフトはアーキテクチャドキュメンテーションのあらゆるレベルで現れます。

構造的ドリフト

文書化された構造がコードベースと一致しなくなる場合:

  • スタンドアロンコンテナとして文書化されたサービスがモノリスに吸収された
  • コンポーネントの名前が変更されたが、図ではまだ古い名前が表示されている
  • 新しいサービスが作成されたが、アーキテクチャモデルに追加されなかった
  • データベースがMySQLからPostgreSQLに移行されたが、コンテナ図ではまだMySQLと記載されている

振る舞いのドリフト

文書化された振る舞いが現実と一致しなくなる場合:

  • 同期API呼び出しが非同期メッセージに置き換えられたが、リレーションシップはまだ「REST/HTTP」と記載されている
  • データフローがAPI Gatewayを通るように変更されたが、図ではサービス間の直接通信が表示されている
  • システムコンテキスト図に反映されていない認証ステップが追加された

依存関係のドリフト

文書化された依存関係が実際の統合と一致しなくなる場合:

  • サードパーティAPIが社内ソリューションに置き換えられた
  • 新しい外部依存関係(決済プロバイダー、モニタリングサービス)が追加されたが文書化されていない
  • 統合が廃止されたが、システムコンテキスト図にはまだ表示されている

意思決定のドリフト

文書化されたアーキテクチャ上の意思決定がもはや遵守されていない場合:

  • ADRは「すべての永続ストレージにPostgreSQLを使用する」と述べているが、あるチームがMongoDBの使用を開始した
  • 適合性ルールは「フロントエンドからのデータベース直接アクセスは不可」と述べているが、誰かがクライアントサイドのSupabase統合を追加した
  • デプロイメントアーキテクチャは「単一リージョン」と述べているが、サービスが複数リージョンにデプロイされた

なぜアーキテクチャドリフトが発生するか

ドリフトの原因を理解することは、その予防に不可欠です。ドリフトは通常、悪意があるものでも怠慢によるものでもありません。ソフトウェアの開発方法から生じる自然な結果です。

ドキュメンテーションよりスピード優先

金曜日までに機能をリリースする際、アーキテクチャ図の更新は最初に省かれるものです。コード変更が成果物です。ドキュメンテーションの更新はオーバーヘッドです。これは短期的には合理的な行動ですが、長期的には壊滅的です。

多数の小さな変更

ドリフトは一つの劇的な瞬間で発生することはめったにありません。何百もの小さな変更を通じて蓄積されます。それぞれがドキュメンテーション更新を保証するには些細すぎるものです。

  • ファイル名の変更
  • ユーティリティパッケージの追加
  • ライブラリ依存関係の切り替え
  • 関数を別モジュールへの抽出

個々の変更はドキュメンテーション更新をトリガーするほど重要ではありません。しかし合わせると、アーキテクチャを変容させます。

チームの入れ替わり

エンジニアが退職すると、暗黙知を持ち去ります。新しいチームはコードベースを引き継ぎますが、なぜそのように構造化されているかの理解は引き継ぎません。彼らはドキュメンテーションが述べることではなく、コードに見えるものに基づいて変更を加え、ドリフトを拡大させます。

フィードバックループの欠如

ドキュメンテーションが現実と一致するかどうかを誰も確認しなければ、ドリフトは見えません。検出メカニズムがなければ、ドリフトを発見する唯一の方法はインシデント中、監査中、または新しいエンジニアが図がコードと一致しないことを指摘する時です。その時までに、ドリフトは広範囲に及んでいる可能性があります。

緊急対応

本番インシデントでは、しばしばアーキテクチャ上のショートカットが必要になります。APIレイヤーを経由する代わりにデータベースへの直接接続。設定サービスを使う代わりにハードコードされた設定。一時的なキャッシュが恒久的になる。これらの変更は通常のレビュープロセスをバイパスし、めったに文書化されません。

アーキテクチャドリフトのコスト

ドリフトは美的な問題だけではありません。具体的で測定可能なコストがあります。

悪い意思決定

アーキテクトが古いドキュメンテーションに基づいて意思決定を行うと、その決定は間違っている可能性があります。「このサービスはトラフィックが少ないので、同期依存関係を許容できる」 -- ただしドキュメンテーションが古く、サービスが実際には文書化された負荷の10倍を処理しているとしたら。

オンボーディングの遅延

新しいエンジニアはメンタルモデルを構築するためにアーキテクチャドキュメンテーションに頼ります。ドキュメンテーションが間違っていれば、間違ったメンタルモデルを構築します。実際のアーキテクチャに合わないコードを書きます。混乱を露呈する質問をし、シニアエンジニアの時間を消費します。

インシデント対応

本番インシデント中、アーキテクチャ図はチームが影響範囲と依存関係を理解するのに役立つべきです。図が間違っていれば、チームは間違った依存関係チェーンを辿るか、重要な上流システムを見逃して、貴重な時間を浪費します。

コンプライアンスと監査の失敗

規制産業では、アーキテクチャドキュメンテーションはコンプライアンス(SOC 2、ISO 27001、HIPAA)のために要求されることがよくあります。監査人がドキュメンテーションが現実と一致しないことを発見すれば、それは指摘事項になります。深刻なものになる可能性もあります。

AIエージェントの混乱

AIコーディングエージェントがより普及するにつれ、コンテキストのためにアーキテクチャドキュメンテーションへの依存が増しています。古いC4モデルを読むエージェントは、文書化されたアーキテクチャに合うコードを生成しますが、実際のアーキテクチャには合いません。これはドリフトを修正するのではなく増幅させます。

アーキテクチャドリフトの検出方法

手動レビュー(従来のアプローチ)

最もシンプルなアプローチは定期的な手動レビューです。チームを集め、アーキテクチャ図を確認し、それらがまだ現実と一致するかをチェックします。

うまくいく場合: 小規模チーム、シンプルなアーキテクチャ、四半期ごとの頻度。

失敗する場合: 大規模システム、変化の速いチーム、またはコードを最もよく知っている人がレビュー会議の時間がない場合。手動レビューは確認バイアスにも弱く、人は期待するものを見る傾向があります。

Architecture Fitness Function

Fitness Function(Neal Fordと「Building Evolutionary Architectures」の書籍で広まった)は、アーキテクチャの特性を検証する自動テストです。

// 例:ハンドラーパッケージでデータベースの直接インポートがないことを確認
func TestNoDatabaseImportsInHandlers(t *testing.T) {
    packages := analyzeImports("./internal/handler/...")
    for _, pkg := range packages {
        for _, imp := range pkg.Imports {
            assert.NotContains(t, imp, "database/sql",
                "Handler %s imports database/sql directly", pkg.Name)
            assert.NotContains(t, imp, "gorm.io",
                "Handler %s imports GORM directly", pkg.Name)
        }
    }
}

Fitness Functionは特定のルールを強制するのに強力ですが、作成と保守に初期投資が必要です。完全なモデルではなく制約をチェックします。

静的解析ツール

ArchUnit(Java)、Deptrac(PHP)、go-arch-lint(Go)などのツールはコード構造を分析し、依存関係ルールを強制します。

// go-arch-lint設定
components:
  handler:
    in: ./internal/handler/
  service:
    in: ./internal/service/
  repository:
    in: ./internal/repository/

rules:
  handler:
    can_depend_on: [service]
  service:
    can_depend_on: [repository]
  repository:
    can_depend_on: []

これらのツールは単一コードベース内のレイヤードアーキテクチャを強制するのに優れています。サービス横断のドリフトに対処したり、アーキテクチャモデルがコードと一致するかを検証するものではありません。

自動ドリフトスコアリング

これはArchylが採用するアプローチです。特定のルールをチェックする代わりに、アーキテクチャモデル全体をコードベースに対して検証します。

  • 文書化された各システムがリポジトリに一致するか?
  • 文書化された各コンテナがコードベース内のディレクトリに一致するか?
  • 文書化された各コード要素がまだ存在するファイルを参照しているか?
  • 文書化された各リレーションシップの両端がまだ有効か?

結果はドリフトスコア(0〜100)と何がドリフトしたかを正確に示す詳細な内訳です。完全なモデルを検証するため、特定の制約だけでなく最も包括的なアプローチです。

Archylのドリフト検出における主要な設計判断:

軽量。 AIトークンの消費なし、ファイル内容の読み取りなし。Gitプロバイダー APIに対するファイルパスの存在チェックのみ。つまり、ドリフトスコアリングは数分ではなく数秒で完了します。

決定論的。 同じコードベース、同じモデル、同じスコア。LLMのtemperatureやプロンプトエンジニアリングによる変動なし。

低コスト。 コストを気にせずプッシュごとに実行可能。1日に100回の計算でも問題ありません。

アクション可能。 内訳はどの要素がドリフトしたかを正確に示すので、何を修正すべきかがわかります。

アーキテクチャドリフトを予防する方法

検出は必要ですが十分ではありません。目標は、そもそもドリフトが蓄積されるのを防ぐことです。

ドキュメンテーション更新を「完了の定義」に含める

コード変更がアーキテクチャを変更する場合、PRにはドキュメンテーション更新を含めるべきです。PRテンプレートにチェックボックスを追加します。

## チェックリスト
- [ ] テスト合格
- [ ] コードレビュー済み
- [ ] アーキテクチャドキュメンテーション更新済み(該当する場合)

すべてを捕捉するわけではありませんが、ドキュメンテーションがファーストクラスの成果物であるという期待を確立します。

CIでドリフト検出を自動化する

最も効果的な予防メカニズムは、ドリフトがしきい値を超えた場合に失敗するCIゲートです。

on:
  push:
    branches: [main]

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: archyl-com/actions/drift-score@v1
        with:
          api-key: ${{ secrets.ARCHYL_API_KEY }}
          organization-id: ${{ secrets.ARCHYL_ORG_ID }}
          project-id: 'your-project-uuid'
          threshold: '70'

ドリフトスコアが下がったためにビルドが失敗すると、マージ前に誰かが修正する必要があります。ドキュメンテーションの精度はテストに合格するのと同様に譲れないものになります。

低いしきい値(50〜60%)から始め、チームが習慣を身につけるにつれて徐々に上げていきます。

Architecture-as-Codeを使用する

アーキテクチャモデルがテキストベースのフォーマット(Structurizr DSL、Archyl YAML)で定義されている場合、コードと一緒にバージョン管理できます。これにより以下が可能になります。

  • アーキテクチャの変更がプルリクエストに表示される
  • 変更がチームによってレビューされる
  • アーキテクチャの進化の履歴がGitに記録される

これは、変更が不可視でレビュー不可能なGUIツールで定義されたアーキテクチャよりも大幅に優れています。

ドリフトアラートを設定する

ArchylはドリフトイベントのWebhookアラートをサポートしています。

  • drift.score_computed: すべてのドリフト計算で発火。Slackチャンネルに投稿して可視性を確保。
  • drift.score_degraded: スコアが10ポイント以上下がった場合に発火。これは早期警告システムです。

チームがモニタリングしているチャンネルにこれらのアラートを設定します。認識がアクションへの第一歩です。

アーキテクチャレビューを実施する

月次または四半期のアーキテクチャレビューは複数の目的を果たします。

  • 文書化されたアーキテクチャがまだ現実と一致するか検証する
  • 自動ツールが見逃したドリフト(振る舞いのドリフトなど)を特定する
  • ドリフトしたコンポーネントをコードで更新すべきかドキュメンテーションで更新すべきか議論する
  • 見直しが必要な意思決定のADRをレビュー・更新する

適合性ルールを採用する

適合性ルールは、常に真であるべきアーキテクチャ制約を定義します。

  • 「フロントエンドコンテナはデータベースコンテナに依存してはならない」
  • 「すべてのパブリックAPIはAPI Gatewayを通る必要がある」
  • 「各サービスは自身のデータベースを所有する(共有データベースなし)」

Archylでは、適合性ルールはプラットフォームで定義され、適合性チェック機能を通じて強制されます。AIエージェントはMCPを通じてこれらのルールを読み取り、コード生成時にそれらを尊重できます。

適合性ルールはドリフト検出の補完です。ドリフト検出はモデルが現実と一致するかをチェックします。適合性チェックは現実がルールに従っているかをチェックします。

アーキテクチャドリフト vs アーキテクチャ侵食

これらの用語は関連していますが異なります。

アーキテクチャドリフトはドキュメンテーションと実装の間の乖離です。コードは完全に問題ないかもしれません。ドキュメンテーションが間違っているだけです。

アーキテクチャ侵食はアーキテクチャ自体の劣化です。コードがアーキテクチャ原則に違反し、技術的負債が蓄積し、メンテナンスがより困難になります。侵食はコード品質の問題です。ドリフトはドキュメンテーション精度の問題です。

これらはしばしば同時に発生します。ドキュメンテーションがドリフトすると、チームは意図されたアーキテクチャへの認識を失います。その認識がなければ、アーキテクチャを侵食する変更を行います。ドリフトは侵食を可能にします。

これがドリフト検出がドキュメンテーションの精度以上に重要な理由です。正確なドキュメンテーションは侵食を防ぐリファレンスとして機能します。誰もが意図されたアーキテクチャを見ることができれば、それを維持する可能性が高くなります。

ドリフトの経時的な測定と追跡

単一のドリフトスコアは有用です。トレンドは強力です。

ベースラインを確立する

最初のドリフト計算を実行して現在の状態を確認します。スコアが低くてもパニックにならないでください。アーキテクチャドキュメンテーションを積極的にメンテナンスしていないほとんどのチームは、40〜70%のスコアになります。

目標を設定する

改善のための現実的な目標を設定します。

  • 1ヶ月目: 最も明らかなドリフトを修正してベースラインから60%に改善
  • 3ヶ月目: ワークフローにドキュメンテーション更新を組み込んで75%に到達
  • 6ヶ月目: CIゲートと定期レビューにより80%以上を維持

トレンドを追跡する

Archylはすべてのドリフト計算を完全な内訳とともに保存します。ドリフト履歴ビューはスコアのタイムラインを表示するので、以下を確認できます。

  • ドリフトは時間とともに改善しているか悪化しているか?
  • 特定のスプリントやリリースが大幅な低下を引き起こしたか?
  • CIしきい値が劣化を防いでいるか?

改善を称える

チームがドリフトスコアを改善した場合、それを認めてください。アーキテクチャドキュメンテーションは報われない仕事です。進捗を可視化して認めることは、その行動を強化します。

AI支援開発におけるドリフト検出の役割

AIコーディングエージェントの台頭により、ドリフト検出はこれまで以上に重要になっています。

AIエージェントはコンテキストのためにアーキテクチャドキュメンテーションへの依存を増しています。MCPのようなプロトコルを通じて、エージェントはコード生成前にC4モデル、ADR、適合性ルールを読むことができます。これによりエージェントはより効果的になります。推測するのではなく、アーキテクチャに合ったコードを生成します。

しかし、これはドキュメンテーションが正確な場合にのみ機能します。古いC4モデルを読んでそれに基づいてコードを生成するエージェントは、間違ったアーキテクチャに合うコードを生成します。エージェントはドリフトを防ぐのではなく増幅させます。

ドリフト検出は、AIエージェントの誠実さを保つフィードバックループを作成します。

  1. エージェントがアーキテクチャを読む -- MCP経由
  2. エージェントがコードを生成する -- 文書化されたアーキテクチャに合わせて
  3. コードがマージされる -- 実際のアーキテクチャを変更する可能性がある
  4. ドリフト検出が実行される -- 乖離を検出する
  5. CIゲートが失敗する -- ドリフトがしきい値を超えた場合
  6. チームがドキュメンテーションを更新する -- 現実を反映するために
  7. エージェントが更新されたアーキテクチャを読む -- ループが閉じる

ステップ4がなければ、ループは開いています。ドキュメンテーションはますますフィクションになります。エージェントはますます架空のアーキテクチャに合うコードを生成します。ギャップはコミットごとに広がります。

ドリフト検出はこのループを閉じるメカニズムです。

ドリフト検出を始める

アーキテクチャドキュメンテーションがない場合

AI発見から始めてください。リポジトリをArchylに接続し、発見を実行し、生成されたC4モデルをレビューします。これにより約70〜80%の精度のベースラインモデルが得られます。そしてその精度を維持するためにドリフト検出を設定します。

既存のドキュメンテーションがある場合

アーキテクチャモデルをドリフト検出をサポートするツールにインポートまたは再作成します。最初のドリフト計算を実行します。スコアは現在のドキュメンテーションの精度を正確に教えてくれ、内訳は最初に何を修正すべきかを示します。

既にドリフトを追跡している場合

ドリフト検出をCIに統合してください。しきい値を設定してください。アラートを設定してください。トレンドの追跡を開始してください。ドリフトを一回限りの監査ではなく、チームの指標にしてください。

どこから始めるにしても

最も重要なことは始めることです。アーキテクチャドリフトは技術的負債と同様に、時間とともに複利で増加します。対処するのを待つほど、追いつくために必要な作業が増えます。しかし技術的負債と異なり、ドリフト検出は数分で設定でき、即座に価値を提供します。

アーキテクチャドキュメンテーションは現実を反映しているか、していないかのどちらかです。今やそのどちらであるかを測定できます。


アーキテクチャドキュメンテーションのメンテナンスについて詳しく学ぶ: アーキテクチャドリフトスコア:仕組み | C4モデルとは? | AI搭載アーキテクチャドキュメンテーション。またはArchylを無料でお試しいただき、数分で最初のドリフトスコアを計算してください。