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,688 @@
1
+ # DDD Pattern Implementation Examples
2
+
3
+ ## Context
4
+
5
+ This guide provides concrete implementation examples for each DDD tactical pattern. Use these when translating domain concepts into code, reviewing existing domain models, or teaching DDD practices to a team. Examples use TypeScript pseudocode with the e-commerce domain as a consistent thread.
6
+
7
+ ---
8
+
9
+ ## Entities
10
+
11
+ Entities have identity that persists over time. Two entities are equal if they share the same ID, regardless of attribute differences.
12
+
13
+ ### Correct Implementation
14
+
15
+ ```typescript
16
+ class Order {
17
+ private readonly id: OrderId;
18
+ private status: OrderStatus;
19
+ private items: OrderItem[];
20
+ private customerId: CustomerId; // Reference by ID — never embed Customer object
21
+
22
+ constructor(id: OrderId, customerId: CustomerId) {
23
+ this.id = id;
24
+ this.customerId = customerId;
25
+ this.status = OrderStatus.Draft;
26
+ this.items = [];
27
+ }
28
+
29
+ addItem(productId: ProductId, quantity: Quantity, price: Money): void {
30
+ this.guardDraftStatus('add items');
31
+ const existing = this.items.find(i => i.productId.equals(productId));
32
+ if (existing) {
33
+ existing.increaseQuantity(quantity);
34
+ } else {
35
+ this.items.push(new OrderItem(productId, quantity, price));
36
+ }
37
+ }
38
+
39
+ submit(): void {
40
+ this.guardDraftStatus('submit');
41
+ if (this.items.length === 0) throw new Error('Cannot submit empty order');
42
+ this.status = OrderStatus.Placed;
43
+ this.addEvent(new OrderPlaced(this.id, this.customerId, this.items, this.total));
44
+ }
45
+
46
+ get total(): Money {
47
+ return this.items.reduce(
48
+ (sum, item) => sum.add(item.subtotal),
49
+ Money.zero('USD')
50
+ );
51
+ }
52
+
53
+ equals(other: Order): boolean {
54
+ return this.id.equals(other.id); // Identity-based equality
55
+ }
56
+
57
+ private guardDraftStatus(operation: string): void {
58
+ if (this.status !== OrderStatus.Draft) {
59
+ throw new Error(`Cannot ${operation} on a ${this.status} order`);
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### Anti-Pattern: Anemic Entity
66
+
67
+ ```typescript
68
+ // WRONG: State exposed, no behavior, logic lives elsewhere
69
+ class Order {
70
+ id: string;
71
+ status: string;
72
+ items: any[];
73
+ customerId: string;
74
+ }
75
+
76
+ // WRONG: OrderService doing what Order should do
77
+ class OrderService {
78
+ submit(order: Order): void {
79
+ if (order.status !== 'draft') throw new Error('...');
80
+ if (order.items.length === 0) throw new Error('...');
81
+ order.status = 'placed'; // Mutating from outside
82
+ }
83
+ }
84
+ ```
85
+
86
+ **Why it matters**: Business rule "cannot submit an empty order" lives in `OrderService`. When a second service also needs to submit orders, the rule either gets duplicated or the service becomes a dependency. The entity cannot protect its own invariants.
87
+
88
+ ---
89
+
90
+ ## Value Objects
91
+
92
+ Value objects describe characteristics. Two value objects are equal if all their attributes match. They are always immutable — operations return new instances.
93
+
94
+ ### Correct Implementation: Money
95
+
96
+ ```typescript
97
+ class Money {
98
+ private readonly amount: number;
99
+ private readonly currency: Currency;
100
+
101
+ constructor(amount: number, currency: Currency) {
102
+ if (amount < 0) throw new Error(`Amount cannot be negative: ${amount}`);
103
+ if (!currency) throw new Error('Currency is required');
104
+ this.amount = Math.round(amount * 100) / 100; // Store in cents precision
105
+ this.currency = currency;
106
+ }
107
+
108
+ static zero(currency: string): Money {
109
+ return new Money(0, Currency.of(currency));
110
+ }
111
+
112
+ add(other: Money): Money {
113
+ this.guardSameCurrency(other);
114
+ return new Money(this.amount + other.amount, this.currency);
115
+ }
116
+
117
+ subtract(other: Money): Money {
118
+ this.guardSameCurrency(other);
119
+ const result = this.amount - other.amount;
120
+ return new Money(result, this.currency);
121
+ }
122
+
123
+ multiply(factor: number): Money {
124
+ if (factor < 0) throw new Error('Cannot multiply by negative factor');
125
+ return new Money(this.amount * factor, this.currency);
126
+ }
127
+
128
+ isGreaterThan(other: Money): boolean {
129
+ this.guardSameCurrency(other);
130
+ return this.amount > other.amount;
131
+ }
132
+
133
+ format(): string {
134
+ return `${this.currency.symbol}${this.amount.toFixed(2)}`;
135
+ }
136
+
137
+ equals(other: Money): boolean {
138
+ return this.amount === other.amount && this.currency.equals(other.currency);
139
+ }
140
+
141
+ private guardSameCurrency(other: Money): void {
142
+ if (!this.currency.equals(other.currency)) {
143
+ throw new Error(`Currency mismatch: ${this.currency} vs ${other.currency}`);
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Correct Implementation: Address
150
+
151
+ ```typescript
152
+ class Address {
153
+ constructor(
154
+ public readonly street: string,
155
+ public readonly city: string,
156
+ public readonly postalCode: PostalCode,
157
+ public readonly country: Country
158
+ ) {
159
+ if (!street.trim()) throw new Error('Street is required');
160
+ if (!city.trim()) throw new Error('City is required');
161
+ }
162
+
163
+ // Returns new instance — does not mutate
164
+ withCity(city: string): Address {
165
+ return new Address(this.street, city, this.postalCode, this.country);
166
+ }
167
+
168
+ equals(other: Address): boolean {
169
+ return (
170
+ this.street === other.street &&
171
+ this.city === other.city &&
172
+ this.postalCode.equals(other.postalCode) &&
173
+ this.country.equals(other.country)
174
+ );
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Anti-Pattern: Primitive Obsession
180
+
181
+ ```typescript
182
+ // WRONG: Primitive types for domain concepts
183
+ function placeOrder(
184
+ customerId: string, // Is this a UUID? An email? A user number?
185
+ shippingStreet: string,
186
+ shippingCity: string,
187
+ shippingZip: string, // Is zip validation done? Where?
188
+ price: number, // USD? EUR? Cents? Dollars?
189
+ currency: string
190
+ ): void { ... }
191
+
192
+ // CORRECT: Value objects carry meaning and validate themselves
193
+ function placeOrder(
194
+ customerId: CustomerId,
195
+ shippingAddress: Address, // Validated, complete, typed
196
+ price: Money // Currency-aware, non-negative
197
+ ): void { ... }
198
+ ```
199
+
200
+ **Why it matters**: `string` for a postal code accepts `"hello"`. `PostalCode` validates format at construction. The type system communicates intent and prevents entire classes of bugs.
201
+
202
+ ---
203
+
204
+ ## Aggregates
205
+
206
+ An aggregate is a cluster of domain objects treated as a single unit. The aggregate root is the only entry point — external code cannot reach inside to modify children directly.
207
+
208
+ ### Correct Implementation: Order Aggregate
209
+
210
+ ```typescript
211
+ class Order { // Aggregate root
212
+ private readonly id: OrderId;
213
+ private items: OrderItem[]; // Part of aggregate — not exposed directly
214
+ private status: OrderStatus;
215
+ private readonly events: DomainEvent[] = [];
216
+
217
+ // Only the root exposes behavior — callers never touch OrderItem directly
218
+ addItem(productId: ProductId, quantity: Quantity, unitPrice: Money): void {
219
+ this.guardCanModify();
220
+ const item = this.findItem(productId);
221
+ if (item) {
222
+ item.increaseQuantity(quantity);
223
+ } else {
224
+ this.items.push(OrderItem.create(productId, quantity, unitPrice));
225
+ }
226
+ }
227
+
228
+ removeItem(productId: ProductId): void {
229
+ this.guardCanModify();
230
+ const index = this.items.findIndex(i => i.productId.equals(productId));
231
+ if (index === -1) throw new Error(`Item ${productId} not in order`);
232
+ this.items.splice(index, 1);
233
+ }
234
+
235
+ // Invariant: cannot submit with zero items
236
+ // Invariant: cannot submit non-draft order
237
+ submit(): void {
238
+ this.guardCanModify();
239
+ if (this.items.length === 0) {
240
+ throw new DomainError('Order must have at least one item to be submitted');
241
+ }
242
+ this.status = OrderStatus.Placed;
243
+ this.events.push(new OrderPlaced(this.id, this.snapshot()));
244
+ }
245
+
246
+ // Read model — callers can observe but not mutate children
247
+ getItems(): ReadonlyArray<OrderItemSnapshot> {
248
+ return this.items.map(i => i.toSnapshot());
249
+ }
250
+
251
+ pullEvents(): DomainEvent[] {
252
+ const events = [...this.events];
253
+ this.events.length = 0;
254
+ return events;
255
+ }
256
+
257
+ private findItem(productId: ProductId): OrderItem | undefined {
258
+ return this.items.find(i => i.productId.equals(productId));
259
+ }
260
+
261
+ private guardCanModify(): void {
262
+ if (this.status !== OrderStatus.Draft) {
263
+ throw new DomainError(`Cannot modify a ${this.status} order`);
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ ### Aggregate: Cross-Reference by ID
270
+
271
+ ```typescript
272
+ // WRONG: Holding object reference across aggregate boundary
273
+ class Order {
274
+ customer: Customer; // Entire Customer aggregate embedded — creates coupling
275
+ }
276
+
277
+ // CORRECT: Reference by identity
278
+ class Order {
279
+ customerId: CustomerId; // Lookup when needed, no coupling to Customer aggregate
280
+ }
281
+
282
+ // WRONG: Order directly modifying inventory
283
+ class OrderService {
284
+ async submitOrder(orderId: OrderId): Promise<void> {
285
+ const order = await this.orderRepo.findById(orderId);
286
+ const inventory = await this.inventoryRepo.findByProduct(order.productId);
287
+ order.submit();
288
+ inventory.decrement(order.quantity); // Two aggregates in one transaction
289
+ await this.db.transaction(() => {
290
+ this.orderRepo.save(order);
291
+ this.inventoryRepo.save(inventory); // Violates one-aggregate-per-transaction
292
+ });
293
+ }
294
+ }
295
+
296
+ // CORRECT: Order raises event, inventory responds eventually
297
+ class OrderService {
298
+ async submitOrder(orderId: OrderId): Promise<void> {
299
+ const order = await this.orderRepo.findById(orderId);
300
+ order.submit(); // Produces OrderPlaced event
301
+ await this.orderRepo.save(order); // Single aggregate persisted
302
+ // OrderPlaced event → InventoryHandler reduces stock (eventual consistency)
303
+ }
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Repositories
310
+
311
+ Repositories abstract persistence. They provide collection-like semantics — you put aggregates in, you get aggregates out — without leaking database concerns into the domain.
312
+
313
+ ### Correct Implementation
314
+
315
+ ```typescript
316
+ // Interface in domain layer — no persistence technology references
317
+ interface OrderRepository {
318
+ findById(id: OrderId): Promise<Order | null>;
319
+ findByCustomer(customerId: CustomerId, options?: QueryOptions): Promise<Order[]>;
320
+ findPendingFulfillment(): Promise<Order[]>;
321
+ save(order: Order): Promise<void>;
322
+ delete(id: OrderId): Promise<void>;
323
+ }
324
+
325
+ // Implementation in infrastructure layer
326
+ class PostgresOrderRepository implements OrderRepository {
327
+ constructor(private readonly db: DatabaseClient) {}
328
+
329
+ async findById(id: OrderId): Promise<Order | null> {
330
+ const row = await this.db.queryOne(
331
+ 'SELECT * FROM orders WHERE id = $1',
332
+ [id.value]
333
+ );
334
+ return row ? this.reconstitute(row) : null;
335
+ }
336
+
337
+ async save(order: Order): Promise<void> {
338
+ const data = this.decompose(order);
339
+ await this.db.upsert('orders', data);
340
+ // Dispatch domain events after successful save
341
+ const events = order.pullEvents();
342
+ await this.eventBus.publishAll(events);
343
+ }
344
+
345
+ // Reconstitution rebuilds the full aggregate from raw data
346
+ private reconstitute(row: OrderRow): Order {
347
+ return Order.reconstitute({
348
+ id: OrderId.of(row.id),
349
+ customerId: CustomerId.of(row.customer_id),
350
+ status: OrderStatus[row.status],
351
+ items: row.items.map(this.reconstituteItem),
352
+ });
353
+ }
354
+
355
+ private decompose(order: Order): OrderRow {
356
+ // Snapshot aggregate to persistence format
357
+ const snapshot = order.toSnapshot();
358
+ return {
359
+ id: snapshot.id,
360
+ customer_id: snapshot.customerId,
361
+ status: snapshot.status,
362
+ items: snapshot.items,
363
+ updated_at: new Date(),
364
+ };
365
+ }
366
+ }
367
+ ```
368
+
369
+ ### Anti-Pattern: Repository as Query Dumping Ground
370
+
371
+ ```typescript
372
+ // WRONG: Repository leaking infrastructure concerns into callers
373
+ interface OrderRepository {
374
+ findById(id: string): Promise<OrderRow>; // Returns DB row, not domain object
375
+ executeQuery(sql: string): Promise<any[]>; // Domain layer writing SQL
376
+ findWithJoin(table: string, on: string): Promise<any>; // Structural leakage
377
+ }
378
+
379
+ // WRONG: Domain service building queries
380
+ class OrderService {
381
+ async getOrdersForDashboard(): Promise<Order[]> {
382
+ return this.db.query(`
383
+ SELECT o.*, c.name, p.title
384
+ FROM orders o
385
+ JOIN customers c ON o.customer_id = c.id
386
+ ...
387
+ `);
388
+ }
389
+ }
390
+
391
+ // CORRECT: Named queries that reveal domain intent
392
+ interface OrderRepository {
393
+ findById(id: OrderId): Promise<Order | null>;
394
+ findPendingFulfillment(): Promise<Order[]>; // Domain language, not SQL
395
+ findByCustomer(customerId: CustomerId): Promise<Order[]>;
396
+ }
397
+ ```
398
+
399
+ ---
400
+
401
+ ## Domain Events
402
+
403
+ Domain events record facts about the past. They are immutable and named in past tense using domain vocabulary.
404
+
405
+ ### Correct Implementation
406
+
407
+ ```typescript
408
+ // Base contract — all events share this structure
409
+ interface DomainEvent {
410
+ readonly eventId: string;
411
+ readonly occurredAt: Date;
412
+ readonly aggregateId: string;
413
+ readonly eventType: string;
414
+ }
415
+
416
+ class OrderPlaced implements DomainEvent {
417
+ readonly eventId = crypto.randomUUID();
418
+ readonly occurredAt = new Date();
419
+ readonly eventType = 'OrderPlaced';
420
+
421
+ constructor(
422
+ readonly aggregateId: string, // orderId
423
+ readonly customerId: string,
424
+ readonly items: ReadonlyArray<OrderItemData>,
425
+ readonly totalAmount: MoneyData,
426
+ readonly placedAt: Date // Domain-meaningful timestamp
427
+ ) {}
428
+ }
429
+
430
+ class PaymentFailed implements DomainEvent {
431
+ readonly eventId = crypto.randomUUID();
432
+ readonly occurredAt = new Date();
433
+ readonly eventType = 'PaymentFailed';
434
+
435
+ constructor(
436
+ readonly aggregateId: string, // orderId
437
+ readonly reason: PaymentFailureReason,
438
+ readonly attemptedAmount: MoneyData,
439
+ readonly failedAt: Date
440
+ ) {}
441
+ }
442
+
443
+ // Aggregate collects events, repository dispatches them after save
444
+ class Order {
445
+ private events: DomainEvent[] = [];
446
+
447
+ submit(): void {
448
+ this.status = OrderStatus.Placed;
449
+ this.events.push(new OrderPlaced(
450
+ this.id.value,
451
+ this.customerId.value,
452
+ this.items.map(i => i.toData()),
453
+ this.total.toData(),
454
+ new Date()
455
+ ));
456
+ }
457
+
458
+ pullEvents(): DomainEvent[] {
459
+ const events = [...this.events];
460
+ this.events.length = 0;
461
+ return events;
462
+ }
463
+ }
464
+ ```
465
+
466
+ ### Event Handlers
467
+
468
+ ```typescript
469
+ // Handler in the same service — synchronous or via in-process bus
470
+ class InventoryHandler {
471
+ async handle(event: OrderPlaced): Promise<void> {
472
+ for (const item of event.items) {
473
+ const inventory = await this.inventoryRepo.findByProduct(
474
+ ProductId.of(item.productId)
475
+ );
476
+ inventory.reserve(Quantity.of(item.quantity));
477
+ await this.inventoryRepo.save(inventory);
478
+ }
479
+ }
480
+ }
481
+
482
+ // Handler across service boundary — integration event via message broker
483
+ class NotificationService {
484
+ async handle(event: OrderPlaced): Promise<void> {
485
+ const customer = await this.customerRepo.findById(
486
+ CustomerId.of(event.customerId)
487
+ );
488
+ await this.emailGateway.sendOrderConfirmation(customer.email, event);
489
+ }
490
+ }
491
+ ```
492
+
493
+ ### Anti-Pattern: Command Masquerading as Event
494
+
495
+ ```typescript
496
+ // WRONG: Event telling other contexts what to do
497
+ class OrderSubmitted {
498
+ command = 'RESERVE_INVENTORY'; // Not an event — it's an instruction
499
+ productId: string;
500
+ quantity: number;
501
+ }
502
+
503
+ // WRONG: Mutable event (events are immutable facts)
504
+ class OrderPlaced {
505
+ orderId: string;
506
+ status: string; // Mutable — callers could change it
507
+ items: Item[]; // Mutable array
508
+ }
509
+
510
+ // CORRECT: Immutable fact with all data needed for consumers
511
+ class OrderPlaced {
512
+ constructor(
513
+ readonly orderId: string,
514
+ readonly items: ReadonlyArray<OrderItemData>, // Immutable snapshot
515
+ readonly totalAmount: MoneyData,
516
+ readonly placedAt: Date
517
+ ) {}
518
+ }
519
+ ```
520
+
521
+ ---
522
+
523
+ ## Domain Services
524
+
525
+ Domain services encapsulate business logic that naturally involves multiple aggregates or domain concepts but does not belong to any single one.
526
+
527
+ ### When to Use Domain Services
528
+
529
+ Use a domain service when:
530
+ - The operation spans multiple aggregates
531
+ - The operation requires external data to enforce a business rule
532
+ - Placing the logic in an entity would create an unnatural dependency
533
+
534
+ ### Correct Implementation
535
+
536
+ ```typescript
537
+ // QUESTION: Who calculates shipping cost?
538
+ // Not Order — it doesn't know shipping rates.
539
+ // Not ShippingRate — it doesn't know order contents.
540
+ // A domain service bridges them.
541
+
542
+ class ShippingCostCalculator {
543
+ constructor(private readonly rateRepository: ShippingRateRepository) {}
544
+
545
+ async calculate(order: Order, destination: Address): Promise<Money> {
546
+ const weight = order.totalWeight;
547
+ const zone = ShippingZone.forAddress(destination);
548
+ const rate = await this.rateRepository.findRate(weight, zone);
549
+
550
+ if (!rate) {
551
+ throw new DomainError(`No shipping rate for zone ${zone} at weight ${weight}`);
552
+ }
553
+
554
+ return rate.applyTo(weight);
555
+ }
556
+ }
557
+
558
+ // Transfer between accounts — neither Account alone can enforce the rule
559
+ class FundsTransferService {
560
+ async transfer(
561
+ sourceId: AccountId,
562
+ destinationId: AccountId,
563
+ amount: Money
564
+ ): Promise<void> {
565
+ const source = await this.accountRepo.findById(sourceId);
566
+ const destination = await this.accountRepo.findById(destinationId);
567
+
568
+ if (!source.hasSufficientFunds(amount)) {
569
+ throw new InsufficientFundsError(sourceId, amount);
570
+ }
571
+
572
+ source.debit(amount);
573
+ destination.credit(amount);
574
+
575
+ // Each save publishes its own events — no cross-aggregate transaction
576
+ await this.accountRepo.save(source);
577
+ await this.accountRepo.save(destination);
578
+ }
579
+ }
580
+ ```
581
+
582
+ ### Anti-Pattern: Domain Service as Catch-All
583
+
584
+ ```typescript
585
+ // WRONG: Domain service doing what the entity should do
586
+ class OrderService {
587
+ calculateTotal(order: Order): Money { // Order should own this
588
+ return order.items.reduce((sum, item) => sum + item.price, 0);
589
+ }
590
+
591
+ isEligibleForDiscount(order: Order): boolean { // Business rule — belongs in Order
592
+ return order.items.length > 5;
593
+ }
594
+
595
+ canBeShipped(order: Order): boolean { // Order invariant — belongs in Order
596
+ return order.status === 'placed';
597
+ }
598
+ }
599
+ ```
600
+
601
+ **Why it matters**: When business logic lives in a service instead of the entity, every caller must import the service to perform basic operations. The domain model becomes a passive data structure — the anemic domain model anti-pattern at the service level.
602
+
603
+ ---
604
+
605
+ ## Bounded Contexts
606
+
607
+ Bounded contexts establish explicit boundaries where a specific domain model and ubiquitous language apply.
608
+
609
+ ### Modeling the Same Concept Differently
610
+
611
+ ```
612
+ Scenario: "Product" in an e-commerce platform
613
+
614
+ ┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐
615
+ │ Catalog Context │ │ Inventory Context │ │ Pricing Context │
616
+ ├──────────────────────────┤ ├──────────────────────────┤ ├──────────────────────────┤
617
+ │ Product: │ │ StockKeepingUnit: │ │ PricedItem: │
618
+ │ - name │ │ - sku │ │ - productId │
619
+ │ - description │ │ - warehouseLocation │ │ - basePrice │
620
+ │ - images │ │ - quantity │ │ - discountRules │
621
+ │ - categories │ │ - reorderThreshold │ │ - taxCategory │
622
+ │ - specifications │ │ - supplierId │ │ - effectiveFrom │
623
+ └──────────────────────────┘ └──────────────────────────┘ └──────────────────────────┘
624
+ │ │ │
625
+ └──────────────────────────────┴──────────────────────────────┘
626
+ Shared identifier: productId
627
+ Different model, different language, different team
628
+ ```
629
+
630
+ ### Anti-Corruption Layer
631
+
632
+ ```typescript
633
+ // External supplier API speaks a different language
634
+ // ACL translates without polluting the domain model
635
+
636
+ interface SupplierApi {
637
+ getProductData(ean: string): Promise<SupplierProductRecord>;
638
+ }
639
+
640
+ // Raw supplier vocabulary — not your domain's language
641
+ interface SupplierProductRecord {
642
+ GTIN: string;
643
+ PROD_NAME: string;
644
+ WAREHOUSE_CODE: string;
645
+ AVAIL_QTY: number;
646
+ REORDER_LVL: number;
647
+ }
648
+
649
+ // ACL translates to your ubiquitous language
650
+ class SupplierInventoryAdapter {
651
+ constructor(private readonly supplierApi: SupplierApi) {}
652
+
653
+ async getStockLevel(productId: ProductId): Promise<StockLevel> {
654
+ const raw = await this.supplierApi.getProductData(productId.ean);
655
+ return new StockLevel(
656
+ ProductId.of(raw.GTIN),
657
+ Quantity.of(raw.AVAIL_QTY),
658
+ Quantity.of(raw.REORDER_LVL),
659
+ WarehouseCode.of(raw.WAREHOUSE_CODE)
660
+ );
661
+ }
662
+ }
663
+
664
+ // Domain service uses your language — supplier details hidden behind adapter
665
+ class InventoryService {
666
+ async checkReorderNeeded(productId: ProductId): Promise<boolean> {
667
+ const stock = await this.supplierAdapter.getStockLevel(productId);
668
+ return stock.isBelowReorderThreshold();
669
+ }
670
+ }
671
+ ```
672
+
673
+ ---
674
+
675
+ ## Common Mistakes Summary
676
+
677
+ | Mistake | Symptom | Fix |
678
+ |---------|---------|-----|
679
+ | **Anemic domain model** | Services contain all business logic, entities are data bags | Move business rules into entity methods |
680
+ | **Oversized aggregates** | Lock contention, slow loads, unrelated data changes together | Split on invariant boundaries, use eventual consistency |
681
+ | **Primitive obsession** | `string` for email, `number` for price, no validation | Create value objects for domain concepts |
682
+ | **Cross-aggregate references** | Entity holds reference to another aggregate's object | Reference by ID only |
683
+ | **Multiple aggregates per transaction** | Transaction spans two repositories | Use domain events and eventual consistency |
684
+ | **Repository as query service** | Repository has 30 methods, returns DTOs or raw rows | Separate read models from aggregate repositories |
685
+ | **Commands masquerading as events** | Event names like `SendConfirmationEmail` | Name events in past tense: `OrderPlaced` |
686
+ | **Business logic in event handlers** | Handlers make business decisions, not just reactions | Handlers update state only; business rules stay in aggregates |
687
+ | **Missing ubiquitous language** | Code uses technical terms, domain experts can't read it | Rename classes and methods to match domain vocabulary |
688
+ | **Shared database between contexts** | Two services join tables across context boundaries | Each context owns its data; integrate via events or APIs |