@el-j/magic-helix-plugins 4.0.0-beta.2 → 4.0.0-beta.4
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/dist/architecture/codeowners.md +123 -0
- package/dist/architecture/monorepo.md +146 -0
- package/dist/architecture/nx.md +122 -0
- package/dist/architecture/turborepo.md +114 -0
- package/dist/ci/github-actions.md +268 -0
- package/dist/ci/gitlab-ci.md +330 -0
- package/dist/containers/docker-multistage.md +120 -0
- package/dist/containers/kubernetes-deploy.md +210 -0
- package/dist/cpp/index.cjs +79 -0
- package/dist/cpp/index.mjs +209 -0
- package/dist/csharp/index.cjs +2 -2
- package/dist/csharp/{index.js → index.mjs} +17 -11
- package/dist/csharp/templates/framework-aspnetcore.md +205 -0
- package/dist/csharp/templates/framework-blazor.md +271 -0
- package/dist/csharp/templates/lang-csharp.md +162 -0
- package/dist/devops/docker-compose.md +111 -0
- package/dist/devops/docker-dockerfile.md +94 -0
- package/dist/devops/github-actions.md +160 -0
- package/dist/devops/gitlab-ci.md +210 -0
- package/dist/generic/lang-typescript.md +57 -0
- package/dist/generic/state-redux.md +21 -0
- package/dist/generic/state-rxjs.md +6 -0
- package/dist/generic/style-mui.md +23 -0
- package/dist/generic/style-tailwind.md +76 -0
- package/dist/generic/test-cypress.md +21 -0
- package/dist/generic/test-jest.md +20 -0
- package/dist/generic/test-playwright.md +21 -0
- package/dist/generic/test-vitest.md +131 -0
- package/dist/go/index.cjs +3 -3
- package/dist/go/{index.js → index.mjs} +18 -15
- package/dist/go/templates/lang-go.md +571 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +24 -0
- package/dist/java/index.cjs +2 -2
- package/dist/java/{index.js → index.mjs} +25 -19
- package/dist/java/templates/build-gradle.md +102 -0
- package/dist/java/templates/build-maven.md +86 -0
- package/dist/java/templates/framework-spring-boot.md +179 -0
- package/dist/java/templates/lang-java.md +78 -0
- package/dist/java/templates/lang-kotlin.md +88 -0
- package/dist/meta/magic-helix-meta.md +213 -0
- package/dist/meta/meta-debug.md +459 -0
- package/dist/meta/meta-implement.md +450 -0
- package/dist/meta/meta-roadmap.md +265 -0
- package/dist/nodejs/templates/angular-core.md +19 -0
- package/dist/nodejs/templates/lang-typescript.md +57 -0
- package/dist/nodejs/templates/nestjs-core.md +7 -0
- package/dist/nodejs/templates/react-core.md +677 -0
- package/dist/nodejs/templates/react-zustand.md +7 -0
- package/dist/nodejs/templates/state-redux.md +21 -0
- package/dist/nodejs/templates/state-rxjs.md +6 -0
- package/dist/nodejs/templates/style-primevue.md +6 -0
- package/dist/nodejs/templates/style-quasar.md +22 -0
- package/dist/nodejs/templates/style-tailwind.md +76 -0
- package/dist/nodejs/templates/test-cypress.md +21 -0
- package/dist/nodejs/templates/test-jest.md +20 -0
- package/dist/nodejs/templates/test-playwright.md +21 -0
- package/dist/nodejs/templates/test-vitest.md +131 -0
- package/dist/nodejs/templates/vue-core.md +108 -0
- package/dist/nodejs/templates/vue-pinia.md +5 -0
- package/dist/patterns/architecture/clean-architecture.md +469 -0
- package/dist/patterns/architecture/dependency-injection.md +517 -0
- package/dist/patterns/architecture/domain-driven-design.md +621 -0
- package/dist/patterns/architecture/layered-architecture.md +382 -0
- package/dist/patterns/architecture/repository-pattern.md +408 -0
- package/dist/patterns/domain-expertise/nextjs-rules.md +115 -0
- package/dist/patterns/domain-expertise/react-patterns.md +181 -0
- package/dist/patterns/domain-expertise/server-components.md +212 -0
- package/dist/patterns/domain-expertise/shadcn-ui.md +52 -0
- package/dist/patterns/domain-expertise/tailwind-patterns.md +52 -0
- package/dist/patterns/environment/container-awareness.md +17 -0
- package/dist/patterns/environment/ide-features.md +17 -0
- package/dist/patterns/environment/os-commands.md +17 -0
- package/dist/patterns/organization/heading-hierarchy.md +103 -0
- package/dist/patterns/organization/sequential-workflows.md +102 -0
- package/dist/patterns/organization/xml-rule-groups.md +64 -0
- package/dist/patterns/reasoning/agent-loop.md +151 -0
- package/dist/patterns/reasoning/confirmation-gates.md +141 -0
- package/dist/patterns/reasoning/dependency-analysis.md +132 -0
- package/dist/patterns/reasoning/one-tool-per-iteration.md +152 -0
- package/dist/patterns/reasoning/preview-before-action.md +194 -0
- package/dist/patterns/reasoning/reflection-checkpoints.md +166 -0
- package/dist/patterns/reasoning/result-verification.md +157 -0
- package/dist/patterns/reasoning/subtask-breakdown.md +131 -0
- package/dist/patterns/reasoning/thinking-tags.md +100 -0
- package/dist/patterns/role-definition/capability-declarations.md +72 -0
- package/dist/patterns/role-definition/expert-identity.md +45 -0
- package/dist/patterns/role-definition/scope-boundaries.md +61 -0
- package/dist/patterns/safety/code-safety-rules.md +17 -0
- package/dist/patterns/safety/credential-handling.md +17 -0
- package/dist/patterns/safety/destructive-warnings.md +17 -0
- package/dist/patterns/safety/refusal-messages.md +17 -0
- package/dist/patterns/tone/adaptive-tone.md +17 -0
- package/dist/patterns/tone/concise-communication.md +17 -0
- package/dist/patterns/tone/forbidden-phrases.md +17 -0
- package/dist/patterns/tool-guidelines/function-schemas.md +143 -0
- package/dist/patterns/tool-guidelines/parameter-examples.md +137 -0
- package/dist/patterns/tool-guidelines/usage-policies.md +105 -0
- package/dist/php/index.cjs +2 -2
- package/dist/php/{index.js → index.mjs} +12 -6
- package/dist/php/templates/framework-laravel.md +112 -0
- package/dist/php/templates/lang-php.md +94 -0
- package/dist/python/index.cjs +4 -4
- package/dist/python/{index.js → index.mjs} +10 -7
- package/dist/python/templates/lang-python.md +508 -0
- package/dist/ruby/index.cjs +2 -2
- package/dist/ruby/{index.js → index.mjs} +16 -10
- package/dist/ruby/templates/framework-rails.md +309 -0
- package/dist/ruby/templates/framework-sinatra.md +227 -0
- package/dist/ruby/templates/lang-ruby.md +216 -0
- package/dist/rust/index.cjs +3 -3
- package/dist/rust/{index.js → index.mjs} +24 -18
- package/dist/rust/templates/lang-rust.md +89 -0
- package/dist/swift/index.cjs +32 -0
- package/dist/swift/index.mjs +112 -0
- package/dist/swift/templates/framework-vapor.md +352 -0
- package/dist/swift/templates/lang-swift.md +291 -0
- package/package.json +31 -21
- package/dist/index.js +0 -20
- /package/dist/nodejs/{index.js → index.mjs} +0 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
# Dependency Injection Pattern
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Remove hard dependencies between components. Inject dependencies from outside rather than creating them internally. Improves testability, flexibility, and maintainability.
|
|
5
|
+
|
|
6
|
+
## Core Principle
|
|
7
|
+
|
|
8
|
+
**"Don't create dependencies, receive them."**
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// ❌ Bad: Hard dependency (tight coupling)
|
|
12
|
+
class UserService {
|
|
13
|
+
private repository = new PostgresUserRepository(); // Creates its own dependency
|
|
14
|
+
|
|
15
|
+
async getUser(id: string) {
|
|
16
|
+
return this.repository.findById(id);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ✅ Good: Dependency injection (loose coupling)
|
|
21
|
+
class UserService {
|
|
22
|
+
constructor(private repository: IUserRepository) {} // Receives dependency
|
|
23
|
+
|
|
24
|
+
async getUser(id: string) {
|
|
25
|
+
return this.repository.findById(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Dependency Injection Methods
|
|
31
|
+
|
|
32
|
+
### 1. Constructor Injection (Recommended)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// ✅ Inject dependencies via constructor
|
|
36
|
+
export class OrderService {
|
|
37
|
+
constructor(
|
|
38
|
+
private orderRepository: IOrderRepository,
|
|
39
|
+
private paymentService: IPaymentService,
|
|
40
|
+
private emailService: IEmailService
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async createOrder(items: Item[]): Promise<Order> {
|
|
44
|
+
const order = new Order(items);
|
|
45
|
+
await this.orderRepository.save(order);
|
|
46
|
+
await this.paymentService.charge(order.total);
|
|
47
|
+
await this.emailService.sendOrderConfirmation(order);
|
|
48
|
+
return order;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Usage (manual wiring)
|
|
53
|
+
const orderService = new OrderService(
|
|
54
|
+
new PostgresOrderRepository(db),
|
|
55
|
+
new StripePaymentService(apiKey),
|
|
56
|
+
new SendGridEmailService(sendGridKey)
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Pros:**
|
|
61
|
+
- Dependencies are explicit and immutable
|
|
62
|
+
- Easy to test (pass mocks in constructor)
|
|
63
|
+
- Compiler enforces all dependencies are provided
|
|
64
|
+
|
|
65
|
+
### 2. Property Injection
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// ⚠️ Less common: Inject via properties
|
|
69
|
+
export class UserService {
|
|
70
|
+
public repository!: IUserRepository; // Set externally
|
|
71
|
+
|
|
72
|
+
async getUser(id: string) {
|
|
73
|
+
return this.repository.findById(id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Usage
|
|
78
|
+
const service = new UserService();
|
|
79
|
+
service.repository = new PostgresUserRepository(db);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Pros:**
|
|
83
|
+
- Optional dependencies
|
|
84
|
+
- Circular dependency resolution
|
|
85
|
+
|
|
86
|
+
**Cons:**
|
|
87
|
+
- Dependencies not explicit
|
|
88
|
+
- Can forget to set properties
|
|
89
|
+
- Harder to test
|
|
90
|
+
|
|
91
|
+
### 3. Method Injection
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// ⚠️ Rare: Inject per method call
|
|
95
|
+
export class ReportGenerator {
|
|
96
|
+
generateReport(data: Data, formatter: IReportFormatter): string {
|
|
97
|
+
return formatter.format(data);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Usage
|
|
102
|
+
const generator = new ReportGenerator();
|
|
103
|
+
const pdf = generator.generateReport(data, new PdfFormatter());
|
|
104
|
+
const excel = generator.generateReport(data, new ExcelFormatter());
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Use when:** Dependency varies per call
|
|
108
|
+
|
|
109
|
+
## Inversion of Control (IoC) Containers
|
|
110
|
+
|
|
111
|
+
### Manual Wiring (Simple Projects)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// ✅ composition-root.ts - Wire dependencies manually
|
|
115
|
+
export function createApp() {
|
|
116
|
+
// Infrastructure
|
|
117
|
+
const db = new PostgresDatabase(config.dbUrl);
|
|
118
|
+
const redis = new Redis(config.redisUrl);
|
|
119
|
+
|
|
120
|
+
// Repositories
|
|
121
|
+
const userRepo = new PostgresUserRepository(db);
|
|
122
|
+
const orderRepo = new PostgresOrderRepository(db);
|
|
123
|
+
|
|
124
|
+
// Services
|
|
125
|
+
const emailService = new SendGridEmailService(config.sendGridKey);
|
|
126
|
+
const paymentService = new StripePaymentService(config.stripeKey);
|
|
127
|
+
|
|
128
|
+
// Use Cases / Services
|
|
129
|
+
const userService = new UserService(userRepo, emailService);
|
|
130
|
+
const orderService = new OrderService(orderRepo, paymentService, emailService);
|
|
131
|
+
|
|
132
|
+
// Controllers
|
|
133
|
+
const userController = new UserController(userService);
|
|
134
|
+
const orderController = new OrderController(orderService);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
userController,
|
|
138
|
+
orderController,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### IoC Container (Larger Projects)
|
|
144
|
+
|
|
145
|
+
#### TypeScript with InversifyJS
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Container, injectable, inject } from 'inversify';
|
|
149
|
+
import 'reflect-metadata';
|
|
150
|
+
|
|
151
|
+
// ✅ Define identifiers
|
|
152
|
+
const TYPES = {
|
|
153
|
+
UserRepository: Symbol.for('IUserRepository'),
|
|
154
|
+
EmailService: Symbol.for('IEmailService'),
|
|
155
|
+
UserService: Symbol.for('UserService'),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ✅ Mark classes as injectable
|
|
159
|
+
@injectable()
|
|
160
|
+
class PostgresUserRepository implements IUserRepository {
|
|
161
|
+
constructor(@inject(TYPES.Database) private db: Database) {}
|
|
162
|
+
// ...
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@injectable()
|
|
166
|
+
class SendGridEmailService implements IEmailService {
|
|
167
|
+
// ...
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@injectable()
|
|
171
|
+
class UserService {
|
|
172
|
+
constructor(
|
|
173
|
+
@inject(TYPES.UserRepository) private userRepo: IUserRepository,
|
|
174
|
+
@inject(TYPES.EmailService) private emailService: IEmailService
|
|
175
|
+
) {}
|
|
176
|
+
// ...
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ✅ Configure container
|
|
180
|
+
const container = new Container();
|
|
181
|
+
container.bind<IUserRepository>(TYPES.UserRepository).to(PostgresUserRepository);
|
|
182
|
+
container.bind<IEmailService>(TYPES.EmailService).to(SendGridEmailService);
|
|
183
|
+
container.bind<UserService>(TYPES.UserService).to(UserService);
|
|
184
|
+
|
|
185
|
+
// ✅ Resolve dependencies
|
|
186
|
+
const userService = container.get<UserService>(TYPES.UserService);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### NestJS (Built-in DI)
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// ✅ NestJS uses decorators for DI
|
|
193
|
+
@Injectable()
|
|
194
|
+
export class UserService {
|
|
195
|
+
constructor(
|
|
196
|
+
@Inject('IUserRepository') private userRepo: IUserRepository,
|
|
197
|
+
private emailService: EmailService // Auto-resolves by type
|
|
198
|
+
) {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ✅ Register in module
|
|
202
|
+
@Module({
|
|
203
|
+
providers: [
|
|
204
|
+
UserService,
|
|
205
|
+
EmailService,
|
|
206
|
+
{
|
|
207
|
+
provide: 'IUserRepository',
|
|
208
|
+
useClass: PostgresUserRepository,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
export class UserModule {}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Python with dependency-injector
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from dependency_injector import containers, providers
|
|
219
|
+
|
|
220
|
+
# ✅ Define container
|
|
221
|
+
class Container(containers.DeclarativeContainer):
|
|
222
|
+
config = providers.Configuration()
|
|
223
|
+
|
|
224
|
+
# Infrastructure
|
|
225
|
+
database = providers.Singleton(
|
|
226
|
+
PostgresDatabase,
|
|
227
|
+
connection_string=config.db_url
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Repositories
|
|
231
|
+
user_repository = providers.Factory(
|
|
232
|
+
PostgresUserRepository,
|
|
233
|
+
db=database
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Services
|
|
237
|
+
email_service = providers.Singleton(
|
|
238
|
+
SendGridEmailService,
|
|
239
|
+
api_key=config.sendgrid_key
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
user_service = providers.Factory(
|
|
243
|
+
UserService,
|
|
244
|
+
user_repository=user_repository,
|
|
245
|
+
email_service=email_service
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# ✅ Usage
|
|
249
|
+
container = Container()
|
|
250
|
+
container.config.from_yaml('config.yaml')
|
|
251
|
+
|
|
252
|
+
user_service = container.user_service()
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Go (Manual DI recommended)
|
|
256
|
+
|
|
257
|
+
```go
|
|
258
|
+
// ✅ Go uses explicit dependency passing
|
|
259
|
+
type UserService struct {
|
|
260
|
+
userRepo UserRepository
|
|
261
|
+
emailService EmailService
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
func NewUserService(repo UserRepository, email EmailService) *UserService {
|
|
265
|
+
return &UserService{
|
|
266
|
+
userRepo: repo,
|
|
267
|
+
emailService: email,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ✅ Wire dependencies in main
|
|
272
|
+
func main() {
|
|
273
|
+
db := postgres.NewDatabase(connString)
|
|
274
|
+
userRepo := postgres.NewUserRepository(db)
|
|
275
|
+
emailService := sendgrid.NewEmailService(apiKey)
|
|
276
|
+
|
|
277
|
+
userService := NewUserService(userRepo, emailService)
|
|
278
|
+
|
|
279
|
+
// ...
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Or use wire.go for compile-time DI
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Service Locator (Anti-Pattern, Avoid)
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// ❌ Service Locator (looks like DI but isn't)
|
|
289
|
+
class ServiceLocator {
|
|
290
|
+
private services = new Map<string, any>();
|
|
291
|
+
|
|
292
|
+
register(name: string, service: any) {
|
|
293
|
+
this.services.set(name, service);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
get<T>(name: string): T {
|
|
297
|
+
return this.services.get(name);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ❌ Hidden dependency (bad)
|
|
302
|
+
class UserService {
|
|
303
|
+
async getUser(id: string) {
|
|
304
|
+
// Dependency hidden inside method - hard to test
|
|
305
|
+
const repo = ServiceLocator.get<IUserRepository>('UserRepository');
|
|
306
|
+
return repo.findById(id);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Why it's bad:**
|
|
312
|
+
- Hidden dependencies (not visible in constructor)
|
|
313
|
+
- Runtime errors if service not registered
|
|
314
|
+
- Hard to test
|
|
315
|
+
- Hard to understand dependencies
|
|
316
|
+
|
|
317
|
+
**Use constructor injection instead!**
|
|
318
|
+
|
|
319
|
+
## Testing with DI
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// ✅ Easy to test with constructor injection
|
|
323
|
+
describe('UserService', () => {
|
|
324
|
+
it('sends email after creating user', async () => {
|
|
325
|
+
// Create mocks
|
|
326
|
+
const mockRepo: IUserRepository = {
|
|
327
|
+
save: jest.fn(),
|
|
328
|
+
findByEmail: jest.fn().mockResolvedValue(null),
|
|
329
|
+
};
|
|
330
|
+
const mockEmail: IEmailService = {
|
|
331
|
+
send: jest.fn(),
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Inject mocks
|
|
335
|
+
const service = new UserService(mockRepo, mockEmail);
|
|
336
|
+
|
|
337
|
+
// Test
|
|
338
|
+
await service.createUser('test@example.com', 'password');
|
|
339
|
+
|
|
340
|
+
expect(mockEmail.send).toHaveBeenCalledWith(
|
|
341
|
+
expect.objectContaining({ to: 'test@example.com' })
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Lifetime Management
|
|
348
|
+
|
|
349
|
+
### Singleton (One instance for application)
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// ✅ Singleton: Shared instance
|
|
353
|
+
class DatabaseConnection {
|
|
354
|
+
private static instance: DatabaseConnection;
|
|
355
|
+
|
|
356
|
+
private constructor(private connectionString: string) {}
|
|
357
|
+
|
|
358
|
+
static getInstance(connectionString: string): DatabaseConnection {
|
|
359
|
+
if (!DatabaseConnection.instance) {
|
|
360
|
+
DatabaseConnection.instance = new DatabaseConnection(connectionString);
|
|
361
|
+
}
|
|
362
|
+
return DatabaseConnection.instance;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Or use IoC container
|
|
367
|
+
container.bind<Database>(TYPES.Database).toSingleton(PostgresDatabase);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Use for:** Database connections, configuration, caching services
|
|
371
|
+
|
|
372
|
+
### Transient (New instance per request)
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// ✅ Transient: New instance every time
|
|
376
|
+
container.bind<IUserService>(TYPES.UserService).toTransient(UserService);
|
|
377
|
+
|
|
378
|
+
// Every call creates new instance
|
|
379
|
+
const service1 = container.get<IUserService>(TYPES.UserService);
|
|
380
|
+
const service2 = container.get<IUserService>(TYPES.UserService);
|
|
381
|
+
// service1 !== service2
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Use for:** Stateful services, request handlers
|
|
385
|
+
|
|
386
|
+
### Scoped (One instance per scope/request)
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// ✅ Scoped: One instance per HTTP request
|
|
390
|
+
container.bind<IUserService>(TYPES.UserService).toScoped(UserService);
|
|
391
|
+
|
|
392
|
+
// In HTTP middleware
|
|
393
|
+
app.use((req, res, next) => {
|
|
394
|
+
req.container = container.createChild(); // New scope
|
|
395
|
+
next();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
app.get('/users', (req, res) => {
|
|
399
|
+
const service = req.container.get<IUserService>(TYPES.UserService);
|
|
400
|
+
// Same instance within this request
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Use for:** Request-scoped data (current user, transaction)
|
|
405
|
+
|
|
406
|
+
## Best Practices
|
|
407
|
+
|
|
408
|
+
### 1. Depend on Interfaces, Not Implementations
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// ✅ Good: Depend on interface
|
|
412
|
+
class UserService {
|
|
413
|
+
constructor(private repository: IUserRepository) {} // Interface
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ❌ Bad: Depend on concrete class
|
|
417
|
+
class UserService {
|
|
418
|
+
constructor(private repository: PostgresUserRepository) {} // Concrete
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### 2. Keep Dependencies Explicit
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// ✅ Good: All dependencies in constructor
|
|
426
|
+
class OrderService {
|
|
427
|
+
constructor(
|
|
428
|
+
private orderRepo: IOrderRepository,
|
|
429
|
+
private paymentService: IPaymentService,
|
|
430
|
+
private emailService: IEmailService,
|
|
431
|
+
private logger: ILogger
|
|
432
|
+
) {}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ❌ Bad: Hidden service locator
|
|
436
|
+
class OrderService {
|
|
437
|
+
async createOrder() {
|
|
438
|
+
const logger = ServiceLocator.get('Logger'); // Hidden!
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 3. Don't Inject Configuration Objects
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// ❌ Bad: Inject entire config
|
|
447
|
+
class EmailService {
|
|
448
|
+
constructor(private config: AppConfig) {
|
|
449
|
+
this.apiKey = config.sendGrid.apiKey; // Knows too much
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ✅ Good: Inject only what's needed
|
|
454
|
+
class EmailService {
|
|
455
|
+
constructor(private apiKey: string) {}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 4. Avoid Circular Dependencies
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// ❌ Bad: Circular dependency
|
|
463
|
+
class UserService {
|
|
464
|
+
constructor(private orderService: OrderService) {}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
class OrderService {
|
|
468
|
+
constructor(private userService: UserService) {} // Circular!
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ✅ Good: Extract shared logic to new service
|
|
472
|
+
class UserService {
|
|
473
|
+
constructor(private notificationService: NotificationService) {}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
class OrderService {
|
|
477
|
+
constructor(private notificationService: NotificationService) {}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## File Organization
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
src/
|
|
485
|
+
├── core/
|
|
486
|
+
│ ├── interfaces/ # Dependency interfaces
|
|
487
|
+
│ │ ├── IUserRepository.ts
|
|
488
|
+
│ │ ├── IEmailService.ts
|
|
489
|
+
│ │ └── IPaymentService.ts
|
|
490
|
+
│ └── services/
|
|
491
|
+
│ ├── UserService.ts # Depends on interfaces
|
|
492
|
+
│ └── OrderService.ts
|
|
493
|
+
│
|
|
494
|
+
├── infrastructure/
|
|
495
|
+
│ ├── repositories/ # Interface implementations
|
|
496
|
+
│ │ └── PostgresUserRepository.ts
|
|
497
|
+
│ ├── services/
|
|
498
|
+
│ │ ├── SendGridEmailService.ts
|
|
499
|
+
│ │ └── StripePaymentService.ts
|
|
500
|
+
│ └── di/ # DI configuration
|
|
501
|
+
│ └── container.ts
|
|
502
|
+
│
|
|
503
|
+
└── composition-root.ts # Wire dependencies
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Rules Summary
|
|
507
|
+
|
|
508
|
+
- **ALWAYS** use constructor injection for required dependencies
|
|
509
|
+
- **ALWAYS** depend on interfaces, not concrete classes
|
|
510
|
+
- **ALWAYS** keep dependencies explicit (visible in constructor)
|
|
511
|
+
- **ALWAYS** wire dependencies at composition root (e.g., main.ts, app.ts)
|
|
512
|
+
- **NEVER** use service locator pattern
|
|
513
|
+
- **NEVER** create dependencies inside classes (new SomeDependency())
|
|
514
|
+
- **NEVER** access global state from classes
|
|
515
|
+
- **PREFER** manual DI for simple projects, IoC containers for complex ones
|
|
516
|
+
- **PREFER** immutable dependencies (constructor injection)
|
|
517
|
+
- **CONSIDER** property injection only for optional dependencies or circular refs
|