Skip to content
cccskills
FreePublished on GitHub

aggregate-transaction-boundary

集約とトランザクション境界の関係を明確化し、複数集約を単一トランザクションに含めるアンチパターンを 検出・是正する。集約は強い整合性境界であり、ユースケースで複数集約を更新する場合は結果整合性を 使うべきという原則を適用する。コードレビュー、ユースケース設計、リファクタリング時に トランザクション境界の問題を検出する場合に使用。

対象言語: 言語非依存(Java, Kotlin, Scala, TypeScript, Go, Rust, Python等すべて)。

トリガー:「複数集約を同じトランザクションで更新している」「ユースケースに@Transactionalがある」 「集約間の整合性をどう取るか」「Sagaパターンを使うべきか」「トランザクション境界の設計」 「1トランザクション1集約」「結果整合性の実装」「集約をまたぐトランザクション」 といったトランザクション境界関連リクエストで起動。

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md10.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

集約とトランザクション境界

集約は強い整合性境界である。ユースケースで複数集約を更新する場合は結果整合性を使う。

核心原則

1トランザクション = 1集約。これを逸脱してはならない。

集約の定義そのものが「強い整合性の境界」である。複数集約を単一トランザクションに含めることは、集約の定義からの逸脱であり、モジュラリティとスケーラビリティを破壊する。

権威ある定義

エリック・エヴァンス(DDD原典)

複数の集約にまたがるルールはどれも、常に最新の状態にあるということが期待できない。イベント処理やバッチ処理、その他の更新の仕組みを通じて、他の依存関係は一定の時間内に解消できる。

ヴァーン・ヴァーノン(実践ドメイン駆動設計)

ひとつの集約上でコマンドを実行するときに、他の集約のコマンドも実行するようなビジネスルールが求められるのなら、その場合は結果整合性を使うこと。

Lightbend Academy

トランザクションは複数の集約ルートを費やすべきではありません。

トランザクションは複数のエンティティにまたがりますか? この質問の答えがイエスならば、間違った集約ルートを持っていると言えるでしょう。

アンチパターン:ユースケースをトランザクション境界にする

問題のあるコード

class CreateTaskUseCase(
    private val taskRepository: TaskRepository,
    private val taskReportRepository: TaskReportRepository,
) {
    @Transactional  // ← 複数集約を1トランザクションに閉じ込めている
    fun execute(taskName: String) {
        val task = Task(taskName)
        taskRepository.insert(task)
        val taskReport = TaskReport(task)
        taskReportRepository.insert(taskReport)
    }
}

なぜ問題か:

  1. 集約の定義違反: 集約は強い整合性境界。複数集約を1トランザクションにすると、実質的に1つの巨大な整合性境界を作っている
  2. スケーラビリティの阻害: 集約Aと集約Bの更新が常にセットになり、後のスケーリングやサーバ分散が困難になる
  3. 異種DB環境で不可能: 集約ごとに異なるデータストアを使う場合、同一トランザクションは実装不可能
  4. マイクロサービスへの発展を阻害: 集約を別サービスに分離できなくなる

修正後のコード

class CreateTaskUseCase(
    private val taskRepository: TaskRepository,
    private val taskReportRepository: TaskReportRepository,
) {
    fun execute(taskName: String) {
        // 集約ごとに独立したトランザクション
        val task = Task(taskName)
        taskRepository.insert(task)

        val taskReport = TaskReport(task)
        taskReportRepository.insert(taskReport)
    }
}

そもそもモデリングの問題ではないか

複数集約を同一トランザクションで更新したくなる場合、まずモデリングを見直すべきである。

判断フロー

2つの集約を常に一緒に更新する必要がある
    ↓
Task : TaskReport の関係は?
    ├─ 1:1 → 同一集約に統合を検討
    │         例: Task(taskReport: TaskReport)
    │         → 1トランザクションで問題なし
    │
    ├─ 1:N(少量) → 要件調整で小規模化できないか検討
    │         → 可能なら同一集約に統合
    │
    ├─ 1:N(大量) → 別集約 + 結果整合性
    │         → ドメインイベントで連携
    │
    └─ TaskReportは純粋なクエリ要件か?
          ├─ YES → CQRS: リードモデルとして構築
          │         → 集約ではなくプロジェクションで対応
          └─ NO → ドメイン知識を持つ → 独立集約 + 結果整合性

同一集約への統合(1:1の場合)

// TaskReportが常にTaskと1:1なら同一集約に統合
class Task private constructor(
    val id: TaskId,
    val name: TaskName,
    val report: TaskReport  // 集約内に含める
) {
    companion object {
        fun create(name: TaskName): Task {
            val id = TaskId.generate()
            return Task(id, name, TaskReport.create(id))
        }
    }
}

結果整合性の実現方法

集約が独立している場合、結果整合性で連携する。

ドメインイベント + 非同期処理

// 1. Task集約がドメインイベントを発行
class Task private constructor(
    val id: TaskId,
    val name: TaskName,
    private val events: List<DomainEvent>
) {
    companion object {
        fun create(name: TaskName): Task {
            val id = TaskId.generate()
            return Task(
                id, name,
                listOf(TaskCreated(id, name))  // イベント発行
            )
        }
    }

    fun domainEvents(): List<DomainEvent> = events.toList()
}

// 2. ユースケースでTask保存後、イベントを発行
class CreateTaskUseCase(
    private val taskRepository: TaskRepository,
    private val eventPublisher: DomainEventPublisher,
) {
    fun execute(taskName: String) {
        val task = Task.create(TaskName(taskName))
        taskRepository.store(task)
        eventPublisher.publishAll(task.domainEvents())
    }
}

// 3. イベントハンドラでTaskReport作成(別トランザクション)
class TaskCreatedEventHandler(
    private val taskReportRepository: TaskReportRepository,
) {
    fun handle(event: TaskCreated) {
        val taskReport = TaskReport.create(event.taskId)
        taskReportRepository.store(taskReport)
    }
}

ダブルコミット問題とSaga

独立トランザクションでは、集約Bの更新失敗時に集約Aはコミット済みという状態が発生する。

対処方法:

方法適用場面
リトライ一時的な障害の場合
Saga(補償トランザクション)複雑な複数集約の協調が必要な場合
Outboxパターンイベント発行の確実性が必要な場合

Sagaは並行性問題を防止・軽減する設計テクニックであり、マイクロサービス環境での分散トランザクション管理に必須となる。

なぜこの規律が必要か

モジュラリティの確保

同一トランザクションに複数集約を含めると、暗黙的な結合が生まれる。

同一トランザクション:
  Task ←──強結合──→ TaskReport
  (分離不可能、独立スケーリング不可能)

結果整合性:
  Task ──イベント──→ TaskReport
  (独立デプロイ可能、独立スケーリング可能)

スケーラビリティへの道

モノリス(結果整合性を採用)
  → 集約ごとに独立したDB/テーブルへの分離が容易
  → マイクロサービス化が容易
  → 集約ごとの独立スケーリングが可能

モノリス(同一トランザクション)
  → 集約間の暗黙的結合で分離困難
  → マイクロサービス化に大規模リファクタリング必要
  → スケーリングはシステム全体でしかできない

aggregate-design スキルとの関係

観点本スキルaggregate-design スキル
焦点トランザクション境界と結果整合性集約の内部設計と構造
対象集約間の連携パターン集約単体の設計原則
用途ユースケース設計・レビュー集約のモデリング・レビュー

aggregate-design のルール8「1トランザクション = 1集約」とVernon Rule 4「結果整合性」を深掘りしたものが本スキルである。

レビューチェックリスト

トランザクション境界

  • @Transactional(またはトランザクション制御)の範囲内で、複数のリポジトリを呼び出していないか
  • 1つのユースケースで複数集約を更新する場合、結果整合性を採用しているか
  • 「便利だから」という理由で複数集約を同一トランザクションに含めていないか

モデリング

  • 常に一緒に更新される2つの「集約」は、本来1つの集約ではないか
  • 片方がクエリ要件のみなら、CQRSのリードモデルで対応できないか
  • 1:Nの関係でNが大量になる場合、別集約 + 結果整合性に分離しているか

結果整合性の実装

  • ドメインイベントによる集約間連携が実装されているか
  • イベント発行の確実性が担保されているか(Outboxパターン等)
  • 失敗時のリカバリ戦略(リトライ、Saga)が定義されているか
  • ダブルコミット問題を認識し、対処方法が明確か

関連スキル(併読推奨)

このスキルを使用する際は、以下のスキルも併せて参照すること:

  • aggregate-design: 集約の設計ルール(トランザクション境界の根拠)
  • cross-aggregate-constraints: 集約間の制約と結果整合性の設計
  • cqrs-aggregate-modeling: CQRS/ESによる集約軽量化とトランザクション問題の解消

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

DDDの集約(Aggregate)設計ルールに基づくコードレビュー・設計支援・リファクタリングを行う。 Evans Rules、Vernon's 4 Rules、Design by Contractに基づき、集約の境界定義、不変条件の検証、 不変(Immutable)設計、ID参照、結果整合性、ドメインイベント連携を包括的にガイドする。 以下のいずれかに該当する場合は必ずこのスキルを使用すること: - 集約(Aggregate)の新規設計・実装・リファクタリング(どの言語でも) - 既存の集約やエンティティクラスのDDD観点でのコードレビュー - 集約の境界決定(「AとBは同じ集約にすべきか?」「この集約は大きすぎるか?」) - 集約内の不変条件・整合性境界の設計 - 集約間の連携方式の判断(ドメインイベント、結果整合性、Sagaパターン) - 可変(Mutable)な集約コードを不変(Immutable)設計にリファクタリングする - publicフィールド、直接参照、push/appendなどカプセル化違反の検出・修正 キーワード例:集約、Aggregate、aggregate boundary、集約ルート、AggregateRoot、 エンティティ設計、DDD実装、Vernon Rules、Evans Rules、集約の分割、真の不変条件

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

後方互換性がゴミコードを量産する構造を検出し、互換性を「契約と撤去計画」として管理する ガバナンスを支援するスキル。公開API境界の明確化、非推奨化サイクル(deprecation cycle)の 制度化、互換層の局所化(Adapter/Strangler Fig)、契約テスト(CDC)による互換性検証、 AI生成コードの互換性ゲート設計を含む。コードレビュー、API設計、リファクタリング、 レガシー移行時に互換性起因の技術的負債を防ぐために使用。 対象言語: 言語非依存(Java, TypeScript, Go, Python, Rust等すべて)。 トリガー:「後方互換性を保ちたい」「非推奨APIをどうする」「互換層が増えてきた」 「レガシー移行の戦略」「API設計レビュー」「互換性のためのコードが多い」 「deprecation policyを作りたい」「破壊的変更の管理」といった互換性管理関連リクエストで起動。

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

getterの濫用を防ぐための命名規約スキル。ドメインモデルでgetterが必要な場合(永続化、JSON変換など)に `breachEncapsulationOf` プレフィックスを付与することで、カプセル化を破っていることを明示する。 これにより、Tell Don't Ask原則の違反を未然に防ぎ、getterの意図しない使用を抑制する。 コードレビュー、新規実装、リファクタリング時にgetter設計が必要な場合に使用。 対象言語: Java, Kotlin, Scala, TypeScript, Python, Go, Rust。 トリガー:「getterの命名規約」「カプセル化を破るgetter」「永続化用のgetter」 「breachEncapsulation」「getterを作りたいが濫用を防ぎたい」といったgetter命名関連リクエストで起動。

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

クリーンアーキテクチャを採用しているプロジェクト向けの設計・レビュー支援。4層構造(ドメイン層、 ユースケース層、インターフェースアダプタ層、インフラストラクチャ層)に基づく。特にインフラ層は 横断的関心事(ロギング、設定管理)のみ、永続化やRPCはインターフェースアダプタ層に配置すべき という原則を適用する。トリガー:「クリーンアーキテクチャで」「クリーンアーキテクチャに従って」 「クリーンアーキテクチャのレビュー」など、クリーンアーキテクチャを明示的に指定した場合のみ起動。 一般的な「設計レビュー」「アーキテクチャ相談」では起動しない。

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

CQRS/ESが集約の境界定義とモデリングに与える影響を解説する。CQRSを導入すると集約は コマンド実行に必要な最小限の状態のみ保持すればよくなり、読み取り責務はリードモデルに 委譲できる。大きすぎる集約の軽量化、集約境界の再定義、イベントによる状態管理を支援する。 集約設計、CQRS導入時のモデリング見直し、パフォーマンス問題の解決時に使用。 対象言語: 言語非依存。 トリガー:「CQRSで集約が変わる」「集約が大きすぎる」「集約にメッセージ1000件」 「集約の更新が重い」「CQRS導入で集約を見直す」「集約を軽量化したい」 「集約にクエリ用データが混ざっている」「集約の境界を再定義」 といったCQRS/モデリング関連リクエストで起動。

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

CQRSの実装においてイベントソーシングが必然的に必要となる理由を論理的に説明する。 C側からQ側へのデータ同期問題(計算された値の同期不可、トリガーの限界、ポーリングの スケーラビリティ問題、ダブルコミット問題)を段階的に分析し、イベントを真のデータソースに する設計への到達過程を示す。CQRS導入検討、アーキテクチャ設計時に使用。 対象言語: 言語非依存。 トリガー:「CQRSにイベントソーシングは必要か」「C側とQ側の同期方法」 「CQRSでモデルを分ける必要はないのか」「リードモデルの更新方法」 「なぜイベントソーシングが必要か」「ダブルコミット問題」「CQRSの同期問題」 「CQRSはESなしでも動くか」といったCQRS/ES必然性関連リクエストで起動。

The English summary is being prepared. Showing the original description.

j5ik2o/okite-ai80Updated Apr 25, 2026

See all skills by j5ik2o

Report a problem with this skill