@aicgen/aicgen 1.0.0-beta.2 → 1.0.1

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 (39) hide show
  1. package/.agent/rules/api-design.md +649 -0
  2. package/.agent/rules/architecture.md +2507 -0
  3. package/.agent/rules/best-practices.md +622 -0
  4. package/.agent/rules/code-style.md +308 -0
  5. package/.agent/rules/design-patterns.md +577 -0
  6. package/.agent/rules/devops.md +230 -0
  7. package/.agent/rules/error-handling.md +417 -0
  8. package/.agent/rules/instructions.md +28 -0
  9. package/.agent/rules/language.md +786 -0
  10. package/.agent/rules/performance.md +710 -0
  11. package/.agent/rules/security.md +587 -0
  12. package/.agent/rules/testing.md +572 -0
  13. package/.agent/workflows/add-documentation.md +10 -0
  14. package/.agent/workflows/generate-integration-tests.md +10 -0
  15. package/.agent/workflows/generate-unit-tests.md +11 -0
  16. package/.agent/workflows/performance-audit.md +11 -0
  17. package/.agent/workflows/refactor-extract-module.md +12 -0
  18. package/.agent/workflows/security-audit.md +12 -0
  19. package/.gemini/instructions.md +4843 -0
  20. package/AGENTS.md +9 -11
  21. package/bun.lock +755 -4
  22. package/claude.md +2 -2
  23. package/config.example.yml +129 -0
  24. package/config.yml +38 -0
  25. package/data/guideline-mappings.yml +128 -0
  26. package/data/language/dart/async.md +289 -0
  27. package/data/language/dart/basics.md +280 -0
  28. package/data/language/dart/error-handling.md +355 -0
  29. package/data/language/dart/index.md +10 -0
  30. package/data/language/dart/testing.md +352 -0
  31. package/data/language/swift/basics.md +477 -0
  32. package/data/language/swift/concurrency.md +654 -0
  33. package/data/language/swift/error-handling.md +679 -0
  34. package/data/language/swift/swiftui-mvvm.md +795 -0
  35. package/data/language/swift/testing.md +708 -0
  36. package/data/version.json +10 -8
  37. package/dist/index.js +50295 -29101
  38. package/jest.config.js +46 -0
  39. package/package.json +13 -2
@@ -0,0 +1,2507 @@
1
+ # Architecture Rules
2
+
3
+ # Service Boundaries
4
+
5
+ ## Defining Service Boundaries
6
+
7
+ Each service should own a specific business capability:
8
+
9
+ ```
10
+ ✅ Good Boundaries:
11
+ - User Service: Authentication, profiles, preferences
12
+ - Order Service: Order processing, fulfillment
13
+ - Payment Service: Payment processing, billing
14
+ - Notification Service: Emails, SMS, push notifications
15
+
16
+ ❌ Bad Boundaries:
17
+ - Data Access Service (technical, not business)
18
+ - Utility Service (too generic)
19
+ - God Service (does everything)
20
+ ```
21
+
22
+ ## Bounded Contexts
23
+
24
+ Use Domain-Driven Design to identify boundaries:
25
+
26
+ - Each service represents a bounded context
27
+ - Services are organized around business domains
28
+ - Clear ownership of data and logic
29
+ - Services should be independently deployable
30
+
31
+ ## Ownership Rules
32
+
33
+ **Each service:**
34
+ - Owns its own database (no shared databases)
35
+ - Owns its domain logic
36
+ - Exposes well-defined APIs
37
+ - Can be developed by autonomous teams
38
+
39
+ ## Communication Rules
40
+
41
+ **Avoid:**
42
+ - Direct database access between services
43
+ - Chatty communication (N+1 service calls)
44
+ - Tight coupling through shared libraries
45
+
46
+ **Prefer:**
47
+ - API-based communication
48
+ - Event-driven for data synchronization
49
+ - Async messaging where possible
50
+
51
+ ## Data Ownership
52
+
53
+ ```text
54
+ // ✅ Good - Service owns its data
55
+ Class OrderService:
56
+ Method CreateOrder(data):
57
+ # Order service owns order data
58
+ Order = OrderRepository.Save(data)
59
+
60
+ # Publish event for other services
61
+ EventBus.Publish("order.created", {
62
+ orderId: Order.id,
63
+ userId: Order.userId,
64
+ total: Order.total
65
+ })
66
+
67
+ Return Order
68
+
69
+ // ❌ Bad - Direct access to another service's database
70
+ Class OrderService:
71
+ Method CreateOrder(data):
72
+ # Don't do this!
73
+ User = UserDatabase.FindOne({ id: data.userId })
74
+ ```
75
+
76
+ ## Sizing Guidelines
77
+
78
+ Keep services:
79
+ - Small enough to be maintained by a small team (2-3 developers)
80
+ - Large enough to provide business value
81
+ - Focused on a single bounded context
82
+ - Independently deployable and scalable
83
+
84
+
85
+ ---
86
+
87
+ # Microservices Communication
88
+
89
+ ## Synchronous vs Asynchronous
90
+
91
+ ```text
92
+ # ⚠️ Synchronous creates coupling and multiplicative downtime
93
+ # If Service A calls B calls C, any failure breaks the chain
94
+
95
+ # ✅ Prefer asynchronous messaging for most inter-service communication
96
+ # Limit synchronous calls to one per user request
97
+
98
+ # Async Message Format Example
99
+ Event: "order.created"
100
+ Data: {
101
+ orderId: "ord_123",
102
+ userId: "user_456",
103
+ items: [...]
104
+ }
105
+
106
+ # Subscribers process independently
107
+ Service Inventory -> ReserveItems(items)
108
+ Service Notification -> SendEmail(user)
109
+ ```
110
+
111
+ ## API-Based Communication
112
+
113
+ ```text
114
+ # ✅ Well-defined REST APIs between services
115
+ GET /users/{userId}
116
+
117
+ # ✅ Use circuit breaker for resilience
118
+ Function getUserSafe(userId):
119
+ Try:
120
+ return UserClient.getUser(userId)
121
+ Catch Error:
122
+ return getCachedUser(userId) # Fallback
123
+ ```
124
+
125
+ ## Event-Driven Integration
126
+
127
+ ```text
128
+ # ✅ Publish events for state changes
129
+ Function CreateOrder(data):
130
+ order = Repository.Save(data)
131
+
132
+ # Failures here don't block the user
133
+ EventBus.Publish("order.created", {
134
+ orderId: order.id,
135
+ userId: order.userId,
136
+ timestamp: Now()
137
+ })
138
+
139
+ return order
140
+
141
+ # ✅ Consumers handle events independently (Decoupled)
142
+ Service Notification:
143
+ On("order.created"): SendConfirmation(event)
144
+
145
+ Service Inventory:
146
+ On("order.created"): ReserveInventory(event)
147
+ ```
148
+
149
+ ## Tolerant Reader Pattern
150
+
151
+ ```text
152
+ # ✅ Don't fail on unknown fields - enables independent evolution
153
+ Structure UserResponse:
154
+ id: string
155
+ name: string
156
+ ...ignore other fields...
157
+
158
+ # ✅ Use sensible defaults for missing optional fields
159
+ Function ParseUser(data):
160
+ return User {
161
+ id: data.id,
162
+ name: data.name,
163
+ role: data.role OR 'user', # Default
164
+ avatar: data.avatar OR null
165
+ }
166
+ ```
167
+
168
+ ## Anti-Patterns
169
+
170
+ ```text
171
+ # ❌ Chatty communication (N+1 service calls)
172
+ For each orderId in orderIds:
173
+ GetOrder(orderId) # N network calls!
174
+
175
+ # ✅ Batch requests
176
+ GetOrders(orderIds) # 1 network call
177
+
178
+ # ❌ Tight coupling via shared databases
179
+ # Service A directly queries Service B's tables
180
+
181
+ # ✅ API-based communication
182
+ UserClient.GetUsers(userIds)
183
+ ```
184
+
185
+
186
+ ---
187
+
188
+ # Microservices Data Management
189
+
190
+ ## Database Per Service
191
+
192
+ ```
193
+ Each service owns its database:
194
+
195
+ ✅ Order Service → order_db (PostgreSQL)
196
+ ✅ User Service → user_db (PostgreSQL)
197
+ ✅ Catalog Service → catalog_db (MongoDB)
198
+ ✅ Search Service → search_index (Elasticsearch)
199
+
200
+ ❌ Never share databases between services
201
+ ❌ Never query another service's tables directly
202
+ ```
203
+
204
+ ## Polyglot Persistence
205
+
206
+ ```text
207
+ # Each service uses the best database for its needs
208
+
209
+ # User Service -> Relational (ACID, relationships)
210
+ Repository UserRepository:
211
+ Method Create(user):
212
+ SQL "INSERT INTO users..."
213
+
214
+ # Catalog Service -> Document (Flexible schema)
215
+ Repository ProductRepository:
216
+ Method Create(product):
217
+ Collection("products").Insert(product)
218
+
219
+ # Analytics Service -> Time-Series (High write volume)
220
+ Repository MetricsRepository:
221
+ Method Record(metric):
222
+ InfluxDB.Write(metric)
223
+ ```
224
+
225
+ ## Eventual Consistency
226
+
227
+ ```text
228
+ # ✅ Embrace eventual consistency for cross-service data
229
+
230
+ 1. Order Service: Save Order -> Publish "order.created"
231
+ 2. Inventory Service: Listen "order.created" -> Reserve Inventory
232
+
233
+ # Data may be temporarily inconsistent - that's OK
234
+
235
+ # ✅ Use compensating actions for failures
236
+ Function ProcessOrder(order):
237
+ Try:
238
+ InventoryService.Reserve(order.items)
239
+ PaymentService.Charge(order.total)
240
+ Catch Error:
241
+ # Compensate: undo previous actions
242
+ InventoryService.Release(order.items)
243
+ Throw Error
244
+ ```
245
+
246
+ ## Data Synchronization Patterns
247
+
248
+ ```text
249
+ # Pattern: Event Sourcing / CQRS
250
+ Service OrderQuery:
251
+ On("product.updated"):
252
+ # Update local read-optimized copy
253
+ Cache.Set(event.productId, { name: event.name, price: event.price })
254
+
255
+ # Pattern: Saga for distributed transactions
256
+ Saga CreateOrder:
257
+ Step 1:
258
+ Action: Inventory.Reserve()
259
+ Compensate: Inventory.Release()
260
+ Step 2:
261
+ Action: Payment.Charge()
262
+ Compensate: Payment.Refund()
263
+ ```
264
+
265
+ ## Data Ownership
266
+
267
+ ```text
268
+ # ✅ Each service is the source of truth for its data
269
+ Service User:
270
+ Function UpdateEmail(userId, email):
271
+ Database.Update(userId, email)
272
+ EventBus.Publish("user.email.changed", { userId, email })
273
+
274
+ # Other services maintain their own copies (projections)
275
+ Service Order:
276
+ On("user.email.changed"):
277
+ # Update local cache, never query User DB directly
278
+ LocalUserCache.Update(event.userId, event.email)
279
+ ```
280
+
281
+
282
+ ---
283
+
284
+ # Microservices Resilience
285
+
286
+ ## Circuit Breaker Pattern
287
+
288
+ ```text
289
+ Class CircuitBreaker:
290
+ State: CLOSED | OPEN | HALF_OPEN
291
+
292
+ Method Execute(operation):
293
+ If State is OPEN:
294
+ If TimeoutExpired:
295
+ State = HALF_OPEN
296
+ Else:
297
+ Throw Error("Circuit Open")
298
+
299
+ Try:
300
+ Result = operation()
301
+ OnSuccess()
302
+ Return Result
303
+ Catch Error:
304
+ OnFailure()
305
+ Throw Error
306
+ ```
307
+
308
+ ## Retry with Exponential Backoff
309
+
310
+ ```text
311
+ Function Retry(operation, maxAttempts, baseDelay):
312
+ For attempt in 1..maxAttempts:
313
+ Try:
314
+ return operation()
315
+ Catch Error:
316
+ If attempt == maxAttempts: Throw Error
317
+
318
+ # Exponential Backoff + Jitter
319
+ delay = baseDelay * (2 ^ attempt) + RandomJitter()
320
+ Sleep(delay)
321
+ ```
322
+
323
+ ## Bulkhead Pattern
324
+
325
+ ```text
326
+ # Isolate resources to prevent cascading failures
327
+ Class Bulkhead:
328
+ MaxConcurrent = 5
329
+ Active = 0
330
+
331
+ Method Execute(operation):
332
+ If Active >= MaxConcurrent:
333
+ Throw Error("Bulkhead Full")
334
+
335
+ Active++
336
+ Try:
337
+ return operation()
338
+ Finally:
339
+ Active--
340
+
341
+ # Usage: Separate bulkheads per dependency
342
+ PaymentBulkhead = New Bulkhead(5)
343
+ EmailBulkhead = New Bulkhead(10)
344
+ ```
345
+
346
+ ## Timeouts
347
+
348
+ ```text
349
+ Function WithTimeout(operation, timeoutMs):
350
+ Race:
351
+ 1. Result = operation()
352
+ 2. Sleep(timeoutMs) -> Throw Error("Timeout")
353
+
354
+ # Always set timeouts for external calls
355
+ Result = WithTimeout(UserService.GetUser(id), 5000)
356
+ ```
357
+
358
+ ## Graceful Degradation
359
+
360
+ ```text
361
+ Function GetProductRecommendations(userId):
362
+ Try:
363
+ return RecommendationService.GetPersonalized(userId)
364
+ Catch Error:
365
+ # Fallback to cached popular items
366
+ Log("Recommendation service unavailable")
367
+ return GetPopularProducts()
368
+
369
+ # Partial responses instead of complete failure
370
+ Function GetDashboard(userId):
371
+ User = GetUser(userId) OR null
372
+ Orders = GetOrders(userId) OR []
373
+ Stats = GetStats(userId) OR null
374
+
375
+ return { User, Orders, Stats }
376
+ ```
377
+
378
+ ## Health Checks
379
+
380
+ ```text
381
+ Endpoint GET /health:
382
+ Checks = [
383
+ CheckDatabase(),
384
+ CheckRedis(),
385
+ CheckExternalAPI()
386
+ ]
387
+
388
+ Healthy = All(Checks) passed
389
+
390
+ Return HTTP 200/503 {
391
+ status: Healthy ? "healthy" : "degraded",
392
+ checks: { ...details... }
393
+ }
394
+ ```
395
+
396
+
397
+ ---
398
+
399
+ # API Gateway
400
+
401
+ ## Overview
402
+
403
+ An API Gateway acts as a single entry point for a group of microservices. It handles cross-cutting concerns and routes requests to the appropriate backend services.
404
+
405
+ ## Core Responsibilities
406
+
407
+ 1. **Routing**: Forwarding requests to the correct service (e.g., `/api/users` -> User Service).
408
+ 2. **Authentication & Authorization**: Verifying identity and permissions at the edge.
409
+ 3. **Rate Limiting**: Protecting services from abuse.
410
+ 4. **Protocol Translation**: Converting public HTTP/REST to internal gRPC or AMQP.
411
+ 5. **Response Aggregation**: Combining data from multiple services into a single response.
412
+
413
+ ## Patterns
414
+
415
+ ### Backend for Frontend (BFF)
416
+
417
+ Create separate gateways for different client types (Mobile, Web, 3rd Party) to optimize the API for each consumer.
418
+
419
+ ```mermaid
420
+ graph TD
421
+ Web[Web App] --> WebBFF[Web BFF]
422
+ Mobile[Mobile App] --> MobileBFF[Mobile BFF]
423
+
424
+ WebBFF --> SvcA[Service A]
425
+ WebBFF --> SvcB[Service B]
426
+
427
+ MobileBFF --> SvcA
428
+ MobileBFF --> SvcC[Service C]
429
+ ```
430
+
431
+ ## Implementation
432
+
433
+ ### Cross-Cutting Concerns
434
+
435
+ Handle these at the gateway to keep microservices focused on business logic:
436
+
437
+ - **SSL Termination**: Decrypt HTTPS at the gateway.
438
+ - **CORS**: Handle Cross-Origin Resource Sharing headers.
439
+ - **Request Validation**: Basic schema validation before hitting services.
440
+ - **Caching**: Cache common responses.
441
+
442
+ ### When to Use
443
+
444
+ | Use API Gateway When... | Avoid API Gateway When... |
445
+ |-------------------------|---------------------------|
446
+ | You have multiple microservices | You have a monolithic application |
447
+ | You need centralized auth/security | You need ultra-low latency (extra hop) |
448
+ | You have diverse clients (Web, Mobile) | Your architecture is very simple |
449
+
450
+ ## Best Practices
451
+
452
+ - **Keep it Logic-Free**: Don't put business logic in the gateway. It should be a router, not a processor.
453
+ - **High Availability**: The gateway is a single point of failure; deploy it in a cluster.
454
+ - **Observability**: Ensure the gateway generates trace IDs and logs all traffic.
455
+
456
+
457
+ ---
458
+
459
+ # Modular Monolith Structure
460
+
461
+ ## Project Organization
462
+
463
+ ```
464
+ project-root/
465
+ ├── apps/
466
+ │ └── api/
467
+ │ ├── src/
468
+ │ │ ├── app/ # Application bootstrap
469
+ │ │ ├── modules/ # Business modules
470
+ │ │ │ ├── auth/
471
+ │ │ │ ├── user/
472
+ │ │ │ ├── booking/
473
+ │ │ │ ├── payment/
474
+ │ │ │ └── notification/
475
+ │ │ ├── common/ # Shared infrastructure
476
+ │ │ │ ├── decorators/
477
+ │ │ │ ├── guards/
478
+ │ │ │ └── interceptors/
479
+ │ │ └── prisma/ # Database service
480
+ │ └── main.ts
481
+ ├── libs/ # Shared libraries
482
+ │ └── shared-types/
483
+ └── package.json
484
+ ```
485
+
486
+ ## Module Structure
487
+
488
+ ```
489
+ modules/booking/
490
+ ├── entities/ # Domain models and DTOs
491
+ │ ├── booking.entity.ts
492
+ │ ├── create-booking.dto.ts
493
+ │ └── booking-response.dto.ts
494
+ ├── repositories/ # Data access layer
495
+ │ └── booking.repository.ts
496
+ ├── services/ # Business logic
497
+ │ ├── booking.service.ts
498
+ │ └── availability.service.ts
499
+ ├── controllers/ # HTTP/API layer
500
+ │ └── bookings.controller.ts
501
+ └── booking.module.ts # Module definition
502
+ ```
503
+
504
+ ## Module Definition
505
+
506
+ ```typescript
507
+ @Module({
508
+ imports: [
509
+ PrismaModule,
510
+ forwardRef(() => AuthModule),
511
+ NotificationsModule,
512
+ ],
513
+ controllers: [BookingsController],
514
+ providers: [
515
+ BookingService,
516
+ AvailabilityService,
517
+ BookingRepository,
518
+ ],
519
+ exports: [BookingService], // Only export public API
520
+ })
521
+ export class BookingsModule {}
522
+ ```
523
+
524
+ ## Layered Architecture Within Modules
525
+
526
+ ```typescript
527
+ // Controller - HTTP layer
528
+ @Controller('api/v1/bookings')
529
+ export class BookingsController {
530
+ constructor(private bookingService: BookingService) {}
531
+
532
+ @Get('calendar')
533
+ async getCalendarBookings(@Query() dto: GetBookingsDto) {
534
+ return this.bookingService.getBookingsForCalendar(dto);
535
+ }
536
+ }
537
+
538
+ // Service - Business logic
539
+ @Injectable()
540
+ export class BookingService {
541
+ constructor(
542
+ private bookingRepository: BookingRepository,
543
+ private availabilityService: AvailabilityService,
544
+ ) {}
545
+
546
+ async getBookingsForCalendar(dto: GetBookingsDto) {
547
+ const bookings = await this.bookingRepository.findByDateRange(
548
+ dto.startDate,
549
+ dto.endDate
550
+ );
551
+ return bookings.map(this.mapToCalendarDto);
552
+ }
553
+ }
554
+
555
+ // Repository - Data access
556
+ @Injectable()
557
+ export class BookingRepository {
558
+ constructor(private prisma: PrismaService) {}
559
+
560
+ async findByDateRange(start: Date, end: Date) {
561
+ return this.prisma.booking.findMany({
562
+ where: {
563
+ startTime: { gte: start },
564
+ endTime: { lte: end }
565
+ }
566
+ });
567
+ }
568
+ }
569
+ ```
570
+
571
+ ## Shared Infrastructure
572
+
573
+ ```typescript
574
+ // common/guards/jwt-auth.guard.ts
575
+ @Injectable()
576
+ export class JwtAuthGuard extends AuthGuard('jwt') {
577
+ canActivate(context: ExecutionContext) {
578
+ const isPublic = this.reflector.get<boolean>('isPublic', context.getHandler());
579
+ return isPublic ? true : super.canActivate(context);
580
+ }
581
+ }
582
+
583
+ // common/decorators/current-user.decorator.ts
584
+ export const CurrentUser = createParamDecorator(
585
+ (data: unknown, ctx: ExecutionContext) => {
586
+ return ctx.switchToHttp().getRequest().user;
587
+ }
588
+ );
589
+ ```
590
+
591
+
592
+ ---
593
+
594
+ # Modular Monolith Boundaries
595
+
596
+ ## High Cohesion, Low Coupling
597
+
598
+ ```typescript
599
+ // ❌ Bad: Tight coupling - direct repository access
600
+ @Injectable()
601
+ export class OrderService {
602
+ constructor(private userRepo: UserRepository) {} // Crosses module boundary
603
+
604
+ async createOrder(userId: string) {
605
+ const user = await this.userRepo.findById(userId); // Direct access
606
+ }
607
+ }
608
+
609
+ // ✅ Good: Loose coupling via service
610
+ @Injectable()
611
+ export class OrderService {
612
+ constructor(private userService: UserService) {} // Service dependency
613
+
614
+ async createOrder(userId: string) {
615
+ const user = await this.userService.findById(userId); // Through public API
616
+ }
617
+ }
618
+ ```
619
+
620
+ ## No Direct Cross-Module Database Access
621
+
622
+ ```typescript
623
+ // ❌ Never query another module's tables directly
624
+ class BookingService {
625
+ async createBooking(data: CreateBookingDto) {
626
+ const user = await this.prisma.user.findUnique({ where: { id: data.userId } });
627
+ // This bypasses the User module!
628
+ }
629
+ }
630
+
631
+ // ✅ Use the module's public service API
632
+ class BookingService {
633
+ constructor(private userService: UserService) {}
634
+
635
+ async createBooking(data: CreateBookingDto) {
636
+ const user = await this.userService.findById(data.userId);
637
+ // Properly goes through User module
638
+ }
639
+ }
640
+ ```
641
+
642
+ ## Separated Interface Pattern
643
+
644
+ ```typescript
645
+ // Define interface in consuming module
646
+ // modules/order/interfaces/user-provider.interface.ts
647
+ export interface UserProvider {
648
+ findById(id: string): Promise<User>;
649
+ validateUser(id: string): Promise<boolean>;
650
+ }
651
+
652
+ // Implement in providing module
653
+ // modules/user/user.service.ts
654
+ @Injectable()
655
+ export class UserService implements UserProvider {
656
+ async findById(id: string): Promise<User> {
657
+ return this.userRepo.findById(id);
658
+ }
659
+
660
+ async validateUser(id: string): Promise<boolean> {
661
+ const user = await this.findById(id);
662
+ return user && user.isActive;
663
+ }
664
+ }
665
+ ```
666
+
667
+ ## Domain Events for Loose Coupling
668
+
669
+ ```typescript
670
+ // ✅ Publish events instead of direct calls
671
+ @Injectable()
672
+ export class UserService {
673
+ constructor(private eventEmitter: EventEmitter2) {}
674
+
675
+ async createUser(dto: CreateUserDto): Promise<User> {
676
+ const user = await this.userRepo.create(dto);
677
+
678
+ this.eventEmitter.emit('user.created', new UserCreatedEvent(user.id, user.email));
679
+
680
+ return user;
681
+ }
682
+ }
683
+
684
+ // Other modules subscribe to events
685
+ @Injectable()
686
+ export class NotificationListener {
687
+ @OnEvent('user.created')
688
+ async handleUserCreated(event: UserCreatedEvent) {
689
+ await this.notificationService.sendWelcomeEmail(event.email);
690
+ }
691
+ }
692
+ ```
693
+
694
+ ## Handling Circular Dependencies
695
+
696
+ ```typescript
697
+ // Use forwardRef() when modules depend on each other
698
+ @Module({
699
+ imports: [
700
+ forwardRef(() => AuthModule), // Break circular dependency
701
+ UserModule,
702
+ ],
703
+ })
704
+ export class UserModule {}
705
+
706
+ @Module({
707
+ imports: [
708
+ forwardRef(() => UserModule),
709
+ ],
710
+ })
711
+ export class AuthModule {}
712
+ ```
713
+
714
+ ## Export Only What's Necessary
715
+
716
+ ```typescript
717
+ @Module({
718
+ providers: [
719
+ UserService, // Public service
720
+ UserRepository, // Internal
721
+ PasswordHasher, // Internal
722
+ ],
723
+ exports: [UserService], // Only export the service, not internals
724
+ })
725
+ export class UserModule {}
726
+ ```
727
+
728
+
729
+ ---
730
+
731
+ # SOLID Principles
732
+
733
+ ## Single Responsibility Principle (SRP)
734
+
735
+ A class should have only one reason to change.
736
+
737
+ **Bad:**
738
+ ```typescript
739
+ class UserService {
740
+ createUser(data: UserData): User { /* ... */ }
741
+ sendWelcomeEmail(user: User): void { /* ... */ }
742
+ generateReport(users: User[]): Report { /* ... */ }
743
+ }
744
+ ```
745
+
746
+ **Good:**
747
+ ```typescript
748
+ class UserService {
749
+ createUser(data: UserData): User { /* ... */ }
750
+ }
751
+
752
+ class EmailService {
753
+ sendWelcomeEmail(user: User): void { /* ... */ }
754
+ }
755
+
756
+ class ReportService {
757
+ generateUserReport(users: User[]): Report { /* ... */ }
758
+ }
759
+ ```
760
+
761
+ ## Open/Closed Principle (OCP)
762
+
763
+ Open for extension, closed for modification.
764
+
765
+ **Bad:**
766
+ ```typescript
767
+ class PaymentProcessor {
768
+ process(payment: Payment): void {
769
+ if (payment.type === 'credit') { /* credit logic */ }
770
+ else if (payment.type === 'paypal') { /* paypal logic */ }
771
+ // Must modify class to add new payment types
772
+ }
773
+ }
774
+ ```
775
+
776
+ **Good:**
777
+ ```typescript
778
+ interface PaymentHandler {
779
+ process(payment: Payment): void;
780
+ }
781
+
782
+ class CreditCardHandler implements PaymentHandler {
783
+ process(payment: Payment): void { /* credit logic */ }
784
+ }
785
+
786
+ class PayPalHandler implements PaymentHandler {
787
+ process(payment: Payment): void { /* paypal logic */ }
788
+ }
789
+
790
+ class PaymentProcessor {
791
+ constructor(private handlers: Map<string, PaymentHandler>) {}
792
+
793
+ process(payment: Payment): void {
794
+ this.handlers.get(payment.type)?.process(payment);
795
+ }
796
+ }
797
+ ```
798
+
799
+ ## Liskov Substitution Principle (LSP)
800
+
801
+ Subtypes must be substitutable for their base types.
802
+
803
+ **Bad:**
804
+ ```typescript
805
+ class Bird {
806
+ fly(): void { /* flying logic */ }
807
+ }
808
+
809
+ class Penguin extends Bird {
810
+ fly(): void {
811
+ throw new Error("Penguins can't fly!"); // Violates LSP
812
+ }
813
+ }
814
+ ```
815
+
816
+ **Good:**
817
+ ```typescript
818
+ interface Bird {
819
+ move(): void;
820
+ }
821
+
822
+ class FlyingBird implements Bird {
823
+ move(): void { this.fly(); }
824
+ private fly(): void { /* flying logic */ }
825
+ }
826
+
827
+ class Penguin implements Bird {
828
+ move(): void { this.swim(); }
829
+ private swim(): void { /* swimming logic */ }
830
+ }
831
+ ```
832
+
833
+ ## Interface Segregation Principle (ISP)
834
+
835
+ Clients shouldn't depend on interfaces they don't use.
836
+
837
+ **Bad:**
838
+ ```typescript
839
+ interface Worker {
840
+ work(): void;
841
+ eat(): void;
842
+ sleep(): void;
843
+ }
844
+
845
+ class Robot implements Worker {
846
+ work(): void { /* ... */ }
847
+ eat(): void { throw new Error("Robots don't eat"); }
848
+ sleep(): void { throw new Error("Robots don't sleep"); }
849
+ }
850
+ ```
851
+
852
+ **Good:**
853
+ ```typescript
854
+ interface Workable {
855
+ work(): void;
856
+ }
857
+
858
+ interface Eatable {
859
+ eat(): void;
860
+ }
861
+
862
+ interface Sleepable {
863
+ sleep(): void;
864
+ }
865
+
866
+ class Human implements Workable, Eatable, Sleepable {
867
+ work(): void { /* ... */ }
868
+ eat(): void { /* ... */ }
869
+ sleep(): void { /* ... */ }
870
+ }
871
+
872
+ class Robot implements Workable {
873
+ work(): void { /* ... */ }
874
+ }
875
+ ```
876
+
877
+ ## Dependency Inversion Principle (DIP)
878
+
879
+ Depend on abstractions, not concretions.
880
+
881
+ **Bad:**
882
+ ```typescript
883
+ class UserService {
884
+ private database = new MySQLDatabase();
885
+
886
+ getUser(id: string): User {
887
+ return this.database.query(`SELECT * FROM users WHERE id = '${id}'`);
888
+ }
889
+ }
890
+ ```
891
+
892
+ **Good:**
893
+ ```typescript
894
+ interface Database {
895
+ query(sql: string): any;
896
+ }
897
+
898
+ class UserService {
899
+ constructor(private database: Database) {}
900
+
901
+ getUser(id: string): User {
902
+ return this.database.query(`SELECT * FROM users WHERE id = '${id}'`);
903
+ }
904
+ }
905
+
906
+ // Can inject any database implementation
907
+ const userService = new UserService(new MySQLDatabase());
908
+ const testService = new UserService(new InMemoryDatabase());
909
+ ```
910
+
911
+ ## Best Practices
912
+
913
+ - Apply SRP at class, method, and module levels
914
+ - Use interfaces and dependency injection for flexibility
915
+ - Prefer composition over inheritance
916
+ - Design small, focused interfaces
917
+ - Inject dependencies rather than creating them internally
918
+
919
+
920
+ ---
921
+
922
+ # Clean Architecture
923
+
924
+ ## Core Principle
925
+
926
+ Dependencies point inward. Inner layers know nothing about outer layers.
927
+
928
+ ```
929
+ ┌─────────────────────────────────────────────┐
930
+ │ Frameworks & Drivers │
931
+ │ ┌─────────────────────────────────────┐ │
932
+ │ │ Interface Adapters │ │
933
+ │ │ ┌─────────────────────────────┐ │ │
934
+ │ │ │ Application Business │ │ │
935
+ │ │ │ ┌─────────────────────┐ │ │ │
936
+ │ │ │ │ Enterprise Business│ │ │ │
937
+ │ │ │ │ (Entities) │ │ │ │
938
+ │ │ │ └─────────────────────┘ │ │ │
939
+ │ │ │ (Use Cases) │ │ │
940
+ │ │ └─────────────────────────────┘ │ │
941
+ │ │ (Controllers, Gateways) │ │
942
+ │ └─────────────────────────────────────┘ │
943
+ │ (Web, DB, External APIs) │
944
+ └─────────────────────────────────────────────┘
945
+ ```
946
+
947
+ ## The Dependency Rule
948
+
949
+ Source code dependencies only point inward.
950
+
951
+ ## Layer Structure
952
+
953
+ ### Entities (Enterprise Business Rules)
954
+
955
+ ```typescript
956
+ class Order {
957
+ constructor(
958
+ public readonly id: string,
959
+ private items: OrderItem[],
960
+ private status: OrderStatus
961
+ ) {}
962
+
963
+ calculateTotal(): Money {
964
+ return this.items.reduce(
965
+ (sum, item) => sum.add(item.subtotal()),
966
+ Money.zero()
967
+ );
968
+ }
969
+
970
+ canBeCancelled(): boolean {
971
+ return this.status === OrderStatus.Pending;
972
+ }
973
+ }
974
+ ```
975
+
976
+ ### Use Cases (Application Business Rules)
977
+
978
+ ```typescript
979
+ class CreateOrderUseCase {
980
+ constructor(
981
+ private orderRepository: OrderRepository,
982
+ private productRepository: ProductRepository
983
+ ) {}
984
+
985
+ async execute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
986
+ const products = await this.productRepository.findByIds(request.productIds);
987
+ const order = new Order(generateId(), this.createItems(products));
988
+ await this.orderRepository.save(order);
989
+ return { orderId: order.id };
990
+ }
991
+ }
992
+ ```
993
+
994
+ ### Interface Adapters
995
+
996
+ ```typescript
997
+ // Controller (adapts HTTP to use case)
998
+ class OrderController {
999
+ constructor(private createOrder: CreateOrderUseCase) {}
1000
+
1001
+ async create(req: Request, res: Response) {
1002
+ const result = await this.createOrder.execute(req.body);
1003
+ res.json(result);
1004
+ }
1005
+ }
1006
+
1007
+ // Repository Implementation (adapts use case to database)
1008
+ class PostgreSQLOrderRepository implements OrderRepository {
1009
+ async save(order: Order): Promise<void> {
1010
+ await this.db.query('INSERT INTO orders...');
1011
+ }
1012
+ }
1013
+ ```
1014
+
1015
+ ### Frameworks & Drivers
1016
+
1017
+ ```typescript
1018
+ // Express setup
1019
+ const app = express();
1020
+ app.post('/orders', (req, res) => orderController.create(req, res));
1021
+
1022
+ // Database connection
1023
+ const db = new Pool({ connectionString: process.env.DATABASE_URL });
1024
+ ```
1025
+
1026
+ ## Best Practices
1027
+
1028
+ - Keep entities pure with no framework dependencies
1029
+ - Use cases orchestrate domain logic
1030
+ - Interfaces defined in inner layers, implemented in outer layers
1031
+ - Cross boundaries with simple data structures
1032
+ - Test use cases independently of frameworks
1033
+
1034
+
1035
+ ---
1036
+
1037
+ # DDD Tactical Patterns
1038
+
1039
+ ## Entities
1040
+
1041
+ Objects with identity that persists through state changes.
1042
+
1043
+ ```typescript
1044
+ class User {
1045
+ constructor(
1046
+ public readonly id: UserId,
1047
+ private email: Email,
1048
+ private name: string
1049
+ ) {}
1050
+
1051
+ changeEmail(newEmail: Email): void {
1052
+ this.email = newEmail;
1053
+ }
1054
+
1055
+ equals(other: User): boolean {
1056
+ return this.id.equals(other.id);
1057
+ }
1058
+ }
1059
+ ```
1060
+
1061
+ ## Value Objects
1062
+
1063
+ Immutable objects defined by their attributes.
1064
+
1065
+ ```typescript
1066
+ class Email {
1067
+ private readonly value: string;
1068
+
1069
+ constructor(email: string) {
1070
+ if (!this.isValid(email)) {
1071
+ throw new InvalidEmailError(email);
1072
+ }
1073
+ this.value = email.toLowerCase();
1074
+ }
1075
+
1076
+ equals(other: Email): boolean {
1077
+ return this.value === other.value;
1078
+ }
1079
+ }
1080
+
1081
+ class Money {
1082
+ constructor(
1083
+ public readonly amount: number,
1084
+ public readonly currency: Currency
1085
+ ) {
1086
+ Object.freeze(this);
1087
+ }
1088
+
1089
+ add(other: Money): Money {
1090
+ this.assertSameCurrency(other);
1091
+ return new Money(this.amount + other.amount, this.currency);
1092
+ }
1093
+ }
1094
+ ```
1095
+
1096
+ ## Aggregates
1097
+
1098
+ Cluster of entities and value objects with a root entity.
1099
+
1100
+ ```typescript
1101
+ class Order {
1102
+ private items: OrderItem[] = [];
1103
+
1104
+ constructor(
1105
+ public readonly id: OrderId,
1106
+ private customerId: CustomerId
1107
+ ) {}
1108
+
1109
+ addItem(product: Product, quantity: number): void {
1110
+ const item = new OrderItem(product.id, product.price, quantity);
1111
+ this.items.push(item);
1112
+ }
1113
+
1114
+ // All modifications go through aggregate root
1115
+ removeItem(productId: ProductId): void {
1116
+ this.items = this.items.filter(item => !item.productId.equals(productId));
1117
+ }
1118
+ }
1119
+ ```
1120
+
1121
+ ## Domain Events
1122
+
1123
+ Capture something that happened in the domain.
1124
+
1125
+ ```typescript
1126
+ class OrderPlaced implements DomainEvent {
1127
+ constructor(
1128
+ public readonly orderId: OrderId,
1129
+ public readonly customerId: CustomerId,
1130
+ public readonly occurredOn: Date = new Date()
1131
+ ) {}
1132
+ }
1133
+
1134
+ class Order {
1135
+ private events: DomainEvent[] = [];
1136
+
1137
+ place(): void {
1138
+ this.status = OrderStatus.Placed;
1139
+ this.events.push(new OrderPlaced(this.id, this.customerId));
1140
+ }
1141
+
1142
+ pullEvents(): DomainEvent[] {
1143
+ const events = [...this.events];
1144
+ this.events = [];
1145
+ return events;
1146
+ }
1147
+ }
1148
+ ```
1149
+
1150
+ ## Repositories
1151
+
1152
+ Abstract persistence for aggregates.
1153
+
1154
+ ```typescript
1155
+ interface OrderRepository {
1156
+ findById(id: OrderId): Promise<Order | null>;
1157
+ save(order: Order): Promise<void>;
1158
+ nextId(): OrderId;
1159
+ }
1160
+ ```
1161
+
1162
+ ## Best Practices
1163
+
1164
+ - One repository per aggregate root
1165
+ - Aggregates should be small
1166
+ - Reference other aggregates by ID
1167
+ - Publish domain events for cross-aggregate communication
1168
+ - Keep value objects immutable
1169
+
1170
+
1171
+ ---
1172
+
1173
+ # DDD Strategic Patterns
1174
+
1175
+ ## Ubiquitous Language
1176
+
1177
+ Use the same terminology in code, documentation, and conversations.
1178
+
1179
+ ```typescript
1180
+ // Domain experts say "place an order"
1181
+ class Order {
1182
+ place(): void { /* not submit(), not create() */ }
1183
+ }
1184
+
1185
+ // Domain experts say "items are added to cart"
1186
+ class ShoppingCart {
1187
+ addItem(product: Product): void { /* not insert(), not push() */ }
1188
+ }
1189
+ ```
1190
+
1191
+ ## Bounded Contexts
1192
+
1193
+ Explicit boundaries where a model applies consistently.
1194
+
1195
+ ```
1196
+ ┌─────────────────┐ ┌─────────────────┐
1197
+ │ Sales │ │ Warehouse │
1198
+ │ Context │ │ Context │
1199
+ ├─────────────────┤ ├─────────────────┤
1200
+ │ Order │ │ Order │
1201
+ │ - customerId │ │ - shipmentId │
1202
+ │ - items[] │ │ - pickingList │
1203
+ │ - total │ │ - status │
1204
+ └─────────────────┘ └─────────────────┘
1205
+ Same term, different model
1206
+ ```
1207
+
1208
+ ## Context Mapping Patterns
1209
+
1210
+ ### Shared Kernel
1211
+ Two contexts share a subset of the model.
1212
+
1213
+ ### Customer/Supplier
1214
+ Upstream context provides what downstream needs.
1215
+
1216
+ ### Conformist
1217
+ Downstream adopts upstream's model entirely.
1218
+
1219
+ ### Anti-Corruption Layer
1220
+ Translate between contexts to protect domain model.
1221
+
1222
+ ```typescript
1223
+ class InventoryAntiCorruptionLayer {
1224
+ constructor(private legacyInventorySystem: LegacyInventory) {}
1225
+
1226
+ checkAvailability(productId: ProductId): Promise<boolean> {
1227
+ // Translate from legacy format to domain model
1228
+ const legacyResult = await this.legacyInventorySystem.getStock(
1229
+ productId.toString()
1230
+ );
1231
+ return legacyResult.qty > 0;
1232
+ }
1233
+ }
1234
+ ```
1235
+
1236
+ ## Module Organization
1237
+
1238
+ ```
1239
+ src/
1240
+ ├── sales/ # Sales bounded context
1241
+ │ ├── domain/
1242
+ │ │ ├── order.ts
1243
+ │ │ └── customer.ts
1244
+ │ ├── application/
1245
+ │ │ └── place-order.ts
1246
+ │ └── infrastructure/
1247
+ │ └── order-repository.ts
1248
+ ├── warehouse/ # Warehouse bounded context
1249
+ │ ├── domain/
1250
+ │ │ └── shipment.ts
1251
+ │ └── ...
1252
+ └── shared/ # Shared kernel
1253
+ └── money.ts
1254
+ ```
1255
+
1256
+ ## Best Practices
1257
+
1258
+ - Define context boundaries based on team structure and business capabilities
1259
+ - Use ubiquitous language within each context
1260
+ - Communicate between contexts via events or explicit APIs
1261
+ - Protect domain model with anti-corruption layers when integrating legacy systems
1262
+
1263
+
1264
+ ---
1265
+
1266
+ # Event-Driven Architecture
1267
+
1268
+ ## Event Sourcing
1269
+
1270
+ Store state as a sequence of events.
1271
+
1272
+ ```typescript
1273
+ interface Event {
1274
+ id: string;
1275
+ aggregateId: string;
1276
+ type: string;
1277
+ data: unknown;
1278
+ timestamp: Date;
1279
+ version: number;
1280
+ }
1281
+
1282
+ class Account {
1283
+ private balance = 0;
1284
+ private version = 0;
1285
+
1286
+ static fromEvents(events: Event[]): Account {
1287
+ const account = new Account();
1288
+ events.forEach(event => account.apply(event));
1289
+ return account;
1290
+ }
1291
+
1292
+ private apply(event: Event): void {
1293
+ switch (event.type) {
1294
+ case 'MoneyDeposited':
1295
+ this.balance += (event.data as { amount: number }).amount;
1296
+ break;
1297
+ case 'MoneyWithdrawn':
1298
+ this.balance -= (event.data as { amount: number }).amount;
1299
+ break;
1300
+ }
1301
+ this.version = event.version;
1302
+ }
1303
+ }
1304
+ ```
1305
+
1306
+ ## CQRS (Command Query Responsibility Segregation)
1307
+
1308
+ Separate read and write models.
1309
+
1310
+ ```typescript
1311
+ // Write Model (Commands)
1312
+ class OrderCommandHandler {
1313
+ async handle(cmd: PlaceOrderCommand): Promise<void> {
1314
+ const order = new Order(cmd.orderId, cmd.items);
1315
+ await this.eventStore.save(order.changes());
1316
+ }
1317
+ }
1318
+
1319
+ // Read Model (Queries)
1320
+ class OrderQueryService {
1321
+ async getOrderSummary(orderId: string): Promise<OrderSummaryDTO> {
1322
+ return this.readDb.query('SELECT * FROM order_summaries WHERE id = $1', [orderId]);
1323
+ }
1324
+ }
1325
+
1326
+ // Projection updates read model from events
1327
+ class OrderProjection {
1328
+ async handle(event: OrderPlaced): Promise<void> {
1329
+ await this.readDb.insert('order_summaries', {
1330
+ id: event.orderId,
1331
+ status: 'placed',
1332
+ total: event.total
1333
+ });
1334
+ }
1335
+ }
1336
+ ```
1337
+
1338
+ ## Saga Pattern
1339
+
1340
+ Manage long-running transactions across services.
1341
+
1342
+ ```typescript
1343
+ class OrderSaga {
1344
+ async execute(orderId: string): Promise<void> {
1345
+ try {
1346
+ await this.paymentService.charge(orderId);
1347
+ await this.inventoryService.reserve(orderId);
1348
+ await this.shippingService.schedule(orderId);
1349
+ } catch (error) {
1350
+ await this.compensate(orderId, error);
1351
+ }
1352
+ }
1353
+
1354
+ private async compensate(orderId: string, error: Error): Promise<void> {
1355
+ await this.shippingService.cancel(orderId);
1356
+ await this.inventoryService.release(orderId);
1357
+ await this.paymentService.refund(orderId);
1358
+ }
1359
+ }
1360
+ ```
1361
+
1362
+ ## Event Versioning
1363
+
1364
+ Handle schema changes gracefully.
1365
+
1366
+ ```typescript
1367
+ interface EventUpgrader {
1368
+ upgrade(event: Event): Event;
1369
+ }
1370
+
1371
+ class OrderPlacedV1ToV2 implements EventUpgrader {
1372
+ upgrade(event: Event): Event {
1373
+ const oldData = event.data as OrderPlacedV1Data;
1374
+ return {
1375
+ ...event,
1376
+ type: 'OrderPlaced',
1377
+ version: 2,
1378
+ data: {
1379
+ ...oldData,
1380
+ currency: 'USD' // New field with default
1381
+ }
1382
+ };
1383
+ }
1384
+ }
1385
+ ```
1386
+
1387
+ ## Best Practices
1388
+
1389
+ - Events are immutable facts
1390
+ - Include enough context in events for consumers
1391
+ - Version events from the start
1392
+ - Use idempotent event handlers
1393
+ - Design for eventual consistency
1394
+ - Consider snapshots for aggregates with many events
1395
+
1396
+
1397
+ ---
1398
+
1399
+ # Event-Driven Messaging
1400
+
1401
+ ## Message Types
1402
+
1403
+ ### Commands
1404
+ Request to perform an action. Directed to a single handler.
1405
+
1406
+ ```typescript
1407
+ interface CreateOrderCommand {
1408
+ type: 'CreateOrder';
1409
+ orderId: string;
1410
+ customerId: string;
1411
+ items: OrderItem[];
1412
+ timestamp: Date;
1413
+ }
1414
+
1415
+ // Single handler processes the command
1416
+ class CreateOrderHandler {
1417
+ async handle(command: CreateOrderCommand): Promise<void> {
1418
+ const order = Order.create(command);
1419
+ await this.repository.save(order);
1420
+ await this.eventBus.publish(new OrderCreatedEvent(order));
1421
+ }
1422
+ }
1423
+ ```
1424
+
1425
+ ### Events
1426
+ Notification that something happened. Published to multiple subscribers.
1427
+
1428
+ ```typescript
1429
+ interface OrderCreatedEvent {
1430
+ type: 'OrderCreated';
1431
+ orderId: string;
1432
+ customerId: string;
1433
+ totalAmount: number;
1434
+ occurredAt: Date;
1435
+ }
1436
+
1437
+ // Multiple handlers can subscribe
1438
+ class InventoryService {
1439
+ @Subscribe('OrderCreated')
1440
+ async onOrderCreated(event: OrderCreatedEvent): Promise<void> {
1441
+ await this.reserveInventory(event.orderId);
1442
+ }
1443
+ }
1444
+
1445
+ class NotificationService {
1446
+ @Subscribe('OrderCreated')
1447
+ async onOrderCreated(event: OrderCreatedEvent): Promise<void> {
1448
+ await this.sendConfirmation(event.customerId);
1449
+ }
1450
+ }
1451
+ ```
1452
+
1453
+ ### Queries
1454
+ Request for data. Returns a response.
1455
+
1456
+ ```typescript
1457
+ interface GetOrderQuery {
1458
+ type: 'GetOrder';
1459
+ orderId: string;
1460
+ }
1461
+
1462
+ class GetOrderHandler {
1463
+ async handle(query: GetOrderQuery): Promise<Order> {
1464
+ return this.repository.findById(query.orderId);
1465
+ }
1466
+ }
1467
+ ```
1468
+
1469
+ ## Message Bus Patterns
1470
+
1471
+ ### In-Memory Bus
1472
+
1473
+ ```typescript
1474
+ class EventBus {
1475
+ private handlers = new Map<string, Function[]>();
1476
+
1477
+ subscribe(eventType: string, handler: Function): void {
1478
+ const handlers = this.handlers.get(eventType) || [];
1479
+ handlers.push(handler);
1480
+ this.handlers.set(eventType, handlers);
1481
+ }
1482
+
1483
+ async publish(event: Event): Promise<void> {
1484
+ const handlers = this.handlers.get(event.type) || [];
1485
+ await Promise.all(handlers.map(h => h(event)));
1486
+ }
1487
+ }
1488
+ ```
1489
+
1490
+ ### Message Queue Integration
1491
+
1492
+ ```typescript
1493
+ // RabbitMQ example
1494
+ class RabbitMQPublisher {
1495
+ async publish(event: Event): Promise<void> {
1496
+ const message = JSON.stringify({
1497
+ type: event.type,
1498
+ data: event,
1499
+ metadata: {
1500
+ correlationId: uuid(),
1501
+ timestamp: new Date().toISOString()
1502
+ }
1503
+ });
1504
+
1505
+ await this.channel.publish(
1506
+ 'events',
1507
+ event.type,
1508
+ Buffer.from(message),
1509
+ { persistent: true }
1510
+ );
1511
+ }
1512
+ }
1513
+
1514
+ class RabbitMQConsumer {
1515
+ async consume(queue: string, handler: EventHandler): Promise<void> {
1516
+ await this.channel.consume(queue, async (msg) => {
1517
+ if (!msg) return;
1518
+
1519
+ try {
1520
+ const event = JSON.parse(msg.content.toString());
1521
+ await handler.handle(event);
1522
+ this.channel.ack(msg);
1523
+ } catch (error) {
1524
+ this.channel.nack(msg, false, true); // Requeue
1525
+ }
1526
+ });
1527
+ }
1528
+ }
1529
+ ```
1530
+
1531
+ ## Delivery Guarantees
1532
+
1533
+ ### At-Least-Once Delivery
1534
+
1535
+ ```typescript
1536
+ // Producer: persist before publish
1537
+ async function publishWithRetry(event: Event): Promise<void> {
1538
+ // 1. Save to outbox
1539
+ await db.insert('outbox', {
1540
+ id: event.id,
1541
+ type: event.type,
1542
+ payload: JSON.stringify(event),
1543
+ status: 'pending'
1544
+ });
1545
+
1546
+ // 2. Publish (may fail)
1547
+ try {
1548
+ await messageBus.publish(event);
1549
+ await db.update('outbox', event.id, { status: 'sent' });
1550
+ } catch {
1551
+ // Retry worker will pick it up
1552
+ }
1553
+ }
1554
+
1555
+ // Consumer: idempotent handling
1556
+ async function handleIdempotent(event: Event): Promise<void> {
1557
+ const processed = await db.findOne('processed_events', event.id);
1558
+ if (processed) return; // Already handled
1559
+
1560
+ await handleEvent(event);
1561
+ await db.insert('processed_events', { id: event.id });
1562
+ }
1563
+ ```
1564
+
1565
+ ### Outbox Pattern
1566
+
1567
+ ```typescript
1568
+ // Transaction includes outbox write
1569
+ async function createOrder(data: OrderData): Promise<Order> {
1570
+ return await db.transaction(async (tx) => {
1571
+ // 1. Business logic
1572
+ const order = Order.create(data);
1573
+ await tx.insert('orders', order);
1574
+
1575
+ // 2. Outbox entry (same transaction)
1576
+ await tx.insert('outbox', {
1577
+ id: uuid(),
1578
+ aggregateId: order.id,
1579
+ type: 'OrderCreated',
1580
+ payload: JSON.stringify(order)
1581
+ });
1582
+
1583
+ return order;
1584
+ });
1585
+ }
1586
+
1587
+ // Separate process polls and publishes
1588
+ async function processOutbox(): Promise<void> {
1589
+ const pending = await db.query(
1590
+ 'SELECT * FROM outbox WHERE status = $1 ORDER BY created_at LIMIT 100',
1591
+ ['pending']
1592
+ );
1593
+
1594
+ for (const entry of pending) {
1595
+ await messageBus.publish(JSON.parse(entry.payload));
1596
+ await db.update('outbox', entry.id, { status: 'sent' });
1597
+ }
1598
+ }
1599
+ ```
1600
+
1601
+ ## Dead Letter Queues
1602
+
1603
+ ```typescript
1604
+ class DeadLetterHandler {
1605
+ maxRetries = 3;
1606
+
1607
+ async handleFailure(message: Message, error: Error): Promise<void> {
1608
+ const retryCount = message.metadata.retryCount || 0;
1609
+
1610
+ if (retryCount < this.maxRetries) {
1611
+ // Retry with backoff
1612
+ await this.scheduleRetry(message, retryCount + 1);
1613
+ } else {
1614
+ // Move to DLQ
1615
+ await this.moveToDLQ(message, error);
1616
+ }
1617
+ }
1618
+
1619
+ async moveToDLQ(message: Message, error: Error): Promise<void> {
1620
+ await this.dlqChannel.publish('dead-letter', {
1621
+ originalMessage: message,
1622
+ error: error.message,
1623
+ failedAt: new Date()
1624
+ });
1625
+
1626
+ // Alert operations
1627
+ await this.alerting.notify('Message moved to DLQ', { message, error });
1628
+ }
1629
+ }
1630
+ ```
1631
+
1632
+ ## Best Practices
1633
+
1634
+ - Use correlation IDs to trace message flows
1635
+ - Make consumers idempotent
1636
+ - Use dead letter queues for failed messages
1637
+ - Monitor queue depths and consumer lag
1638
+ - Design for eventual consistency
1639
+ - Version your message schemas
1640
+ - Include metadata (timestamp, correlationId, causationId)
1641
+
1642
+
1643
+ ---
1644
+
1645
+ # Layered Architecture
1646
+
1647
+ ## Layer Structure
1648
+
1649
+ ```
1650
+ ┌─────────────────────────────────────┐
1651
+ │ Presentation Layer │
1652
+ │ (Controllers, Views, APIs) │
1653
+ └───────────────┬─────────────────────┘
1654
+
1655
+ ┌───────────────▼─────────────────────┐
1656
+ │ Domain Layer │
1657
+ │ (Business Logic, Services) │
1658
+ └───────────────┬─────────────────────┘
1659
+
1660
+ ┌───────────────▼─────────────────────┐
1661
+ │ Data Access Layer │
1662
+ │ (Repositories, ORM, DAOs) │
1663
+ └─────────────────────────────────────┘
1664
+ ```
1665
+
1666
+ ## Presentation Layer
1667
+
1668
+ Handles user interaction and HTTP requests.
1669
+
1670
+ ```typescript
1671
+ class OrderController {
1672
+ constructor(private orderService: OrderService) {}
1673
+
1674
+ async createOrder(req: Request, res: Response): Promise<void> {
1675
+ const dto = req.body as CreateOrderDTO;
1676
+ const result = await this.orderService.createOrder(dto);
1677
+ res.status(201).json(result);
1678
+ }
1679
+ }
1680
+ ```
1681
+
1682
+ ## Domain Layer
1683
+
1684
+ Contains business logic and rules.
1685
+
1686
+ ```typescript
1687
+ class OrderService {
1688
+ constructor(
1689
+ private orderRepository: OrderRepository,
1690
+ private productRepository: ProductRepository
1691
+ ) {}
1692
+
1693
+ async createOrder(dto: CreateOrderDTO): Promise<Order> {
1694
+ const products = await this.productRepository.findByIds(dto.productIds);
1695
+
1696
+ if (products.length !== dto.productIds.length) {
1697
+ throw new ProductNotFoundError();
1698
+ }
1699
+
1700
+ const order = new Order(dto.customerId, products);
1701
+ order.calculateTotal();
1702
+
1703
+ await this.orderRepository.save(order);
1704
+ return order;
1705
+ }
1706
+ }
1707
+ ```
1708
+
1709
+ ## Data Access Layer
1710
+
1711
+ Handles persistence operations.
1712
+
1713
+ ```typescript
1714
+ class OrderRepository {
1715
+ constructor(private db: Database) {}
1716
+
1717
+ async save(order: Order): Promise<void> {
1718
+ await this.db.query(
1719
+ 'INSERT INTO orders (id, customer_id, total) VALUES ($1, $2, $3)',
1720
+ [order.id, order.customerId, order.total]
1721
+ );
1722
+ }
1723
+
1724
+ async findById(id: string): Promise<Order | null> {
1725
+ const row = await this.db.queryOne('SELECT * FROM orders WHERE id = $1', [id]);
1726
+ return row ? this.mapToOrder(row) : null;
1727
+ }
1728
+ }
1729
+ ```
1730
+
1731
+ ## Layer Rules
1732
+
1733
+ 1. Upper layers depend on lower layers
1734
+ 2. Never skip layers
1735
+ 3. Each layer exposes interfaces to the layer above
1736
+ 4. Domain layer should not depend on data access implementation
1737
+
1738
+ ## Best Practices
1739
+
1740
+ - Keep layers focused on their responsibility
1741
+ - Use DTOs to transfer data between layers
1742
+ - Define interfaces in domain layer, implement in data access
1743
+ - Avoid business logic in presentation or data access layers
1744
+ - Consider dependency inversion for testability
1745
+
1746
+
1747
+ ---
1748
+
1749
+ # Serverless Architecture
1750
+
1751
+ ## Key Principles
1752
+
1753
+ - **Stateless functions**: Each invocation is independent
1754
+ - **Event-driven**: Functions triggered by events
1755
+ - **Auto-scaling**: Platform handles scaling
1756
+ - **Pay-per-use**: Billed by execution
1757
+
1758
+ ## Function Design
1759
+
1760
+ ```typescript
1761
+ // Handler pattern
1762
+ export async function handler(
1763
+ event: APIGatewayEvent,
1764
+ context: Context
1765
+ ): Promise<APIGatewayProxyResult> {
1766
+ try {
1767
+ const body = JSON.parse(event.body || '{}');
1768
+ const result = await processOrder(body);
1769
+
1770
+ return {
1771
+ statusCode: 200,
1772
+ body: JSON.stringify(result)
1773
+ };
1774
+ } catch (error) {
1775
+ return {
1776
+ statusCode: 500,
1777
+ body: JSON.stringify({ error: 'Internal error' })
1778
+ };
1779
+ }
1780
+ }
1781
+ ```
1782
+
1783
+ ## Cold Start Optimization
1784
+
1785
+ ```typescript
1786
+ // Initialize outside handler (reused across invocations)
1787
+ const dbPool = createPool(process.env.DATABASE_URL);
1788
+
1789
+ export async function handler(event: Event): Promise<Response> {
1790
+ // Use cached connection
1791
+ const result = await dbPool.query('SELECT * FROM orders');
1792
+ return { statusCode: 200, body: JSON.stringify(result) };
1793
+ }
1794
+ ```
1795
+
1796
+ ## State Management
1797
+
1798
+ ```typescript
1799
+ // Use external state stores
1800
+ class OrderService {
1801
+ constructor(
1802
+ private dynamodb: DynamoDB,
1803
+ private redis: Redis
1804
+ ) {}
1805
+
1806
+ async getOrder(id: string): Promise<Order> {
1807
+ // Check cache first
1808
+ const cached = await this.redis.get(`order:${id}`);
1809
+ if (cached) return JSON.parse(cached);
1810
+
1811
+ // Fall back to database
1812
+ const result = await this.dynamodb.get({ Key: { id } });
1813
+ await this.redis.set(`order:${id}`, JSON.stringify(result));
1814
+ return result;
1815
+ }
1816
+ }
1817
+ ```
1818
+
1819
+ ## Best Practices
1820
+
1821
+ - Keep functions small and focused
1822
+ - Use environment variables for configuration
1823
+ - Minimize dependencies to reduce cold start time
1824
+ - Handle timeouts gracefully
1825
+ - Use async/await for all I/O operations
1826
+ - Implement idempotency for event handlers
1827
+ - Log structured data for observability
1828
+ - Set appropriate memory and timeout limits
1829
+
1830
+
1831
+ ---
1832
+
1833
+ # Serverless Best Practices
1834
+
1835
+ ## Function Design
1836
+
1837
+ ### Single Responsibility
1838
+
1839
+ ```typescript
1840
+ // ❌ Bad: Multiple responsibilities
1841
+ export const handler = async (event: APIGatewayEvent) => {
1842
+ if (event.path === '/users') {
1843
+ // Handle users
1844
+ } else if (event.path === '/orders') {
1845
+ // Handle orders
1846
+ } else if (event.path === '/products') {
1847
+ // Handle products
1848
+ }
1849
+ };
1850
+
1851
+ // ✅ Good: One function per responsibility
1852
+ // createUser.ts
1853
+ export const handler = async (event: APIGatewayEvent) => {
1854
+ const userData = JSON.parse(event.body);
1855
+ const user = await userService.create(userData);
1856
+ return { statusCode: 201, body: JSON.stringify(user) };
1857
+ };
1858
+ ```
1859
+
1860
+ ### Keep Functions Small
1861
+
1862
+ ```typescript
1863
+ // ✅ Good: Small, focused function
1864
+ export const handler = async (event: SNSEvent) => {
1865
+ for (const record of event.Records) {
1866
+ const message = JSON.parse(record.Sns.Message);
1867
+ await processMessage(message);
1868
+ }
1869
+ };
1870
+
1871
+ // Extract business logic to separate module
1872
+ async function processMessage(message: OrderMessage): Promise<void> {
1873
+ const order = await orderService.process(message);
1874
+ await notificationService.sendConfirmation(order);
1875
+ }
1876
+ ```
1877
+
1878
+ ## Cold Start Optimization
1879
+
1880
+ ### Minimize Dependencies
1881
+
1882
+ ```typescript
1883
+ // ❌ Bad: Heavy imports at top level
1884
+ import * as AWS from 'aws-sdk';
1885
+ import moment from 'moment';
1886
+ import _ from 'lodash';
1887
+
1888
+ // ✅ Good: Import only what you need
1889
+ import { DynamoDB } from '@aws-sdk/client-dynamodb';
1890
+
1891
+ // ✅ Good: Lazy load optional dependencies
1892
+ let heavyLib: typeof import('heavy-lib') | undefined;
1893
+
1894
+ async function useHeavyFeature() {
1895
+ if (!heavyLib) {
1896
+ heavyLib = await import('heavy-lib');
1897
+ }
1898
+ return heavyLib.process();
1899
+ }
1900
+ ```
1901
+
1902
+ ### Initialize Outside Handler
1903
+
1904
+ ```typescript
1905
+ // ✅ Good: Reuse connections across invocations
1906
+ import { DynamoDB } from '@aws-sdk/client-dynamodb';
1907
+
1908
+ // Created once, reused
1909
+ const dynamodb = new DynamoDB({});
1910
+ let cachedConnection: Connection | undefined;
1911
+
1912
+ export const handler = async (event: Event) => {
1913
+ // Reuse existing connection
1914
+ if (!cachedConnection) {
1915
+ cachedConnection = await createConnection();
1916
+ }
1917
+
1918
+ return process(event, cachedConnection);
1919
+ };
1920
+ ```
1921
+
1922
+ ### Provisioned Concurrency
1923
+
1924
+ ```yaml
1925
+ # serverless.yml
1926
+ functions:
1927
+ api:
1928
+ handler: handler.api
1929
+ provisionedConcurrency: 5 # Keep 5 instances warm
1930
+ ```
1931
+
1932
+ ## Error Handling
1933
+
1934
+ ### Structured Error Responses
1935
+
1936
+ ```typescript
1937
+ class LambdaError extends Error {
1938
+ constructor(
1939
+ message: string,
1940
+ public statusCode: number,
1941
+ public code: string
1942
+ ) {
1943
+ super(message);
1944
+ }
1945
+ }
1946
+
1947
+ export const handler = async (event: APIGatewayEvent) => {
1948
+ try {
1949
+ const result = await processRequest(event);
1950
+ return {
1951
+ statusCode: 200,
1952
+ body: JSON.stringify(result)
1953
+ };
1954
+ } catch (error) {
1955
+ if (error instanceof LambdaError) {
1956
+ return {
1957
+ statusCode: error.statusCode,
1958
+ body: JSON.stringify({
1959
+ error: { code: error.code, message: error.message }
1960
+ })
1961
+ };
1962
+ }
1963
+
1964
+ console.error('Unexpected error:', error);
1965
+ return {
1966
+ statusCode: 500,
1967
+ body: JSON.stringify({
1968
+ error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
1969
+ })
1970
+ };
1971
+ }
1972
+ };
1973
+ ```
1974
+
1975
+ ### Retry and Dead Letter Queues
1976
+
1977
+ ```yaml
1978
+ # CloudFormation
1979
+ Resources:
1980
+ MyFunction:
1981
+ Type: AWS::Lambda::Function
1982
+ Properties:
1983
+ DeadLetterConfig:
1984
+ TargetArn: !GetAtt DeadLetterQueue.Arn
1985
+
1986
+ DeadLetterQueue:
1987
+ Type: AWS::SQS::Queue
1988
+ Properties:
1989
+ QueueName: my-function-dlq
1990
+ ```
1991
+
1992
+ ## State Management
1993
+
1994
+ ### Use External State Stores
1995
+
1996
+ ```typescript
1997
+ // ❌ Bad: In-memory state (lost between invocations)
1998
+ let requestCount = 0;
1999
+
2000
+ export const handler = async () => {
2001
+ requestCount++; // Unreliable!
2002
+ };
2003
+
2004
+ // ✅ Good: External state store
2005
+ import { DynamoDB } from '@aws-sdk/client-dynamodb';
2006
+
2007
+ const dynamodb = new DynamoDB({});
2008
+
2009
+ export const handler = async (event: Event) => {
2010
+ // Atomic counter in DynamoDB
2011
+ await dynamodb.updateItem({
2012
+ TableName: 'Counters',
2013
+ Key: { id: { S: 'requests' } },
2014
+ UpdateExpression: 'ADD #count :inc',
2015
+ ExpressionAttributeNames: { '#count': 'count' },
2016
+ ExpressionAttributeValues: { ':inc': { N: '1' } }
2017
+ });
2018
+ };
2019
+ ```
2020
+
2021
+ ### Step Functions for Workflows
2022
+
2023
+ ```yaml
2024
+ # Step Functions state machine
2025
+ StartAt: ValidateOrder
2026
+ States:
2027
+ ValidateOrder:
2028
+ Type: Task
2029
+ Resource: arn:aws:lambda:...:validateOrder
2030
+ Next: ProcessPayment
2031
+
2032
+ ProcessPayment:
2033
+ Type: Task
2034
+ Resource: arn:aws:lambda:...:processPayment
2035
+ Catch:
2036
+ - ErrorEquals: [PaymentFailed]
2037
+ Next: NotifyFailure
2038
+ Next: FulfillOrder
2039
+
2040
+ FulfillOrder:
2041
+ Type: Task
2042
+ Resource: arn:aws:lambda:...:fulfillOrder
2043
+ End: true
2044
+
2045
+ NotifyFailure:
2046
+ Type: Task
2047
+ Resource: arn:aws:lambda:...:notifyFailure
2048
+ End: true
2049
+ ```
2050
+
2051
+ ## Security
2052
+
2053
+ ### Least Privilege IAM
2054
+
2055
+ ```yaml
2056
+ # serverless.yml
2057
+ provider:
2058
+ iam:
2059
+ role:
2060
+ statements:
2061
+ # Only the permissions needed
2062
+ - Effect: Allow
2063
+ Action:
2064
+ - dynamodb:GetItem
2065
+ - dynamodb:PutItem
2066
+ Resource: arn:aws:dynamodb:*:*:table/Users
2067
+
2068
+ - Effect: Allow
2069
+ Action:
2070
+ - s3:GetObject
2071
+ Resource: arn:aws:s3:::my-bucket/*
2072
+ ```
2073
+
2074
+ ### Secrets Management
2075
+
2076
+ ```typescript
2077
+ import { SecretsManager } from '@aws-sdk/client-secrets-manager';
2078
+
2079
+ const secretsManager = new SecretsManager({});
2080
+ let cachedSecret: string | undefined;
2081
+
2082
+ async function getSecret(): Promise<string> {
2083
+ if (!cachedSecret) {
2084
+ const response = await secretsManager.getSecretValue({
2085
+ SecretId: 'my-api-key'
2086
+ });
2087
+ cachedSecret = response.SecretString;
2088
+ }
2089
+ return cachedSecret!;
2090
+ }
2091
+ ```
2092
+
2093
+ ## Monitoring and Observability
2094
+
2095
+ ### Structured Logging
2096
+
2097
+ ```typescript
2098
+ import { Logger } from '@aws-lambda-powertools/logger';
2099
+
2100
+ const logger = new Logger({
2101
+ serviceName: 'order-service',
2102
+ logLevel: 'INFO'
2103
+ });
2104
+
2105
+ export const handler = async (event: Event, context: Context) => {
2106
+ logger.addContext(context);
2107
+
2108
+ logger.info('Processing order', {
2109
+ orderId: event.orderId,
2110
+ customerId: event.customerId
2111
+ });
2112
+
2113
+ try {
2114
+ const result = await processOrder(event);
2115
+ logger.info('Order processed', { orderId: event.orderId });
2116
+ return result;
2117
+ } catch (error) {
2118
+ logger.error('Order processing failed', { error, event });
2119
+ throw error;
2120
+ }
2121
+ };
2122
+ ```
2123
+
2124
+ ### Tracing
2125
+
2126
+ ```typescript
2127
+ import { Tracer } from '@aws-lambda-powertools/tracer';
2128
+
2129
+ const tracer = new Tracer({ serviceName: 'order-service' });
2130
+
2131
+ export const handler = async (event: Event) => {
2132
+ const segment = tracer.getSegment();
2133
+ const subsegment = segment.addNewSubsegment('ProcessOrder');
2134
+
2135
+ try {
2136
+ const result = await processOrder(event);
2137
+ subsegment.close();
2138
+ return result;
2139
+ } catch (error) {
2140
+ subsegment.addError(error);
2141
+ subsegment.close();
2142
+ throw error;
2143
+ }
2144
+ };
2145
+ ```
2146
+
2147
+ ## Cost Optimization
2148
+
2149
+ - Set appropriate memory (more memory = faster CPU)
2150
+ - Use ARM architecture when possible (cheaper)
2151
+ - Batch operations to reduce invocations
2152
+ - Use reserved concurrency to limit costs
2153
+ - Monitor and alert on spending
2154
+ - Clean up unused functions and versions
2155
+
2156
+
2157
+ ---
2158
+
2159
+ # Hexagonal Architecture (Ports & Adapters)
2160
+
2161
+ ## Core Principle
2162
+
2163
+ The application core (domain logic) is isolated from external concerns through ports (interfaces) and adapters (implementations).
2164
+
2165
+ ## Structure
2166
+
2167
+ ```
2168
+ src/
2169
+ ├── domain/ # Pure business logic, no external dependencies
2170
+ │ ├── models/ # Domain entities and value objects
2171
+ │ ├── services/ # Domain services
2172
+ │ └── ports/ # Interface definitions (driven & driving)
2173
+ ├── application/ # Use cases, orchestration
2174
+ │ └── services/ # Application services
2175
+ ├── adapters/
2176
+ │ ├── primary/ # Driving adapters (controllers, CLI, events)
2177
+ │ │ ├── http/
2178
+ │ │ ├── grpc/
2179
+ │ │ └── cli/
2180
+ │ └── secondary/ # Driven adapters (repositories, clients)
2181
+ │ ├── persistence/
2182
+ │ ├── messaging/
2183
+ │ └── external-apis/
2184
+ └── config/ # Dependency injection, configuration
2185
+ ```
2186
+
2187
+ ## Port Types
2188
+
2189
+ ### Driving Ports (Primary)
2190
+ Interfaces that the application exposes to the outside world:
2191
+
2192
+ ```typescript
2193
+ // domain/ports/driving/user-service.port.ts
2194
+ export interface UserServicePort {
2195
+ createUser(data: CreateUserDTO): Promise<User>;
2196
+ getUser(id: string): Promise<User | null>;
2197
+ updateUser(id: string, data: UpdateUserDTO): Promise<User>;
2198
+ }
2199
+ ```
2200
+
2201
+ ### Driven Ports (Secondary)
2202
+ Interfaces that the application needs from the outside world:
2203
+
2204
+ ```typescript
2205
+ // domain/ports/driven/user-repository.port.ts
2206
+ export interface UserRepositoryPort {
2207
+ save(user: User): Promise<void>;
2208
+ findById(id: string): Promise<User | null>;
2209
+ findByEmail(email: string): Promise<User | null>;
2210
+ }
2211
+
2212
+ // domain/ports/driven/email-sender.port.ts
2213
+ export interface EmailSenderPort {
2214
+ send(to: string, subject: string, body: string): Promise<void>;
2215
+ }
2216
+ ```
2217
+
2218
+ ## Adapter Implementation
2219
+
2220
+ ### Primary Adapter (HTTP Controller)
2221
+
2222
+ ```typescript
2223
+ // adapters/primary/http/user.controller.ts
2224
+ export class UserController {
2225
+ constructor(private userService: UserServicePort) {}
2226
+
2227
+ async create(req: Request, res: Response) {
2228
+ const user = await this.userService.createUser(req.body);
2229
+ res.status(201).json(user);
2230
+ }
2231
+ }
2232
+ ```
2233
+
2234
+ ### Secondary Adapter (Repository)
2235
+
2236
+ ```typescript
2237
+ // adapters/secondary/persistence/postgres-user.repository.ts
2238
+ export class PostgresUserRepository implements UserRepositoryPort {
2239
+ constructor(private db: DatabaseConnection) {}
2240
+
2241
+ async save(user: User): Promise<void> {
2242
+ await this.db.query('INSERT INTO users...', user);
2243
+ }
2244
+
2245
+ async findById(id: string): Promise<User | null> {
2246
+ const row = await this.db.query('SELECT * FROM users WHERE id = $1', [id]);
2247
+ return row ? this.toDomain(row) : null;
2248
+ }
2249
+ }
2250
+ ```
2251
+
2252
+ ## Dependency Rule
2253
+
2254
+ Dependencies always point inward:
2255
+ - Adapters depend on Ports
2256
+ - Application depends on Domain
2257
+ - Domain has no external dependencies
2258
+
2259
+ ```
2260
+ [External World] → [Adapters] → [Ports] → [Domain]
2261
+ ```
2262
+
2263
+ ## Testing Benefits
2264
+
2265
+ ```typescript
2266
+ // Test with mock adapters
2267
+ class InMemoryUserRepository implements UserRepositoryPort {
2268
+ private users = new Map<string, User>();
2269
+
2270
+ async save(user: User) { this.users.set(user.id, user); }
2271
+ async findById(id: string) { return this.users.get(id) || null; }
2272
+ }
2273
+
2274
+ // Domain logic tested without infrastructure
2275
+ describe('UserService', () => {
2276
+ it('creates user', async () => {
2277
+ const repo = new InMemoryUserRepository();
2278
+ const service = new UserService(repo);
2279
+ const user = await service.createUser({ name: 'Test' });
2280
+ expect(user.name).toBe('Test');
2281
+ });
2282
+ });
2283
+ ```
2284
+
2285
+ ## When to Use
2286
+
2287
+ - Applications needing multiple entry points (HTTP, CLI, events)
2288
+ - Systems requiring easy infrastructure swapping
2289
+ - Projects prioritizing testability
2290
+ - Long-lived applications expecting technology changes
2291
+
2292
+
2293
+ ---
2294
+
2295
+ # GUI Architecture Patterns
2296
+
2297
+ ## MVC (Model-View-Controller)
2298
+
2299
+ ```typescript
2300
+ // Model - data and business logic
2301
+ class UserModel {
2302
+ private users: User[] = [];
2303
+
2304
+ getUsers(): User[] { return this.users; }
2305
+ addUser(user: User): void { this.users.push(user); }
2306
+ }
2307
+
2308
+ // View - presentation
2309
+ class UserView {
2310
+ render(users: User[]): void {
2311
+ console.log('Users:', users);
2312
+ }
2313
+ }
2314
+
2315
+ // Controller - handles input, coordinates
2316
+ class UserController {
2317
+ constructor(
2318
+ private model: UserModel,
2319
+ private view: UserView
2320
+ ) {}
2321
+
2322
+ handleAddUser(userData: UserData): void {
2323
+ const user = new User(userData);
2324
+ this.model.addUser(user);
2325
+ this.view.render(this.model.getUsers());
2326
+ }
2327
+ }
2328
+ ```
2329
+
2330
+ ## MVP (Model-View-Presenter)
2331
+
2332
+ ```typescript
2333
+ // View interface - defines what presenter can call
2334
+ interface UserView {
2335
+ showUsers(users: User[]): void;
2336
+ showError(message: string): void;
2337
+ }
2338
+
2339
+ // Presenter - all presentation logic
2340
+ class UserPresenter {
2341
+ constructor(
2342
+ private view: UserView,
2343
+ private model: UserModel
2344
+ ) {}
2345
+
2346
+ loadUsers(): void {
2347
+ try {
2348
+ const users = this.model.getUsers();
2349
+ this.view.showUsers(users);
2350
+ } catch (error) {
2351
+ this.view.showError('Failed to load users');
2352
+ }
2353
+ }
2354
+ }
2355
+
2356
+ // View implementation - passive, no logic
2357
+ class UserListView implements UserView {
2358
+ showUsers(users: User[]): void { /* render list */ }
2359
+ showError(message: string): void { /* show error */ }
2360
+ }
2361
+ ```
2362
+
2363
+ ## MVVM (Model-View-ViewModel)
2364
+
2365
+ ```typescript
2366
+ // ViewModel - exposes observable state
2367
+ class UserViewModel {
2368
+ users = observable<User[]>([]);
2369
+ isLoading = observable(false);
2370
+
2371
+ async loadUsers(): Promise<void> {
2372
+ this.isLoading.set(true);
2373
+ const users = await this.userService.getUsers();
2374
+ this.users.set(users);
2375
+ this.isLoading.set(false);
2376
+ }
2377
+ }
2378
+
2379
+ // View binds to ViewModel
2380
+ const UserList = observer(({ viewModel }: { viewModel: UserViewModel }) => (
2381
+ <div>
2382
+ {viewModel.isLoading.get() ? (
2383
+ <Spinner />
2384
+ ) : (
2385
+ viewModel.users.get().map(user => <UserItem key={user.id} user={user} />)
2386
+ )}
2387
+ </div>
2388
+ ));
2389
+ ```
2390
+
2391
+ ## Component Architecture (React/Vue)
2392
+
2393
+ ```typescript
2394
+ // Presentational component - no state, just props
2395
+ const UserCard = ({ user, onDelete }: UserCardProps) => (
2396
+ <div className="user-card">
2397
+ <h3>{user.name}</h3>
2398
+ <button onClick={() => onDelete(user.id)}>Delete</button>
2399
+ </div>
2400
+ );
2401
+
2402
+ // Container component - manages state
2403
+ const UserListContainer = () => {
2404
+ const [users, setUsers] = useState<User[]>([]);
2405
+
2406
+ useEffect(() => {
2407
+ userService.getUsers().then(setUsers);
2408
+ }, []);
2409
+
2410
+ const handleDelete = (id: string) => {
2411
+ userService.deleteUser(id).then(() => {
2412
+ setUsers(users.filter(u => u.id !== id));
2413
+ });
2414
+ };
2415
+
2416
+ return <UserList users={users} onDelete={handleDelete} />;
2417
+ };
2418
+ ```
2419
+
2420
+ ## Best Practices
2421
+
2422
+ - Separate UI logic from business logic
2423
+ - Keep views as simple as possible
2424
+ - Use unidirectional data flow when possible
2425
+ - Make components reusable and testable
2426
+ - Choose pattern based on framework and team familiarity
2427
+
2428
+
2429
+ ---
2430
+
2431
+ # Feature Toggles
2432
+
2433
+ ## Toggle Types
2434
+
2435
+ ### Release Toggles
2436
+ Hide incomplete features in production.
2437
+
2438
+ ```typescript
2439
+ if (featureFlags.isEnabled('new-checkout')) {
2440
+ return <NewCheckout />;
2441
+ }
2442
+ return <LegacyCheckout />;
2443
+ ```
2444
+
2445
+ ### Experiment Toggles
2446
+ A/B testing and gradual rollouts.
2447
+
2448
+ ```typescript
2449
+ const variant = featureFlags.getVariant('pricing-experiment', userId);
2450
+ if (variant === 'new-pricing') {
2451
+ return calculateNewPricing(cart);
2452
+ }
2453
+ return calculateLegacyPricing(cart);
2454
+ ```
2455
+
2456
+ ### Ops Toggles
2457
+ Runtime operational control.
2458
+
2459
+ ```typescript
2460
+ if (featureFlags.isEnabled('enable-caching')) {
2461
+ return cache.get(key) || fetchFromDatabase(key);
2462
+ }
2463
+ return fetchFromDatabase(key);
2464
+ ```
2465
+
2466
+ ## Implementation
2467
+
2468
+ ```typescript
2469
+ interface FeatureFlags {
2470
+ isEnabled(flag: string, context?: Context): boolean;
2471
+ getVariant(flag: string, userId: string): string;
2472
+ }
2473
+
2474
+ class FeatureFlagService implements FeatureFlags {
2475
+ constructor(private config: Map<string, FlagConfig>) {}
2476
+
2477
+ isEnabled(flag: string, context?: Context): boolean {
2478
+ const config = this.config.get(flag);
2479
+ if (!config) return false;
2480
+
2481
+ if (config.percentage) {
2482
+ return this.isInPercentage(context?.userId, config.percentage);
2483
+ }
2484
+
2485
+ return config.enabled;
2486
+ }
2487
+
2488
+ private isInPercentage(userId: string | undefined, percentage: number): boolean {
2489
+ if (!userId) return false;
2490
+ const hash = this.hashUserId(userId);
2491
+ return (hash % 100) < percentage;
2492
+ }
2493
+ }
2494
+ ```
2495
+
2496
+ ## Best Practices
2497
+
2498
+ - Remove toggles after feature is stable
2499
+ - Use clear naming conventions
2500
+ - Log toggle decisions for debugging
2501
+ - Test both toggle states
2502
+ - Limit number of active toggles
2503
+ - Document toggle purpose and expiration
2504
+
2505
+
2506
+ ---
2507
+ *Generated by aicgen*