「アーキテクチャの劣化は気づかないうちに進む」と佐藤CTOは言った。「フィットネス関数は、アーキテクチャの健全性を継続的に測定する自動化されたチェックだ。ArchUnitのようなツールで依存関係ルールをテストとして書けば、CIで毎回検証できる。」
1. フィットネス関数の概念
“Building Evolutionary Architectures” (O’Reilly) で提唱された概念
| 種類 | 説明 | 例 |
|---|---|---|
| アトミック | 単一の特性を測定 | レイテンシ < 500ms |
| ホリスティック | 複数の特性を組み合わせて評価 | セキュリティ + パフォーマンス |
| トリガー型 | イベント駆動で実行 | デプロイ時にチェック |
| 継続型 | 常時監視 | SLOダッシュボード |
2. 自動化されたアーキテクチャテスト
ArchUnit(Java/TypeScript相当のアプローチ)
// アーキテクチャルールをテストとして定義
// dependency-cruiser を使用(TypeScript向け)
// .dependency-cruiser.cjs
module.exports = {
forbidden: [
{
name: 'domain-must-not-depend-on-adapters',
comment: 'ドメイン層はアダプター層に依存してはいけない',
severity: 'error',
from: { path: '^src/domain' },
to: { path: '^src/adapters' },
},
{
name: 'domain-must-not-depend-on-application',
comment: 'ドメイン層はアプリケーション層に依存してはいけない',
severity: 'error',
from: { path: '^src/domain' },
to: { path: '^src/application' },
},
{
name: 'no-circular-dependencies',
comment: '循環依存を禁止',
severity: 'error',
from: {},
to: { circular: true },
},
{
name: 'adapters-must-not-depend-on-each-other',
comment: 'アダプター間の直接依存を禁止',
severity: 'warn',
from: { path: '^src/adapters/([^/]+)' },
to: { path: '^src/adapters/(?!$1)' },
},
],
};
// CI で実行: npx depcruise src --config .dependency-cruiser.cjs
カスタムフィットネス関数
// テストとして書くフィットネス関数の例
describe('Architecture Fitness Functions', () => {
test('APIレスポンスサイズが1MB以下', async () => {
const endpoints = ['/api/products', '/api/users', '/api/orders'];
for (const endpoint of endpoints) {
const res = await fetch(`http://localhost:3000${endpoint}`);
const body = await res.text();
expect(body.length).toBeLessThan(1_000_000);
}
});
test('外部依存のライセンスがOSS互換', () => {
const pkg = require('./package.json');
const forbiddenLicenses = ['GPL-3.0', 'AGPL-3.0'];
// license-checker 等で検証
});
test('DBマイグレーションが後方互換', () => {
// 新しいマイグレーションが destructive change を含まないことを検証
// DROP COLUMN, ALTER TYPE 等がないことを確認
});
});
3. メトリクスベースのフィットネス関数
// 組織のアーキテクチャ適合度スコアリング
interface FitnessScore {
category: string;
metric: string;
target: number;
actual: number;
weight: number;
score: number; // 0-100
}
function calculateArchitectureFitness(scores: FitnessScore[]): {
overallScore: number;
details: FitnessScore[];
} {
const totalWeight = scores.reduce((sum, s) => sum + s.weight, 0);
const weightedSum = scores.reduce((sum, s) => sum + s.score * s.weight, 0);
return {
overallScore: Math.round(weightedSum / totalWeight),
details: scores,
};
}
const architectureFitness = calculateArchitectureFitness([
{ category: 'Performance', metric: 'p95 Latency (ms)', target: 500, actual: 380,
weight: 3, score: 95 },
{ category: 'Reliability', metric: 'Availability (%)', target: 99.9, actual: 99.95,
weight: 3, score: 100 },
{ category: 'Security', metric: 'CVE Count', target: 0, actual: 2,
weight: 2, score: 60 },
{ category: 'Modularity', metric: 'Circular Dependencies', target: 0, actual: 1,
weight: 2, score: 70 },
{ category: 'Deployability', metric: 'Deploy Frequency (per week)', target: 10, actual: 8,
weight: 1, score: 80 },
]);
// overallScore: 84/100
4. フィットネス関数のCI/CD統合
# .github/workflows/architecture-fitness.yml
name: Architecture Fitness Check
on: [pull_request]
jobs:
fitness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Dependency Rule Check
run: npx depcruise src --config .dependency-cruiser.cjs
- name: Bundle Size Check
run: npx size-limit
- name: License Check
run: npx license-checker --failOn 'GPL-3.0;AGPL-3.0'
- name: Architecture Tests
run: npm test -- --testPathPattern='fitness'
まとめ
| トピック | 要点 |
|---|---|
| フィットネス関数 | アーキテクチャの健全性を自動テストで継続的に検証 |
| 依存関係ルール | dependency-cruiserで層間の依存ルールを強制 |
| メトリクス評価 | 性能・信頼性・セキュリティ・モジュラリティをスコアリング |
| CI/CD統合 | PRごとにアーキテクチャ適合度を自動チェック |
チェックリスト
- フィットネス関数の種類(アトミック/ホリスティック)を説明できる
- dependency-cruiserで依存関係ルールを定義できる
- アーキテクチャ適合度スコアリングを設計できる
- CI/CDにフィットネス関数を統合できる
次のステップへ
フィットネス関数を学んだ。次は 演習 で、アーキテクチャガバナンスを設計しよう。
推定読了時間: 40分