@venizia/ignis-docs 0.0.3 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +51 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# Architecture Decisions Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you make informed architectural decisions when building applications with Ignis. Learn when to use different patterns and how to scale your application.
|
|
4
|
+
|
|
5
|
+
## Common Decision Points
|
|
6
|
+
|
|
7
|
+
| Decision | Options | Recommendation |
|
|
8
|
+
|----------|---------|----------------|
|
|
9
|
+
| Service layer? | Direct repo vs Service | Use Service for business logic |
|
|
10
|
+
| Component vs inline? | Reusable vs one-off | Component if used 2+ times |
|
|
11
|
+
| Repository methods? | CRUD only vs custom | Start CRUD, add custom as needed |
|
|
12
|
+
| Error handling? | Service vs Controller | Handle in Controller, log in Service |
|
|
13
|
+
| Transactions? | Manual vs automatic | Use repository transaction support |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. When to Use Services vs Direct Repository
|
|
18
|
+
|
|
19
|
+
### Use Direct Repository Access When:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Simple CRUD with no business logic
|
|
23
|
+
@controller({ path: '/items' })
|
|
24
|
+
export class ItemController extends BaseController {
|
|
25
|
+
constructor(
|
|
26
|
+
@inject('repositories.ItemRepository')
|
|
27
|
+
private itemRepo: ItemRepository,
|
|
28
|
+
) {
|
|
29
|
+
super({ scope: 'ItemController', path: '/items' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@get({ configs: { path: '/:id' } })
|
|
33
|
+
async getItem(c: Context) {
|
|
34
|
+
const item = await this.itemRepo.findById(c.req.param('id'));
|
|
35
|
+
return c.json(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Good for:**
|
|
41
|
+
- Simple read operations
|
|
42
|
+
- Basic CRUD endpoints
|
|
43
|
+
- Prototypes and MVPs
|
|
44
|
+
- Admin panels
|
|
45
|
+
|
|
46
|
+
### Use Service Layer When:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Complex business logic needs a service
|
|
50
|
+
@controller({ path: '/orders' })
|
|
51
|
+
export class OrderController extends BaseController {
|
|
52
|
+
constructor(
|
|
53
|
+
@inject('services.OrderService')
|
|
54
|
+
private orderService: OrderService,
|
|
55
|
+
) {
|
|
56
|
+
super({ scope: 'OrderController', path: '/orders' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@post({ configs: { path: '/' } })
|
|
60
|
+
async createOrder(c: Context) {
|
|
61
|
+
const data = await c.req.json();
|
|
62
|
+
// Service handles: validation, inventory check, payment, notifications
|
|
63
|
+
const order = await this.orderService.createOrder(data);
|
|
64
|
+
return c.json(order, 201);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Good for:**
|
|
70
|
+
- Multiple repository interactions
|
|
71
|
+
- External service calls (payments, email)
|
|
72
|
+
- Complex validation rules
|
|
73
|
+
- Transaction management
|
|
74
|
+
- Business rule enforcement
|
|
75
|
+
|
|
76
|
+
### Decision Matrix
|
|
77
|
+
|
|
78
|
+
| Scenario | Repository | Service |
|
|
79
|
+
|----------|------------|---------|
|
|
80
|
+
| Get user by ID | Yes | No |
|
|
81
|
+
| Create order with payment | No | Yes |
|
|
82
|
+
| List products with filters | Yes | No |
|
|
83
|
+
| User registration with email | No | Yes |
|
|
84
|
+
| Update product price | Yes | Maybe |
|
|
85
|
+
| Process refund | No | Yes |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. When to Create Components
|
|
90
|
+
|
|
91
|
+
### Create a Component When:
|
|
92
|
+
|
|
93
|
+
1. **Functionality is used across multiple applications**
|
|
94
|
+
2. **Feature is self-contained with its own configuration**
|
|
95
|
+
3. **You want to share with the team/community**
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Component: Self-contained, configurable, reusable
|
|
99
|
+
@component({ scope: 'NotificationComponent' })
|
|
100
|
+
export class NotificationComponent extends BaseComponent {
|
|
101
|
+
private emailService: EmailService;
|
|
102
|
+
private smsService: SMSService;
|
|
103
|
+
private pushService: PushService;
|
|
104
|
+
|
|
105
|
+
override configure() {
|
|
106
|
+
// Setup services based on configuration
|
|
107
|
+
this.emailService = new EmailService(this.config.email);
|
|
108
|
+
if (this.config.sms?.enabled) {
|
|
109
|
+
this.smsService = new SMSService(this.config.sms);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async notify(opts: NotifyOptions) {
|
|
114
|
+
// Unified notification API
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Keep Inline When:
|
|
120
|
+
|
|
121
|
+
1. **Feature is specific to one application**
|
|
122
|
+
2. **Logic is simple and unlikely to change**
|
|
123
|
+
3. **No configuration needed**
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Inline: Simple, one-off, no need for abstraction
|
|
127
|
+
@controller({ path: '/health' })
|
|
128
|
+
export class HealthController extends BaseController {
|
|
129
|
+
@get({ configs: { path: '/' } })
|
|
130
|
+
healthCheck(c: Context) {
|
|
131
|
+
return c.json({ status: 'ok', timestamp: new Date() });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Component vs Service vs Inline
|
|
137
|
+
|
|
138
|
+
| Pattern | Scope | Reusability | Configuration |
|
|
139
|
+
|---------|-------|-------------|---------------|
|
|
140
|
+
| **Component** | Cross-app | High | External config |
|
|
141
|
+
| **Service** | Single app | Medium | Internal |
|
|
142
|
+
| **Inline** | Single controller | None | None |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 3. Repository Method Design
|
|
147
|
+
|
|
148
|
+
### Start with Standard CRUD
|
|
149
|
+
|
|
150
|
+
Every repository gets these methods from `BaseRepository`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Inherited methods - use these first
|
|
154
|
+
find(filter) // List with filters
|
|
155
|
+
findById(id) // Get by ID
|
|
156
|
+
findOne(filter) // Get first match
|
|
157
|
+
create(data) // Create new
|
|
158
|
+
updateById(id, data) // Update existing
|
|
159
|
+
deleteById(id) // Delete
|
|
160
|
+
count(filter) // Count matches
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Add Custom Methods When:
|
|
164
|
+
|
|
165
|
+
1. **Query is complex and reusable**
|
|
166
|
+
2. **Business logic belongs at data layer**
|
|
167
|
+
3. **Performance optimization needed**
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Custom repository methods
|
|
171
|
+
export class OrderRepository extends BaseRepository<Order> {
|
|
172
|
+
// Complex query that's used in multiple places
|
|
173
|
+
async findPendingOrdersOlderThan(hours: number) {
|
|
174
|
+
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
175
|
+
return this.find({
|
|
176
|
+
where: {
|
|
177
|
+
status: 'pending',
|
|
178
|
+
createdAt: { lt: cutoff },
|
|
179
|
+
},
|
|
180
|
+
orderBy: { createdAt: 'asc' },
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Performance-optimized query
|
|
185
|
+
async getOrderStats(userId: string) {
|
|
186
|
+
return this.db.execute(sql`
|
|
187
|
+
SELECT
|
|
188
|
+
COUNT(*) as total,
|
|
189
|
+
SUM(total) as revenue,
|
|
190
|
+
AVG(total) as average
|
|
191
|
+
FROM orders
|
|
192
|
+
WHERE user_id = ${userId}
|
|
193
|
+
`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Business logic at data layer
|
|
197
|
+
async softDelete(id: string) {
|
|
198
|
+
return this.updateById(id, {
|
|
199
|
+
deletedAt: new Date(),
|
|
200
|
+
status: 'deleted',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 4. Error Handling Strategy
|
|
209
|
+
|
|
210
|
+
### Controller Level: Format Response
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
@controller({ path: '/users' })
|
|
214
|
+
export class UserController extends BaseController {
|
|
215
|
+
@post({ configs: { path: '/' } })
|
|
216
|
+
async createUser(c: Context) {
|
|
217
|
+
try {
|
|
218
|
+
const data = await c.req.json();
|
|
219
|
+
const user = await this.userService.create(data);
|
|
220
|
+
return c.json(user, 201);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// Format error for API response
|
|
223
|
+
if (error.code === 'DUPLICATE_EMAIL') {
|
|
224
|
+
return c.json({ error: 'Email already exists' }, 400);
|
|
225
|
+
}
|
|
226
|
+
throw error; // Let global handler catch unknown errors
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Service Level: Throw Domain Errors
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
@injectable()
|
|
236
|
+
export class UserService extends BaseService {
|
|
237
|
+
async create(data: CreateUserInput) {
|
|
238
|
+
// Validate and throw domain-specific errors
|
|
239
|
+
const existing = await this.userRepo.findByEmail(data.email);
|
|
240
|
+
if (existing) {
|
|
241
|
+
throw getError({
|
|
242
|
+
statusCode: 400,
|
|
243
|
+
code: 'DUPLICATE_EMAIL',
|
|
244
|
+
message: 'User with this email already exists',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Log operations
|
|
249
|
+
this.logger.info('Creating user', { email: data.email });
|
|
250
|
+
|
|
251
|
+
return this.userRepo.create(data);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Repository Level: Let Errors Bubble
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
export class UserRepository extends BaseRepository<User> {
|
|
260
|
+
// Don't catch database errors here
|
|
261
|
+
// Let them bubble up to service/controller
|
|
262
|
+
async findByEmail(email: string) {
|
|
263
|
+
return this.findOne({ where: { email } });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Error Handling Flow
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Repository (DB errors)
|
|
272
|
+
↓ bubbles up
|
|
273
|
+
Service (catches, transforms to domain errors, logs)
|
|
274
|
+
↓ throws
|
|
275
|
+
Controller (catches, formats for API response)
|
|
276
|
+
↓ responds
|
|
277
|
+
Client (receives formatted error)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 5. Scaling Decisions
|
|
283
|
+
|
|
284
|
+
### When to Split Services
|
|
285
|
+
|
|
286
|
+
**Before:**
|
|
287
|
+
```typescript
|
|
288
|
+
// Monolithic service doing too much
|
|
289
|
+
class UserService {
|
|
290
|
+
async register(data) { /* ... */ }
|
|
291
|
+
async login(data) { /* ... */ }
|
|
292
|
+
async updateProfile(data) { /* ... */ }
|
|
293
|
+
async sendPasswordReset(email) { /* ... */ }
|
|
294
|
+
async verifyEmail(token) { /* ... */ }
|
|
295
|
+
async sendWelcomeEmail(userId) { /* ... */ }
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**After:**
|
|
300
|
+
```typescript
|
|
301
|
+
// Split by domain
|
|
302
|
+
class AuthService {
|
|
303
|
+
async register(data) { /* ... */ }
|
|
304
|
+
async login(data) { /* ... */ }
|
|
305
|
+
async sendPasswordReset(email) { /* ... */ }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
class ProfileService {
|
|
309
|
+
async updateProfile(data) { /* ... */ }
|
|
310
|
+
async verifyEmail(token) { /* ... */ }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class NotificationService {
|
|
314
|
+
async sendWelcomeEmail(userId) { /* ... */ }
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Signs You Need to Split
|
|
319
|
+
|
|
320
|
+
| Symptom | Solution |
|
|
321
|
+
|---------|----------|
|
|
322
|
+
| Service > 500 lines | Split by domain |
|
|
323
|
+
| > 10 dependencies | Extract sub-services |
|
|
324
|
+
| Circular dependencies | Restructure or use events |
|
|
325
|
+
| Hard to test | Smaller, focused services |
|
|
326
|
+
|
|
327
|
+
### Microservices vs Monolith
|
|
328
|
+
|
|
329
|
+
| Factor | Stay Monolith | Consider Microservices |
|
|
330
|
+
|--------|---------------|------------------------|
|
|
331
|
+
| Team size | < 10 developers | > 20 developers |
|
|
332
|
+
| Deployment | Single deploy OK | Need independent deploys |
|
|
333
|
+
| Scale | Uniform scaling | Different scaling needs |
|
|
334
|
+
| Data | Shared database OK | Need data isolation |
|
|
335
|
+
| Complexity | Keep simple | Worth the overhead |
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 6. Data Access Patterns
|
|
340
|
+
|
|
341
|
+
### Repository per Aggregate
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Good: One repository per aggregate root
|
|
345
|
+
OrderRepository // Manages Order + OrderItems
|
|
346
|
+
UserRepository // Manages User + UserSettings
|
|
347
|
+
ProductRepository // Manages Product + ProductVariants
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Avoid: Repository per Table
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Avoid: Too granular, leads to anemic domain model
|
|
354
|
+
OrderRepository
|
|
355
|
+
OrderItemRepository // Should be part of OrderRepository
|
|
356
|
+
OrderStatusRepository // Probably doesn't need its own repo
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### When to Use Raw Queries
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Use repository methods for most cases
|
|
363
|
+
const orders = await orderRepo.find({ where: { userId } });
|
|
364
|
+
|
|
365
|
+
// Use raw queries for:
|
|
366
|
+
// 1. Complex aggregations
|
|
367
|
+
const stats = await db.execute(sql`
|
|
368
|
+
SELECT category, COUNT(*), AVG(price)
|
|
369
|
+
FROM products
|
|
370
|
+
GROUP BY category
|
|
371
|
+
`);
|
|
372
|
+
|
|
373
|
+
// 2. Performance-critical paths
|
|
374
|
+
const results = await db.execute(sql`
|
|
375
|
+
SELECT * FROM products
|
|
376
|
+
WHERE tsv @@ plainto_tsquery(${search})
|
|
377
|
+
LIMIT 10
|
|
378
|
+
`);
|
|
379
|
+
|
|
380
|
+
// 3. Database-specific features
|
|
381
|
+
const nearby = await db.execute(sql`
|
|
382
|
+
SELECT * FROM stores
|
|
383
|
+
WHERE ST_DWithin(location, ${point}, 5000)
|
|
384
|
+
`);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 7. Configuration Strategy
|
|
390
|
+
|
|
391
|
+
### Environment Variables
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Use for: secrets, environment-specific values
|
|
395
|
+
const config = {
|
|
396
|
+
database: {
|
|
397
|
+
host: EnvHelper.get('APP_ENV_POSTGRES_HOST'),
|
|
398
|
+
password: EnvHelper.get('APP_ENV_POSTGRES_PASSWORD'),
|
|
399
|
+
},
|
|
400
|
+
stripe: {
|
|
401
|
+
secretKey: EnvHelper.get('STRIPE_SECRET_KEY'),
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Application Config
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// Use for: application defaults, feature flags
|
|
410
|
+
const appConfig = {
|
|
411
|
+
pagination: {
|
|
412
|
+
defaultLimit: 20,
|
|
413
|
+
maxLimit: 100,
|
|
414
|
+
},
|
|
415
|
+
features: {
|
|
416
|
+
enableBetaFeatures: process.env.NODE_ENV !== 'production',
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Component Config
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// Use for: component-specific settings
|
|
425
|
+
this.component(SwaggerComponent, {
|
|
426
|
+
title: 'My API',
|
|
427
|
+
version: '1.0.0',
|
|
428
|
+
path: '/doc',
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 8. Testing Strategy
|
|
435
|
+
|
|
436
|
+
### What to Test at Each Layer
|
|
437
|
+
|
|
438
|
+
| Layer | Test Type | Focus |
|
|
439
|
+
|-------|-----------|-------|
|
|
440
|
+
| **Controller** | Integration | HTTP, validation, response format |
|
|
441
|
+
| **Service** | Unit | Business logic, edge cases |
|
|
442
|
+
| **Repository** | Integration | Queries, data integrity |
|
|
443
|
+
| **Component** | Unit | Configuration, lifecycle |
|
|
444
|
+
|
|
445
|
+
### Test Pyramid
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
/\
|
|
449
|
+
/ \ E2E (few)
|
|
450
|
+
/----\
|
|
451
|
+
/ \ Integration (some)
|
|
452
|
+
/--------\
|
|
453
|
+
/ \ Unit (many)
|
|
454
|
+
--------------
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Quick Reference
|
|
460
|
+
|
|
461
|
+
### Checklist for New Features
|
|
462
|
+
|
|
463
|
+
1. **[ ] Is it cross-cutting?** → Component
|
|
464
|
+
2. **[ ] Has business logic?** → Service
|
|
465
|
+
3. **[ ] Simple CRUD?** → Repository directly
|
|
466
|
+
4. **[ ] Reusable query?** → Custom repository method
|
|
467
|
+
5. **[ ] Complex validation?** → Service layer
|
|
468
|
+
6. **[ ] External API?** → Service with error handling
|
|
469
|
+
7. **[ ] Needs transactions?** → Service orchestrating repos
|
|
470
|
+
|
|
471
|
+
### Common Mistakes to Avoid
|
|
472
|
+
|
|
473
|
+
| Mistake | Better Approach |
|
|
474
|
+
|---------|-----------------|
|
|
475
|
+
| Fat controllers | Move logic to services |
|
|
476
|
+
| Anemic services | Add business logic, not just pass-through |
|
|
477
|
+
| Repository per table | Repository per aggregate |
|
|
478
|
+
| Catching all errors | Let appropriate errors bubble |
|
|
479
|
+
| Premature optimization | Start simple, optimize when needed |
|
|
480
|
+
| Over-engineering | YAGNI - build what you need now |
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## See Also
|
|
485
|
+
|
|
486
|
+
- [Architectural Patterns](./architectural-patterns.md) - Layered architecture details
|
|
487
|
+
- [Core Concepts](../guides/core-concepts/application/) - Framework fundamentals
|
|
488
|
+
- [Performance Optimization](./performance-optimization.md) - Scaling techniques
|