Skip to content

Application

Handler

  • [Required] Handler は use case の実行単位にする。

  • [Required] Handlerはvalidation済みのapplication inputを受け取り、業務判断と状態変更に集中する。HTTP入力からapplication inputへの変換と失敗の扱いはvalidation.mdに従う。

  • [Recommended] public method は原則 HandleAsync にする。

  • [Contextual] HandleAsyncの引数がarchitectureでValue Object化の条件を満たす場合は、Value Object / feature-local modelとして受け取る。例: PlayerId callerPlayerId, RoomId roomId

  • [Required] HandleAsync に同じ primitive 型の domain 値を複数並べない。例: string callerPlayerID, string roomID は避ける。

  • [Contextual] HandleAsync の domain 引数が 5 個以上、または設定値や投票値のように意味の近い値が複数並ぶ場合は {Area}{UseCase}Command record にまとめる。例: RoomCreateCommandRoomUpdateSettingsCommand

  • [Required] command record は use case directory に置き、request DTO ではなく validation 済みの application input として扱う。Swagger に出す API contract と兼用しない。

  • [Required] command recordはHTTP request DTOと兼用せず、Value Object化の条件を満たすpropertyにはarchitectureで定義した型を使う。追加制約のないbool ReverseModeなどはprimitiveのまま扱う。

  • [Required] command record を作る endpoint / test では named arguments を使い、positional argument の順序依存を避ける。

  • [Required] logging は障害解析に必要な箇所に限定し、個人情報を出さない。

  • [Required] Handler に HTTP / provider SDK の詳細を漏らさない。

Handler Results

  • [Required] Handler の public method はuse case固有のapplication resultまたは業務エラーをErrorOr<T>で返す。application resultはHTTP Response DTOと兼用しない。
  • [Required] application resultは{Area}{UseCase}Resultとし、handlerと同じuse case directoryに置く。HTTP status、header、URL、provider SDK型を持たせない。
csharp
public sealed class RoomCreateHandler(
    IRoomCreateRepository repository,
    IRoomIDGenerator ids,
    TimeProvider clock)
{
    public async Task<ErrorOr<RoomCreateResult>> HandleAsync(
        PlayerId hostPlayerId,
        PlayerName hostName,
        DiscussionMinutes discussionMinutes,
        VoteSeconds voteSeconds,
        CancellationToken ct)
    {
        var now = clock.GetUtcNow().UtcDateTime;
        var activeRoom = await repository.FindActiveHostRoomAsync(hostPlayerId, now, ct);
        if (activeRoom is not null)
        {
            return RoomErrors.ActiveHostRoomExists();
        }

        var room = Ensure.Valid(Room.Create(
            hostPlayerId,
            hostName,
            discussionMinutes,
            voteSeconds,
            ids.NewRoomID(),
            ids.NewRoomCode(),
            now));

        return new RoomCreateResult(room.RoomID, room.RoomCode);
    }
}
  • [Required] Task<ErrorOr<TResult>>をhandlerの標準戻り値にする。Endpointは成功resultをHTTP Response DTOへ明示的にmappingする。
  • [Required] 業務上予期できる失敗は return {Area}Errors.SomeError(...) にする。
  • [Required] throw を業務分岐、権限不足、not found、競合、状態不正の通常表現に使わない。
  • [Required] handler は FluentValidation を呼ばない。
  • [Required] handler は Entity / feature-local model の method を呼び、状態遷移 rule を Entity 外へ漏らさない。
  • [Contextual] handler 内で Entity / feature-local model の factory が不変条件を再確認する場合は、失敗時に実装バグとして分かる helper を使う。例: Ensure.Valid(...)

Error Definitions

  • [Required] 業務エラーは area が所有する {Area}Errors に集約する。
  • [Required] ErrorOr の Error には API response へ変換できる code、description、metadata を持たせる。
csharp
public static class RoomErrors
{
    public static Error ActiveHostRoomExists()
        => Error.Conflict(
            code: "room.active_host_room_exists",
            description: "The authenticated player already has an active host room.");

    public static Error NotFound()
        => Error.NotFound(
            code: "room.not_found",
            description: "The requested room was not found.");
}
  • [Required] error code は area を含む安定した文字列にする。
  • [Required] API 利用者へ返す message は {Area}Errors か response mapping に置き、handler に散らさない。
  • [Required] HTTP status は endpoint / problem result mapping で決める。
  • [Required] 同じ業務エラーを ExceptionError の両方で定義しない。
  • [Required] 通常の業務分岐は1件のerrorを返す。HTTP mapperはErrorOrが複数errorを保持していても先頭の1件だけをProblem Detailsへ変換する。
  • [Contextual] application内部の一括処理で複数errorを保持してよいが、そのcollectionをAPI responseへそのまま公開しない。