Skip to content

Architecture

  • [Required] Backend は top-level で Features/Infrastructure/Shared/Program.cs に分ける。Shared/は複数areaが共同所有する技術的なAPI、validation、runtime基盤だけに使い、area固有の業務概念を置かない。

  • [Required] 業務モデル、業務エラー、Value Object、feature が使う port は、その概念を所有する Features/{Area}/ 配下に置く。domain型はDomain/、業務エラーとarea共通portはarea直下、use case固有portはuse case directoryに分ける。

  • [Required] 構造変更は次の順で判断する。

  • [Required] 第一に、依存方向と責務の境界を守る。

  • [Required] 第二に、近接するコードから対象を予測できる配置にする。

  • [Required] 第三に、現在確認できる肥大化や異なる変更理由を分割する。

  • [Required] 第四に、将来必要になるかもしれない共通化、module、階層は導入しない。

  • [Recommended] 一般的な設計パターンをそのままTo-Beとみなさない。既存構造で上記を満たせるなら維持し、局所的な整理で足りない根拠がある場合だけ大きな構造変更を選ぶ。

text
<backend-project>/
  Features/
    Customers/
      CustomerEndpoints.cs
      Domain/
        Customer.cs
      Signup/
        CustomerSignupHandler.cs
      CustomerErrors.cs
  Infrastructure/
  Shared/
  Program.cs
  • [Required] 依存は feature を中心に内向きにする。

  • [Required] Features/{Area}/ はHTTP境界、ユースケース、Domain/、業務エラー、use case portを持つ。

  • [Contextual] Infrastructure/Features/ で定義した interface を実装してよい。

  • [Required] Features/ から Infrastructure/ を参照しない。

  • [Required] 外部サービス境界は、それを必要とする内側の層で interface を定義し、外側の Infrastructure/ が実装する。

  • [Required] ドメイン知識に関わる抽象は、それを所有する Features/{Area}/ に置く。ユースケース実行のための port は Features/{Area}/{UseCase}/、または area 全体で使うなら Features/{Area}/ に置く。

  • [Contextual] provider SDKやORMの属性・型を持つ永続化modelは純粋なdomain modelと呼ばない。必要ならfeature-local modelまたはInfrastructure DTOとして扱う。

  • [Required] Program.cs は composition root として扱い、Feature ごとの実装詳細を書かない。

  • [Contextual] 小規模な Minimal API では探索性を優先し、独立したdomain layerが必要になる根拠がなければtop-level Domain/を切らない。Featureは業務概念とHTTP機能の所有境界、Infrastructureは永続化や外部サービスの技術詳細として分ける。

Feature Domain

  • [Required] Entity、Aggregate、Value Object、domain enum、domain rule、domain event、domain resultは、型数にかかわらずFeatures/{Area}/Domain/に置く。
  • [Required] request DTOではなくdomain conceptとして名前を持つ値は、RequestValues.csのようなHTTP境界寄りの寄せ集めfileにまとめず、Domain/配下へ1型1fileで置く。
  • [Required] Handler、Command、application result、request / response DTO、validator、repository port、{Area}ErrorsDomain/に置かない。areaまたはuse caseの配置はapplication、http、persistenceの各referenceに従う。
  • [Recommended] domain型は種類別subdirectoryへ分けずDomain/直下に置く。現在確認できる規模や異なる変更理由がない段階でValueObjects/などの階層を追加しない。
text
Features/
  Customers/
    Domain/
      Customer.cs
      EmailAddress.cs
    CustomerErrors.cs
    Signup/
      CustomerSignupHandler.cs
  • [Required] Entity / Aggregate は class にする。
  • [Required] Value Object は record または readonly record struct にする。
  • [Required] Domain/配下に置いた型のnamespaceはdirectoryに合わせてFeatures.{Area}.Domainにする。参照側は明示的にusingする。
  • [Recommended] Domain/配下では原則1型1fileにする。例: VoteSeconds.csPlayerName.csRoomCode.cs
  • [Contextual] Value Object化の条件を満たす値で、同じprimitiveでもdomain上の意味が違う値は別型にする。例: DiscussionMinutesVoteSecondsDurationへ早すぎる共通化でまとめない。
  • [Required] VO の file には Min / Max などの境界定数、private constructor、ValueCreate を同居させ、validator に同じ数値制約を直書きしない。
  • [Required] Entity に record を使わない。record の値等価性は ID ベースの同一性と相性が悪い。
  • [Required] domainの状態や分類を表すenumは、所有するareaのDomain/配下に置く。
  • [Required] 複数 area で共有する型は、同じ意味・同じ制約で共有できると分かった時点で shared concept として切り出す。先に共通化しない。
  • [Required] public set は避け、状態変更は意図が分かる method 経由にする。
  • [Contextual] DB保存のためだけの都合はInfrastructure側のmapping / adapterで吸収する。provider属性付きmodelをFeatureに置く場合は、純粋なdomain modelではなく永続化境界を兼ねるmodelとして扱う。

Value Object は ROI の高いものから段階的に作る。

  • [Required] 固有format制約がある値はValue Object化する。例: BillingYearMonthInvoiceNumber*DisplayIdEmailAddress

  • [Contextual] 実コードまたは要件から、不変条件、同型値との取り違えリスク、生成失敗の扱いのいずれかを確認できる値はValue Object化する。

  • [Required] primitiveであることだけを理由にValue Object化しない。制約や取り違えリスクを確認できない場合は規約違反ではなく確認事項として扱う。

  • [Required] 特定areaのValue Objectはまず当該Features/{Area}/Domain/に置く。複数areaに広がった時点でshared conceptへの移動を検討する。

  • [Required] 単一primitiveを包むValue Objectは保存境界でprimitiveへ変換し、永続化schemaを保つ。repository / Infrastructure DTOのmappingで.Valueへ展開し、HTTPやdomain modelにprovider SDK型を漏らさない。

  • [Required] Domain event は副作用を含まない事実として record で表現する。発火は entity / aggregate 内、購読と dispatch は use case / application 側の責務にする。履歴永続化 entity は domain event とは別概念として扱う。

Features

  • [Required] Features/{Area}/{UseCase}/にはHTTP境界とユースケースを置く。
  • [Required] AreaはHTTP routeの形よりも、業務上の主語、変更理由、モデルの所有境界で決める。
  • [Required] Use case は作成、参加、退出、開始、設定更新、投票のような操作単位にする。

Area と use case の切り分けは次の基準で決める。

  • [Required] top-level Features/{Area} directoryとnamespace segmentは複数形の名詞にする。例: RoomsCustomers。業務型とpublic型のprefixは単数形のconcept名を使ってよい。例: Features/Customers/Signup/CustomerSignupHandler.cs
  • [Required] Area は route group と必ず一致させなくてよい。HTTP path が親 resource 配下でも、業務上の主語と変更理由が独立しているなら top-level area に分けてよい。
  • [Required] 動詞を含む操作名は top-level area にせず、既存 area の use case にする。例: Features/Rooms/JoinFeatures/Rooms/LeaveFeatures/Rooms/StartRound
  • [Recommended] 親 resource の ID を受け取る use case でも、対象 concept が明確な lifecycle、状態遷移、業務ルールを持つなら top-level area にしてよい。例: /rooms/{roomId}/rounds:start は route 上は rooms 配下でも、round 開始と進行を扱うなら Features/Rounds/Start を検討する。
  • [Recommended] Area 配下にさらに subarea / nested resource directory を切ることは原則避ける。まず top-level area と use case の組み合わせで表現する。
  • [Contextual] Subarea を切ってよいのは、top-level area にするほど独立した変更理由がなく、かつ use case を平坦に置くと同じ所有概念のファイルが大量に混ざって探索性が落ちる場合に限る。
  • [Required] VotingReverseAnswerSettings のような操作カテゴリだけを subarea にしない。業務概念として独立しているなら top-level area、独立していないなら use case 名に含める。
  • [Required] 複数use caseで共有するdomain modelとValue ObjectはFeatures/{Area}/Domain/、業務エラーとarea共通portはFeatures/{Area}/直下に置く。
  • [Required] use case 固有の handler、request / response DTO、validator、repository port、persistence result は Features/{Area}/{UseCase}/ に置く。
  • [Contextual] area が肥大化した場合は、HTTP route ではなく変更理由を見直す。別の業務概念の lifecycle や rule が混ざっているなら top-level area を追加する。
text
Features/
  Rooms/
    RoomEndpoints.cs
    Domain/
      Room.cs
    RoomErrors.cs
    Join/
      RoomJoinHandler.cs
      IRoomJoinTransaction.cs
      IRoomJoinTransactionScope.cs
      RoomJoinRequestValidator.cs
      RoomJoinResponse.cs
    Leave/
      RoomLeaveHandler.cs
      IRoomLeaveTransaction.cs
      IRoomLeaveTransactionScope.cs
      RoomLeaveResponse.cs
  Rounds/
    Domain/
      Round.cs
    RoundErrors.cs
    Start/
      RoundStartHandler.cs
      IRoundStartTransaction.cs
      IRoundStartTransactionScope.cs
      RoundStartResponse.cs
    CastNormalVote/
      RoundCastNormalVoteHandler.cs
      RoundCastNormalVoteResponse.cs
  • [Required] {Area}{UseCase}Handler.cs はユースケースの実行単位にする。

  • [Required] {Area}{UseCase}RequestValidator.cs は HTTP request DTO の validation にする。

  • [Required] I{Area}{UseCase}Repository.cs は use case が必要とする永続化 port にする。

  • [Required] {Area}{UseCase}PersistenceResult.cs は永続化結果を use case に返す enum / record にする。

  • [Required] Feature 配下の public 型名は原則 {Area}{UseCase}{Role} にする。DI、logger category、Swagger schema、生成 client、検索性のため、RequestResponseSignupRequest のような曖昧な型名は禁止する。

  • [Contextual] Healthzのようにarea自体が単一目的で複数use caseを持たない場合はEndpoints.cs / Endpointsを許可する。areaとuse caseが同義で重複すると不自然な場合も重複させない。

  • [Required] namespace は directory に合わせた file-scoped namespace にする。

csharp
namespace Backend.Features.Customers.Signup;
  • [Required] 別 namespace の同名 type と衝突する場合は using alias を使う。

Infrastructure

Infrastructure/ には技術的詳細を置く。

text
Infrastructure/
  Persistence/
    {Area}/
      {Provider}{Area}Repository.cs
      {Provider}{Model}PersistenceModel.cs
      {Provider}{UseCase}Transaction.cs
  • [Required] database client、外部SDK、repository実装、永続化DTOはInfrastructure/に置く。
  • [Contextual] Persistence/ が複数の業務 area を扱い、平坦な配置で探索性が落ちた場合は、所有する Persistence/{Area}/ に分ける。少数の型しかない段階では無理に directory を増やさない。
  • [Recommended] top-level Features/Infrastructure/ の分離を初期値にする。全面的な module 化は、module ごとの独立した変更理由、依存境界、十分な規模が揃い、局所的な area 分割では解決できない場合だけ検討する。
  • [Required] Application と Infrastructure を同じ feature directory に混在させない。module 化する場合も、module 内で内側の application/domain code と外側の infrastructure adapter の境界を明示する。
  • [Required] Infrastructure の namespace は directory に合わせる。
  • [Recommended] top-level type は backend 全体で原則 1 file に 1 つとし、file 名を type 名に合わせる。実装詳細である private nested type は、所有する type と同じ file に置いてよい。transaction の private Scope はこの例外に含む。
  • [Required] Features/ で定義した interface を実装する。
  • [Required] Infrastructure/ の型を Features/ に漏らさない。
  • [Required] repository interfaceにdatabase client、persistence model、queryなどprovider SDK型を出さない。
  • [Required] query / document path / persistence mapping は repository 実装内で閉じる。