Agent Skill
2/7/2026

typescript-ddd-standards

ドメイン駆動設計(以下DDD)におけるTypeScriptの開発規約。バックエンドでのTypescriptコード作成時に使用する。各レイヤーでの実装パターンを定義。

P
posiposi
0GitHub Stars
1Views
npx skills add posiposi/dragons-counter

SKILL.md

Nametypescript-ddd-standards
Descriptionドメイン駆動設計(以下DDD)におけるTypeScriptの開発規約。バックエンドでのTypescriptコード作成時に使用する。各レイヤーでの実装パターンを定義。

name: typescript-ddd-standards description: ドメイン駆動設計(以下DDD)におけるTypeScriptの開発規約。バックエンドでのTypescriptコード作成時に使用する。各レイヤーでの実装パターンを定義。DDD実装・コードレビュー・リファクタリング時に使用する。 allowed-tools: Read, Grep, Glob

TypeScriptドメイン駆動設計開発規約

ドメイン層

ドメイン層はビジネスロジックの中核を担う層であり、他の層に依存してはならない。

共通原則

インスタンス生成規則

  • クラスのインスタンスはnewキーワードで直接生成せず、必ずファクトリメソッドを使用する
  • コンストラクタはprivateとして外部からの直接呼び出しを禁止する
  • ファクトリメソッドはcreateまたは意図を表す名前(fromPrimitivesreconstruct等)を使用する
  • ファクトリメソッド内でバリデーションを行い、不正な状態のオブジェクト生成を防ぐ
  • バリデーション失敗時はResultパターンまたはドメイン例外をスローする

不変性の原則

  • ドメインオブジェクトは**不変(イミュータブル)**として設計する
  • プロパティはreadonly修飾子を付与する
  • プロパティはprivateを付与して外部からの直接参照を防止する
  • 状態変更が必要な場合は、新しいインスタンスを返すようにする
  • プロパティへのアクセスはgetterを使用するように実装する

値オブジェクト (Value Object)

定義と特徴

  • 識別子を持たず、属性の値によって同一性を判断するオブジェクト
  • 完全に不変であること
  • 自己検証を行い、常に有効な状態を保証する

実装規則

  • コンストラクタはprivateとし、静的ファクトリメソッドでインスタンスを生成する
  • equalsメソッドを実装し、値による等価性比較を可能にする
  • プリミティブ型への変換メソッド(toValuetoString等)を提供する
  • ドメインロジックに関連する振る舞いをメソッドとして実装する

命名規則

  • クラス名はドメインの概念を表す名詞を使用する(例: EmailMoneyDateRange
  • ファイル名はケバブケースで<概念名>.tsとする

エンティティ (Entity)

定義と特徴

  • 一意の識別子(ID)によって同一性を判断するオブジェクト
  • ライフサイクルを通じて識別子は変更されない
  • 状態変更が許容されるが、常に有効な状態を維持する

実装規則

  • コンストラクタはprivateとし、静的ファクトリメソッドでインスタンスを生成する
  • 識別子用の値オブジェクトを定義する(例: UserIdOrderId
  • equalsメソッドを実装し、識別子による等価性比較を行う
  • 状態変更は専用のメソッドを通じてのみ行い、直接のプロパティ変更は禁止する
  • 状態変更メソッドは事前条件を検証し、ビジネスルールに違反する操作を拒否する

命名規則

  • クラス名はドメインの主要な概念を表す名詞を使用する(例: UserOrderGame
  • ファイル名はケバブケースで<概念名>.tsとする

集約 (Aggregate)

定義と特徴

  • 関連するエンティティと値オブジェクトをまとめた整合性の境界
  • 集約ルート(Aggregate Root)を通じてのみアクセスする
  • トランザクション整合性の単位となる

実装規則

  • 集約ルートのエンティティのみが外部に公開される
  • 集約内部のオブジェクトへの直接参照を返さない(必要に応じてコピーを返す)
  • 集約間の参照は識別子(ID)のみで行い、オブジェクト参照は持たない
  • 集約のサイズは小さく保ち、トランザクションの競合を最小化する

ドメインサービス (Domain Service)

定義と特徴

  • 特定のエンティティや値オブジェクトに属さないドメインロジックを実装する
  • ステートレスであること
  • 複数の集約にまたがる操作や、外部リソースとの連携を抽象化する
  • 過剰なドメインサービスの実装はドメインモデル貧血症に繋がるため注意を要する

実装規則

  • インターフェースを定義し、実装はインフラ層に配置する(依存性逆転)
  • メソッド名はドメインの操作を表す動詞を使用する
  • 引数と戻り値はドメインオブジェクトを使用する

命名規則

  • インターフェース名は<概念名>Serviceとする(例: PasswordHashService
  • ファイル名はケバブケースで<概念名>-service.tsとする

Port(CQRS)

本プロジェクトではCQRS(Command Query Responsibility Segregation)パターンを採用し、書き込み操作と読み取り操作の責務を分離する。

CQRS の原則

  • Command(コマンド): 状態を変更する操作。集約を通じてビジネスルールを適用する
  • Query(クエリ): 状態を読み取る操作。集約を経由せず、最適化された読み取りモデルを使用する
  • コマンドとクエリは明確に分離し、同一のインターフェースに混在させない

Command Port

定義と特徴

  • 集約の永続化・更新・削除を担当するインターフェース
  • ドメイン層にインターフェースを定義し、実装はインフラ層に配置する(依存性逆転)
  • 集約単位でトランザクション整合性を保証する

実装規則

  • 集約ルートごとに1つのCommand Portを定義する
  • 基本操作としてsavedeleteを定義する
  • saveメソッドは新規作成と更新の両方を担当する(Upsertパターン)
  • 識別子による取得(findById)はCommand Portに含める(更新時の整合性確認用)
  • 戻り値の型はPromiseでラップし、非同期操作に対応する
  • 集約全体を引数として受け取り、集約全体を永続化する

命名規則

  • インターフェース名は<集約ルート名>CommandPortとする(例: UserCommandPort
  • ファイル名はケバブケースで<集約ルート名>-command-port.tsとする

Query Port

定義と特徴

  • データの読み取り専用操作を担当するインターフェース
  • ドメイン層にインターフェースを定義し、実装はインフラ層に配置する(依存性逆転)
  • パフォーマンス最適化のため、集約を経由せずに直接データを取得できる

実装規則

  • ユースケースの読み取り要件に応じてQuery Portを定義する
  • 戻り値はDTO(Data Transfer Object)または読み取り専用モデルとする
  • 集約やエンティティをそのまま返さない(読み取りモデルへの変換を行う)
  • 複雑な検索条件に対応するメソッドを定義できる
  • ページネーション、ソート、フィルタリングのパラメータを受け取れる
  • 戻り値の型はPromiseでラップし、非同期操作に対応する

命名規則

  • インターフェース名は<集約ルート名>QueryPortとする(例: UserQueryPort
  • ファイル名はケバブケースで<集約ルート名>-query-port.tsとする

ドメイン例外 (Domain Exception)

定義と特徴

  • ドメインルール違反を表現するカスタム例外
  • ビジネスルールに基づいた明確なエラーメッセージを提供する

実装規則

  • 基底クラスDomainExceptionを継承する
  • エラーコードとメッセージを持つ
  • 例外の種類ごとにクラスを定義する

命名規則

  • クラス名は<エラー内容>Exceptionとする(例: InvalidEmailExceptionInsufficientBalanceException
  • ファイル名はケバブケースで<例外名>.tsとする

DDD層別の実装順序

新機能の実装やリファクタリングでは、以下の順序で実装を進める。

  1. ドメイン層: 値オブジェクト → エンティティ → 集約 → Port → ドメインサービス → ドメイン例外
  2. Adapter層: Command Adapter → Query Adapter → サービスAdapter
  3. UseCase層: Command UseCase → Query UseCase
  4. Controller層: リクエストDTO → Controller → モジュール定義

ドメイン層ディレクトリ構成

src/domain/
├── entities/           # エンティティ
├── value-objects/      # 値オブジェクト
├── aggregates/         # 集約(集約ルートを含む)
├── services/           # ドメインサービスインターフェース
├── ports/
│   ├── command/        # Command Port(書き込み用)
│   └── query/          # Query Port(読み取り用)
└── exceptions/         # ドメイン例外

Adapter層(インフラストラクチャ層)

Adapter層はドメイン層で定義されたPortインターフェースの具体的な実装を提供する。データベースアクセスや外部サービス連携などの技術的関心事を担当する。

共通原則

  • ドメイン層のPortインターフェースを実装する
  • NestJSの@Injectable()デコレータを付与し、DIコンテナに登録する
  • 技術的な詳細(ORM、外部API等)をこの層に閉じ込める
  • ドメインオブジェクトと永続化モデル間のマッピングはMapperクラスに集約する

Command Adapter

定義と特徴

  • Command Portの実装クラス
  • 集約の永続化・更新・削除を担当する
  • ドメインオブジェクトと永続化モデル間の双方向マッピングを行う

実装規則

  • 1つのCommand Portに対して1つのCommand Adapterを実装する
  • ドメインオブジェクトと永続化モデル間の変換はMapperクラスに委譲する
  • saveの実装ではUpsert(存在すれば更新、なければ作成)を使用する
  • トランザクションが必要な場合はAdapter内で制御する

命名規則

  • クラス名は<集約ルート名>CommandAdapterとする(例: UserCommandAdapter
  • ファイル名はケバブケースで<集約ルート名>-command-adapter.tsとする

Query Adapter

定義と特徴

  • Query Portの実装クラス
  • 読み取り専用のデータ取得を担当する
  • 永続化モデルからDTOへの変換を行う

実装規則

  • 1つのQuery Portに対して1つのQuery Adapterを実装する
  • 永続化モデルからの変換はMapperクラスに委譲する
  • パフォーマンスを考慮し、必要なカラムのみを取得するクエリを記述する
  • ページネーション等の共通処理はAdapter内で実装する

命名規則

  • クラス名は<集約ルート名>QueryAdapterとする(例: UserQueryAdapter
  • ファイル名はケバブケースで<集約ルート名>-query-adapter.tsとする

Mapper

定義と特徴

  • ドメインオブジェクトと永続化モデル(ORMエンティティ)間の変換ロジックを集約するクラス
  • Command AdapterとQuery Adapterの両方から利用され、マッピングロジックの重複を排除する
  • ステートレスな静的メソッドで構成する

実装規則

  • 集約ルートごとに1つのMapperクラスを定義する
  • すべてのメソッドはstaticとする(状態を持たない)
  • toDomainEntity: 永続化モデルからドメインオブジェクトへの変換
  • toPersistence: ドメインオブジェクトから永続化モデルへの変換
  • enum変換メソッド: ドメインenumと永続化enumの相互変換
  • 変換に失敗する場合(未知のenum値等)は明示的にエラーをスローする

命名規則

  • クラス名は<集約ルート名>Mapperとする(例: UserMapperGameMapper
  • ファイル名はケバブケースで<集約ルート名>.mapper.tsとする
  • 配置先: src/infrastructure/adapters/mappers/

ドメインサービス Adapter

定義と特徴

  • ドメイン層で定義されたドメインサービスインターフェースの実装クラス
  • 外部ライブラリや技術的な詳細を隠蔽する

実装規則

  • ドメインサービスインターフェースを実装する
  • 外部ライブラリへの依存はAdapter内に閉じ込める

命名規則

  • クラス名は<概念名>ServiceAdapterとする(例: PasswordHashServiceAdapter
  • ファイル名はケバブケースで<概念名>-service-adapter.tsとする

NestJS モジュール連携

DIトークンの定義

  • Portインターフェースに対応するDIトークンを定義する
  • トークン名はPortインターフェース名と同一の文字列を使用する

プロバイダー登録

  • Adapterクラスをprovidersに登録し、対応するPortのトークンで提供する
  • useClassを使用してインターフェースと実装を紐づける

Adapter層ディレクトリ構成

src/infrastructure/
├── adapters/
│   ├── command/            # Command Adapter
│   ├── query/              # Query Adapter
│   ├── mappers/            # Mapper(ドメイン⇔永続化モデル変換)
│   └── services/           # ドメインサービスAdapter
└── typeorm/                # TypeORMエンティティ・設定

UseCase層

UseCase層はアプリケーションのユースケース(ビジネスシナリオ)を実現するオーケストレーション層である。ドメインオブジェクトとPortを組み合わせてビジネスロジックの実行を制御する。

共通原則

単一責務

  • 1つのUseCaseクラスは1つのユースケースのみを担当する
  • 公開メソッドはexecuteのみとする
  • UseCaseの名前でそのクラスが何をするかが明確にわかるようにする

CQRSとの対応

  • UseCaseはCommand UseCaseとQuery UseCaseに分離する
  • Command UseCase: 状態変更を伴う操作。Command Portを使用する
  • Query UseCase: 読み取り専用操作。Query Portを使用する
  • 1つのUseCaseにCommandとQueryの責務を混在させない

依存関係

  • PortおよびドメインサービスはNestJSの@Inject()でDIトークンを指定して注入する
  • UseCase間の直接的な依存・呼び出しは禁止する
  • フレームワーク固有の例外(NotFoundException等)はUseCase層で使用してよい

Command UseCase

定義と特徴

  • 状態を変更するユースケースを実装する(作成・更新・削除)
  • Command Portを通じて集約を取得・永続化する
  • ドメインオブジェクトのファクトリメソッドや振る舞いメソッドを呼び出す

実装規則

  • executeメソッドの引数にはプリミティブ型またはDTOを使用する
  • executeメソッド内でプリミティブ型からドメインオブジェクト(値オブジェクト等)への変換を行う
  • ドメインオブジェクトの生成にはファクトリメソッドを使用する
  • 戻り値はPromise<void>または操作結果を表すオブジェクトとする
  • 事前条件の検証(存在確認等)はUseCase内で行う

命名規則

  • クラス名は<動詞><対象>Usecaseとする(例: RegisterUserUsecaseDeleteGameUsecase
  • ファイル名はケバブケースで<動詞>-<対象>.usecase.tsとする

Query UseCase

定義と特徴

  • 読み取り専用のユースケースを実装する
  • Query Portを通じてDTOを取得し、そのまま返却する

実装規則

  • executeメソッドの引数には検索条件のプリミティブ型またはDTOを使用する
  • 戻り値はQuery Portから取得したDTOをそのまま返す
  • ドメインオブジェクトへの変換は行わない(Query Portが直接DTOを返すため)
  • 追加のビジネスロジックが不要な場合でも、Controllerから直接Query Portを呼ばず、必ずUseCaseを経由する

命名規則

  • クラス名は<動詞><対象>Usecaseとする(例: GetUserUsecaseGetGamesUsecase
  • ファイル名はケバブケースで<動詞>-<対象>.usecase.tsとする

テスト方針

  • Portをモック化してUseCaseのロジックのみをテストする
  • Command UseCaseではPortの呼び出し引数を検証する
  • Query UseCaseではPortの戻り値がそのまま返却されることを検証する
  • テストファイル名は<usecase名>.usecase.spec.tsとする

UseCase層ディレクトリ構成

src/domain/usecases/
├── command/                # Command UseCase
└── query/                  # Query UseCase

Controller層(プレゼンテーション層)

Controller層はHTTPリクエストの受け付けとレスポンスの返却を担当する。ビジネスロジックは一切持たず、UseCaseへの処理委譲に徹する薄い層である。

共通原則

責務の限定

  • HTTPリクエストの受け付け、バリデーション、UseCaseの呼び出し、レスポンスの返却のみを担当する
  • ビジネスロジック、データ変換、永続化処理をController内に記述しない
  • 1つのControllerクラスは1つのエンドポイント(1つのUseCase)のみを担当する

UseCaseとの連携

  • UseCaseはコンストラクタインジェクションで注入する
  • Controllerのハンドラメソッドは受け取ったリクエストデータをUseCaseのexecuteメソッドに渡すのみとする
  • UseCaseからの戻り値をそのままレスポンスとして返却する

リクエストDTO

定義と特徴

  • HTTPリクエストボディのバリデーションを担当するクラス
  • class-validatorのデコレータでバリデーションルールを宣言する
  • class-transformer@Type()でネストされたオブジェクトの型変換を行う

実装規則

  • リクエストDTOはclassで定義する(class-validatorのデコレータ使用のため)
  • プロパティごとにバリデーションデコレータを付与する
  • ネストされたオブジェクトには@ValidateNested()@Type()を使用する
  • NestJSのValidationPipeによってバリデーションが自動実行される

命名規則

  • クラス名は<操作名>RequestDtoとする(例: RegisterUserRequestDto
  • ファイル名はケバブケースで<操作名>-request.dto.tsとする
  • 配置先: src/application/dto/request/

レスポンスDTO

定義と特徴

  • HTTPレスポンスの型を定義する
  • Query PortのDTOをそのまま使用できる場合は新たに定義しない

実装規則

  • レスポンスDTOはinterfaceで定義する
  • Query PortのDTOと構造が同じ場合は再定義せず、Query PortのDTOを直接使用する
  • API固有の整形(日付のフォーマット等)が必要な場合のみレスポンスDTOを別途定義する

命名規則

  • インターフェース名は<対象名>ResponseDtoとする(例: UserResponseDto
  • ファイル名はケバブケースで<対象名>-response.dto.tsとする
  • 配置先: src/application/dto/response/

Controller

実装規則

  • NestJSの@Controller()デコレータでルートパスを指定する
  • HTTPメソッドデコレータ(@Get()@Post()@Put()@Delete()等)でエンドポイントを定義する
  • パスパラメータは@Param()、リクエストボディは@Body()、クエリパラメータは@Query()で取得する
  • レスポンスのHTTPステータスコードは@HttpCode()で明示的に指定する
    • POST(作成): HttpStatus.CREATED(201)
    • DELETEPUT/PATCH(戻り値なし): HttpStatus.NO_CONTENT(204)
    • その他: NestJSのデフォルト(200)に従う
  • エラーハンドリングはNestJSの例外フィルターに委譲する(Controller内でのtry-catchは原則不要)

命名規則

  • クラス名は<動詞><対象>Controllerとする(例: RegisterUserControllerGetUsersController
  • ファイル名はケバブケースで<動詞>-<対象>.controller.tsとする

テスト方針

  • UseCaseをモック化してControllerの振る舞いのみをテストする
  • HTTPステータスコード、レスポンスボディの構造を検証する
  • テストファイル名は<controller名>.controller.spec.tsとする

Controller層ディレクトリ構成

src/application/
├── controllers/            # Controller
├── dto/
│   ├── request/            # リクエストDTO
│   └── response/           # レスポンスDTO
├── filters/                # カスタム例外フィルター
└── <対象>.module.ts         # NestJSモジュール定義

例外処理

例外フローの原則

各層でスローされた例外はController層やUseCase層でtry-catchせず、NestJSの例外フィルターが最終的にキャッチしてHTTPレスポンスに変換する。

ドメイン層 (DomainException)
  ↓ そのまま伝播
UseCase層 (NestJS HttpException / DomainException)
  ↓ そのまま伝播
Controller層 (try-catchしない)
  ↓ そのまま伝播
例外フィルター → HTTPレスポンスに変換

例外の種類と発生箇所

例外の種類発生箇所説明
DomainExceptionドメイン層ビジネスルール違反(バリデーション失敗、不正な状態遷移等)
NestJS HttpExceptionUseCase層アプリケーション固有のエラー(NotFoundException等)
ValidationPipe例外Controller層(自動)リクエストDTOのバリデーション失敗

カスタム例外フィルター

定義と特徴

  • DomainExceptionはNestJSのHttpExceptionを継承していないため、そのままではすべて500エラーになる
  • カスタム例外フィルターでDomainExceptionをキャッチし、適切なHTTPステータスコードに変換する
  • これによりドメイン層はフレームワークに依存せず純粋性を保てる

実装規則

  • NestJSのExceptionFilterインターフェースを実装する
  • @Catch(DomainException)デコレータでDomainExceptionのみをキャッチする
  • DomainExceptionのエラーコードに基づいてHTTPステータスコードをマッピングする
  • マッピングに該当しないエラーコードはデフォルトで400 Bad Requestとする
  • グローバルフィルターとしてアプリケーション全体に適用する

ステータスコードマッピング方針

  • NOT_FOUND系 → 404 Not Found
  • ALREADY_EXISTS系 / CONFLICT系 → 409 Conflict
  • UNAUTHORIZED系 → 401 Unauthorized
  • FORBIDDEN系 → 403 Forbidden
  • その他のドメインルール違反 → 400 Bad Request

命名規則

  • クラス名はDomainExceptionFilterとする
  • ファイル名はdomain-exception.filter.tsとする
  • 配置先: src/application/filters/
Skills Info
Original Name:typescript-ddd-standardsAuthor:posiposi