The Standard Architecture について
The Standard Architectureは、The Standardのアーキテクチャ規則をシステム設計やレビューに適用するスキルです。モデル、ブローカー、サービス、公開層、API、ユーザーインターフェースの責務と依存方向を定めます。
主な機能
- 01目的、依存、公開の観点でシステムを分解
- 02モデルとブローカーの責務を定義
- 03サービスの役割と依存ルールを設定
- 04公開層、通信プロトコル、APIを設計
- 05リファクタリングにアーキテクチャ規則を適用
活用シーン
- 01システム構成とレイヤー境界をレビューする
- 02サービス分割と依存方向を計画する
- 03APIやユーザーインターフェースの構成を設計する
- 04The Standardに沿ってコード構造を見直す
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-architectureインストール後、Claude Code で/the-standard-architectureを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
manifest.jsoncontracts/contracts.jsonexamples/bad/example_bad_broker.csexample_bad_service.csexamples/good/example_logging_broker.csexample_processing_service.csexample_broker.csexample_foundation_service.csrules/rules.jsonrules.mdtemplates/logging_broker_template.csfoundation_service_template.csbroker_template.csprocessing_service_template.csvalidations/anti-patterns.mdchecklist.md詳細
標準アーキテクチャ
このスキルについて
このスキルは、『The Standard』のアーキテクチャに関する章を実践に適用します。 システムの分解方法、責務の割り当て方法、依存関係の流れを定めます。 モデル、ブローカー、サービス、集約、エクスポーザー、通信プロトコル、API、ユーザーインターフェースのアーキテクチャを含みます。
明示的な対象範囲
このスキルの明示的な対象は次のとおりです。
- 0.1.2 モデリング
- データキャリアモデル
- プライマリモデル
- セカンダリモデル
- リレーショナルモデル
- ハイブリッドモデル
- 運用モデル
- 統合モデル(ブローカー)
- 処理モデル(サービス)
- 公開モデル(公開コンポーネント)
- 構成モデル
- データキャリアモデル
- 1 ブローカー
- はじめに
- 全体像
- 特徴
- ローカルインターフェースを実装する
- フロー制御を行わない
- 例外処理を行わない
- 独自の構成を持つ
- プリミティブからネイティブを作成する
- 命名規則
- 言語
- 上位と横方向
- 1つのリソースにつき1つのブローカー
- 構成
- ブローカーの種類
- エンティティブローカー
- サポートブローカー
- 実装
- 非同期化の抽象化
- よくある質問/補足説明
- 標準化プロバイダー抽象化ライブラリ(SPAL)
- 拡張性
- 構成可能性
- 配布可能性
- 外部モック可能性(クラウドに依存しない)
- ローカルからグローバルへ
- 2 サービス
- はじめに
- サービスの操作
- 検証
- 処理
- 統合
- サービスの種類
- バリデーター
- オーケストレーター
- アグリゲーター
- 全般的なルール
- 実行するか委譲する
- 2〜3(Floranceパターン)
- 公開ポイントは1つ
- 入出力モデルは同一またはプリミティブ
- 各サービスは独立して動作する
- フローは前方へ進める
- API向け
- 基盤サービス(ブローカーに隣接)
- はじめに
- 全体像
- 特徴
- 純粋なプリミティブ
- 単一エンティティの統合
- ビジネス言語
- 責務
- 抽象化
- 検証
- サーキットブレーカー検証
- 継続的検証
- 更新可能な例外
- 動的ルール
- ルールと検証の集約
- ハイブリッドな継続的検証
- 構造検証
- 論理検証
- 外部検証
- 依存関係の検証
- マッピング
- 非ローカルモデル
- 例外処理
- 例外のマッピング
- 外部例外をローカル化する
- 例外を分類する
- ロギング
- 例外のマッピング
- 処理サービス(高次ビジネスロジック)
- はじめに
- 全体像
- 特徴
- 言語
- 関数の言語
- パススルー
- クラスレベルの言語
- 依存関係
- 1つの基盤
- 使用データのみを検証する
- 責務
- 高次ロジック
- シフター
- 組み合わせ
- シグネチャマッピング
- 例外ではないローカルモデル
- 例外処理
- 基盤サービスの例外をラップ解除してローカル化する
- 例外を分類する
- ロギング
- オーケストレーションサービス(複雑な高次ロジック)
- はじめに
- 全体像
- 特徴
- 言語
- 関数の言語
- パススルー
- クラスレベルの言語
- 依存関係
- 依存関係のバランス(Floranceパターン)
- 2〜3
- 完全な正規化
- セミ正規化
- 非正規化
- 意味のある分割
- コントラクト
- 物理コントラクト
- 仮想コントラクト
- 行き止まり
- 責務
- 高度なロジック
- フローの組み合わせ
- 呼び出し順序
- 自然な順序
- 強制された順序
- 例外処理
- 基盤サービスの例外をラップ解除してローカル化する
- 例外を分類する
- ロギング
- バリエーション
- 調整
- 管理
- 統括管理
- 作業単位
- 集約サービス(結び目)
- はじめに
- 全体像
- 特徴
- 依存関係の制限なし
- 順序の検証なし
- 基本的な検証
- パススルー
- オプション性
- ルーチンレベルの集約
- 純粋な依存関係コントラクト
- 責務
- 抽象化
- 例外の集約
- 3 公開コンポーネント
- はじめに
- 目的
- 純粋なマッピング
- 公開コンポーネントの種類
- 通信プロトコル
- ユーザーインターフェース
- 入出力コンポーネント
- 単一の窓口
- まとめ
- 3.1 通信プロトコル
- 原則とルール
- 結果の伝達
- エラーレポート
- RESTful API
- 言語
- CRUDルーチンを超えて
- 類似する動詞
- ルート規則
- 名詞と動詞
- コントローラールート
- ルーチン/メソッド固有のルート
- 複数形-単数形-複数形
- クエリパラメーターとOData
- X-WWW-Form-UrlEncodedパラメーター
- コードとレスポンス
- 成功コード(2xx)
- ユーザーエラーコード(4xx)
- システムエラーコード(5xx)
- すべてのコード
- 単一の依存関係
- 単一のコントラクト
- 構成
- ホームコントローラー
- 原則とルール
- 3.2 ユーザーインターフェース
- 原則とルール
- 進行状況(読み込み)
- 基本的な進行状況
- 残りの進行状況
- 詳細な進行状況
- 結果
- シンプル
- 部分的な詳細
- 詳細全体
- エラーレポート
- 情報提供
- 参照/暗黙的なアクション
- 対応可能
- 単一の依存関係
- 構造
- ベース
- コンポーネント
- コンテナー
- UIコンポーネントの種類
- ウェブアプリケーション
- モバイルアプリケーション
- その他の種類
- ウェブアプリケーション
- 構造
- ベースコンポーネント
- 実装
- 利用
- 制限事項
- コアコンポーネント
- 要素
- 存在
- プロパティの割り当て
- IDによる検索
- 一般的な検索
- プロパティ
- アクション
- 存在
- スタイル
- アクション
- 制限事項
- 要素
- ページ
- 控えめな設計
- 構成
次のトピックは、このスキルを拡張する専用スキルによって管理されます。
有効化の指針については、
## Related skillsを参照してください。
- ベースコンポーネント
- 進行状況(読み込み)
- CulDeSac のイベントブローカー、イベントサービス、パブリッシュ/サブスクライブ、DI 登録、起動時の有効化 →
the-standard-events - リリースのバージョニング、ファイルのバージョニング、API のバージョニング、非推奨化 →
the-standard-versioning
- 原則とルール
使用する場面
システム設計、アーキテクチャレビュー、分解、リファクタリング、API 設計、UI アーキテクチャ、依存関係の流れの設計、または振る舞いをどこに配置するか判断する際に、このスキルを使用してください。
関連スキル
このスキルでは、システム全体の構造上のルールを定義します。 特定のアーキテクチャパターンを選択した場合、その詳細な実装は専用スキルで管理します。 パターンが特定されたら、実装に着手するまで待たずに、該当するスキルを有効化してください。
| パターン | スキル | 有効化するタイミング |
|---|---|---|
| CulDeSac のイベント処理:イベントブローカー、イベントサービス、パブリッシュ/サブスクライブ、DI 登録、起動時の有効化 | the-standard-events | オーケストレーションサービスで、同期的な戻り値を伴わずにドメインイベントを発行または購読する必要がある場合 |
| リリースのバージョニング、ファイルのバージョニング、API のバージョニング、非推奨化 | the-standard-versioning | モデル、サービス、API コントラクトに変更を加える場合、リリースを作成する場合、または既存のコードを非推奨にする必要がある場合 |
システム全体のアーキテクチャルール
- すべてのシステムは、目的、依存関係、公開範囲に分解する必要があります。
- 低レベルでは、次のようになります。
- ブローカー = 依存関係
- サービス = 目的
- エクスポーザー = 公開範囲
- フローは順方向に保ってください。
- エクスポーザー -> サービス -> ブローカー -> 外部リソース
- フローを逆向きにしてはいけません。
- レイヤーの境界を曖昧にしてはいけません。
- 魔法のような抽象化でアーキテクチャを隠してはいけません。
モデリングルール
データ運搬モデル
- 主モデルはシステムの柱です。
- 物理的に自己完結しています。
- 副モデルは主モデルに依存します。
- リレーショナルモデルは主モデルを結び付け、主に参照を保持します。
- ハイブリッドモデルが許可されるのは、関係そのものに状態や詳細を持たせる必要がある場合に限ります。
- 物理モデルは貧血型でフラットに保ってください。
- 仮想コントラクトを使用するのは、オーケストレーションで真に必要な場合に限ります。
運用モデル
- 統合モデルはブローカーです。
- 処理モデルはサービスです。
- 公開モデルはエクスポーザーです。
- 構成モデルはシステムを構成し、起動、DI、ミドルウェア、またはプラットフォーム固有の設定を管理します。
ブローカーのルール
- ブローカーは、ビジネスロジックと外部の世界をつなぐ仲介役です。
- ブローカーはローカルインターフェースを実装する必要があります。
- ブローカーにビジネスロジックを含めてはいけません。
- ブローカーにフロー制御を含めてはいけません。
- ビジネス上の判断に
if文を使ってはいけません。 - ビジネス上の判断にループを使ってはいけません。
- ビジネス上の判断に
switch文を使ってはいけません。
- ビジネス上の判断に
- ブローカーは例外を処理してはいけません。
- ネイティブ例外はブローカーに隣接するサービスまで伝播させます。
- ブローカーは自身の設定を所有する必要があります。
- ブローカーはプリミティブな入力からネイティブモデルを構築できます。
- ブローカーは自身のテクノロジーの用語を使う必要があります。
- ストレージ → Insert/Select/Update/Delete
- キュー → Enqueue/Dequeue
- REST → Get/Post/Put/Delete/必要に応じてカスタム HTTP メソッド
- ブローカーは他のブローカーを呼び出せません。
- ブローカーはサービスに依存できません。
- 1 つのリソースにつき、ブローカーは 1 つです。
- 時刻やロギングなどの汎用機能には、サポートブローカーを使用します。
- リソースまたはエンティティ固有の統合には、エンティティブローカー/API ブローカーを使用します。
- 複数エンティティを扱うブローカーを整理するには、部分インターフェースと部分クラスを優先します。
- エンティティ部分クラスがネイティブクライアントに直接触れないよう、ブローカーのルート部分クラスに汎用ヘルパーメソッドを設けます。
- 非同期の抽象化を一貫して使用します。
- Standard の例や抽象化では、実装プロファイルに合致する場合、
ValueTaskを優先します。 - ブローカーは Brokers/ とその名前空間に配置します。
- ブローカーの設定は appsettings.json または同等の設定ストアに配置します。
- ブローカーの設定クラスは Brokers/ とその名前空間に配置します。
非同期化の抽象化(§1.5.1)
ブローカー、サービス、公開インターフェースを含め、公開されるすべてのインターフェースメソッドは、
現在の実装で内部的に何も await しない場合でも、ValueTask または ValueTask<T> を
返す必要があります。
これは The Standard §1.5.1 の要件です。公開コントラクトを一律に非同期にすることで、 実装が後に真の非同期処理になっても、呼び出し側を変更する必要がありません。
| パターン | 判定 |
|---|---|
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message); | 正しい — async キーワードを使用し、直接呼び出しています |
public ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => ValueTask.FromResult(...) | 正しい — ValueTask.FromResult が同期結果をラップしています |
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => await this.SelectAllAsync<Student>(); | 正しい — 汎用ヘルパーを通じて async/await を使用しています |
public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking(); | 誤り — 同期処理で、ValueTask を返していません |
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => ...)); | 誤り — 同期呼び出しを Task.Run で不必要にラップしています |
サービスへの影響: ILoggingBroker.LogErrorAsync は ValueTask を返すため、
CreateAndLog* ヘルパーも async にする必要があります。catch ブロックでは throw await を使います。
// WRONG
private StudentValidationException CreateAndLogValidationException(Xeption exception)
{
...
this.loggingBroker.LogError(studentValidationException); // no such sync method
return studentValidationException;
}
// CORRECT
private async ValueTask<StudentValidationException> CreateAndLogValidationException(
Xeption exception)
{
var studentValidationException = new StudentValidationException(...);
await this.loggingBroker.LogErrorAsync(studentValidationException);
return studentValidationException;
}
// CORRECT call site in TryCatch
catch (NullStudentException nullStudentException)
{
throw await CreateAndLogValidationException(nullStudentException);
}
ブローカーに関する補足
- ブローカーはリポジトリより広い概念です。
- プロバイダーはブローカーと同じものではありません。
- 設計上、ネイティブ例外がブローカーを通じて漏れる場合があります。基盤サービスがそれらを局所化します。
- 複数エンティティを扱うブローカーでは、設定の所有権を一元化できるため、部分クラス化を優先します。
- 本当に必要な場合はプロジェクトレベルで警告を抑制します。それ以外の場合は警告を修正します。
SPAL のルール
- 標準化されたプロバイダー抽象化ライブラリは、拡張可能でなければなりません。
- 設定可能でなければなりません。
- 配布可能でなければなりません。
- ローカル環境または機内モードで動作できるよう、外部からモック可能でなければなりません。
- 可能であれば、ローカルのニーズからグローバルに再利用可能なライブラリへと発展させます。
- サブシステムであり、それ自体も Brokers/Services/Exposers の構成に従う必要があります。
サービス全体のルール
- サービスにはビジネスロジックを含めます。
- サービスの処理は、検証、処理、連携に分けられます。
- サービスの種類は、バリデーター、オーケストレーター、アグリゲーターに分けられます。
- 自ら実行するか、委任するかのどちらかにし、両方を行ってはいけません。
- 該当する場合は、Florance Pattern を適用します。
- 公開レイヤーは、ビジネスロジックとの接点を単一にする必要があります。
- サービスは、同じコントラクト、またはそのコントラクトのプリミティブ値/集約値を受け取り、返す必要があります。
- プリミティブな入力を受け取るメソッドでは、引数の数が3つを超える場合にコントラクトモデルの使用を検討する必要があります。
- 各サービスは、自身の入力と出力を検証します。
- サービスは同じレベルの他のサービスを呼び出すことはできません。
- サービスのメソッドは同じレベルの他のサービスメソッドを呼び出すことはできません。
- 共通のロジックがある場合は、両方のパブリックメソッドから呼び出せるプライベートメソッドに切り出します。
- パブリック API は同じレベルの他のパブリック API を呼び出すことはできません。
- 処理の流れは前方向にのみ進めます。
基盤サービスのルール
- 基盤サービスは、ブローカーに隣接するサービスです。
- その目的は、検証、抽象化、マッピング、プリミティブなビジネス用語の提供です。
- 純粋なプリミティブの範囲にとどめる必要があります。
- 複数ステップから成る高次のビジネスロジックを含めてはいけません。
- 連携先となるエンティティブローカーは1つだけにする必要があります。
- ロギングや時刻などを扱うサポートブローカーは使用できます。
- 技術用語をビジネス用語に変換する必要があります。
- Insert -> Add
- Select -> Retrieve
- Update -> Modify
- Delete -> Remove
- TryCatch / 例外ノイズのキャンセルでロジックをラップする必要があります。
- 依存先を呼び出す前に検証する必要があります。
- コアビジネスロジックの直前に位置する最後の抽象化レイヤーです。
- 基盤サービスは Services/Foundations の下に配置します。
- 基盤サービスのモデルは Models/Foundations/{Entity Plural}/{Entity}.cs に配置します。
- 基盤サービスの例外モデルは Models/Foundations/{Entity Plural}/Exceptions/ に配置します。
検証ルール
- 検証の順序は必須です。
- 構造
- 論理
- 外部
- サーキットブレーカーとなる検証は、ただちに処理を終了します。
- 継続可能な検証ではエラーを蓄積し、その後サーキットを遮断します。
- 蓄積された検証データには、更新可能な例外を使用します。
- 条件と人が読めるメッセージの両方を含む動的ルールを使用します。
- ルールコレクターで集約してからスローします。
- ネストされたモデルには、ハイブリッドな継続検証を使用します。
- 現在のルーチンで使用する場合は、送信データを検証します。
- ネイティブの失敗をローカルの例外モデルにマッピングします。
基盤サービスの例外ルール
- ネイティブ例外をローカライズします。
- ネイティブ例外のデータディクショナリを、ローカライズされた例外のデータディクショナリに割り当てる必要があります。
- 可能な場合、ローカライズされた例外の内部例外にはネイティブ例外を指定する必要があります。
- 例外を、検証、依存先の検証、依存先、サービスの各例外に分類します。
- 上流に伝播する際も、ローカライズされた内部例外を保持します。
- 適切な重大度でログに記録します。
- 重大な依存先の障害は、インフラストラクチャ/構成の障害です。
- 生のネイティブ例外の意味を、上流の純粋なビジネスロジックに漏らしてはいけません。
処理サービスのルール
- 処理サービスは、単一エンティティに関する高次のビジネスロジックを保持します。
- 1 つの基盤サービスのプリミティブな操作を組み合わせることができます。
- ユーティリティブローカーを使用できます。
- エンティティブローカーを直接使用することはできません。
- 依存できる基盤サービスは 1 つだけです。
- 名前にはエンティティ名と Processing サフィックスを含める必要があります。
- 入力のうち、使用するものだけを検証します。
- 結果を bool や int などのプリミティブ型に変換できます。
- 複数のプリミティブルーチンを 1 つの高次ルーチンに組み合わせることができます。
- 基盤レイヤーの例外を処理レイヤーの例外カテゴリにマッピングします。
- 基盤レイヤーの例外をローカライズし、分類する必要があります。
- 処理サービスは Services/Processings の下に配置します。
- 処理サービスの仮想モデルは Models/Processings/{Entity Plural}/{Entity}.cs に配置します。
- 処理サービスの例外モデルは Models/Processings/{Entity Plural}/Exceptions/ に配置します。
オーケストレーションサービスのルール
- オーケストレーションサービスは、複数エンティティにまたがる操作を組み合わせます。
- 基盤サービスまたは処理サービスに依存できますが、エンティティ/ビジネスに関する依存先として両方を混在させることはできません。
- 追加でユーティリティブローカーを使用できます。
- Florance Pattern は必須です。
- エンティティ/ビジネスサービスの依存関係には Two-Three rule が必須です。
- 依存関係がルールの上限を超える場合は、正規化します。
- 完全な正規化
- 半正規化
- 非正規化は最後の手段としてのみ使用
- 分割はすべて意味のあるものでなければなりません。
- コントラクトの純粋性が保たれる場合、オーケストレーションサービスはパススルーにできます。
- オーケストレーションサービスは、物理コントラクトまたは仮想コントラクトを公開できます。
- マッピングと分岐を担当します。
- 依存先を適切な順序で呼び出す責任を負います。
- 依存先に順序が必要な場合は、自然な順序を優先します。
- 呼び出し依存関係が入出力の関係に自然に反映されない場合は、強制された順序をテストする必要があります。
- Cul-De-Sac オーケストレーションは、イベント/リスナーのシナリオで有効です。
- バリエーションには、調整サービス、管理サービス、uber-management サービスがあります。
- 作業単位の考え方を維持します。
- 安全にオーケストレーションを簡素化できる場合は、イベント駆動を優先します。
- 基盤レイヤーまたは処理レイヤーの例外を、オーケストレーションレイヤーの例外カテゴリにマッピングします。
- 基盤レイヤーまたは処理レイヤーの例外をローカライズし、分類する必要があります。
- オーケストレーションサービスは Services/Orchestrations の下に配置します。
- オーケストレーションサービスの仮想モデルは Models/Orchestrations/{Entity Plural}/{Entity}.cs に配置します。
- オーケストレーションサービスの例外モデルは Models/Orchestrations/{Entity Plural}/Exceptions/ に配置します。
- Cul-De-Sac のイベント駆動を選択した場合(ルール 13 および 16)、
the-standard-eventsスキルを有効にしてください。このスキルは、イベントレイヤーのイベントブローカー、イベントサービス、検証、例外処理、DI のライフタイム、起動時の有効化を規定します。
集約サービスのルール
- 集約サービスは、コアビジネスロジックの境界にある結節点です。
- 同じバリエーションの複数のサービスが同じコントラクトを共有する場合、単一の公開ポイントを提供します。
- ビジネスロジックは追加しません。
- 同じバリエーションの依存先を複数持つことができます。
- 呼び出し順序は検証しません。
- 基本的な構造検証のみを行います。
- 複数呼び出しルーチンまたはパススルーメソッドによって集約できます。
- 任意です。
- 依存先は同じコントラクトファミリーを共有する必要があります。
- オーケストレーションに類似したサービスと同じ方法で例外を集約する必要があります。
- オーケストレーションに類似したサービスと同じ方法で例外をローカライズし、分類する必要があります。
- 集約サービスは Services/Aggregations の下に配置します。
- 集約サービスの例外モデルは Models/Aggregations/{Entity Plural}/Exceptions/ に配置します。
エクスポーザーのルール
- エクスポーザーは破棄可能なマッピングレイヤーです。
- その目的は、コアビジネスロジックとの間で双方向のマッピングを行うことです。
- マッピングのみを行います。
- ブローカーと通信してはいけません。
- ビジネスロジックを含めてはいけません。
- サービス依存関係は1つだけ持つことができます。
- その依存関係はブローカーではなく、サービスでなければなりません。
- 単一の連絡窓口を提供しなければなりません。
エクスポージャーコンポーネントの種類
- 通信プロトコル
- ユーザーインターフェース
- I/O コンポーネント/バックグラウンドデーモン
通信プロトコルのルール
- コアビジネスの結果を、プロトコルの形式で返します。
- エラーレポートを忠実に、標準化されたコードとともに返します。
- 結果の通知とエラー報告をサポートします。
- マッピングは薄く、明示的に保ちます。
RESTful API のルール
- コントローラーでは HTTP 動詞の表記を使用します。
- Post / Get / Put / Delete
- ビジネス操作が CRUD を超え、標準の動詞が適さない場合は、カスタム動詞を使用できます。
- 名前とルートで区別できる場合は、異なるルーチン間で類似した動詞を使用できます。
- ルートに動詞を含めてはいけません。
- ルートには名詞を使用します。
- 単数名詞の原則を徹底します。
- 名詞の衝突より、リソースの交差を優先します。
- コントローラークラス名は複数形にします。
- コントローラーのルートは通常、api/[controller] に従います。
- メソッド固有のルートは、コントローラーのルートを拡張します。
- ネストされたリソースの交差には、複数形-単数形-複数形のルートパターンを使用します。
- クエリ可能な読み取りには、適切にクエリパラメーターと OData を使用します。
- フォーム形式のエンドポイント入力では、x-www-form-urlencoded を使用できます。
- コントローラーメソッドが受け取るパラメーターは3つまでにします。それを超える場合は、モデルを設計してください。
- コントローラーが持つサービス依存関係は1つです。
- コントローラーは単一の契約ファミリーに準拠します。
- コントローラーは Controllers の下に配置します。
- すべてのシステムに、ハートビートエンドポイントとして HomeController を設ける必要があります。
- HomeController ではセキュリティを要求せず、稼働状態のみを示します。
REST レスポンスコードのルール
- 成功レスポンス:
- GET / PUT / DELETE 形式の操作が成功した場合は 200 OK
- POST が成功した場合は 201 Created
- 委任された送信、または結果整合性に基づく送信の場合は 202 Accepted
- ユーザーエラーレスポンス:
- 入力やドメインの問題がユーザーによって修正可能なものとしてマッピングされる場合、検証および依存関係の検証に関する問題には 400 BadRequest
- 対象が見つからない場合は 404 NotFound
- すでに存在する場合は 409 Conflict
- リソースがロックされている場合は 423 Locked
- 無効な参照の場合は 424 FailedDependency
- システムエラーレスポンス:
- 依存関係またはサービスの障害には 500 InternalServerError
- 該当する場合、内部ストレージの問題には 507 InsufficientStorage
- セキュリティ境界を維持します。
- プロトコルでサニタイズされた形式が求められる場合を除き、依存関係やサービスの障害に関する内部詳細を公開してはいけません。
UI のルール
- UI 公開インターフェイスは、進捗、結果、エラーを対応付ける必要があります。
- 進捗を偽装してはいけません。
- 進捗の報告レベルは意図的に選択してください。
- 基本的な進捗
- 残りの進捗
- 詳細な進捗
- 結果の報告レベルは意図的に選択してください。
- シンプル
- 一部の詳細
- 詳細情報すべて
- 送信後、何が起きたかを示さずにユーザーを別の場所へ誘導することは違反です。
- エラーレポートでは、何が起きたか、なぜ起きたか、可能であれば次に取るべき行動をユーザーに伝える必要があります。
- エラーレポートの種類:
- 情報提供型
- 参照型/暗黙的なアクション
- 対応可能型
- 技術的なエラー表現を、ユーザーに適した表現に置き換えてください。
- UI 公開インターフェイスが依存する対象は1つだけです。
- UI の構成は、次の要素を分離する必要があります。
- 基盤
- コンポーネント
- コンテナー
- 基盤コンポーネントは、ネイティブまたはサードパーティ製の UI 要素をラップします。
- コアコンポーネントは、1つだけのビューサービスと統合します。
- コンテナー/ページが行うのは、UI コンポーネントとルートのオーケストレーションだけです。
- コンテナーに UI ロジックを含めてはいけません。
- コンテナーから基盤コンポーネントを直接使用してはいけません。
- 回避できる場合、基盤コンポーネントがラップする非ローカルコンポーネントは1つを超えてはいけません。
- 基盤コンポーネントには、ビジネスロジック、例外処理、検証、計算を含めてはいけません。
- ページはルートコンテナーであり、ビジネスロジックは不要です。
- マークアップ、コードビハインド、スタイルファイルを分離してください。
- UI は
Views/Bases、Views/Components、Views/Pagesの下に整理してください。 - ドメイン駆動型の UI 構成を使用してください。
Web アプリケーション固有の事項
- 基盤コンポーネントはブローカーのように振る舞います。
- コアコンポーネントはサービスとコントローラーを組み合わせたように振る舞います。
- コアコンポーネントはテスト駆動で開発します。
- コアコンポーネントは次の要素で構成されます。
- 要素
- スタイル
- アクション
- 要素のテストでは、次の項目を網羅する必要があります。
- 存在
- プロパティ
- アクション
- 存在のテストには、次の方法を使用できます。
- プロパティの割り当て
- ID による検索
- 一般的な検索
- スタイルは、主に基盤ではなくコアコンポーネントに属します。
- ページはコンポーネントを組み合わせ、ルートを表します。
- 通常、ページには単体テストは不要です。
- UI は控えめに保ち、CSS、C#、マークアップを同じファイルに配置しないでください。
- コアコンポーネントは、同じ階層にある他のコアコンポーネントを呼び出しません。
- 1つのビューサービスは、1つのコアコンポーネントと1つのビュー・モデルに対応します。
バージョン管理と破壊的変更
このスキルでは、各レイヤーで正しい構造が何であるかを定めます。
構造変更によって既存の契約が破られる場合、たとえばモデルのプロパティの追加や削除、サービスのシグネチャの変更、API ルートやレスポンス形式の変更がある場合、それはもはや純粋なアーキテクチャ上の判断ではありません。
その時点で the-standard-versioning スキルを有効にしてください。
バージョン管理スキルでは、次の事項を定めます。
- リリースバージョンをいつ、どのように引き上げるか
- 以前のバージョンを上書きせず、
Vnサブフォルダーに新しいモデルバージョンを導入する方法 - 以前のバージョンと並行して新しいサービスバージョンを導入する方法
- 以前の利用者に影響を与えないよう API ルートをバージョン管理する方法
- モデル、サービス、ルートの非推奨を通知する方法
アーキテクチャスキルとバージョン管理スキルは相互に補完します。
- アーキテクチャが答える問い:構成は適切か?
- バージョン管理が答える問い:安全に変更するにはどうすればよいか?
アーキテクチャレビューのチェックリスト
アーキテクチャをレビューまたは生成する際は、以下のすべてを確認してください。
- 目的が明確であること。
- モデルが目的に沿ってスコープ設定されていること。
- 依存関係と公開範囲が目的と分離されていること。
- ブローカーの責務が最小限で、破棄可能であること。
- サービスがビジネスロジックを担っていること。
- 検証の順序が正しいこと。
- サービス層のフローが前方向のみであること。
- 同じ階層のサービス同士が呼び出し合っていないこと。
- Florance Pattern が守られていること。
- 公開層の連絡窓口が単一であること。
- API がルートおよびステータスコードの規則に従っていること。
- UI が単一依存、非侵襲性、および構造の規則に従っていること。
- アーキテクチャが可読性、自律性、書き換え可能性を維持していること。
- Cul-De-Sac イベント処理を使用する場合、
the-standard-eventsスキルが有効化され、その規則を満たしていること。 - モデル、サービス、または API コントラクトが変更された場合、またはリリースを行う場合は、
the-standard-versioningスキルが有効化され、その規則を満たしていること。
提供された実装仕様に含まれる .NET 実装プロファイル
以下の補足では、提供された実装プロファイルを保持し、このスキルが抽象的な Standard と、添付された具体的な .NET 実装スタイルの両方を適用できるようにします。
1. 概要
Standard は、Hassan Habib が策定した、特定の考え方に基づくソフトウェアエンジニアリング標準です。 次の 三つの性質を持つ アーキテクチャを規定しています。
| 層 | 責務 |
|---|---|
| ブローカー | 外部依存関係(DB、API、ログなど)に対する薄い抽象化レイヤー |
| サービス | すべてのビジネスロジック(検証、オーケストレーション、調整) |
| エクスポーザー | サービスを公開するエントリーポイント(コントローラー、エンドポイント) |
このプロジェクトでは現在、LegacyUser エンティティ(ストレージベース)と Person エンティティ(API ベース)向けの ブローカー と ファウンデーションサービス、および Person エンティティを外部 API に送信するための API ブローカー を実装しています。
2. プロジェクト構成
RedRhino.Core.Synchronizer/
├── Brokers/
│ ├── Apis/
│ │ ├── IModernApiBroker.cs (partial interface — base)
│ │ ├── IModernApiBroker.Persons.cs (partial interface — entity)
│ │ ├── ModernApiBroker.cs (partial class — base)
│ │ └── ModernApiBroker.Persons.cs (partial class — entity)
│ ├── Loggings/
│ │ ├── ILoggingBroker.cs
│ │ └── LoggingBroker.cs
│ └── Storages/
│ ├── IStorageBroker.cs (partial interface — base)
│ ├── IStorageBroker.LegacyUsers.cs (partial interface — entity)
│ ├── StorageBroker.cs (partial class — base)
│ └── StorageBroker.LegacyUsers.cs (partial class — entity)
├── Models/
│ ├── Foundations/
│ ├── Persons/
│ │ ├── Person.cs
│ │ ├── PersonType.cs
│ │ ├── PersonRecordState.cs
│ │ └── Exceptions/
│ │ ├── NullPersonException.cs
│ │ ├── InvalidPersonException.cs
│ │ ├── AlreadyExistsPersonException.cs
│ │ ├── FailedPersonDependencyException.cs
│ │ ├── FailedPersonServiceException.cs
│ │ ├── PersonValidationException.cs
│ │ ├── PersonDependencyException.cs
│ │ ├── PersonDependencyValidationException.cs
│ │ └── PersonServiceException.cs
│ └── LegacyUsers/
│ ├── LegacyUser.cs
│ └── Exceptions/
│ ├── NullLegacyUserException.cs
│ ├── InvalidLegacyUserException.cs
│ ├── AlreadyExistsLegacyUserException.cs
│ ├── FailedStorageLegacyUserDependencyException.cs
│ ├── FailedLegacyUserServiceException.cs
│ ├── LegacyUserValidationException.cs
│ ├── LegacyUserDependencyException.cs
│ ├── LegacyUserDependencyValidationException.cs
│ └── LegacyUserServiceException.cs
├── Services/
│ └── Foundations/
│ ├── LegacyUsers/
│ │ ├── ILegacyUserService.cs
│ │ ├── LegacyUserService.cs (partial — logic)
│ │ ├── LegacyUserService.Validations.cs (partial — validations)
│ │ └── LegacyUserService.Exceptions.cs (partial — exception handling)
│ └── Persons/
│ ├── IPersonService.cs
│ ├── PersonService.cs (partial — logic)
│ ├── PersonService.Validations.cs (partial — validations)
│ └── PersonService.Exceptions.cs (partial — exception handling)
└── Program.cs
RedRhino.Core.Synchronizer.Tests.Units/
└── Services/
└── Foundations/
├── LegacyUsers/
│ ├── LegacyUserServiceTests.cs (partial — setup & helpers)
│ ├── LegacyUserServiceTests.Logic.{Method}.cs (partial — happy-path tests)
│ ├── LegacyUserServiceTests.Validations.{Method}.cs (partial — validation tests)
│ └── LegacyUserServiceTests.Exceptions.{Method}.cs (partial — exception tests)
└── Persons/
├── PersonServiceTests.cs (partial — setup & helpers)
├── PersonServiceTests.Logic.{Method}.cs (partial — happy-path tests)
├── PersonServiceTests.Validations.{Method}.cs (partial — validation tests)
└── PersonServiceTests.Exceptions.{Method}.cs (partial — exception tests)
3. ブローカー
ブローカーは外部リソースを包む 薄いラッパー です。ビジネスロジックは 一切含みません。
規則 — 汎用ヘルパー: ブローカーの各ベース partial は、基盤となるクライアント呼び出しをカプセル化するプライベートなジェネリックヘルパーメソッド(例:
InsertAsync<T>、PostAsync<T>)を必ず公開しなければなりません。エンティティ partial ファイルは、プライベートなクライアントメンバー(例:this.apiClient、this.Entry(...))を直接参照してはならず、代わりにジェネリックヘルパーに処理を委譲します。これにより、エンティティ partial を具体的なクライアントから切り離し、基盤となる実装を単一の場所で差し替えられるようにします。
3.1 ストレージブローカー
| 項目 | 実装 |
|---|---|
| 基底クラス | EFxceptionsContext(EFxceptions ライブラリのクラス。DbContext を、意味のある EF 例外でラップします) |
| インターフェース | partial interface IStorageBroker — エンティティごとに分割 |
| クラス | partial class StorageBroker — エンティティごとに分割 |
| 汎用 CRUD ヘルパー | 基底のパーシャルクラスにあるプライベートヘルパー:InsertAsync<T>、SelectAllAsync<T>、SelectAsync<T>、UpdateAsync<T>、DeleteAsync<T> |
| 設定 | IConfiguration から DefaultConnection を読み込み、コンストラクターで this.Database.Migrate() を呼び出します |
基底パーシャルクラス — StorageBroker.cs
このファイルが担当するのは、IConfiguration、OnConfiguring、コンストラクター(Database.Migrate() を含む)、およびプライベートな汎用 CRUD ヘルパーです。それ以外は何も担当しません。
arch-014 — 基底パーシャルクラスに
DbSet<>を置かないでください。DbSet<Student> Studentsはここではなく、StorageBroker.Students.csに置きます。 基底パーシャルクラスでエンティティ固有のメンバーを宣言してはいけません。
public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
// No DbSet<> properties here. Each entity partial declares its own.
private readonly IConfiguration configuration;
public StorageBroker(IConfiguration configuration)
{
this.configuration = configuration;
// arch-011: Must be called — applies pending migrations at startup.
// Omitting this means the schema is never applied.
this.Database.Migrate();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString =
this.configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
// arch-012: All EF operations live here. Entity partials call these helpers only.
private async ValueTask<T> InsertAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Added;
await this.SaveChangesAsync();
return entity;
}
// SelectAllAsync<T> wraps IQueryable in a ValueTask so all entity methods
// follow the uniform async/await pattern. AsNoTracking() is applied here.
private ValueTask<IQueryable<T>> SelectAllAsync<T>() where T : class =>
ValueTask.FromResult<IQueryable<T>>(this.Set<T>().AsNoTracking());
private async ValueTask<T> SelectAsync<T>(Guid entityId) where T : class =>
await this.FindAsync<T>(entityId);
private async ValueTask<T> UpdateAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Modified;
await this.SaveChangesAsync();
return entity;
}
private async ValueTask<T> DeleteAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Deleted;
await this.SaveChangesAsync();
return entity;
}
}
エンティティパーシャルクラス — StorageBroker.Students.cs
arch-014:
DbSet<Student>はStorageBroker.csではなく、エンティティパーシャルクラスであるここに宣言します。
public partial class StorageBroker
{
// DbSet<Student> belongs in this entity partial file, not in StorageBroker.cs.
public DbSet<Student> Students { get; set; }
public async ValueTask<Student> InsertStudentAsync(Student student) =>
await this.InsertAsync(student);
// arch-009: SelectAll* must be async ValueTask<IQueryable<T>>.
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking();
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Set<Student>();
// CORRECT: delegate to SelectAllAsync<T>() — never touch DbSet or EF members directly.
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() =>
await this.SelectAllAsync<Student>();
public async ValueTask<Student> SelectStudentByIdAsync(Guid studentId) =>
await this.SelectAsync<Student>(studentId);
public async ValueTask<Student> UpdateStudentAsync(Student student) =>
await this.UpdateAsync(student);
public async ValueTask<Student> DeleteStudentAsync(Student student) =>
await this.DeleteAsync(student);
}
ルール: インターフェースとクラスの両方で、エンティティごとに独自のパーシャルファイルを用意します。
ルール — スキャフォールディング前にスコープを決める(arch-013):
BROKERS-student-insertというブランチで実装するのは、InsertStudentAsyncのみです。必要な操作がプロンプトから明確でない場合、エージェントはブランチを作成したり、コードを書いたりする前に確認しなければなりません。ブローカーのブランチ1つにつき、操作は1つです。ルール — ブランチ名で使うアクションの表現(prac-016): ブローカーのブランチ名では、インフラストラクチャの動詞である
insert、select-all、select-by-id、update、deleteを使います。BROKERSのブランチ名では、ビジネスの動詞(add、retrieve、modify、remove)を使ってはいけません。
3.2 API ブローカー
| 項目 | 実装 |
|---|---|
| HTTP クライアント | IRESTFulApiFactoryClient(HttpClient をラップする RESTFulSense ライブラリのクラス) |
| インターフェース | partial interface IModernApiBroker — エンティティごとに分割 |
| クラス | partial class ModernApiBroker — エンティティごとに分割 |
| 汎用 HTTP ヘルパー | エンティティ間で再利用するため、基底パーシャルクラスに PostAsync<T>() をプライベートメソッドとして配置します |
| 設定 | IConfiguration から ApiConfigurations:Url を読み込み、HttpClient.BaseAddress を設定します |
基底パーシャルクラス — ModernApiBroker.cs
public partial class ModernApiBroker : IModernApiBroker
{
private readonly IRESTFulApiFactoryClient apiClient;
public ModernApiBroker(IConfiguration configuration)
{
var httpClient = new HttpClient()
{
BaseAddress =
new Uri(configuration.GetValue<string>("ApiConfigurations:Url"))
};
this.apiClient = new RESTFulApiFactoryClient(httpClient);
}
private async ValueTask<T> PostAsync<T>(string relativeUrl, T content) =>
await this.apiClient.PostContentAsync<T>(relativeUrl, content);
}
エンティティパーシャルクラス — ModernApiBroker.Persons.cs
public partial class ModernApiBroker
{
private const string PersonsRelativeUrl = "api/persons";
public async ValueTask<Person> PostPersonAsync(Person person) =>
await PostAsync(PersonsRelativeUrl, person);
}
ルール: エンティティパーシャルクラスは汎用ヘルパー(
PostAsync<T>)に処理を委譲します。this.apiClientを直接呼び出してはいけません。
3.3 ロギングブローカー
ILogger<T> を抽象化した専用の機能です。ブローカーが公開するのは、標準のログレベルに対応する非同期の ValueTask メソッドのみです。
| メソッド | ログレベル |
|---|---|
LogInformationAsync | Information |
LogTraceAsync | Trace |
LogDebugAsync | Debug |
LogWarningAsync | Warning |
LogErrorAsync | Error |
LogCriticalAsync | Critical |
LoggingBroker.cs
public class LoggingBroker : ILoggingBroker
{
private readonly ILogger<LoggingBroker> logger;
public LoggingBroker(ILogger<LoggingBroker> logger) =>
this.logger = logger;
public async ValueTask LogInformationAsync(string message) =>
this.logger.LogInformation(message);
public async ValueTask LogTraceAsync(string message) =>
this.logger.LogTrace(message);
public async ValueTask LogDebugAsync(string message) =>
this.logger.LogDebug(message);
public async ValueTask LogWarningAsync(string message) =>
this.logger.LogWarning(message);
public async ValueTask LogErrorAsync(Exception exception) =>
this.logger.LogError(exception, exception.Message);
public async ValueTask LogCriticalAsync(Exception exception) =>
this.logger.LogCritical(exception, exception.Message);
}
ILoggingBroker.cs
public interface ILoggingBroker
{
ValueTask LogInformationAsync(string message);
ValueTask LogTraceAsync(string message);
ValueTask LogDebugAsync(string message);
ValueTask LogWarningAsync(string message);
ValueTask LogErrorAsync(Exception exception);
ValueTask LogCriticalAsync(Exception exception);
}
ルール — 非同期の式本体を使用し、Task.Run は使用しないでください: 各メソッドでは
asyncキーワードを使用し、ILogger<T>に直接委譲します。 呼び出しをTask.Run()やnew ValueTask(Task.Run(...))でラップしないでください。ILogger<T>は同期型です。これをTask.Run()でラップすると、 不要なスレッドプールのオーバーヘッドが発生し、ヒープ割り当てを伴う非効率なValueTaskが生成されます。誤り:
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => this.logger.LogWarning(message)));正しい例:
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message);
ルール — DI 登録: ロギングブローカーは
Program.csに明示的に登録する必要があります。ILogger<LoggingBroker>が正しく解決されるよう、AddLogging()(またはホストビルダーの既定の設定)も必要です。builder.Services.AddLogging(); builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();どちらかの行を省略すると、実行時に DI の解決に失敗します。
4. モデル
4.1 エンティティモデル
エンティティクラスは、Models/{Service Type}/{Entity}/ 配下に配置される、単純な POCO です。振る舞いは含まず、プロパティのみを含みます。プロパティに付けるドメインコメントには、検証の意図(null 許容ルール、メール形式、電話番号形式など)を記載します。
LegacyUser クラスは Models/Foundations/LegacyUsers/ 配下に配置されます。
Person クラスは Models/Foundations/Persons/ 配下に配置されます。
4.2 例外モデル
例外モデルは Models/{Service Type}/{Entity}/Exceptions/ に配置され、The Standard に従った2 層の例外階層を形成します。
LegacyUser クラスは Models/Foundations/LegacyUsers/Exceptions/ 配下に配置されます。
Person クラスは Models/Foundations/Persons/Exceptions/ 配下に配置されます。
内側と外側の例外階層は、Foundation Service が利用するブローカーの種類によって異なります。ストレージベースのサービス(例:LegacyUserService)は SQL/EF の例外を処理し、API ベースのサービス(例:PersonService)は RESTFulSense の HTTP 例外を処理します。
4.2.1 ストレージベースの例外(LegacyUser)
内側(ローカル)の例外
| 例外 | 目的 |
|---|---|
NullLegacyUserException | エンティティが null である |
InvalidLegacyUserException | 1 つ以上のプロパティレベルの検証に失敗した |
AlreadyExistsLegacyUserException | 重複キーが検出された |
FailedStorageLegacyUserDependencyException | SQL/ストレージレベルの障害 |
FailedLegacyUserServiceException | 予期しない実行時障害 |
外側(カテゴリ別)例外
| 例外 | カテゴリ | ラップされる内側の例外 |
|---|---|---|
LegacyUserValidationException | Validation | NullLegacyUserException、InvalidLegacyUserException |
LegacyUserDependencyException | Dependency | FailedStorageLegacyUserDependencyException |
LegacyUserDependencyValidationException | DependencyValidation | AlreadyExistsLegacyUserException、InvalidLegacyUserException(DbUpdateException に起因) |
LegacyUserServiceException | Service | FailedLegacyUserServiceException |
4.2.2 API ベースの例外(Person)
内側(ローカル)例外
| 例外 | 用途 |
|---|---|
NullPersonException | エンティティが null です |
InvalidPersonException | 1 つ以上のプロパティレベルの検証に失敗した場合、または API から BadRequest が返された場合 |
AlreadyExistsPersonException | API から Conflict(409)が返された場合 |
FailedPersonDependencyException | HTTP レベルの失敗(すべての HTTP エラーまたは HttpRequestException) |
FailedPersonServiceException | 予期しない実行時エラー |
外側(カテゴリ別)例外
| 例外 | カテゴリ | ラップされる内側の例外 |
|---|---|---|
PersonValidationException | Validation | NullPersonException、InvalidPersonException |
PersonDependencyValidationException | DependencyValidation | InvalidPersonException(BadRequest に起因)、AlreadyExistsPersonException(Conflict に起因) |
PersonDependencyException | Dependency(クリティカル) | FailedPersonDependencyException(Unauthorized、Forbidden、NotFound、UrlNotFound、HttpRequestException に起因) |
PersonDependencyException | Dependency(非クリティカル) | FailedPersonDependencyException(InternalServerError、ServiceUnavailable に起因) |
PersonServiceException | Service | FailedPersonServiceException |
すべての例外は Xeption(Xeption NuGet パッケージ由来)を継承しており、集約された検証データのための
UpsertDataList と ThrowIfContainsErrors が提供されます。
5. サービス — 基盤
基盤サービスはブローカーの直上に位置し、ブローカーを利用する唯一のコンシューマーです。
5.1 部分クラスの構成
サービスは 3 つの部分ファイルに分割されています。
| 部分ファイル | 内容 |
|---|---|
{Entity}Service.cs | コンストラクター、DI フィールド、公開ビジネスロジック |
{Entity}Service.Validations.cs | すべての Validate* および IsInvalid* メソッド |
{Entity}Service.Exceptions.cs | TryCatch デリゲートパターン、CreateAndLog* ヘルパー |
5.2 依存性注入
Foundation Service が依存するのはブローカーのみであり、他のサービスには依存しません。
ストレージベースのサービス(LegacyUserService):
public partial class LegacyUserService : ILegacyUserService
{
private readonly IStorageBroker storageBroker;
private readonly ILoggingBroker loggingBroker;
public LegacyUserService(
IStorageBroker storageBroker,
ILoggingBroker loggingBroker) { ... }
}
API ベースのサービス(PersonService):
public partial class PersonService : IPersonService
{
private readonly IModernApiBroker modernApiBroker;
private readonly ILoggingBroker loggingBroker;
public PersonService(
IModernApiBroker modernApiBroker,
ILoggingBroker loggingBroker) { ... }
}
5.3 ビジネスロジック — TryCatch パターン
すべてのパブリックメソッドは、カテゴリ分けされた例外をキャッチ、ラップ、ログ記録して再スローする TryCatch ラッパーに処理を委譲します。
ストレージベース:
public ValueTask<LegacyUser> AddLegacyUserAsync(LegacyUser legacyUser) =>
TryCatch(async () =>
{
ValidateLegacyUser(legacyUser);
return await this.storageBroker.InsertLegacyUserAsync(legacyUser);
});
API ベース:
public ValueTask<Person> AddPersonAsync(Person person) =>
TryCatch(async () =>
{
ValidatePerson(person);
return await this.modernApiBroker.PostPersonAsync(person);
});
5.4 バリデーション
バリデーションには、動的ルール+集約パターンを使用します。
Validate(
(IsInvalid(legacyUser.Id), nameof(LegacyUser.Id)),
(IsInvalid(legacyUser.UserPK), nameof(LegacyUser.UserPK)),
(IsInvalid(legacyUser.UserName), nameof(LegacyUser.UserName)),
...);
各 IsInvalid オーバーロードは、Condition(bool)と Message(string)を持つ匿名オブジェクトを返します。
Validate メソッドは、UpsertDataList を使ってすべての失敗を単一の InvalidLegacyUserException に集約し、
ThrowIfContainsErrors() を呼び出します。
ドメイン固有のルールには、カスタムバリデーターを使用します。
| バリデーター | ルール |
|---|---|
IsInvalid(Guid) | Guid.Empty であってはなりません |
IsInvalid(int) | default (0) であってはなりません |
IsInvalid(string) | null、空、空白のみであってはなりません |
IsInvalidLob(int) | 0 より大きくなければなりません |
IsInvalidEmail | 正規表現によるメール形式のチェック |
6. 例外処理
各 {Entity}Service.Exceptions.cs の TryCatch メソッドは、The Standard の
例外マッピングパターンを実装します。具体的な catch ブロックは、サービスが使用するブローカーの種類によって異なります。
6.1 ストレージベースの例外マッピング(LegacyUser)
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception
──────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullLegacyUserException → LegacyUserValidationException
(validation rules fail) → InvalidLegacyUserException → LegacyUserValidationException
SqlException → FailedStorage...Exception → LegacyUserDependencyException (Critical)
DuplicateKeyException → AlreadyExists...Exception → LegacyUserDependencyValidationException
DbUpdateException → InvalidLegacy...Exception → LegacyUserDependencyValidationException
Exception (catch-all) → FailedService...Exception → LegacyUserServiceException
6.2 API ベースの例外マッピング(Person)
RESTFulSense を介して外部 API を呼び出すサービスでは、例外マッピングによって
すべての HTTP レスポンス例外を処理します。TryCatch 内の catch ブロックの順序は重要です。より具体的な例外を、基底クラスより前に記述する必要があります。
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception Log Level
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullPersonException → PersonValidationException LogError
(validation rules fail) → InvalidPersonException → PersonValidationException LogError
HttpResponseBadRequestException → InvalidPersonException → PersonDependencyValidationException LogError
HttpResponseConflictException → AlreadyExistsPersonException → PersonDependencyValidationException LogError
HttpResponseUnauthorizedException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseForbiddenException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseUrlNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseInternalServerErrorException → FailedPersonDependencyException → PersonDependencyException LogError
HttpResponseServiceUnavailableException → FailedPersonDependencyException → PersonDependencyException LogError
HttpRequestException → FailedPersonDependencyException → PersonDependencyException LogCritical
Exception (catch-all) → FailedPersonServiceException → PersonServiceException LogError
ルール — 重大な依存関係と重大でない依存関係: API エンドポイントに到達できない、またはアクセスできないことを示す例外(
Unauthorized、Forbidden、NotFound、UrlNotFound、HttpRequestException)は、設定またはインフラストラクチャの障害を示すため、LogCriticalAsyncでログに記録します。サーバー側のエラー(InternalServerError、ServiceUnavailable)は、一時的な可能性があるため、LogErrorAsyncでログに記録します。
各 CreateAndLog* ヘルパーは、以下を行います。
- 内部例外をカテゴリ別の外部例外でラップします。
- 適切なレベル(
LogErrorAsyncまたはLogCriticalAsync)でILoggingBrokerを介してログに記録します。 - 外部例外を返し、
TryCatchがそれをthrowできるようにします。
7. 依存関係の登録(Exposers)
The Standard の明示的な登録スタイルに従い、すべての登録を Program.cs で行います。
builder.Services.AddDbContext<StorageBroker>();
builder.Services.AddTransient<IStorageBroker, StorageBroker>();
builder.Services.AddTransient<IModernApiBroker, ModernApiBroker>();
builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();
builder.Services.AddTransient<ILegacyUserService, LegacyUserService>();
builder.Services.AddTransient<IPersonService, PersonService>();
ブローカーと Foundation Services は Transient として登録します。
10. Naming Conventions
| Element | Pattern | Example |
|---|---|---|
| Broker interface | I{Resource}Broker | IStorageBroker, IModernApiBroker, ILoggingBroker |
| Broker class | {Resource}Broker | StorageBroker, ModernApiBroker, LoggingBroker |
| Broker method | {Action}{Entity}Async | InsertLegacyUserAsync, PostPersonAsync |
| Service interface | I{Entity}Service | ILegacyUserService |
| Service class | {Entity}Service | LegacyUserService |
| Service method | Add{Entity}Async | AddLegacyUserAsync |
| Inner exception | {Adjective}{Entity}Exception | NullLegacyUserException |
| Outer exception | {Entity}{Category}Exception | LegacyUserValidationException |
| Test class | {Entity}ServiceTests | LegacyUserServiceTests |
| Test method | Should{Action}Async / ShouldThrow{Exception}On{Action}… | ShouldAddLegacyUserAsync |