前置き
サーキットブレーカーのパターンは、UML(統一モデリング言語)の考え方を適用する上で、最も教科書的な事例の一つです。
状態遷移モデル(State Machine Diagram)
サーキットブレーカーが 「何であるか(What)」 を定義します。
アクティビティ図(Activity Diagram)
サーキットブレーカーが 「どのように振る舞うか(How)」 を定義します。
1. 状態遷移モデル(State Machine Diagram)として
サーキットブレーカーは、「状態(State)」を持つ、典型的なステートマシンです。
🟢 状態1:CLOSED (クローズド)
振る舞い
正常な状態。リクエストはすべて下流のサービスに通過します。
監視
失敗率をカウントしています。
🔴 状態2:OPEN (オープン)
振る舞い
危険な状態。リクエストはすべて即座に失敗させます(Fail Fast)。
下流のサービスには一切リクエストを送りません。
監視
一定時間(例: 30秒)が経過するのを待っています。
🟡 状態3:HALF_OPEN (ハーフオープン)
振る舞い
テスト状態。「お試し」のリクエストを1回だけ下流のサービスに通過させます。
監視
その1回のリクエストが成功するか失敗するかを監視しています。
2. アクティビティ図(Activity Diagram)として
「どのような条件やユースケース(=アクティビティ)で遷移するのか」は、以下のアクティビティ図(プロセスの流れ)として完璧に表現できます。
アクティビティ図の例
サーキットブレーカーの設定にもOCP思想を適用
サーキットブレーカーの「設定を変更したい」という要求は、
オブジェクト指向の設計思想(特にカプセル化)
を適用する完璧なユースケースです。
・アクティビティ図で登場するロジック = 「振る舞い (Behavior)」
・設定(閾値や時間) = 「属性 (Attribute / State)」
として、対応付けられます。
オブジェクト指向の基本は、この「振る舞い」と「属性」を1つのオブジェクトにカプセル化し、「属性(設定)」の変更が「振る舞い(ロジック)」のコード修正を必要としないようにすることです。
カプセル化されるデータ(属性)
アクティビティ図上のロジックが 「使用するデータ」 は、以下のようにカプセル化されます。これらはサーキットブレーカー・オブジェクトの内部的な「属性」です。
ロジック(振る舞い)との関係
この設計の長所点は、アクティビティ図で示した
「複雑な振る舞い」(ロジック)を、execute()のような単一のメソッドとして実装できる
ことです。
そして、failureThresholdやresetTimeoutといった
「属性(設定)」 は、そのオブジェクトの内部に隠蔽(カプセル化)
されます。
❌ 悪い設計(カプセル化なし=ハードコーディング)
もしカプセル化しないと、ロジックはこうなります。
閾値を「10回」から「20回」に変更したい場合、このexecuteRequestという 「振る舞い(ロジック)」自体を「修正」 し、再コンパイル・再デプロイする必要があります。
これはOCP(オープンクローズド原則)に違反します。
✅ 良い設計(カプセル化あり)
thresholdやtimeoutを「属性」としてカプセル化します。
閾値を変更したい場合、cb.setConfig(20, 60000)を呼び出すだけで、「振る舞い(ロジック)」を一切修正せずに挙動を変更できます。これこそがカプセル化の恩恵です。
ドメイン駆動の文脈に応じた保守性
コアドメインは仕様(ビジネスルール)がコロコロ変わりやすいため、それを支えるインフラ(特にサーキットブレーカーのようなレジリエンス機構)も、その変更に追随しなければなりません。
したがって、コアドメインを支えるサーキットブレーカーの設計は、
「閾値(threshold)やタイムアウト(timeout)といった“属性”」が「最も頻繁に変更される」
ことを前提として設計しなければなりません。
この「変更のしやすさ(保守性)」という要件を満たすためには、オブジェクト指向の設計原則、特に「カプセル化(属性と振る舞いの分離)」が絶対に不可欠です。
振る舞い(ロジック)
executeRequest()のアクティビティ図(状態遷移ロジック)自体は、安定しており 「修正に閉じる」。
属性(ポリシー)
・failureThreshold
・resetTimeoutMs
・currentState
・failureCount
・lastFailureTime
コアドメインの要求に応じて 「変更(拡張)に開く」 必要がある。
ドメイン特性に準拠していない時
もし、この閾値がロジックにハードコードされていたらカプセル化違反です。
そのため、コアドメインのビジネス要件(例:「もっと応答性を高めたいので、タイムアウトを3秒から1秒に変えよう」)が変わるたびに、
アプリケーションのロジック(振る舞い)まで「修正」 して再デプロイする必要
があり、アジリティ(俊敏性)が著しく損なわれます。
まとまった単位での属性群
・failureThreshold
・resetTimeoutMs
・currentState
・failureCount
・lastFailureTime
これらの属性の型は、果たして最初から定義すべきでしょうか?
それとも、必要になってから定義するYAGNI原則で考えるべきでしょうか?
実はこの5つの属性は、サーキットブレーカーというパターンを成立させるための「最小限の構成要素」そのものです。
YAGNIの対象ではない「核」
YAGNIの原則は、
「将来必要になるかもしれない追加機能」を実装しない
というものです。 ですが、この5つの属性は「追加機能」ではありません。
これらは「サーキットブレーカー」という オブジェクトの「核となる定義」 です。
・currentState がなければ、そもそもステートマシンが成立しません。
・failureCount と failureThreshold がなければ、「Closed → Open」へ 遷移するロジック(=回路を“切る”ロジック) が書けません。
・lastFailureTime と resetTimeoutMs がなければ、「Open → Half-Open」へ 遷移するロジック(=回路を“復帰”させるロジック) が書けません。
このうち どれか1つでも欠ければ、それはもはや「サーキットブレーカー」パターンとは呼べない、不完全なオブジェクトになってしまいます。
YAGNIを適用すべき部分
では、サーキットブレーカーにおいてYAGNIを適用すべき部分はどこでしょうか?
答えは、この「核」以外の 「追加機能」 です。
successThreshold
Half-OpenからClosedに戻るために必要な「連続成功回数」。
(多くの場合「1回」で十分なため、最初はハードコードしておき、必要になってから属性として定義してもOK)
name
ログやメトリクスに出力するための名前。
metrics_exporter
状態遷移を外部(Prometheusなど)に通知するための依存オブジェクト。
結論
車のオブジェクトを設計する際、「engine(エンジン)」や「wheels(車輪)」を「必要になってから定義する」ということはありません。
それらは「車」の核となる定義で、すべて揃っているべきものだからです。
同様に、上で挙げた5つの属性は「サーキットブレーカー」の核となる属性定義です。
これらは、保守性やOCPのために(ロジックから分離された属性として)最初からすべて考慮・設計すべきです。




