@qazuor/claude-code-config 0.4.0 → 0.6.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.
- package/README.md +395 -50
- package/dist/bin.cjs +3207 -165
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3207 -165
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +75 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -1
- package/dist/index.d.ts +284 -1
- package/dist/index.js +75 -58
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/docs/_registry.json +54 -0
- package/templates/docs/standards/code-standards.md +20 -0
- package/templates/docs/standards/design-standards.md +13 -0
- package/templates/docs/standards/documentation-standards.md +13 -0
- package/templates/docs/standards/performance-standards.md +524 -0
- package/templates/docs/standards/security-standards.md +496 -0
- package/templates/docs/standards/testing-standards.md +15 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vertical-slice-architecture
|
|
3
|
+
category: patterns
|
|
4
|
+
description: Vertical Slice Architecture organizing code by feature instead of technical layers
|
|
5
|
+
usage: Use when you want to minimize cross-cutting changes and maximize feature cohesion
|
|
6
|
+
input: Feature requirements, use cases, domain operations
|
|
7
|
+
output: Self-contained feature slices with all layers co-located by functionality
|
|
8
|
+
config_required:
|
|
9
|
+
- FEATURES_DIR: "Directory for feature slices (e.g., src/features/)"
|
|
10
|
+
- SHARED_DIR: "Directory for truly shared code (e.g., src/shared/)"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Vertical Slice Architecture
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Vertical Slice Architecture, popularized by Jimmy Bogard, organizes code by feature (vertical slices) rather than technical layers (horizontal slices). Each feature contains everything it needs: handlers, validators, DTOs, and data access.
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
| Setting | Description | Example |
|
|
22
|
+
|---------|-------------|---------|
|
|
23
|
+
| FEATURES_DIR | Feature slices location | `src/features/`, `modules/` |
|
|
24
|
+
| SHARED_DIR | Truly shared utilities | `src/shared/`, `common/` |
|
|
25
|
+
| HANDLERS_PATTERN | Handler file naming | `*.handler.ts`, `*.command.ts` |
|
|
26
|
+
|
|
27
|
+
## Traditional Layers vs Vertical Slices
|
|
28
|
+
|
|
29
|
+
### Traditional (Horizontal Layers)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
src/
|
|
33
|
+
├── controllers/
|
|
34
|
+
│ ├── OrderController.ts # All order endpoints
|
|
35
|
+
│ ├── UserController.ts # All user endpoints
|
|
36
|
+
│ └── ProductController.ts # All product endpoints
|
|
37
|
+
├── services/
|
|
38
|
+
│ ├── OrderService.ts # All order logic
|
|
39
|
+
│ ├── UserService.ts # All user logic
|
|
40
|
+
│ └── ProductService.ts # All product logic
|
|
41
|
+
├── repositories/
|
|
42
|
+
│ ├── OrderRepository.ts # All order data access
|
|
43
|
+
│ ├── UserRepository.ts # All user data access
|
|
44
|
+
│ └── ProductRepository.ts # All product data access
|
|
45
|
+
└── models/
|
|
46
|
+
├── Order.ts
|
|
47
|
+
├── User.ts
|
|
48
|
+
└── Product.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Problem:** Adding a feature requires changes across multiple folders.
|
|
52
|
+
|
|
53
|
+
### Vertical Slices
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
src/features/
|
|
57
|
+
├── orders/
|
|
58
|
+
│ ├── create-order/
|
|
59
|
+
│ │ ├── CreateOrder.handler.ts
|
|
60
|
+
│ │ ├── CreateOrder.validator.ts
|
|
61
|
+
│ │ └── CreateOrder.test.ts
|
|
62
|
+
│ ├── get-order/
|
|
63
|
+
│ │ ├── GetOrder.handler.ts
|
|
64
|
+
│ │ └── GetOrder.test.ts
|
|
65
|
+
│ └── cancel-order/
|
|
66
|
+
│ ├── CancelOrder.handler.ts
|
|
67
|
+
│ ├── CancelOrder.validator.ts
|
|
68
|
+
│ └── CancelOrder.test.ts
|
|
69
|
+
├── users/
|
|
70
|
+
│ ├── register-user/
|
|
71
|
+
│ ├── login-user/
|
|
72
|
+
│ └── update-profile/
|
|
73
|
+
└── products/
|
|
74
|
+
├── create-product/
|
|
75
|
+
├── search-products/
|
|
76
|
+
└── update-inventory/
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Benefit:** Adding a feature means adding one folder.
|
|
80
|
+
|
|
81
|
+
## Slice Structure
|
|
82
|
+
|
|
83
|
+
### Command/Query Pattern
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// features/orders/create-order/CreateOrder.command.ts
|
|
87
|
+
export interface CreateOrderCommand {
|
|
88
|
+
userId: string;
|
|
89
|
+
items: Array<{
|
|
90
|
+
productId: string;
|
|
91
|
+
quantity: number;
|
|
92
|
+
}>;
|
|
93
|
+
shippingAddress: {
|
|
94
|
+
street: string;
|
|
95
|
+
city: string;
|
|
96
|
+
zipCode: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface CreateOrderResult {
|
|
101
|
+
orderId: string;
|
|
102
|
+
total: number;
|
|
103
|
+
estimatedDelivery: Date;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// features/orders/create-order/CreateOrder.handler.ts
|
|
107
|
+
import type { CreateOrderCommand, CreateOrderResult } from './CreateOrder.command';
|
|
108
|
+
import { db } from '../../../shared/database';
|
|
109
|
+
import { sendEmail } from '../../../shared/email';
|
|
110
|
+
import { calculateTotal } from './CreateOrder.utils';
|
|
111
|
+
|
|
112
|
+
export async function handleCreateOrder(
|
|
113
|
+
command: CreateOrderCommand
|
|
114
|
+
): Promise<CreateOrderResult> {
|
|
115
|
+
// Validate user exists
|
|
116
|
+
const user = await db.user.findUnique({ where: { id: command.userId } });
|
|
117
|
+
if (!user) {
|
|
118
|
+
throw new Error('User not found');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get product prices
|
|
122
|
+
const products = await db.product.findMany({
|
|
123
|
+
where: { id: { in: command.items.map((i) => i.productId) } },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Calculate total
|
|
127
|
+
const total = calculateTotal(command.items, products);
|
|
128
|
+
|
|
129
|
+
// Create order
|
|
130
|
+
const order = await db.order.create({
|
|
131
|
+
data: {
|
|
132
|
+
userId: command.userId,
|
|
133
|
+
items: command.items,
|
|
134
|
+
total,
|
|
135
|
+
shippingAddress: command.shippingAddress,
|
|
136
|
+
status: 'pending',
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Send confirmation
|
|
141
|
+
await sendEmail({
|
|
142
|
+
to: user.email,
|
|
143
|
+
subject: 'Order Confirmation',
|
|
144
|
+
body: `Your order ${order.id} has been placed.`,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
orderId: order.id,
|
|
149
|
+
total,
|
|
150
|
+
estimatedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// features/orders/create-order/CreateOrder.utils.ts
|
|
155
|
+
export function calculateTotal(
|
|
156
|
+
items: Array<{ productId: string; quantity: number }>,
|
|
157
|
+
products: Array<{ id: string; price: number }>
|
|
158
|
+
): number {
|
|
159
|
+
return items.reduce((total, item) => {
|
|
160
|
+
const product = products.find((p) => p.id === item.productId);
|
|
161
|
+
return total + (product?.price || 0) * item.quantity;
|
|
162
|
+
}, 0);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Validation
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// features/orders/create-order/CreateOrder.validator.ts
|
|
170
|
+
import { z } from 'zod';
|
|
171
|
+
|
|
172
|
+
export const createOrderSchema = z.object({
|
|
173
|
+
userId: z.string().uuid(),
|
|
174
|
+
items: z
|
|
175
|
+
.array(
|
|
176
|
+
z.object({
|
|
177
|
+
productId: z.string().uuid(),
|
|
178
|
+
quantity: z.number().int().positive().max(100),
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
.min(1)
|
|
182
|
+
.max(50),
|
|
183
|
+
shippingAddress: z.object({
|
|
184
|
+
street: z.string().min(1).max(200),
|
|
185
|
+
city: z.string().min(1).max(100),
|
|
186
|
+
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
export type CreateOrderInput = z.infer<typeof createOrderSchema>;
|
|
191
|
+
|
|
192
|
+
export function validateCreateOrder(input: unknown): CreateOrderInput {
|
|
193
|
+
return createOrderSchema.parse(input);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### API Route
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// features/orders/create-order/CreateOrder.route.ts
|
|
201
|
+
import type { Request, Response } from 'express';
|
|
202
|
+
import { handleCreateOrder } from './CreateOrder.handler';
|
|
203
|
+
import { validateCreateOrder } from './CreateOrder.validator';
|
|
204
|
+
|
|
205
|
+
export async function createOrderRoute(req: Request, res: Response): Promise<void> {
|
|
206
|
+
try {
|
|
207
|
+
const command = validateCreateOrder(req.body);
|
|
208
|
+
const result = await handleCreateOrder(command);
|
|
209
|
+
res.status(201).json(result);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof z.ZodError) {
|
|
212
|
+
res.status(400).json({ errors: error.errors });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Tests
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// features/orders/create-order/CreateOrder.test.ts
|
|
224
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
225
|
+
import { handleCreateOrder } from './CreateOrder.handler';
|
|
226
|
+
import { validateCreateOrder } from './CreateOrder.validator';
|
|
227
|
+
|
|
228
|
+
// Mock shared dependencies
|
|
229
|
+
vi.mock('../../../shared/database', () => ({
|
|
230
|
+
db: {
|
|
231
|
+
user: { findUnique: vi.fn() },
|
|
232
|
+
product: { findMany: vi.fn() },
|
|
233
|
+
order: { create: vi.fn() },
|
|
234
|
+
},
|
|
235
|
+
}));
|
|
236
|
+
|
|
237
|
+
vi.mock('../../../shared/email', () => ({
|
|
238
|
+
sendEmail: vi.fn(),
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
import { db } from '../../../shared/database';
|
|
242
|
+
import { sendEmail } from '../../../shared/email';
|
|
243
|
+
|
|
244
|
+
describe('CreateOrder', () => {
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
vi.clearAllMocks();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('validation', () => {
|
|
250
|
+
it('should accept valid input', () => {
|
|
251
|
+
const input = {
|
|
252
|
+
userId: '123e4567-e89b-12d3-a456-426614174000',
|
|
253
|
+
items: [{ productId: '123e4567-e89b-12d3-a456-426614174001', quantity: 2 }],
|
|
254
|
+
shippingAddress: { street: '123 Main St', city: 'Boston', zipCode: '02101' },
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
expect(() => validateCreateOrder(input)).not.toThrow();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should reject empty items', () => {
|
|
261
|
+
const input = {
|
|
262
|
+
userId: '123e4567-e89b-12d3-a456-426614174000',
|
|
263
|
+
items: [],
|
|
264
|
+
shippingAddress: { street: '123 Main St', city: 'Boston', zipCode: '02101' },
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
expect(() => validateCreateOrder(input)).toThrow();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('handler', () => {
|
|
272
|
+
it('should create order for valid user', async () => {
|
|
273
|
+
// Arrange
|
|
274
|
+
db.user.findUnique.mockResolvedValue({ id: 'user-1', email: 'test@example.com' });
|
|
275
|
+
db.product.findMany.mockResolvedValue([{ id: 'prod-1', price: 10 }]);
|
|
276
|
+
db.order.create.mockResolvedValue({ id: 'order-1' });
|
|
277
|
+
|
|
278
|
+
// Act
|
|
279
|
+
const result = await handleCreateOrder({
|
|
280
|
+
userId: 'user-1',
|
|
281
|
+
items: [{ productId: 'prod-1', quantity: 2 }],
|
|
282
|
+
shippingAddress: { street: '123 Main', city: 'Boston', zipCode: '02101' },
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Assert
|
|
286
|
+
expect(result.orderId).toBe('order-1');
|
|
287
|
+
expect(result.total).toBe(20);
|
|
288
|
+
expect(sendEmail).toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should throw for non-existent user', async () => {
|
|
292
|
+
db.user.findUnique.mockResolvedValue(null);
|
|
293
|
+
|
|
294
|
+
await expect(
|
|
295
|
+
handleCreateOrder({
|
|
296
|
+
userId: 'invalid',
|
|
297
|
+
items: [{ productId: 'prod-1', quantity: 1 }],
|
|
298
|
+
shippingAddress: { street: '123 Main', city: 'Boston', zipCode: '02101' },
|
|
299
|
+
})
|
|
300
|
+
).rejects.toThrow('User not found');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Complete Project Structure
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
src/
|
|
310
|
+
├── features/ # All feature slices
|
|
311
|
+
│ ├── orders/
|
|
312
|
+
│ │ ├── create-order/
|
|
313
|
+
│ │ │ ├── CreateOrder.command.ts
|
|
314
|
+
│ │ │ ├── CreateOrder.handler.ts
|
|
315
|
+
│ │ │ ├── CreateOrder.validator.ts
|
|
316
|
+
│ │ │ ├── CreateOrder.route.ts
|
|
317
|
+
│ │ │ ├── CreateOrder.utils.ts
|
|
318
|
+
│ │ │ └── CreateOrder.test.ts
|
|
319
|
+
│ │ ├── get-order/
|
|
320
|
+
│ │ │ ├── GetOrder.query.ts
|
|
321
|
+
│ │ │ ├── GetOrder.handler.ts
|
|
322
|
+
│ │ │ ├── GetOrder.route.ts
|
|
323
|
+
│ │ │ └── GetOrder.test.ts
|
|
324
|
+
│ │ ├── list-orders/
|
|
325
|
+
│ │ ├── cancel-order/
|
|
326
|
+
│ │ └── index.ts # Feature barrel export
|
|
327
|
+
│ ├── users/
|
|
328
|
+
│ │ ├── register/
|
|
329
|
+
│ │ ├── login/
|
|
330
|
+
│ │ ├── update-profile/
|
|
331
|
+
│ │ └── index.ts
|
|
332
|
+
│ └── products/
|
|
333
|
+
│ ├── create-product/
|
|
334
|
+
│ ├── search-products/
|
|
335
|
+
│ └── index.ts
|
|
336
|
+
├── shared/ # Truly shared code
|
|
337
|
+
│ ├── database/
|
|
338
|
+
│ │ ├── client.ts
|
|
339
|
+
│ │ └── index.ts
|
|
340
|
+
│ ├── email/
|
|
341
|
+
│ │ ├── sender.ts
|
|
342
|
+
│ │ └── index.ts
|
|
343
|
+
│ ├── auth/
|
|
344
|
+
│ │ ├── middleware.ts
|
|
345
|
+
│ │ └── index.ts
|
|
346
|
+
│ └── errors/
|
|
347
|
+
│ ├── AppError.ts
|
|
348
|
+
│ └── index.ts
|
|
349
|
+
├── app.ts # Application setup
|
|
350
|
+
└── routes.ts # Route registration
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Route Registration
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// routes.ts
|
|
357
|
+
import express from 'express';
|
|
358
|
+
import { createOrderRoute } from './features/orders/create-order/CreateOrder.route';
|
|
359
|
+
import { getOrderRoute } from './features/orders/get-order/GetOrder.route';
|
|
360
|
+
import { listOrdersRoute } from './features/orders/list-orders/ListOrders.route';
|
|
361
|
+
import { cancelOrderRoute } from './features/orders/cancel-order/CancelOrder.route';
|
|
362
|
+
import { registerRoute } from './features/users/register/Register.route';
|
|
363
|
+
import { loginRoute } from './features/users/login/Login.route';
|
|
364
|
+
|
|
365
|
+
const router = express.Router();
|
|
366
|
+
|
|
367
|
+
// Orders
|
|
368
|
+
router.post('/orders', createOrderRoute);
|
|
369
|
+
router.get('/orders/:id', getOrderRoute);
|
|
370
|
+
router.get('/orders', listOrdersRoute);
|
|
371
|
+
router.post('/orders/:id/cancel', cancelOrderRoute);
|
|
372
|
+
|
|
373
|
+
// Users
|
|
374
|
+
router.post('/users/register', registerRoute);
|
|
375
|
+
router.post('/users/login', loginRoute);
|
|
376
|
+
|
|
377
|
+
export default router;
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## MediatR Pattern (Optional)
|
|
381
|
+
|
|
382
|
+
For larger applications, use a mediator:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// shared/mediator/index.ts
|
|
386
|
+
type Handler<TRequest, TResponse> = (request: TRequest) => Promise<TResponse>;
|
|
387
|
+
|
|
388
|
+
class Mediator {
|
|
389
|
+
private handlers = new Map<string, Handler<any, any>>();
|
|
390
|
+
|
|
391
|
+
register<TRequest, TResponse>(
|
|
392
|
+
name: string,
|
|
393
|
+
handler: Handler<TRequest, TResponse>
|
|
394
|
+
): void {
|
|
395
|
+
this.handlers.set(name, handler);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async send<TRequest, TResponse>(
|
|
399
|
+
name: string,
|
|
400
|
+
request: TRequest
|
|
401
|
+
): Promise<TResponse> {
|
|
402
|
+
const handler = this.handlers.get(name);
|
|
403
|
+
if (!handler) throw new Error(`No handler for ${name}`);
|
|
404
|
+
return handler(request);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export const mediator = new Mediator();
|
|
409
|
+
|
|
410
|
+
// Registration
|
|
411
|
+
import { handleCreateOrder } from '../features/orders/create-order/CreateOrder.handler';
|
|
412
|
+
mediator.register('CreateOrder', handleCreateOrder);
|
|
413
|
+
|
|
414
|
+
// Usage in route
|
|
415
|
+
router.post('/orders', async (req, res) => {
|
|
416
|
+
const result = await mediator.send('CreateOrder', req.body);
|
|
417
|
+
res.json(result);
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## When to Duplicate vs Share
|
|
422
|
+
|
|
423
|
+
### Duplicate (per slice)
|
|
424
|
+
|
|
425
|
+
- DTOs/Commands/Queries
|
|
426
|
+
- Validation schemas
|
|
427
|
+
- Slice-specific utilities
|
|
428
|
+
- Response mappers
|
|
429
|
+
|
|
430
|
+
### Share (in shared/)
|
|
431
|
+
|
|
432
|
+
- Database client
|
|
433
|
+
- Email/notification services
|
|
434
|
+
- Authentication middleware
|
|
435
|
+
- Error handling
|
|
436
|
+
- Logging
|
|
437
|
+
|
|
438
|
+
### Rule of Thumb
|
|
439
|
+
|
|
440
|
+
> "When in doubt, duplicate. If you find yourself copying the same code into a third slice, consider extracting to shared."
|
|
441
|
+
|
|
442
|
+
## Best Practices
|
|
443
|
+
|
|
444
|
+
### Do's
|
|
445
|
+
|
|
446
|
+
- Keep slices independent and self-contained
|
|
447
|
+
- Test each slice in isolation
|
|
448
|
+
- Use clear naming: `CreateOrder`, `GetUser`, `SearchProducts`
|
|
449
|
+
- Favor duplication over wrong abstraction
|
|
450
|
+
- Keep shared code truly generic
|
|
451
|
+
|
|
452
|
+
### Don'ts
|
|
453
|
+
|
|
454
|
+
- Don't create "shared domain models" used by all slices
|
|
455
|
+
- Don't abstract too early
|
|
456
|
+
- Don't create service layers spanning multiple slices
|
|
457
|
+
- Don't share validators between slices
|
|
458
|
+
- Don't force slices to communicate through shared state
|
|
459
|
+
|
|
460
|
+
## Vertical Slice vs Layered
|
|
461
|
+
|
|
462
|
+
| Aspect | Vertical Slice | Layered Architecture |
|
|
463
|
+
|--------|---------------|---------------------|
|
|
464
|
+
| Code Organization | By feature | By technical concern |
|
|
465
|
+
| Adding Features | One folder | Multiple folders |
|
|
466
|
+
| Dependencies | Slice to shared | Layer to layer |
|
|
467
|
+
| Coupling | Low between slices | High between layers |
|
|
468
|
+
| Reuse | Explicit/intentional | Often forced |
|
|
469
|
+
| Testing | Per slice | Per layer |
|
|
470
|
+
|
|
471
|
+
## When to Use Vertical Slices
|
|
472
|
+
|
|
473
|
+
**Good for:**
|
|
474
|
+
|
|
475
|
+
- Feature-rich applications
|
|
476
|
+
- Teams working on separate features
|
|
477
|
+
- Rapid feature development
|
|
478
|
+
- Microservices preparation
|
|
479
|
+
- CQRS implementations
|
|
480
|
+
|
|
481
|
+
**Less suitable for:**
|
|
482
|
+
|
|
483
|
+
- Very small applications
|
|
484
|
+
- Heavy cross-cutting concerns
|
|
485
|
+
- Highly interconnected features
|
|
486
|
+
- Teams used to layered architecture
|
|
487
|
+
|
|
488
|
+
## Output
|
|
489
|
+
|
|
490
|
+
**Produces:**
|
|
491
|
+
|
|
492
|
+
- Self-contained feature slices
|
|
493
|
+
- Minimal cross-feature dependencies
|
|
494
|
+
- Easy-to-test handlers
|
|
495
|
+
- Clear feature boundaries
|
|
496
|
+
|
|
497
|
+
**Success Criteria:**
|
|
498
|
+
|
|
499
|
+
- Adding a feature = adding one folder
|
|
500
|
+
- Slices don't import from other slices
|
|
501
|
+
- Tests run per-slice without mocking other features
|
|
502
|
+
- Shared code is truly generic
|