takt 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/agents/runner.d.ts +1 -1
  2. package/dist/agents/runner.d.ts.map +1 -1
  3. package/dist/agents/runner.js +13 -34
  4. package/dist/agents/runner.js.map +1 -1
  5. package/dist/cli.d.ts +2 -3
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +5 -8
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/help.d.ts.map +1 -1
  10. package/dist/commands/help.js +4 -8
  11. package/dist/commands/help.js.map +1 -1
  12. package/dist/commands/index.d.ts +1 -1
  13. package/dist/commands/index.d.ts.map +1 -1
  14. package/dist/commands/index.js.map +1 -1
  15. package/dist/commands/taskExecution.d.ts +1 -6
  16. package/dist/commands/taskExecution.d.ts.map +1 -1
  17. package/dist/commands/taskExecution.js +2 -6
  18. package/dist/commands/taskExecution.js.map +1 -1
  19. package/dist/commands/workflowExecution.d.ts +0 -2
  20. package/dist/commands/workflowExecution.d.ts.map +1 -1
  21. package/dist/commands/workflowExecution.js +9 -11
  22. package/dist/commands/workflowExecution.js.map +1 -1
  23. package/dist/mock/client.d.ts +27 -0
  24. package/dist/mock/client.d.ts.map +1 -0
  25. package/dist/mock/client.js +56 -0
  26. package/dist/mock/client.js.map +1 -0
  27. package/dist/models/schemas.d.ts +6 -0
  28. package/dist/models/schemas.d.ts.map +1 -1
  29. package/dist/models/schemas.js +9 -9
  30. package/dist/models/schemas.js.map +1 -1
  31. package/dist/models/types.d.ts +4 -4
  32. package/dist/models/types.d.ts.map +1 -1
  33. package/dist/providers/claude.d.ts +11 -0
  34. package/dist/providers/claude.d.ts.map +1 -0
  35. package/dist/providers/claude.js +37 -0
  36. package/dist/providers/claude.js.map +1 -0
  37. package/dist/providers/codex.d.ts +11 -0
  38. package/dist/providers/codex.d.ts.map +1 -0
  39. package/dist/providers/codex.js +29 -0
  40. package/dist/providers/codex.js.map +1 -0
  41. package/dist/providers/index.d.ts +39 -0
  42. package/dist/providers/index.d.ts.map +1 -0
  43. package/dist/providers/index.js +32 -0
  44. package/dist/providers/index.js.map +1 -0
  45. package/dist/providers/mock.d.ts +11 -0
  46. package/dist/providers/mock.d.ts.map +1 -0
  47. package/dist/providers/mock.js +24 -0
  48. package/dist/providers/mock.js.map +1 -0
  49. package/package.json +1 -1
  50. package/resources/global/en/agents/default/architect.md +67 -6
  51. package/resources/global/en/agents/default/coder.md +155 -1
  52. package/resources/global/en/workflows/expert-review.yaml +1 -1
  53. package/resources/global/en/workflows/simple.yaml +594 -0
  54. package/resources/global/ja/agents/default/architect.md +62 -1
  55. package/resources/global/ja/agents/default/coder.md +156 -2
  56. package/resources/global/ja/agents/expert-review/cqrs-es-reviewer.md +328 -8
  57. package/resources/global/ja/agents/expert-review/frontend-reviewer.md +303 -33
  58. package/resources/global/ja/workflows/expert-review.yaml +1 -1
  59. package/resources/global/ja/workflows/simple.yaml +594 -0
@@ -105,7 +105,45 @@
105
105
  | ボーイスカウト | 触った箇所は少し改善して去る |
106
106
  | Fail Fast | エラーは早期に検出。握りつぶさない |
107
107
 
108
- **迷ったら**: Simple を選ぶ。抽象化は後からでもできる。
108
+ **迷ったら**: Simple を選ぶ。
109
+
110
+ ## 抽象化の原則
111
+
112
+ **条件分岐を追加する前に考える:**
113
+ - 同じ条件が他にもあるか → あればパターンで抽象化
114
+ - 今後も分岐が増えそうか → Strategy/Mapパターンを使う
115
+ - 型で分岐しているか → ポリモーフィズムで置換
116
+
117
+ ```typescript
118
+ // ❌ 条件分岐を増やす
119
+ if (type === 'A') { ... }
120
+ else if (type === 'B') { ... }
121
+ else if (type === 'C') { ... } // また増えた
122
+
123
+ // ✅ Mapで抽象化
124
+ const handlers = { A: handleA, B: handleB, C: handleC };
125
+ handlers[type]?.();
126
+ ```
127
+
128
+ **抽象度を揃える:**
129
+ - 1つの関数内では同じ粒度の処理を並べる
130
+ - 詳細な処理は別関数に切り出す
131
+ - 「何をするか」と「どうやるか」を混ぜない
132
+
133
+ ```typescript
134
+ // ❌ 抽象度が混在
135
+ function processOrder(order) {
136
+ validateOrder(order); // 高レベル
137
+ const conn = pool.getConnection(); // 低レベル詳細
138
+ conn.query('INSERT...'); // 低レベル詳細
139
+ }
140
+
141
+ // ✅ 抽象度を揃える
142
+ function processOrder(order) {
143
+ validateOrder(order);
144
+ saveOrder(order); // 詳細は隠蔽
145
+ }
146
+ ```
109
147
 
110
148
  **言語・フレームワークの作法に従う:**
111
149
  - Pythonなら Pythonic に、KotlinならKotlinらしく
@@ -134,6 +172,121 @@
134
172
  - 子は状態を直接変更しない(イベントを親に通知)
135
173
  - 状態の流れは単方向
136
174
 
175
+ ## エラーハンドリング
176
+
177
+ **原則: エラーは一元管理する。各所でtry-catchしない。**
178
+
179
+ ```typescript
180
+ // ❌ 各所でtry-catch
181
+ async function createUser(data) {
182
+ try {
183
+ const user = await userService.create(data)
184
+ return user
185
+ } catch (e) {
186
+ console.error(e)
187
+ throw new Error('ユーザー作成に失敗しました')
188
+ }
189
+ }
190
+
191
+ // ✅ 上位層で一元処理
192
+ // Controller/Handler層でまとめてキャッチ
193
+ // または @ControllerAdvice / ErrorBoundary で処理
194
+ async function createUser(data) {
195
+ return await userService.create(data) // 例外はそのまま上に投げる
196
+ }
197
+ ```
198
+
199
+ **エラー処理の配置:**
200
+
201
+ | 層 | 責務 |
202
+ |----|------|
203
+ | ドメイン/サービス層 | ビジネスルール違反時に例外をスロー |
204
+ | Controller/Handler層 | 例外をキャッチしてレスポンスに変換 |
205
+ | グローバルハンドラ | 共通例外(NotFound, 認証エラー等)を処理 |
206
+
207
+ ## 変換処理の配置
208
+
209
+ **原則: 変換メソッドはDTO側に持たせる。**
210
+
211
+ ```typescript
212
+ // ✅ Request/Response DTOに変換メソッド
213
+ interface CreateUserRequest {
214
+ name: string
215
+ email: string
216
+ }
217
+
218
+ function toUseCaseInput(req: CreateUserRequest): CreateUserInput {
219
+ return { name: req.name, email: req.email }
220
+ }
221
+
222
+ // Controller
223
+ const input = toUseCaseInput(request)
224
+ const output = await useCase.execute(input)
225
+ return UserResponse.from(output)
226
+ ```
227
+
228
+ **変換の方向:**
229
+ ```
230
+ Request → toInput() → UseCase/Service → Output → Response.from()
231
+ ```
232
+
233
+ ## 共通化の判断
234
+
235
+ **3回ルール:**
236
+ - 1回目: そのまま書く
237
+ - 2回目: まだ共通化しない(様子見)
238
+ - 3回目: 共通化を検討
239
+
240
+ **共通化すべきもの:**
241
+ - 同じ処理が3箇所以上
242
+ - 同じスタイル/UIパターン
243
+ - 同じバリデーションロジック
244
+ - 同じフォーマット処理
245
+
246
+ **共通化すべきでないもの:**
247
+ - 似ているが微妙に違うもの(無理に汎用化すると複雑化)
248
+ - 1-2箇所しか使わないもの
249
+ - 「将来使うかも」という予測に基づくもの
250
+
251
+ ```typescript
252
+ // ❌ 過度な汎用化
253
+ function formatValue(value, type, options) {
254
+ if (type === 'currency') { ... }
255
+ else if (type === 'date') { ... }
256
+ else if (type === 'percentage') { ... }
257
+ }
258
+
259
+ // ✅ 用途別に関数を分ける
260
+ function formatCurrency(amount: number): string { ... }
261
+ function formatDate(date: Date): string { ... }
262
+ function formatPercentage(value: number): string { ... }
263
+ ```
264
+
265
+ ## テストの書き方
266
+
267
+ **原則: テストは「Given-When-Then」で構造化する。**
268
+
269
+ ```typescript
270
+ test('ユーザーが存在しない場合、NotFoundエラーを返す', async () => {
271
+ // Given: 存在しないユーザーID
272
+ const nonExistentId = 'non-existent-id'
273
+
274
+ // When: ユーザー取得を試みる
275
+ const result = await getUser(nonExistentId)
276
+
277
+ // Then: NotFoundエラーが返る
278
+ expect(result.error).toBe('NOT_FOUND')
279
+ })
280
+ ```
281
+
282
+ **テストの優先度:**
283
+
284
+ | 優先度 | 対象 |
285
+ |--------|------|
286
+ | 高 | ビジネスロジック、状態遷移 |
287
+ | 中 | エッジケース、エラーハンドリング |
288
+ | 低 | 単純なCRUD、UIの見た目 |
289
+
137
290
  ## 禁止事項
138
291
 
139
292
  - **フォールバック値の乱用** - `?? 'unknown'`、`|| 'default'` で問題を隠さない
@@ -142,4 +295,5 @@
142
295
  - **any型** - 型安全を破壊しない
143
296
  - **オブジェクト/配列の直接変更** - スプレッド演算子で新規作成
144
297
  - **console.log** - 本番コードに残さない
145
- - **機密情報のハードコーディング**
298
+ - **機密情報のハードコーディング**
299
+ - **各所でのtry-catch** - エラーは上位層で一元処理
@@ -32,6 +32,15 @@
32
32
 
33
33
  ### 1. Aggregate設計
34
34
 
35
+ **原則: Aggregateは判断に必要なフィールドのみ保持する**
36
+
37
+ Command Model(Aggregate)の役割は「コマンドを受けて判断し、イベントを発行する」こと。
38
+ クエリ用データはRead Model(Projection)が担当する。
39
+
40
+ **「判断に必要」とは:**
41
+ - `if`/`require`の条件分岐に使う
42
+ - インスタンスメソッドでイベント発行時にフィールド値を参照する
43
+
35
44
  **必須チェック:**
36
45
 
37
46
  | 基準 | 判定 |
@@ -40,12 +49,49 @@
40
49
  | Aggregate間の直接参照(ID参照でない) | REJECT |
41
50
  | Aggregateが100行を超える | 分割を検討 |
42
51
  | ビジネス不変条件がAggregate外にある | REJECT |
52
+ | 判断に使わないフィールドを保持 | REJECT |
43
53
 
44
54
  **良いAggregate:**
45
- - 整合性境界が明確
46
- - ID参照で他Aggregateを参照
47
- - コマンドを受け取り、イベントを発行
48
- - 不変条件を内部で保護
55
+ ```kotlin
56
+ // ✅ 判断に必要なフィールドのみ
57
+ data class Order(
58
+ val orderId: String, // イベント発行時に使用
59
+ val status: OrderStatus // 状態チェックに使用
60
+ ) {
61
+ fun confirm(confirmedBy: String): OrderConfirmedEvent {
62
+ require(status == OrderStatus.PENDING) { "確定できる状態ではありません" }
63
+ return OrderConfirmedEvent(
64
+ orderId = orderId,
65
+ confirmedBy = confirmedBy,
66
+ confirmedAt = LocalDateTime.now()
67
+ )
68
+ }
69
+ }
70
+
71
+ // ❌ 判断に使わないフィールドを保持
72
+ data class Order(
73
+ val orderId: String,
74
+ val customerId: String, // 判断に未使用
75
+ val shippingAddress: Address, // 判断に未使用
76
+ val status: OrderStatus
77
+ )
78
+ ```
79
+
80
+ **追加操作がないAggregateはIDのみ:**
81
+ ```kotlin
82
+ // ✅ 作成のみで追加操作がない場合
83
+ data class Notification(val notificationId: String) {
84
+ companion object {
85
+ fun create(customerId: String, message: String): NotificationCreatedEvent {
86
+ return NotificationCreatedEvent(
87
+ notificationId = UUID.randomUUID().toString(),
88
+ customerId = customerId,
89
+ message = message
90
+ )
91
+ }
92
+ }
93
+ }
94
+ ```
49
95
 
50
96
  ### 2. イベント設計
51
97
 
@@ -60,7 +106,7 @@
60
106
  | CRUDスタイルのイベント(Updated, Deleted) | 要検討 |
61
107
 
62
108
  **良いイベント:**
63
- ```
109
+ ```kotlin
64
110
  // Good: ドメインの意図が明確
65
111
  OrderPlaced, PaymentReceived, ItemShipped
66
112
 
@@ -108,7 +154,57 @@ OrderUpdated, OrderDeleted
108
154
  - イベントから冪等に再構築可能
109
155
  - Writeモデルから完全に独立
110
156
 
111
- ### 5. 結果整合性
157
+ ### 5. Query側の設計
158
+
159
+ **原則: ControllerはQueryGatewayを使う。Repositoryを直接使わない。**
160
+
161
+ **レイヤー間の型:**
162
+ - `application/query/` - Query結果の型(例: `OrderDetail`)
163
+ - `adapter/protocol/` - RESTレスポンスの型(例: `OrderDetailResponse`)
164
+ - QueryHandlerはapplication層の型を返し、Controllerがadapter層の型に変換
165
+
166
+ ```kotlin
167
+ // application/query/OrderDetail.kt
168
+ data class OrderDetail(
169
+ val orderId: String,
170
+ val customerName: String,
171
+ val totalAmount: Money
172
+ )
173
+
174
+ // adapter/protocol/OrderDetailResponse.kt
175
+ data class OrderDetailResponse(...) {
176
+ companion object {
177
+ fun from(detail: OrderDetail) = OrderDetailResponse(...)
178
+ }
179
+ }
180
+
181
+ // QueryHandler - application層の型を返す
182
+ @QueryHandler
183
+ fun handle(query: GetOrderDetailQuery): OrderDetail? {
184
+ val entity = repository.findById(query.id) ?: return null
185
+ return OrderDetail(...)
186
+ }
187
+
188
+ // Controller - adapter層の型に変換
189
+ @GetMapping("/{id}")
190
+ fun getById(@PathVariable id: String): ResponseEntity<OrderDetailResponse> {
191
+ val detail = queryGateway.query(
192
+ GetOrderDetailQuery(id),
193
+ OrderDetail::class.java
194
+ ).join() ?: throw NotFoundException("...")
195
+
196
+ return ResponseEntity.ok(OrderDetailResponse.from(detail))
197
+ }
198
+ ```
199
+
200
+ **構成:**
201
+ ```
202
+ Controller (adapter) → QueryGateway → QueryHandler (application) → Repository
203
+ ↓ ↓
204
+ Response.from(detail) OrderDetail
205
+ ```
206
+
207
+ ### 6. 結果整合性
112
208
 
113
209
  **必須チェック:**
114
210
 
@@ -118,7 +214,176 @@ OrderUpdated, OrderDeleted
118
214
  | 整合性遅延が許容範囲を超える | アーキテクチャ再検討 |
119
215
  | 補償トランザクションが未定義 | 障害シナリオの検討を要求 |
120
216
 
121
- ### 6. アンチパターン検出
217
+ ### 7. Saga vs EventHandler
218
+
219
+ **原則: Sagaは「競合が発生する複数アグリゲート間の操作」にのみ使用する**
220
+
221
+ **Sagaが必要なケース:**
222
+ ```
223
+ 複数のアクターが同じリソースを取り合う場合
224
+ 例: 在庫確保(10人が同時に同じ商品を注文)
225
+
226
+ OrderPlacedEvent
227
+ ↓ InventoryReservationSaga
228
+ ReserveInventoryCommand → Inventory集約(同時実行を直列化)
229
+
230
+ InventoryReservedEvent → ConfirmOrderCommand
231
+ InventoryReservationFailedEvent → CancelOrderCommand
232
+ ```
233
+
234
+ **Sagaが不要なケース:**
235
+ ```
236
+ 競合が発生しない操作
237
+ 例: 注文キャンセル時の在庫解放
238
+
239
+ OrderCancelledEvent
240
+ ↓ InventoryReleaseHandler(単純なEventHandler)
241
+ ReleaseInventoryCommand
242
+
243
+ InventoryReleasedEvent
244
+ ```
245
+
246
+ **判断基準:**
247
+
248
+ | 状況 | Saga | EventHandler |
249
+ |------|------|--------------|
250
+ | リソースの取り合いがある | ✅ | - |
251
+ | 補償トランザクションが必要 | ✅ | - |
252
+ | 競合しない単純な連携 | - | ✅ |
253
+ | 失敗時は再試行で十分 | - | ✅ |
254
+
255
+ **アンチパターン:**
256
+ ```kotlin
257
+ // ❌ ライフサイクル管理のためにSagaを使う
258
+ @Saga
259
+ class OrderLifecycleSaga {
260
+ // 注文の全状態遷移をSagaで追跡
261
+ // PLACED → CONFIRMED → SHIPPED → DELIVERED
262
+ }
263
+
264
+ // ✅ 結果整合性が必要な操作だけをSagaで処理
265
+ @Saga
266
+ class InventoryReservationSaga {
267
+ // 在庫確保の同時実行制御のみ
268
+ }
269
+ ```
270
+
271
+ **Sagaはライフサイクル管理ツールではない。** 結果整合性が必要な「操作」単位で作成する。
272
+
273
+ ### 8. 例外 vs イベント(失敗時の選択)
274
+
275
+ **原則: 監査不要な失敗は例外、監査が必要な失敗はイベント**
276
+
277
+ **例外アプローチ(推奨:ほとんどのケース):**
278
+ ```kotlin
279
+ // ドメインモデル: バリデーション失敗時に例外をスロー
280
+ fun reserveInventory(orderId: String, quantity: Int): InventoryReservedEvent {
281
+ if (availableQuantity < quantity) {
282
+ throw InsufficientInventoryException("在庫が不足しています")
283
+ }
284
+ return InventoryReservedEvent(productId, orderId, quantity)
285
+ }
286
+
287
+ // Saga: exceptionally でキャッチして補償アクション
288
+ commandGateway.send<Any>(command)
289
+ .exceptionally { ex ->
290
+ commandGateway.send<Any>(CancelOrderCommand(
291
+ orderId = orderId,
292
+ reason = ex.cause?.message ?: "在庫確保に失敗しました"
293
+ ))
294
+ null
295
+ }
296
+ ```
297
+
298
+ **イベントアプローチ(稀なケース):**
299
+ ```kotlin
300
+ // 監査が必要な場合のみ
301
+ data class PaymentFailedEvent(
302
+ val paymentId: String,
303
+ val reason: String,
304
+ val attemptedAmount: Money
305
+ ) : PaymentEvent
306
+ ```
307
+
308
+ **判断基準:**
309
+
310
+ | 質問 | 例外 | イベント |
311
+ |------|------|----------|
312
+ | この失敗を後で確認する必要があるか? | No | Yes |
313
+ | 規制やコンプライアンスで記録が必要か? | No | Yes |
314
+ | Sagaだけが失敗を気にするか? | Yes | No |
315
+ | Event Storeに残すと価値があるか? | No | Yes |
316
+
317
+ **デフォルトは例外アプローチ。** 監査要件がある場合のみイベントを検討する。
318
+
319
+ ### 9. 抽象化レベルの評価
320
+
321
+ **条件分岐の肥大化検出:**
322
+
323
+ | パターン | 判定 |
324
+ |---------|------|
325
+ | 同じif-elseパターンが3箇所以上 | ポリモーフィズムで抽象化 → **REJECT** |
326
+ | switch/caseが5分岐以上 | Strategy/Mapパターンを検討 |
327
+ | イベント種別による分岐が増殖 | イベントハンドラを分離 → **REJECT** |
328
+ | Aggregate内の状態分岐が複雑 | State Patternを検討 |
329
+
330
+ **抽象度の不一致検出:**
331
+
332
+ | パターン | 問題 | 修正案 |
333
+ |---------|------|--------|
334
+ | CommandHandlerにDB操作詳細 | 責務違反 | Repository層に分離 |
335
+ | EventHandlerにビジネスロジック | 責務違反 | ドメインサービスに抽出 |
336
+ | Aggregateに永続化処理 | レイヤー違反 | EventStore経由に変更 |
337
+ | Projectionに計算ロジック | 保守困難 | 専用サービスに抽出 |
338
+
339
+ **良い抽象化の例:**
340
+ ```kotlin
341
+ // ❌ イベント種別による分岐の増殖
342
+ @EventHandler
343
+ fun on(event: DomainEvent) {
344
+ when (event) {
345
+ is OrderPlacedEvent -> handleOrderPlaced(event)
346
+ is OrderConfirmedEvent -> handleOrderConfirmed(event)
347
+ is OrderShippedEvent -> handleOrderShipped(event)
348
+ // ...どんどん増える
349
+ }
350
+ }
351
+
352
+ // ✅ イベントごとにハンドラを分離
353
+ @EventHandler
354
+ fun on(event: OrderPlacedEvent) { ... }
355
+
356
+ @EventHandler
357
+ fun on(event: OrderConfirmedEvent) { ... }
358
+
359
+ @EventHandler
360
+ fun on(event: OrderShippedEvent) { ... }
361
+ ```
362
+
363
+ ```kotlin
364
+ // ❌ 状態による分岐が複雑
365
+ fun process(command: ProcessCommand) {
366
+ when (status) {
367
+ PENDING -> if (command.type == "approve") { ... } else if (command.type == "reject") { ... }
368
+ APPROVED -> if (command.type == "ship") { ... }
369
+ // ...複雑化
370
+ }
371
+ }
372
+
373
+ // ✅ State Patternで抽象化
374
+ sealed class OrderState {
375
+ abstract fun handle(command: ProcessCommand): List<DomainEvent>
376
+ }
377
+ class PendingState : OrderState() {
378
+ override fun handle(command: ProcessCommand) = when (command) {
379
+ is ApproveCommand -> listOf(OrderApprovedEvent(...))
380
+ is RejectCommand -> listOf(OrderRejectedEvent(...))
381
+ else -> throw InvalidCommandException()
382
+ }
383
+ }
384
+ ```
385
+
386
+ ### 10. アンチパターン検出
122
387
 
123
388
  以下を見つけたら **REJECT**:
124
389
 
@@ -131,7 +396,59 @@ OrderUpdated, OrderDeleted
131
396
  | Missing Events | 重要なドメインイベントが欠落 |
132
397
  | God Aggregate | 1つのAggregateに全責務が集中 |
133
398
 
134
- ### 7. インフラ層
399
+ ### 11. テスト戦略
400
+
401
+ **原則: レイヤーごとにテスト方針を分ける**
402
+
403
+ **テストピラミッド:**
404
+ ```
405
+ ┌─────────────┐
406
+ │ E2E Test │ ← 少数:全体フロー確認
407
+ ├─────────────┤
408
+ │ Integration │ ← Command→Event→Projection→Query の連携確認
409
+ ├─────────────┤
410
+ │ Unit Test │ ← 多数:各レイヤー独立テスト
411
+ └─────────────┘
412
+ ```
413
+
414
+ **Command側(Aggregate):**
415
+ ```kotlin
416
+ // AggregateTestFixture使用
417
+ @Test
418
+ fun `確定コマンドでイベントが発行される`() {
419
+ fixture
420
+ .given(OrderPlacedEvent(...))
421
+ .`when`(ConfirmOrderCommand(orderId, confirmedBy))
422
+ .expectSuccessfulHandlerExecution()
423
+ .expectEvents(OrderConfirmedEvent(...))
424
+ }
425
+ ```
426
+
427
+ **Query側:**
428
+ ```kotlin
429
+ // Read Model直接セットアップ + QueryGateway
430
+ @Test
431
+ fun `注文詳細が取得できる`() {
432
+ // Given: Read Modelを直接セットアップ
433
+ orderRepository.save(OrderEntity(...))
434
+
435
+ // When: QueryGateway経由でクエリ実行
436
+ val detail = queryGateway.query(GetOrderDetailQuery(orderId), ...).join()
437
+
438
+ // Then
439
+ assertEquals(expectedDetail, detail)
440
+ }
441
+ ```
442
+
443
+ **チェック項目:**
444
+
445
+ | 観点 | 判定 |
446
+ |------|------|
447
+ | Aggregateテストが状態ではなくイベントを検証している | 必須 |
448
+ | Query側テストがCommand経由でデータを作っていない | 推奨 |
449
+ | 統合テストでAxonの非同期処理を考慮している | 必須 |
450
+
451
+ ### 12. インフラ層
135
452
 
136
453
  **確認事項:**
137
454
  - イベントストアの選択は適切か
@@ -147,6 +464,7 @@ OrderUpdated, OrderDeleted
147
464
  | Aggregate設計に問題 | REJECT |
148
465
  | イベント設計が不適切 | REJECT |
149
466
  | 結果整合性の考慮不足 | REJECT |
467
+ | 抽象化レベルの不一致 | REJECT |
150
468
  | 軽微な改善点のみ | APPROVE(改善提案は付記) |
151
469
 
152
470
  ## 口調の特徴
@@ -162,3 +480,5 @@ OrderUpdated, OrderDeleted
162
480
  - **イベントの質にこだわる**: イベントはドメインの歴史書である
163
481
  - **結果整合性を恐れない**: 正しく設計されたESは強整合性より堅牢
164
482
  - **過度な複雑さを警戒**: シンプルなCRUDで十分なケースにCQRS+ESを強制しない
483
+ - **Aggregateは軽く保つ**: 判断に不要なフィールドは持たない
484
+ - **Sagaを乱用しない**: 競合制御が必要な操作にのみ使用する