opencode-metis 0.1.0

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 (156) hide show
  1. package/README.md +140 -0
  2. package/dist/cli.cjs +63 -0
  3. package/dist/mcp-server.cjs +51 -0
  4. package/dist/plugin.cjs +4 -0
  5. package/dist/worker.cjs +224 -0
  6. package/opencode/agent/the-analyst/feature-prioritization.md +66 -0
  7. package/opencode/agent/the-analyst/market-research.md +77 -0
  8. package/opencode/agent/the-analyst/project-coordination.md +81 -0
  9. package/opencode/agent/the-analyst/requirements-analysis.md +77 -0
  10. package/opencode/agent/the-architect/compatibility-review.md +138 -0
  11. package/opencode/agent/the-architect/complexity-review.md +137 -0
  12. package/opencode/agent/the-architect/quality-review.md +67 -0
  13. package/opencode/agent/the-architect/security-review.md +127 -0
  14. package/opencode/agent/the-architect/system-architecture.md +119 -0
  15. package/opencode/agent/the-architect/system-documentation.md +83 -0
  16. package/opencode/agent/the-architect/technology-research.md +85 -0
  17. package/opencode/agent/the-chief.md +79 -0
  18. package/opencode/agent/the-designer/accessibility-implementation.md +101 -0
  19. package/opencode/agent/the-designer/design-foundation.md +74 -0
  20. package/opencode/agent/the-designer/interaction-architecture.md +75 -0
  21. package/opencode/agent/the-designer/user-research.md +70 -0
  22. package/opencode/agent/the-meta-agent.md +155 -0
  23. package/opencode/agent/the-platform-engineer/ci-cd-pipelines.md +109 -0
  24. package/opencode/agent/the-platform-engineer/containerization.md +106 -0
  25. package/opencode/agent/the-platform-engineer/data-architecture.md +81 -0
  26. package/opencode/agent/the-platform-engineer/dependency-review.md +144 -0
  27. package/opencode/agent/the-platform-engineer/deployment-automation.md +81 -0
  28. package/opencode/agent/the-platform-engineer/infrastructure-as-code.md +107 -0
  29. package/opencode/agent/the-platform-engineer/performance-tuning.md +82 -0
  30. package/opencode/agent/the-platform-engineer/pipeline-engineering.md +81 -0
  31. package/opencode/agent/the-platform-engineer/production-monitoring.md +105 -0
  32. package/opencode/agent/the-qa-engineer/exploratory-testing.md +66 -0
  33. package/opencode/agent/the-qa-engineer/performance-testing.md +81 -0
  34. package/opencode/agent/the-qa-engineer/quality-assurance.md +77 -0
  35. package/opencode/agent/the-qa-engineer/test-execution.md +66 -0
  36. package/opencode/agent/the-software-engineer/api-development.md +78 -0
  37. package/opencode/agent/the-software-engineer/component-development.md +79 -0
  38. package/opencode/agent/the-software-engineer/concurrency-review.md +141 -0
  39. package/opencode/agent/the-software-engineer/domain-modeling.md +66 -0
  40. package/opencode/agent/the-software-engineer/performance-optimization.md +113 -0
  41. package/opencode/command/analyze.md +149 -0
  42. package/opencode/command/constitution.md +178 -0
  43. package/opencode/command/debug.md +194 -0
  44. package/opencode/command/document.md +178 -0
  45. package/opencode/command/implement.md +225 -0
  46. package/opencode/command/refactor.md +207 -0
  47. package/opencode/command/review.md +229 -0
  48. package/opencode/command/simplify.md +267 -0
  49. package/opencode/command/specify.md +191 -0
  50. package/opencode/command/validate.md +224 -0
  51. package/opencode/skill/accessibility-design/SKILL.md +566 -0
  52. package/opencode/skill/accessibility-design/checklists/wcag-checklist.md +435 -0
  53. package/opencode/skill/agent-coordination/SKILL.md +224 -0
  54. package/opencode/skill/api-contract-design/SKILL.md +550 -0
  55. package/opencode/skill/api-contract-design/templates/graphql-schema-template.md +818 -0
  56. package/opencode/skill/api-contract-design/templates/rest-api-template.md +417 -0
  57. package/opencode/skill/architecture-design/SKILL.md +160 -0
  58. package/opencode/skill/architecture-design/examples/architecture-examples.md +170 -0
  59. package/opencode/skill/architecture-design/template.md +749 -0
  60. package/opencode/skill/architecture-design/validation.md +99 -0
  61. package/opencode/skill/architecture-selection/SKILL.md +522 -0
  62. package/opencode/skill/architecture-selection/examples/adrs/001-example-adr.md +71 -0
  63. package/opencode/skill/architecture-selection/examples/architecture-patterns.md +239 -0
  64. package/opencode/skill/bug-diagnosis/SKILL.md +235 -0
  65. package/opencode/skill/code-quality-review/SKILL.md +337 -0
  66. package/opencode/skill/code-quality-review/examples/anti-patterns.md +629 -0
  67. package/opencode/skill/code-quality-review/reference.md +322 -0
  68. package/opencode/skill/code-review/SKILL.md +363 -0
  69. package/opencode/skill/code-review/reference.md +450 -0
  70. package/opencode/skill/codebase-analysis/SKILL.md +139 -0
  71. package/opencode/skill/codebase-navigation/SKILL.md +227 -0
  72. package/opencode/skill/codebase-navigation/examples/exploration-patterns.md +263 -0
  73. package/opencode/skill/coding-conventions/SKILL.md +178 -0
  74. package/opencode/skill/coding-conventions/checklists/accessibility-checklist.md +176 -0
  75. package/opencode/skill/coding-conventions/checklists/performance-checklist.md +154 -0
  76. package/opencode/skill/coding-conventions/checklists/security-checklist.md +127 -0
  77. package/opencode/skill/constitution-validation/SKILL.md +315 -0
  78. package/opencode/skill/constitution-validation/examples/CONSTITUTION.md +202 -0
  79. package/opencode/skill/constitution-validation/reference/rule-patterns.md +328 -0
  80. package/opencode/skill/constitution-validation/template.md +115 -0
  81. package/opencode/skill/context-preservation/SKILL.md +445 -0
  82. package/opencode/skill/data-modeling/SKILL.md +385 -0
  83. package/opencode/skill/data-modeling/templates/schema-design-template.md +268 -0
  84. package/opencode/skill/deployment-pipeline-design/SKILL.md +579 -0
  85. package/opencode/skill/deployment-pipeline-design/templates/pipeline-template.md +633 -0
  86. package/opencode/skill/documentation-extraction/SKILL.md +259 -0
  87. package/opencode/skill/documentation-sync/SKILL.md +431 -0
  88. package/opencode/skill/domain-driven-design/SKILL.md +509 -0
  89. package/opencode/skill/domain-driven-design/examples/ddd-patterns.md +688 -0
  90. package/opencode/skill/domain-driven-design/reference.md +465 -0
  91. package/opencode/skill/drift-detection/SKILL.md +383 -0
  92. package/opencode/skill/drift-detection/reference.md +340 -0
  93. package/opencode/skill/error-recovery/SKILL.md +162 -0
  94. package/opencode/skill/error-recovery/examples/error-patterns.md +484 -0
  95. package/opencode/skill/feature-prioritization/SKILL.md +419 -0
  96. package/opencode/skill/feature-prioritization/examples/rice-template.md +139 -0
  97. package/opencode/skill/feature-prioritization/reference.md +256 -0
  98. package/opencode/skill/git-workflow/SKILL.md +453 -0
  99. package/opencode/skill/implementation-planning/SKILL.md +215 -0
  100. package/opencode/skill/implementation-planning/examples/phase-examples.md +217 -0
  101. package/opencode/skill/implementation-planning/template.md +220 -0
  102. package/opencode/skill/implementation-planning/validation.md +88 -0
  103. package/opencode/skill/implementation-verification/SKILL.md +272 -0
  104. package/opencode/skill/knowledge-capture/SKILL.md +265 -0
  105. package/opencode/skill/knowledge-capture/reference/knowledge-capture.md +402 -0
  106. package/opencode/skill/knowledge-capture/reference.md +444 -0
  107. package/opencode/skill/knowledge-capture/templates/domain-template.md +325 -0
  108. package/opencode/skill/knowledge-capture/templates/interface-template.md +255 -0
  109. package/opencode/skill/knowledge-capture/templates/pattern-template.md +144 -0
  110. package/opencode/skill/observability-design/SKILL.md +291 -0
  111. package/opencode/skill/observability-design/references/monitoring-patterns.md +461 -0
  112. package/opencode/skill/pattern-detection/SKILL.md +171 -0
  113. package/opencode/skill/pattern-detection/examples/common-patterns.md +359 -0
  114. package/opencode/skill/performance-analysis/SKILL.md +266 -0
  115. package/opencode/skill/performance-analysis/references/profiling-tools.md +499 -0
  116. package/opencode/skill/requirements-analysis/SKILL.md +139 -0
  117. package/opencode/skill/requirements-analysis/examples/good-prd.md +66 -0
  118. package/opencode/skill/requirements-analysis/template.md +177 -0
  119. package/opencode/skill/requirements-analysis/validation.md +69 -0
  120. package/opencode/skill/requirements-elicitation/SKILL.md +518 -0
  121. package/opencode/skill/requirements-elicitation/examples/interview-questions.md +226 -0
  122. package/opencode/skill/requirements-elicitation/examples/user-stories.md +414 -0
  123. package/opencode/skill/safe-refactoring/SKILL.md +312 -0
  124. package/opencode/skill/safe-refactoring/reference/code-smells.md +347 -0
  125. package/opencode/skill/security-assessment/SKILL.md +421 -0
  126. package/opencode/skill/security-assessment/checklists/security-review-checklist.md +285 -0
  127. package/opencode/skill/specification-management/SKILL.md +143 -0
  128. package/opencode/skill/specification-management/readme-template.md +32 -0
  129. package/opencode/skill/specification-management/reference.md +115 -0
  130. package/opencode/skill/specification-management/spec.py +229 -0
  131. package/opencode/skill/specification-validation/SKILL.md +397 -0
  132. package/opencode/skill/specification-validation/reference/3cs-framework.md +306 -0
  133. package/opencode/skill/specification-validation/reference/ambiguity-detection.md +132 -0
  134. package/opencode/skill/specification-validation/reference/constitution-validation.md +301 -0
  135. package/opencode/skill/specification-validation/reference/drift-detection.md +383 -0
  136. package/opencode/skill/task-delegation/SKILL.md +607 -0
  137. package/opencode/skill/task-delegation/examples/file-coordination.md +495 -0
  138. package/opencode/skill/task-delegation/examples/parallel-research.md +337 -0
  139. package/opencode/skill/task-delegation/examples/sequential-build.md +504 -0
  140. package/opencode/skill/task-delegation/reference.md +825 -0
  141. package/opencode/skill/tech-stack-detection/SKILL.md +89 -0
  142. package/opencode/skill/tech-stack-detection/references/framework-signatures.md +598 -0
  143. package/opencode/skill/technical-writing/SKILL.md +190 -0
  144. package/opencode/skill/technical-writing/templates/adr-template.md +205 -0
  145. package/opencode/skill/technical-writing/templates/system-doc-template.md +380 -0
  146. package/opencode/skill/test-design/SKILL.md +464 -0
  147. package/opencode/skill/test-design/examples/test-pyramid.md +724 -0
  148. package/opencode/skill/testing/SKILL.md +213 -0
  149. package/opencode/skill/testing/examples/test-pyramid.md +724 -0
  150. package/opencode/skill/user-insight-synthesis/SKILL.md +576 -0
  151. package/opencode/skill/user-insight-synthesis/templates/research-plan-template.md +217 -0
  152. package/opencode/skill/user-research/SKILL.md +508 -0
  153. package/opencode/skill/user-research/examples/interview-questions.md +265 -0
  154. package/opencode/skill/user-research/examples/personas.md +267 -0
  155. package/opencode/skill/vibe-security/SKILL.md +654 -0
  156. package/package.json +45 -0
@@ -0,0 +1,465 @@
1
+ # Aggregate Design Guide
2
+
3
+ Advanced heuristics for defining aggregate boundaries, sizing aggregates correctly, and choosing consistency strategies. Load this when the SKILL.md's high-level rules are insufficient for the decision at hand.
4
+
5
+ ---
6
+
7
+ ## Aggregate Boundaries
8
+
9
+ Aggregate boundaries exist to protect invariants — the business rules that must always be true. The primary question when drawing a boundary is: **which objects must change together to keep a business rule intact?**
10
+
11
+ ### The Invariant Test
12
+
13
+ For each proposed aggregate boundary, identify the invariants it protects:
14
+
15
+ ```
16
+ Example: Should OrderItem be inside Order or its own aggregate?
17
+
18
+ Invariant: "An order's total cannot exceed the customer's credit limit"
19
+
20
+ Test:
21
+ - Can we check this rule with only the Order? YES — Order knows its items and total.
22
+ - Does changing an OrderItem require validating Order-level rules? YES.
23
+ - Can OrderItem exist independently with its own lifecycle? NO — it only exists for an order.
24
+
25
+ Decision: OrderItem is INSIDE the Order aggregate.
26
+ ```
27
+
28
+ ```
29
+ Example: Should Order be inside Customer or its own aggregate?
30
+
31
+ Invariant: "A customer cannot have more than 10 active orders simultaneously"
32
+
33
+ Test:
34
+ - Must Order and Customer change together to protect this rule? NO.
35
+ - Can we check this rule by querying the count of Orders for a CustomerId? YES.
36
+ - Does Customer have a distinct lifecycle from Order? YES.
37
+
38
+ Decision: Order is a SEPARATE aggregate, referenced from Customer by ID.
39
+ The invariant is enforced via a domain service or application-level check before creating an Order.
40
+ ```
41
+
42
+ ### Aggregate Design Canvas
43
+
44
+ When designing a new aggregate, work through this canvas:
45
+
46
+ ```
47
+ ┌─────────────────────────────────────────────────────────────┐
48
+ │ AGGREGATE NAME: │
49
+ ├─────────────────────────────────────────────────────────────┤
50
+ │ INVARIANTS (rules that must always hold): │
51
+ │ 1. │
52
+ │ 2. │
53
+ ├─────────────────────────────────────────────────────────────┤
54
+ │ AGGREGATE ROOT (single entry point for all changes): │
55
+ │ │
56
+ ├─────────────────────────────────────────────────────────────┤
57
+ │ INSIDE THE BOUNDARY (change together to protect invariants):│
58
+ │ Entities: │
59
+ │ Value Objects: │
60
+ ├─────────────────────────────────────────────────────────────┤
61
+ │ OUTSIDE THE BOUNDARY (referenced by ID): │
62
+ │ │
63
+ ├─────────────────────────────────────────────────────────────┤
64
+ │ DOMAIN EVENTS (what this aggregate announces): │
65
+ │ │
66
+ ├─────────────────────────────────────────────────────────────┤
67
+ │ CONSISTENCY TYPE: │
68
+ │ [ ] Transactional — invariants protected within boundary │
69
+ │ [ ] Eventual — consistency with other aggregates via events│
70
+ └─────────────────────────────────────────────────────────────┘
71
+ ```
72
+
73
+ ### Common Boundary Mistakes
74
+
75
+ **Grouping by noun instead of invariant**
76
+
77
+ The mistake is asking "what belongs to an order?" rather than "which objects must change together to enforce order rules?"
78
+
79
+ ```
80
+ // WRONG: Grouped by association
81
+ class Order {
82
+ customer: Customer; // Does Customer's email change when Order changes? No.
83
+ payments: Payment[]; // Does Payment share Order's invariants? No.
84
+ shipments: Shipment[]; // Can Shipment change independently? Yes.
85
+ reviews: Review[]; // No shared invariants with Order.
86
+ }
87
+
88
+ // CORRECT: Grouped by invariant
89
+ class Order { // Invariant: item total <= approved budget
90
+ items: OrderItem[]; // Must change with Order to enforce the rule
91
+ discountCode: DiscountCode; // Applied at order level, affects total
92
+ }
93
+
94
+ // Separate aggregates, referenced by OrderId
95
+ class Payment { orderId: OrderId; ... }
96
+ class Shipment { orderId: OrderId; ... }
97
+ class Review { orderId: OrderId; ... }
98
+ ```
99
+
100
+ **Including historical data**
101
+
102
+ Historical records rarely share invariants with the current state. They grow unbounded and should be separate.
103
+
104
+ ```
105
+ // WRONG: History inside aggregate
106
+ class Account {
107
+ balance: Money;
108
+ transactions: Transaction[]; // Could be millions — never inspected for invariants
109
+ }
110
+
111
+ // CORRECT: Ledger as separate aggregate
112
+ class Account {
113
+ balance: Money; // Current state
114
+ // Invariant: balance = sum of credits - sum of debits
115
+ // Enforced by debit() and credit() methods, not by inspecting transaction history
116
+ }
117
+
118
+ class Transaction {
119
+ accountId: AccountId; // Reference by ID
120
+ amount: Money;
121
+ type: TransactionType;
122
+ occurredAt: Date;
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Aggregate Sizing
129
+
130
+ Start with the smallest possible aggregate — usually a single entity — and expand only when an invariant cannot be protected otherwise.
131
+
132
+ ### Size Signals
133
+
134
+ **Signals that an aggregate is too large:**
135
+
136
+ | Signal | What It Means |
137
+ |--------|--------------|
138
+ | Optimistic lock conflicts on concurrent edits | Multiple users editing unrelated parts simultaneously |
139
+ | Loading thousands of rows for a simple operation | Aggregate includes unbounded collections |
140
+ | Transactional failures involving unrelated data | Scope is wider than invariants require |
141
+ | Slow reconstitution from the repository | Too many child objects |
142
+ | Cross-cutting edits by different bounded contexts | Boundary is in the wrong place |
143
+
144
+ **Signals that an aggregate is too small:**
145
+
146
+ | Signal | What It Means |
147
+ |--------|--------------|
148
+ | Business rules scattered across domain services | Invariant spans objects not in the same boundary |
149
+ | Application code enforcing consistency across multiple saves | Transaction script replacing domain logic |
150
+ | "Eventual" consistency used where the business demands immediate | Boundary was split too aggressively |
151
+
152
+ ### Sizing Heuristics
153
+
154
+ **1. Default to single-entity aggregates**
155
+
156
+ Most aggregates start as a single entity. Add children only when a specific invariant requires it.
157
+
158
+ ```
159
+ // Start here
160
+ class Order {
161
+ id: OrderId;
162
+ status: OrderStatus;
163
+ customerId: CustomerId; // Reference — not inside
164
+ }
165
+
166
+ // Expand when: "total must be recalculated across all items atomically"
167
+ class Order {
168
+ id: OrderId;
169
+ status: OrderStatus;
170
+ customerId: CustomerId;
171
+ items: OrderItem[]; // Now inside — total invariant requires it
172
+ }
173
+ ```
174
+
175
+ **2. Limit unbounded collections**
176
+
177
+ If a collection can grow without bound, it must not be inside the aggregate.
178
+
179
+ ```
180
+ // BAD: Blog with all comments inside
181
+ class BlogPost {
182
+ title: string;
183
+ body: string;
184
+ comments: Comment[]; // Could be 10,000 — all loaded for every post operation
185
+ }
186
+
187
+ // GOOD: Comment is its own aggregate
188
+ class BlogPost {
189
+ title: string;
190
+ body: string;
191
+ commentCount: number; // Denormalized count for display — updated via event
192
+ }
193
+
194
+ class Comment {
195
+ postId: PostId; // Reference — not embedded in post
196
+ body: string;
197
+ approvedAt: Date | null;
198
+ }
199
+ ```
200
+
201
+ **3. Prefer eventual consistency at aggregate boundaries**
202
+
203
+ Business rarely demands immediate consistency across aggregates. Clarify with domain experts whether "immediately" means within the same transaction or "shortly thereafter."
204
+
205
+ ```
206
+ Question to ask: "If placing an order and reserving inventory happened within
207
+ 2 seconds of each other — would that be acceptable to the business?"
208
+
209
+ If YES → eventual consistency, separate aggregates, domain events
210
+ If NO → investigate why. Usually it's a UI concern, not a true invariant.
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Consistency Rules
216
+
217
+ ### Transactional Consistency
218
+
219
+ Use when: **invariants must be true the instant a command completes.**
220
+
221
+ Scope: within a single aggregate, in a single transaction.
222
+
223
+ ```
224
+ Application layer — one aggregate per command:
225
+
226
+ async function addItemToOrder(command: AddItemCommand): Promise<void> {
227
+ const order = await this.orderRepo.findById(command.orderId);
228
+ order.addItem(command.productId, command.quantity, command.price);
229
+ await this.orderRepo.save(order); // Atomic: load → mutate → save
230
+ }
231
+ ```
232
+
233
+ ### Eventual Consistency
234
+
235
+ Use when: **consistency between aggregates can be achieved asynchronously.**
236
+
237
+ Scope: across aggregates, within or across services.
238
+
239
+ ```
240
+ Pattern: publish → subscribe → update own aggregate
241
+
242
+ // Step 1: Order aggregate raises an event
243
+ class Order {
244
+ submit(): void {
245
+ this.status = OrderStatus.Placed;
246
+ this.events.push(new OrderPlaced(this.id, this.items, this.total));
247
+ }
248
+ }
249
+
250
+ // Step 2: Repository publishes after successful save
251
+ class PostgresOrderRepository {
252
+ async save(order: Order): Promise<void> {
253
+ await this.db.save(this.decompose(order));
254
+ await this.eventBus.publishAll(order.pullEvents());
255
+ }
256
+ }
257
+
258
+ // Step 3: Inventory aggregate handles event in its own transaction
259
+ class InventoryProjection {
260
+ async on(event: OrderPlaced): Promise<void> {
261
+ for (const item of event.items) {
262
+ const stock = await this.stockRepo.findByProduct(item.productId);
263
+ stock.reserve(item.quantity);
264
+ await this.stockRepo.save(stock); // Own aggregate, own transaction
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ### Choosing Between Transactional and Eventual
271
+
272
+ ```
273
+ Decision tree:
274
+
275
+ 1. Are the objects part of the same aggregate?
276
+ YES → Transactional (ACID within aggregate boundary)
277
+ NO → Continue ↓
278
+
279
+ 2. Does the business genuinely require atomicity across these objects?
280
+ YES → Reconsider the aggregate boundary — they may belong together
281
+ NO → Continue ↓
282
+
283
+ 3. Is latency measured in seconds acceptable?
284
+ YES → Eventual consistency via domain events
285
+ NO → Investigate. Usually a UI expectation, not a true business requirement.
286
+
287
+ 4. Does failure in the secondary update require rollback of the primary?
288
+ YES → Saga with compensation (not distributed transaction)
289
+ NO → Eventual consistency, idempotent handlers, retry on failure
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Eventual Consistency Between Aggregates
295
+
296
+ ### Idempotent Event Handlers
297
+
298
+ Handlers must be safe to call more than once. Message brokers deliver at-least-once; handlers must not double-apply effects.
299
+
300
+ ```typescript
301
+ class InventoryHandler {
302
+ async on(event: OrderPlaced): Promise<void> {
303
+ // Guard: skip if already processed this event
304
+ const processed = await this.processedEvents.contains(event.eventId);
305
+ if (processed) return;
306
+
307
+ for (const item of event.items) {
308
+ const stock = await this.stockRepo.findByProduct(item.productId);
309
+ stock.reserve(item.quantity);
310
+ await this.stockRepo.save(stock);
311
+ }
312
+
313
+ await this.processedEvents.record(event.eventId);
314
+ }
315
+ }
316
+ ```
317
+
318
+ ### Handling Failures
319
+
320
+ When a handler fails, the business needs a clear answer: is the failure retryable or fatal?
321
+
322
+ ```
323
+ Retryable failures (retry with backoff):
324
+ - Network timeout
325
+ - Downstream service unavailable
326
+ - Optimistic lock conflict
327
+
328
+ Fatal failures (dead letter queue + alert):
329
+ - Invalid event schema — event is malformed
330
+ - Business rule violation — inventory does not exist for the product
331
+ - Permanent downstream rejection — payment provider blacklisted the account
332
+
333
+ Pattern:
334
+ try {
335
+ await handler.on(event)
336
+ } catch (RetryableError) {
337
+ await queue.nack(event) // Return to queue for retry
338
+ } catch (FatalError) {
339
+ await deadLetterQueue.send(event) // Human review required
340
+ await alerting.notify(error)
341
+ }
342
+ ```
343
+
344
+ ### Eventual Consistency and User Experience
345
+
346
+ When the UI must reflect a consistent state before eventual handlers complete, use optimistic updates:
347
+
348
+ ```
349
+ Pattern: Update read model immediately, correct if event fails
350
+
351
+ 1. User places order → UI shows "Order Placed" immediately
352
+ 2. OrderPlaced event → InventoryHandler runs asynchronously
353
+ 3. If inventory is insufficient → OrderFailed event raised
354
+ 4. UI receives OrderFailed notification → shows error, reverts display
355
+
356
+ This preserves responsiveness without requiring synchronous cross-aggregate updates.
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Saga Pattern for Multi-Step Processes
362
+
363
+ Sagas coordinate a sequence of aggregate updates with explicit compensation when steps fail.
364
+
365
+ ### Choreography-Based Saga
366
+
367
+ Each aggregate reacts to events and publishes its own. No central coordinator.
368
+
369
+ ```
370
+ OrderPlaced
371
+ → InventoryHandler: reserves stock → InventoryReserved
372
+ → InventoryReserved → PaymentHandler: charges card → PaymentCharged
373
+ → PaymentCharged → FulfillmentHandler: ships order → OrderShipped
374
+
375
+ Compensation chain on PaymentFailed:
376
+ PaymentFailed → InventoryHandler: releases reservation → InventoryReleased
377
+ InventoryReleased → OrderHandler: cancels order → OrderCancelled
378
+
379
+ Tradeoff: Simple to implement, difficult to trace the full process.
380
+ ```
381
+
382
+ ### Orchestration-Based Saga
383
+
384
+ A saga object drives the steps and handles compensation explicitly.
385
+
386
+ ```typescript
387
+ class OrderFulfillmentSaga {
388
+ private step: FulfillmentStep = FulfillmentStep.ReserveInventory;
389
+
390
+ async handle(event: DomainEvent): Promise<void> {
391
+ switch (this.step) {
392
+ case FulfillmentStep.ReserveInventory:
393
+ if (event instanceof OrderPlaced) {
394
+ await this.inventoryService.reserve(event.items);
395
+ this.step = FulfillmentStep.ChargePayment;
396
+ }
397
+ break;
398
+
399
+ case FulfillmentStep.ChargePayment:
400
+ if (event instanceof InventoryReserved) {
401
+ await this.paymentService.charge(event.orderId, event.amount);
402
+ this.step = FulfillmentStep.Ship;
403
+ }
404
+ if (event instanceof InventoryReservationFailed) {
405
+ await this.orderService.cancel(event.orderId, CancelReason.OutOfStock);
406
+ this.step = FulfillmentStep.Compensating;
407
+ }
408
+ break;
409
+
410
+ case FulfillmentStep.Ship:
411
+ if (event instanceof PaymentCharged) {
412
+ await this.fulfillmentService.ship(event.orderId);
413
+ this.step = FulfillmentStep.Complete;
414
+ }
415
+ if (event instanceof PaymentFailed) {
416
+ await this.inventoryService.release(event.orderId);
417
+ await this.orderService.cancel(event.orderId, CancelReason.PaymentFailed);
418
+ this.step = FulfillmentStep.Compensating;
419
+ }
420
+ break;
421
+ }
422
+ }
423
+ }
424
+ ```
425
+
426
+ ### When to Use Each
427
+
428
+ | Approach | Use When |
429
+ |----------|----------|
430
+ | **Choreography** | Simple 2-3 step processes, teams can coordinate on event contracts |
431
+ | **Orchestration** | Complex multi-step processes, compensation logic is non-trivial |
432
+ | **Neither** | Steps are fast, can be synchronous, business requires atomicity |
433
+
434
+ ---
435
+
436
+ ## Rules of Thumb
437
+
438
+ These are practical defaults, not laws. Use them as starting points and adjust based on invariants.
439
+
440
+ ```
441
+ 1. Default to one entity per aggregate.
442
+ Expand only when you can name the specific invariant that requires it.
443
+
444
+ 2. If you need to update two aggregates simultaneously, question the boundary.
445
+ Either they should be one aggregate, or eventual consistency is acceptable.
446
+
447
+ 3. An aggregate that is never loaded alone is a smell.
448
+ If you always load Order with Customer together, Customer may belong inside Order.
449
+ Or the operation should live in a different bounded context.
450
+
451
+ 4. Prefer many small aggregates over one large one.
452
+ A large aggregate is a coordination bottleneck under concurrent load.
453
+
454
+ 5. A repository method that returns a list of thousands is wrong.
455
+ Either the collection shouldn't be in the aggregate, or you need a read model.
456
+
457
+ 6. Use value objects for anything described by its attributes, not its identity.
458
+ If two instances with the same data are interchangeable, it's a value object.
459
+
460
+ 7. Events describe what happened, not what should happen next.
461
+ "OrderPlaced" is correct. "SendConfirmationEmail" is a command, not an event.
462
+
463
+ 8. Aggregate boundaries are not permanent.
464
+ Model based on current understanding. Refactor when invariants become clearer.
465
+ ```