diff --git a/.agent/rules/api-design.md b/.agent/rules/api-design.md deleted file mode 100644 index cd8ada4a..00000000 --- a/.agent/rules/api-design.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -trigger: always_on ---- - -# RESTful API Design Standards - -## When to Use This Skill - -Use this skill when: -- Creating new API endpoints -- Designing request/response DTOs -- Implementing controllers and routes -- Writing OpenAPI/Swagger documentation -- Standardizing error responses -- Implementing pagination, filtering, and sorting -- Setting up API versioning - -## Core Principles - -1. **Consistency**: All APIs follow the same patterns -2. **Predictability**: Developers can guess endpoint behavior -3. **Simplicity**: Easy to understand and use -4. **Documentation**: Self-documenting through OpenAPI -5. **Error Handling**: Clear, actionable error messages - -## URL Structure - -``` -https://api.goodgo.com/v1/{resource}/{id}/{sub-resource} - -GET /v1/users # List users -POST /v1/users # Create user -GET /v1/users/123 # Get user by ID -PUT /v1/users/123 # Update user -DELETE /v1/users/123 # Delete user -GET /v1/users/123/orders # Get user's orders -``` - -## HTTP Methods - -- **GET**: Retrieve resource(s) - Safe, Idempotent -- **POST**: Create new resource - Not idempotent -- **PUT**: Full update - Idempotent -- **PATCH**: Partial update - Idempotent -- **DELETE**: Remove resource - Idempotent - -## Standard Response Format - -```typescript -// Success -interface SuccessResponse { - success: true; - data: T; - pagination?: { page: number; limit: number; total: number; totalPages: number }; -} - -// Error -interface ErrorResponse { - success: false; - error: { code: string; message: string; details?: any; field?: string }; -} -``` - -## Key Patterns - -### Request DTO - -```typescript -export class CreateUserDto { - @IsEmail() @IsNotEmpty() email: string; - @MinLength(6) @IsNotEmpty() password: string; - @IsOptional() name?: string; -} - -export class QueryUsersDto { - @IsOptional() @Min(1) page?: number = 1; - @IsOptional() @Min(1) @Max(100) limit?: number = 10; - @IsOptional() search?: string; - @IsOptional() @IsIn(['createdAt', 'name']) sortBy?: string = 'createdAt'; - @IsOptional() @IsIn(['asc', 'desc']) order?: 'asc' | 'desc' = 'desc'; -} -``` - -### Controller - -```typescript -@Get() -async list(@Query() query: QueryUsersDto) { - const { data, total } = await this.userService.findAll(query); - return { - success: true, - data: data.map(UserResponseDto.fromEntity), - pagination: { page: query.page, limit: query.limit, total, totalPages: Math.ceil(total / query.limit) } - }; -} - -@Get(':id') -async getById(@Param('id') id: string) { - const user = await this.userService.findById(id); - if (!user) throw new NotFoundException({ success: false, error: { code: 'NOT_FOUND', message: 'User not found' } }); - return { success: true, data: UserResponseDto.fromEntity(user) }; -} -``` - -## Best Practices - -- **Resource Naming**: Use plural nouns (`/users`), kebab-case for multi-word -- **Versioning**: Include version in URL (`/v1/users`), maintain backward compatibility -- **Security**: Use HTTPS, implement rate limiting, validate all inputs -- **Performance**: Implement pagination, use field filtering, cache responses -- **Documentation**: Keep OpenAPI spec up to date, include examples - -## Common Mistakes - -1. **Using Verbs in URLs**: Non-RESTful endpoints - ``` - # BAD: POST /api/v1/createUser - # GOOD: POST /api/v1/users - ``` - -2. **Inconsistent Response Format**: Different structures for different endpoints - ```typescript - // BAD: res.json({ user: data }) vs res.json({ result: data }) - // GOOD: res.json({ success: true, data }) - ``` - -3. **Wrong HTTP Status Codes**: Using 200 for errors - ```typescript - // BAD: res.status(200).json({ error: 'Not found' }); - // GOOD: res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } }); - ``` - -4. **Missing Pagination**: Returning all records - ```typescript - // BAD: prisma.user.findMany() - // GOOD: prisma.user.findMany({ skip: (page - 1) * limit, take: limit }) - ``` - -## Quick Reference - -| HTTP Method | Action | Idempotent | Status Codes | -|-------------|--------|------------|--------------| -| **GET** | Retrieve | Yes | 200, 404 | -| **POST** | Create | No | 201, 400, 409 | -| **PUT** | Full update | Yes | 200, 400, 404 | -| **PATCH** | Partial update | Yes | 200, 400, 404 | -| **DELETE** | Remove | Yes | 204, 404 | - -**Response Format:** -```typescript -// Success: { success: true, data: T, pagination?: {...} } -// Error: { success: false, error: { code: string, message: string } } -``` - -**Common Error Codes:** -- `400` - Bad Request (validation) -- `401` - Unauthorized (no token) -- `403` - Forbidden (no permission) -- `404` - Not Found -- `409` - Conflict (duplicate) -- `422` - Unprocessable (business rule) -- `429` - Rate limited - -## Resources - -- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) - Official OpenAPI docs -- [REST API Design](https://restfulapi.net/) - REST best practices -- [Detailed Code Examples](./references/REFERENCE.md) -- [API Versioning Strategy](../api-versioning-strategy/SKILL.md) - Versioning patterns -- [API Gateway Advanced](../api-gateway-advanced/SKILL.md) - Gateway patterns -- [Middleware Patterns](../middleware-patterns/SKILL.md) - Request handling diff --git a/.agent/rules/api-gateway-advanced.md b/.agent/rules/api-gateway-advanced.md deleted file mode 100644 index a60afcb9..00000000 --- a/.agent/rules/api-gateway-advanced.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -trigger: always_on ---- - -# API Gateway Advanced Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing API composition and aggregation -- Transforming requests/responses at gateway level -- Integrating service mesh (Istio/Linkerd) with Traefik -- Implementing advanced routing strategies -- Adding gateway-level circuit breakers -- Implementing API versioning at gateway -- Optimizing gateway performance - -## Core Concepts - -### API Gateway Responsibilities - -1. **Request Routing**: Route requests to appropriate services -2. **API Composition**: Aggregate multiple service responses -3. **Protocol Translation**: Convert between protocols (HTTP, gRPC, GraphQL) -4. **Request/Response Transformation**: Modify requests and responses -5. **Security**: Authentication, authorization, rate limiting -6. **Resilience**: Circuit breaker, retry, timeout at gateway level -7. **Observability**: Logging, metrics, tracing - -### API Composition Patterns - -- **Aggregation**: Combine multiple service responses into single response -- **Chaining**: Call services sequentially, use previous response in next call -- **Fan-out/Fan-in**: Call multiple services in parallel, aggregate results - -## Key Patterns - -### API Composition - -```typescript -// Fan-out: Call multiple services in parallel -async getUserProfile(userId: string) { - const [user, orders, payments] = await Promise.all([ - this.userClient.get(`/users/${userId}`), - this.orderClient.get(`/orders?userId=${userId}`), - this.paymentClient.get(`/payments?userId=${userId}`), - ]); - - return { - user: user.data, - orders: orders.data.orders, - paymentHistory: payments.data.payments, - }; -} - -// Chaining: Sequential calls with compensation -async createOrderWithPayment(data) { - const order = await this.orderClient.post('/orders', data); - try { - const payment = await this.paymentClient.post('/payments', { orderId: order.data.id }); - return { order: order.data, payment: payment.data }; - } catch (error) { - await this.orderClient.delete(`/orders/${order.data.id}`); // Compensate - throw error; - } -} -``` - -### Gateway Caching - -```typescript -export function gatewayCache(ttl: number = 60) { - return async (req, res, next) => { - if (req.method !== 'GET') return next(); - - const cacheKey = `gateway:${req.path}:${JSON.stringify(req.query)}`; - const cached = await cacheService.get(cacheKey); - if (cached) return res.json(cached); - - const originalJson = res.json.bind(res); - res.json = (data) => { - cacheService.set(cacheKey, data, ttl); - return originalJson(data); - }; - next(); - }; -} -``` - -### Traefik Circuit Breaker - -```yaml -middlewares: - circuit-breaker: - circuitBreaker: - expression: "NetworkErrorRatio() > 0.50" - timeout: - forwardingTimeouts: - dialTimeout: 5s - responseHeaderTimeout: 10s -``` - -## Best Practices - -- **API Composition**: Use for aggregating related data from multiple services -- **Caching**: Cache at gateway for frequently accessed data -- **Circuit Breaker**: Implement at gateway to protect downstream services -- **Transformation**: Keep transformations simple and testable -- **Versioning**: Use path-based or header-based versioning -- **Monitoring**: Monitor gateway metrics (latency, error rate) - -## Common Mistakes - -1. **No Timeout at Gateway**: Requests hanging forever - ```yaml - # GOOD: Set timeout - middlewares: - timeout: - forwardingTimeouts: - dialTimeout: 5s - responseHeaderTimeout: 10s - ``` - -2. **Missing Circuit Breaker**: Cascading failures - ```yaml - # GOOD: Add circuit breaker for each service - middlewares: - circuit-breaker: - circuitBreaker: - expression: "NetworkErrorRatio() > 0.50" - ``` - -3. **No Caching for Static Data**: Unnecessary service load - ```typescript - // GOOD: Cache at gateway - app.get('/api/config', gatewayCache(3600), handler); - ``` - -4. **N+1 API Calls from Client**: Multiple round trips - ```typescript - // BAD: Client makes 3 calls - // GOOD: Use API composition GET /user-profile/123 - ``` - -## Quick Reference - -| Pattern | Use Case | Implementation | -|---------|----------|----------------| -| **API Composition** | Aggregate multiple services | Promise.all() | -| **Request Transform** | Modify incoming requests | Middleware | -| **Response Transform** | Standardize responses | Override res.json | -| **Gateway Caching** | Cache GET responses | Redis/Memory | -| **Circuit Breaker** | Protect from failures | Traefik middleware | - -**Traefik Middleware Order:** -``` -1. Rate Limiting -2. Authentication -3. Circuit Breaker -4. Retry -5. Headers Transform -6. Compression -``` - -**API Composition Patterns:** -```typescript -// Fan-out (parallel) -const [users, orders] = await Promise.all([userClient.get('/users'), orderClient.get('/orders')]); - -// Chaining (sequential) -const order = await orderClient.create(data); -const payment = await paymentClient.process(order.id); -``` - -## Resources - -- [Traefik Documentation](https://doc.traefik.io/traefik/) - Official Traefik docs -- [API Gateway Pattern](https://microservices.io/patterns/apigateway.html) - Gateway patterns -- [Service Mesh](https://istio.io/) - Istio service mesh -- [Detailed Code Examples](./references/REFERENCE.md) -- [Middleware Patterns](../middleware-patterns/SKILL.md) - Middleware patterns -- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker patterns diff --git a/.agent/rules/api-versioning-strategy.md b/.agent/rules/api-versioning-strategy.md deleted file mode 100644 index eeb29f63..00000000 --- a/.agent/rules/api-versioning-strategy.md +++ /dev/null @@ -1,432 +0,0 @@ ---- -trigger: always_on ---- - -# API Versioning Strategy - -## When to Use This Skill - -Use this skill when: -- Versioning APIs -- Handling breaking changes -- Implementing API deprecation -- Maintaining backward compatibility -- Implementing version negotiation -- Managing multiple API versions -- Planning API evolution -- Communicating API changes to consumers - -## Core Concepts - -### Versioning Strategies - -1. **URL Path Versioning**: `/api/v1/users`, `/api/v2/users` -2. **Header Versioning**: `Accept: application/vnd.goodgo.v1+json` -3. **Query Parameter**: `/api/users?version=1` -4. **Semantic Versioning**: Major.Minor.Patch (e.g., 1.2.3) - -### Compatibility Types - -- **Backward Compatible**: New version works with old clients -- **Forward Compatible**: Old version works with new clients -- **Breaking Changes**: Incompatible changes requiring new version - -## URL Path Versioning - -### Implementation - -```typescript -// src/routes/index.ts -// EN: Route versioning -// VI: Route versioning -import { Router } from 'express'; -import v1Router from './v1'; -import v2Router from './v2'; - -const router = Router(); - -// EN: Version 1 routes -// VI: Routes version 1 -router.use('/v1', v1Router); - -// EN: Version 2 routes -// VI: Routes version 2 -router.use('/v2', v2Router); - -export default router; -``` - -### Version Router - -```typescript -// src/routes/v1/index.ts -// EN: Version 1 routes -// VI: Routes version 1 -import { Router } from 'express'; -import userRoutes from './users'; - -const router = Router(); - -router.use('/users', userRoutes); - -export default router; - -// src/routes/v2/index.ts -// EN: Version 2 routes with breaking changes -// VI: Routes version 2 với breaking changes -import { Router } from 'express'; -import userRoutes from './users'; // EN: Different implementation / VI: Implementation khác - -const router = Router(); - -router.use('/users', userRoutes); // EN: Different response format / VI: Format response khác - -export default router; -``` - -## Header-Based Versioning - -### Version Negotiation Middleware - -```typescript -// src/middlewares/version-negotiation.middleware.ts -// EN: Version negotiation middleware -// VI: Middleware version negotiation -import { Request, Response, NextFunction } from 'express'; -import { logger } from '@goodgo/logger'; - -export function versionNegotiation( - req: Request, - res: Response, - next: NextFunction -): void { - // EN: Extract version from Accept header - // VI: Trích xuất version từ Accept header - const acceptHeader = req.headers.accept || ''; - const versionMatch = acceptHeader.match(/application\/vnd\.goodgo\.v(\d+)\+json/); - - if (versionMatch) { - const requestedVersion = parseInt(versionMatch[1], 10); - req.apiVersion = requestedVersion; - - // EN: Check if version is supported - // VI: Kiểm tra xem version có được hỗ trợ không - const supportedVersions = [1, 2]; - if (!supportedVersions.includes(requestedVersion)) { - return res.status(400).json({ - success: false, - error: { - code: 'UNSUPPORTED_VERSION', - message: `API version ${requestedVersion} is not supported. Supported versions: ${supportedVersions.join(', ')}`, - }, - }); - } - } else { - // EN: Default to latest version - // VI: Mặc định version mới nhất - req.apiVersion = 2; - } - - next(); -} -``` - -### Version-Aware Controller - -```typescript -// src/modules/user/user.controller.ts -// EN: Version-aware controller -// VI: Controller nhận biết version -export class UserController { - async getUser(req: Request, res: Response): Promise { - const version = req.apiVersion || 2; - - if (version === 1) { - // EN: Version 1 response format - // VI: Format response version 1 - const user = await this.userService.findById(req.params.id); - res.json({ - id: user.id, - email: user.email, - name: user.name, - }); - } else { - // EN: Version 2 response format - // VI: Format response version 2 - const user = await this.userService.findById(req.params.id); - res.json({ - success: true, - data: { - user: { - id: user.id, - email: user.email, - name: user.name, - profile: user.profile, // EN: New field in v2 / VI: Field mới trong v2 - }, - }, - metadata: { - version: '2.0.0', - timestamp: new Date().toISOString(), - }, - }); - } - } -} -``` - -## Semantic Versioning - -### Version Structure - -``` -MAJOR.MINOR.PATCH - -MAJOR: Breaking changes -MINOR: Backward-compatible additions -PATCH: Backward-compatible bug fixes -``` - -### Version Response - -```typescript -// src/core/api/version.middleware.ts -// EN: Add version to response -// VI: Thêm version vào response -export function versionMiddleware(req: Request, res: Response, next: NextFunction): void { - const originalJson = res.json.bind(res); - - res.json = (data: any) => { - const response = { - ...data, - metadata: { - ...data.metadata, - apiVersion: req.apiVersion || '2.0.0', - serviceVersion: process.env.SERVICE_VERSION || '1.0.0', - }, - }; - - return originalJson(response); - }; - - next(); -} -``` - -## API Deprecation - -### Deprecation Headers - -```typescript -// src/middlewares/deprecation.middleware.ts -// EN: Deprecation warning middleware -// VI: Middleware cảnh báo deprecation -export function deprecationMiddleware(version: string, sunsetDate: string) { - return (req: Request, res: Response, next: NextFunction): void => { - if (req.apiVersion && parseInt(req.apiVersion.toString()) < parseInt(version)) { - res.setHeader('Deprecation', 'true'); - res.setHeader('Sunset', sunsetDate); - res.setHeader('Link', `<${req.url.replace(/\/v\d+/, `/v${version}`)}>; rel="successor-version"`); - res.setHeader('Warning', `299 - "API version ${req.apiVersion} is deprecated. Please migrate to version ${version} by ${sunsetDate}"`); - } - - next(); - }; -} - -// Usage -router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); -``` - -### Deprecation Documentation - -```typescript -// src/docs/deprecation.md -// EN: Deprecation notices -// VI: Thông báo deprecation - -## API Version 1 Deprecation - -**Deprecated**: 2024-01-01 -**Sunset Date**: 2024-12-31 -**Migration Guide**: [Migration Guide](./migration-v1-to-v2.md) - -### Changes in Version 2 - -- New response format -- Additional user profile fields -- Updated authentication flow -``` - -## Backward Compatibility - -### Compatibility Layer - -```typescript -// src/core/api/compatibility.adapter.ts -// EN: Backward compatibility adapter -// VI: Adapter tương thích ngược -export class CompatibilityAdapter { - /** - * EN: Adapt v1 response to v2 format - * VI: Adapt response v1 sang format v2 - */ - adaptV1ToV2(v1Data: any): any { - return { - success: true, - data: { - user: { - ...v1Data, - profile: null, // EN: Add default for new field / VI: Thêm mặc định cho field mới - }, - }, - metadata: { - version: '2.0.0', - adapted: true, - }, - }; - } - - /** - * EN: Adapt v2 request to v1 format - * VI: Adapt request v2 sang format v1 - */ - adaptV2RequestToV1(v2Request: any): any { - return { - email: v2Request.email, - name: v2Request.name, - // EN: Ignore new fields / VI: Bỏ qua các field mới - }; - } -} -``` - -## Breaking Changes Migration - -### Migration Strategy - -```typescript -// src/core/api/migration.strategy.ts -// EN: Migration strategy for breaking changes -// VI: Chiến lược migration cho breaking changes -export class MigrationStrategy { - /** - * EN: Phase 1: Support both versions - * VI: Giai đoạn 1: Hỗ trợ cả hai versions - */ - phase1SupportBoth(): void { - // EN: Keep v1, add v2 - // VI: Giữ v1, thêm v2 - router.use('/v1', v1Router); - router.use('/v2', v2Router); - } - - /** - * EN: Phase 2: Deprecate v1 - * VI: Giai đoạn 2: Deprecate v1 - */ - phase2DeprecateV1(): void { - router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); - router.use('/v2', v2Router); - } - - /** - * EN: Phase 3: Remove v1 - * VI: Giai đoạn 3: Xóa v1 - */ - phase3RemoveV1(): void { - // EN: After sunset date, remove v1 routes - // VI: Sau sunset date, xóa v1 routes - router.use('/v2', v2Router); - } -} -``` - -## Best Practices - -1. **Versioning Strategy**: Choose URL path or header, be consistent -2. **Semantic Versioning**: Use MAJOR.MINOR.PATCH -3. **Deprecation**: Always deprecate before removing -4. **Migration Guide**: Provide clear migration documentation -5. **Backward Compatibility**: Maintain compatibility when possible -6. **Communication**: Clearly communicate version changes - -## Common Mistakes - -1. **No Deprecation Period**: Breaking clients suddenly - ```typescript - // ❌ BAD: Remove v1 immediately - router.use('/v2', v2Router); - - // ✅ GOOD: Deprecate with sunset date - router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); - router.use('/v2', v2Router); - ``` - -2. **Breaking Changes Without Major Version**: Client confusion - ``` - # ❌ BAD: Breaking change in minor version - v1.1.0 → Changed response format - - # ✅ GOOD: Breaking change = new major version - v1.x.x → v2.0.0 (new response format) - ``` - -3. **Inconsistent Versioning Strategy**: Mixed approaches - ```typescript - // ❌ BAD: Mix URL and header versioning - /api/v1/users + Accept: application/vnd.v2+json - - // ✅ GOOD: Choose one approach - /api/v1/users OR Accept: application/vnd.goodgo.v1+json - ``` - -4. **No Migration Guide**: Clients don't know how to upgrade - ``` - # ✅ Always provide: - - Changelog - - Migration guide - - Sunset date - - Deprecation warnings - ``` - -## Quick Reference - -| Strategy | Pros | Cons | Use When | -|----------|------|------|----------| -| **URL Path** | Clear, cacheable | URL changes | Public APIs | -| **Header** | Clean URLs | Less visible | Internal APIs | -| **Query Param** | Simple | Not RESTful | Quick prototypes | - -**Semantic Versioning:** -``` -MAJOR.MINOR.PATCH - │ │ └── Bug fixes (backward compatible) - │ └──────── New features (backward compatible) - └────────────── Breaking changes -``` - -**Version Lifecycle:** -``` -v1 Active → v2 Released → v1 Deprecated → v1 Sunset → v1 Removed - │ │ │ │ │ - │ │ Add headers Remove from Delete - │ Support + warnings docs routes - Solo both -``` - -**Deprecation Headers:** -```http -Deprecation: true -Sunset: Sat, 31 Dec 2024 23:59:59 GMT -Warning: 299 - "API v1 is deprecated. Migrate to v2 by 2024-12-31" -Link: ; rel="successor-version" -``` - -**Version Detection:** -```typescript -// URL path -const version = req.path.match(/\/v(\d+)\//)?.[1] || '2'; - -// Header -const version = req.headers.accept?.match(/vnd\.goodgo\.v(\d+)/)?.[1]; -``` \ No newline at end of file diff --git a/.agent/rules/caching-patterns.md b/.agent/rules/caching-patterns.md deleted file mode 100644 index 04db6676..00000000 --- a/.agent/rules/caching-patterns.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -trigger: always_on ---- - -# Caching Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing caching for frequently accessed data -- Optimizing database queries with caching -- Designing cache key naming conventions -- Setting TTL (Time To Live) strategies -- Implementing cache invalidation patterns -- Using multi-layer cache (L1: Memory, L2: Redis) -- Handling cache failures gracefully - -## Core Concepts - -### Multi-Layer Cache Strategy - -The platform uses a two-layer cache architecture: - -1. **L1 Cache (Memory)**: NodeCache in-memory cache - - Very fast (< 1ms access time) - - Limited capacity (10k keys default) - - Short TTL (60 seconds default, max 5 minutes) - - Per-instance (not shared across instances) - -2. **L2 Cache (Redis)**: Distributed Redis cache - - Fast (< 5ms access time) - - Large capacity - - Longer TTL (configurable) - - Shared across all service instances - -### Cache Flow - -``` -Request → L1 Cache → Hit? Return - ↓ Miss - L2 Cache → Hit? Return + Warm L1 - ↓ Miss - Data Source (DB/API) → Store in L1 & L2 → Return -``` - -## Patterns - -### Cache Service Usage - -```typescript -import { cacheService } from '../core/cache'; - -// Simple get/set -const cached = await cacheService.get('user:123'); -await cacheService.set('user:123', userData, 300); // 5 minutes TTL - -// Get or set pattern (cache-aside) -const user = await cacheService.getOrSet( - 'user:123', - async () => { - return await userRepository.findById('123'); - }, - 300 // TTL in seconds -); -``` - -### Cache Key Naming Conventions - -Use consistent naming patterns: - -```typescript -// Pattern: {entity}:{identifier} -'user:123' -'user:email:user@example.com' -'user:123:permissions' -'user:123:roles' - -// Pattern: {entity}:{identifier}:{sub-resource} -'session:abc123' -'permission:perm_123' -'role:role_123' -``` - -Cache service provides key generators: - -```typescript -cacheService.keys = { - user: (userId: string) => `user:${userId}`, - userPermissions: (userId: string) => `user:${userId}:permissions`, - userRoles: (userId: string) => `user:${userId}:roles`, - token: (token: string) => `token:${token}`, - session: (sessionId: string) => `session:${sessionId}`, -}; -``` - -### Cache-Aside Pattern - -Most common pattern - check cache first, fetch if miss: - -```typescript -async getUserPermissions(userId: string): Promise { - const cacheKey = cacheService.keys.userPermissions(userId); - - // Try cache first - const cached = await cacheService.get(cacheKey); - if (cached) { - return cached; - } - - // Cache miss - fetch from source - const permissions = await calculatePermissions(userId); - - // Store in cache - await cacheService.set(cacheKey, permissions, 300); // 5 min TTL - - return permissions; -} -``` - -### Get or Set Pattern - -Simplified cache-aside pattern: - -```typescript -const permissions = await cacheService.getOrSet( - cacheService.keys.userPermissions(userId), - async () => { - // This function only runs on cache miss - return await calculatePermissions(userId); - }, - 300 // TTL -); -``` - -### TTL Strategies - -Choose TTL based on data characteristics: - -**Short TTL (60-300s)**: Frequently changing data -- User permissions (300s) -- Session data (varies) -- Real-time statistics - -**Medium TTL (300-1800s)**: Moderately changing data -- User profiles (600s) -- Organization data (900s) -- Configuration (1800s) - -**Long TTL (1800-3600s)**: Rarely changing data -- Static configurations (3600s) -- Reference data (7200s) - -**No TTL**: Very stable data (use with caution) -- Rarely use - prefer long TTL instead - -### Cache Invalidation - -Invalidate cache when data changes: - -```typescript -// Single key invalidation -await cacheService.del(cacheService.keys.user(userId)); -await cacheService.del(cacheService.keys.userPermissions(userId)); - -// Pattern-based invalidation -await cacheService.invalidatePattern('user:123:*'); - -// Multiple keys -await cacheService.delMany([ - cacheService.keys.user(userId), - cacheService.keys.userPermissions(userId), - cacheService.keys.userRoles(userId), -]); -``` - -### Cache Warming - -Pre-populate cache with frequently accessed data: - -```typescript -async warmCache() { - const activeUsers = await userRepository.findActiveUsers(); - - for (const user of activeUsers) { - // Pre-cache user data - await cacheService.set( - cacheService.keys.user(user.id), - user, - 600 - ); - - // Pre-cache permissions - const permissions = await calculatePermissions(user.id); - await cacheService.set( - cacheService.keys.userPermissions(user.id), - permissions, - 300 - ); - } -} -``` - -### Error Handling - -Cache failures should not break the application: - -```typescript -async getWithCache(key: string): Promise { - try { - // Try cache first - const cached = await cacheService.get(key); - if (cached) return cached; - } catch (error) { - // Log but continue - fallback to source - logger.warn('Cache get failed, falling back to source', { key, error }); - } - - // Fallback to data source - return await fetchFromSource(); -} -``` - -## Best Practices - -1. **Cache Keys**: Use consistent naming conventions -2. **TTL Selection**: Choose TTL based on data change frequency -3. **Invalidation**: Invalidate cache when data changes -4. **Error Handling**: Don't let cache failures break the app -5. **Cache-Aside**: Use cache-aside pattern for most cases -6. **Avoid Over-Caching**: Don't cache data that changes too frequently -7. **Monitor Hit Rates**: Track cache hit rates to optimize TTL -8. **Warm Cache**: Pre-populate cache for critical data -9. **Use Multi-Layer**: Leverage both L1 and L2 cache -10. **Serialize Properly**: Ensure data is JSON serializable - -## Common Mistakes - -1. **Cache Key Collisions**: Using generic keys that collide -2. **Stale Data**: Not invalidating cache when data changes -3. **Too Short TTL**: Setting TTL too short, negating cache benefits -4. **Too Long TTL**: Setting TTL too long, serving stale data -5. **No Error Handling**: Letting cache errors break the application -6. **Caching Everything**: Caching data that doesn't benefit from caching -7. **Not Warming Cache**: Not pre-populating critical cache data -8. **Ignoring Hit Rates**: Not monitoring cache performance - -## Troubleshooting - -### Low Cache Hit Rate - -**Problem**: Cache hit rate is low, cache not effective -**Solution**: -- Review TTL values - may be too short -- Check cache key patterns - ensure consistent usage -- Verify cache invalidation isn't too aggressive -- Monitor what data is being cached - -### Stale Data Issues - -**Problem**: Serving stale data from cache -**Solution**: -- Review TTL values - may be too long -- Ensure cache invalidation on data updates -- Use shorter TTL for frequently changing data -- Implement cache versioning if needed - -### Cache Performance Issues - -**Problem**: Cache operations are slow -**Solution**: -- Check Redis connection and network latency -- Monitor Redis memory usage -- Review cache key patterns for efficiency -- Consider L1 cache hit rate (should be high) - -## Quick Reference - -| Cache Layer | Speed | Capacity | TTL | Scope | -|-------------|-------|----------|-----|-------| -| **L1 (Memory)** | <1ms | 10k keys | 60s-5min | Per instance | -| **L2 (Redis)** | <5ms | Large | Configurable | Shared | - -**TTL Guidelines:** -| Data Type | TTL | Example | -|-----------|-----|---------| -| Session data | 15-60min | User sessions | -| Permissions | 5min | RBAC cache | -| User profiles | 10min | Profile data | -| Config | 30min | Feature flags | -| Static data | 1-2hr | Reference data | - -**Key Patterns:** -```typescript -// Entity -`user:${userId}` -`session:${sessionId}` - -// Entity + Sub-resource -`user:${userId}:permissions` -`user:${userId}:roles` - -// List/Collection -`users:list:page:${page}` -`products:category:${categoryId}` -``` - -**Essential Operations:** -```typescript -// Get/Set -await cache.get(key); -await cache.set(key, value, ttl); - -// Get or fetch -await cache.getOrSet(key, fetchFn, ttl); - -// Invalidate -await cache.del(key); -await cache.invalidatePattern('user:123:*'); -``` - -## Resources - -- [Multi-Layer Cache](../../services/iam-service/src/core/cache/multi-layer-cache.ts) - Multi-layer cache implementation -- [Cache Service](../../services/iam-service/src/core/cache/cache.service.ts) - Cache service wrapper -- [Cache Usage Example](../../services/iam-service/src/modules/rbac/rbac.service.ts) - Real-world cache usage diff --git a/.agent/rules/cicd-advanced-patterns.md b/.agent/rules/cicd-advanced-patterns.md deleted file mode 100644 index df52f254..00000000 --- a/.agent/rules/cicd-advanced-patterns.md +++ /dev/null @@ -1,484 +0,0 @@ ---- -trigger: always_on ---- - -# CI/CD Advanced Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing blue-green deployments -- Setting up canary releases -- Implementing automated rollback mechanisms -- Creating deployment verification pipelines -- Implementing progressive delivery -- Setting up deployment gates -- Implementing smoke tests -- Managing deployment strategies in Kubernetes - -## Core Concepts - -### Deployment Strategies - -1. **Rolling Update**: Gradual replacement (default K8s) -2. **Blue-Green**: Two identical environments, switch traffic -3. **Canary**: Gradual rollout to subset of users -4. **Recreate**: Stop old, start new (downtime) - -### Deployment Verification - -- Smoke tests -- Health checks -- Performance tests -- Rollback triggers - -## Blue-Green Deployment - -### Kubernetes Implementation - -```yaml -# deployments/production/kubernetes/user-service-blue.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: user-service-blue - labels: - app: user-service - version: blue -spec: - replicas: 3 - selector: - matchLabels: - app: user-service - version: blue - template: - metadata: - labels: - app: user-service - version: blue - spec: - containers: - - name: user-service - image: goodgo/user-service:v1.0.0 - ports: - - containerPort: 5000 - ---- -# deployments/production/kubernetes/user-service-green.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: user-service-green - labels: - app: user-service - version: green -spec: - replicas: 3 - selector: - matchLabels: - app: user-service - version: green - template: - metadata: - labels: - app: user-service - version: green - spec: - containers: - - name: user-service - image: goodgo/user-service:v1.1.0 - ports: - - containerPort: 5000 - ---- -# Service selector switches between blue/green -apiVersion: v1 -kind: Service -metadata: - name: user-service -spec: - selector: - app: user-service - version: blue # EN: Switch to green after verification / VI: Chuyển sang green sau khi xác minh - ports: - - port: 80 - targetPort: 5000 -``` - -### Blue-Green Switch Script - -```bash -#!/bin/bash -# scripts/deployment/blue-green-switch.sh -# EN: Switch between blue and green deployments -# VI: Chuyển đổi giữa blue và green deployments - -SERVICE_NAME=$1 -CURRENT_VERSION=$(kubectl get service $SERVICE_NAME -o jsonpath='{.spec.selector.version}') -NEW_VERSION=$([ "$CURRENT_VERSION" = "blue" ] && echo "green" || echo "blue") - -echo "Switching from $CURRENT_VERSION to $NEW_VERSION" - -# EN: Update service selector -# VI: Cập nhật service selector -kubectl patch service $SERVICE_NAME -p "{\"spec\":{\"selector\":{\"version\":\"$NEW_VERSION\"}}}" - -# EN: Wait for rollout -# VI: Đợi rollout -kubectl rollout status deployment/$SERVICE_NAME-$NEW_VERSION - -# EN: Run smoke tests -# VI: Chạy smoke tests -./scripts/deployment/smoke-tests.sh $SERVICE_NAME - -if [ $? -ne 0 ]; then - echo "Smoke tests failed, rolling back" - kubectl patch service $SERVICE_NAME -p "{\"spec\":{\"selector\":{\"version\":\"$CURRENT_VERSION\"}}}" - exit 1 -fi - -echo "Successfully switched to $NEW_VERSION" -``` - -## Canary Deployment - -### Kubernetes Canary with Service Mesh - -```yaml -# deployments/production/kubernetes/user-service-canary.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: user-service-canary - labels: - app: user-service - version: canary -spec: - replicas: 1 # EN: Start with 1 replica (10% traffic) / VI: Bắt đầu với 1 replica (10% traffic) - selector: - matchLabels: - app: user-service - version: canary - template: - metadata: - labels: - app: user-service - version: canary - spec: - containers: - - name: user-service - image: goodgo/user-service:v1.1.0 - ---- -# VirtualService splits traffic -apiVersion: networking.istio.io/v1alpha3 -kind: VirtualService -metadata: - name: user-service -spec: - hosts: - - user-service - http: - - match: - - headers: - canary: - exact: "true" - route: - - destination: - host: user-service - subset: canary - weight: 100 - - route: - - destination: - host: user-service - subset: stable - weight: 90 - - destination: - host: user-service - subset: canary - weight: 10 # EN: 10% traffic to canary / VI: 10% traffic tới canary -``` - -### Progressive Canary Rollout - -```bash -#!/bin/bash -# scripts/deployment/canary-rollout.sh -# EN: Progressive canary rollout -# VI: Progressive canary rollout - -SERVICE_NAME=$1 -CANARY_PERCENTAGES=(10 25 50 75 100) - -for PERCENTAGE in "${CANARY_PERCENTAGES[@]}"; do - echo "Rolling out to $PERCENTAGE%" - - # EN: Update VirtualService weight - # VI: Cập nhật VirtualService weight - kubectl patch virtualservice $SERVICE_NAME -p "{\"spec\":{\"http\":[{\"route\":[{\"destination\":{\"subset\":\"canary\"},\"weight\":$PERCENTAGE},{\"destination\":{\"subset\":\"stable\"},\"weight\":$((100-PERCENTAGE))}]}]}}" - - # EN: Wait for traffic to stabilize - # VI: Đợi traffic ổn định - sleep 60 - - # EN: Run health checks - # VI: Chạy health checks - ./scripts/deployment/health-checks.sh $SERVICE_NAME - - if [ $? -ne 0 ]; then - echo "Health checks failed at $PERCENTAGE%, rolling back" - kubectl patch virtualservice $SERVICE_NAME -p "{\"spec\":{\"http\":[{\"route\":[{\"destination\":{\"subset\":\"canary\"},\"weight\":0},{\"destination\":{\"subset\":\"stable\"},\"weight\":100}]}]}}" - exit 1 - fi - - echo "Successfully rolled out to $PERCENTAGE%" -done - -echo "Canary rollout complete" -``` - -## Automated Rollback - -### Rollback Script - -```bash -#!/bin/bash -# scripts/deployment/rollback.sh -# EN: Automated rollback to previous version -# VI: Rollback tự động về version trước - -SERVICE_NAME=$1 -NAMESPACE=${2:-production} - -# EN: Get previous deployment revision -# VI: Lấy revision deployment trước -PREVIOUS_REVISION=$(kubectl rollout history deployment/$SERVICE_NAME -n $NAMESPACE --no-headers | tail -1 | awk '{print $1}') - -if [ -z "$PREVIOUS_REVISION" ]; then - echo "No previous revision found" - exit 1 -fi - -echo "Rolling back to revision $PREVIOUS_REVISION" - -# EN: Rollback deployment -# VI: Rollback deployment -kubectl rollout undo deployment/$SERVICE_NAME -n $NAMESPACE --to-revision=$PREVIOUS_REVISION - -# EN: Wait for rollout -# VI: Đợi rollout -kubectl rollout status deployment/$SERVICE_NAME -n $NAMESPACE - -echo "Rollback complete" -``` - -### Automated Rollback on Failure - -```yaml -# .github/workflows/deploy-production.yml -name: Deploy Production - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Deploy to Kubernetes - run: | - kubectl apply -f deployments/production/kubernetes/ - kubectl rollout status deployment/user-service - - - name: Run Smoke Tests - run: ./scripts/deployment/smoke-tests.sh user-service - - - name: Rollback on Failure - if: failure() - run: ./scripts/deployment/rollback.sh user-service production -``` - -## Deployment Verification - -### Smoke Tests - -```typescript -// scripts/deployment/smoke-tests.ts -// EN: Smoke tests for deployment verification -// VI: Smoke tests để xác minh deployment -import axios from 'axios'; - -const SERVICE_URL = process.env.SERVICE_URL || 'http://localhost'; - -async function runSmokeTests(): Promise { - try { - // EN: Health check - // VI: Health check - const healthResponse = await axios.get(`${SERVICE_URL}/health`); - if (healthResponse.status !== 200) { - console.error('Health check failed'); - return false; - } - - // EN: Basic functionality test - // VI: Test chức năng cơ bản - const testResponse = await axios.get(`${SERVICE_URL}/api/v1/users`, { - timeout: 5000, - }); - - if (testResponse.status !== 200) { - console.error('Functionality test failed'); - return false; - } - - console.log('Smoke tests passed'); - return true; - } catch (error) { - console.error('Smoke tests failed', error); - return false; - } -} - -runSmokeTests().then((success) => { - process.exit(success ? 0 : 1); -}); -``` - -### Health Check Script - -```bash -#!/bin/bash -# scripts/deployment/health-checks.sh -# EN: Comprehensive health checks -# VI: Health checks toàn diện - -SERVICE_NAME=$1 -NAMESPACE=${2:-production} - -echo "Running health checks for $SERVICE_NAME" - -# EN: Check pods are ready -# VI: Kiểm tra pods đã ready -READY_PODS=$(kubectl get pods -n $NAMESPACE -l app=$SERVICE_NAME --field-selector=status.phase=Running --no-headers | wc -l) - -if [ $READY_PODS -eq 0 ]; then - echo "No ready pods found" - exit 1 -fi - -# EN: Check service endpoints -# VI: Kiểm tra service endpoints -ENDPOINTS=$(kubectl get endpoints $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w) - -if [ $ENDPOINTS -eq 0 ]; then - echo "No service endpoints found" - exit 1 -fi - -# EN: Check health endpoint -# VI: Kiểm tra health endpoint -SERVICE_URL=$(kubectl get service $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') - -if [ -z "$SERVICE_URL" ]; then - SERVICE_URL="http://$SERVICE_NAME.$NAMESPACE.svc.cluster.local" -fi - -HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $SERVICE_URL/health) - -if [ $HTTP_CODE -ne 200 ]; then - echo "Health endpoint returned $HTTP_CODE" - exit 1 -fi - -echo "Health checks passed" -``` - -## Deployment Gates - -```yaml -# .github/workflows/deploy-with-gates.yml -name: Deploy with Gates - -jobs: - deploy: - steps: - - name: Deploy - run: kubectl apply -f deployments/ - - - name: Wait for Rollout - run: kubectl rollout status deployment/service - - - name: Smoke Tests Gate - id: smoke-tests - run: ./scripts/deployment/smoke-tests.sh - - - name: Performance Tests Gate - if: steps.smoke-tests.outcome == 'success' - run: ./scripts/deployment/performance-tests.sh - - - name: Manual Approval Gate - if: steps.smoke-tests.outcome == 'success' - uses: trstringer/manual-approval@v1 - with: - secret: ${{ secrets.GITHUB_TOKEN }} - approvers: team-leads - minimum-approvals: 1 - issue-title: "Approve deployment" -``` - -## Best Practices - -1. **Blue-Green**: Use for zero-downtime deployments -2. **Canary**: Use for gradual rollouts with monitoring -3. **Automated Rollback**: Always have rollback plan -4. **Smoke Tests**: Run immediately after deployment -5. **Health Checks**: Monitor health continuously -6. **Gates**: Use deployment gates for critical deployments - -## Common Mistakes - -1. **No Rollback Plan**: Can't recover from failed deployment - ```yaml - # ✅ Always have rollback command ready - kubectl rollout undo deployment/service - ``` - -2. **Skipping Smoke Tests**: Catching issues too late - ```yaml - # ✅ Run smoke tests immediately after deploy - - name: Smoke Tests - run: ./scripts/smoke-tests.sh - ``` - -3. **100% Traffic Switch**: All-or-nothing failures - ```yaml - # ❌ BAD: Immediate full switch - # ✅ GOOD: Gradual rollout (10% → 50% → 100%) - ``` - -4. **No Health Monitoring**: Missing deployment issues - ```yaml - # ✅ Monitor health after deployment - - name: Monitor Health - run: kubectl rollout status deployment/service --timeout=5m - ``` - -## Quick Reference - -| Strategy | Risk | Downtime | Resource Cost | -|----------|------|----------|---------------| -| **Blue-Green** | Low | Zero | 2x (temporary) | -| **Canary** | Low | Zero | +10-20% | -| **Rolling** | Medium | Zero | 1x | -| **Recreate** | High | Yes | 1x | - -**Deployment Commands:** -```bash -# Apply deployment -kubectl apply -f kubernetes/ - -# Check rollout status -kubectl rollout status deployment/servi \ No newline at end of file diff --git a/.agent/rules/comment-code.md b/.agent/rules/comment-code.md deleted file mode 100644 index 5242d043..00000000 --- a/.agent/rules/comment-code.md +++ /dev/null @@ -1,430 +0,0 @@ ---- -trigger: always_on ---- - -# Bilingual Code Comments - -Add comprehensive code comments in both Vietnamese and English to improve code readability for international and Vietnamese teams. - -## When to Use - -- Adding comments to new code -- Documenting existing code -- Creating JSDoc/TSDoc documentation -- Writing function/class descriptions -- Explaining complex logic -- Adding inline comments - -## Comment Format - -### Single-line Comments - -```typescript -// EN: Initialize database connection -// VI: Khởi tạo kết nối database -const db = await createConnection(); -``` - -### Multi-line Comments - -```typescript -/** - * EN: Validates user credentials and returns JWT token - * VI: Xác thực thông tin đăng nhập và trả về JWT token - * - * @param email - User email address / Địa chỉ email người dùng - * @param password - User password / Mật khẩu người dùng - * @returns JWT token / Mã JWT token - * @throws AuthenticationError if credentials invalid / Lỗi xác thực nếu thông tin không hợp lệ - */ -async function login(email: string, password: string): Promise { - // Implementation -} -``` - -## Core Comment Patterns - -### Function Documentation -```typescript -/** - * EN: Calculates the total price including tax and discount - * VI: Tính tổng giá bao gồm thuế và giảm giá - * - * @param basePrice - Original price / Giá gốc - * @param taxRate - Tax rate (0-1) / Tỷ lệ thuế (0-1) - * @param discount - Discount amount / Số tiền giảm giá - * @returns Final price / Giá cuối cùng - */ -function calculateTotal( - basePrice: number, - taxRate: number, - discount: number -): number { - // EN: Apply discount first - // VI: Áp dụng giảm giá trước - const discountedPrice = basePrice - discount; - - // EN: Then calculate tax - // VI: Sau đó tính thuế - const tax = discountedPrice * taxRate; - - return discountedPrice + tax; -} -``` - -### Class Documentation -```typescript -/** - * EN: Handles user authentication and authorization - * VI: Xử lý xác thực và phân quyền người dùng - */ -export class AuthService { - /** - * EN: JWT secret key from environment - * VI: Khóa bí mật JWT từ biến môi trường - */ - private readonly jwtSecret: string; - - /** - * EN: Verify JWT token and return user payload - * VI: Xác minh JWT token và trả về thông tin người dùng - * - * @param token - JWT token to verify / JWT token cần xác minh - * @returns User payload / Thông tin người dùng - * @throws TokenExpiredError if token expired / Lỗi token hết hạn - */ - async verifyToken(token: string): Promise { - // Implementation - } -} -``` - -### Interface/Type Documentation -```typescript -/** - * EN: User data transfer object - * VI: Đối tượng truyền dữ liệu người dùng - */ -interface UserDto { - /** EN: Unique user identifier / VI: Mã định danh duy nhất */ - id: string; - - /** EN: User email address / VI: Địa chỉ email người dùng */ - email: string; - - /** EN: User display name / VI: Tên hiển thị người dùng */ - name: string; - - /** EN: User role for authorization / VI: Vai trò người dùng để phân quyền */ - role: 'admin' | 'user' | 'guest'; -} -``` - -### React Components -```typescript -/** - * EN: User profile card component - * VI: Component thẻ hồ sơ người dùng - * - * @param user - User data to display / Dữ liệu người dùng để hiển thị - * @param onEdit - Callback when edit button clicked / Callback khi nhấn nút chỉnh sửa - */ -export function UserCard({ user, onEdit }: UserCardProps) { - // EN: Local state for loading status - // VI: State cục bộ cho trạng thái loading - const [isLoading, setIsLoading] = useState(false); - - return ( -
- {/* EN: Display user avatar / VI: Hiển thị avatar người dùng */} - {user.name} - - {/* EN: User information section / VI: Phần thông tin người dùng */} -
-

{user.name}

-

{user.email}

-
-
- ); -} -``` - -### Prisma Schema -```prisma -/// EN: User model for authentication and profile -/// VI: Model người dùng cho xác thực và hồ sơ -model User { - /// EN: Unique identifier / VI: Mã định danh duy nhất - id String @id @default(cuid()) - - /// EN: User email (unique) / VI: Email người dùng (duy nhất) - email String @unique - - /// EN: Hashed password / VI: Mật khẩu đã mã hóa - password String - - /// EN: Display name / VI: Tên hiển thị - name String - - /// EN: Account creation timestamp / VI: Thời gian tạo tài khoản - createdAt DateTime @default(now()) - - /// EN: Last update timestamp / VI: Thời gian cập nhật cuối - updatedAt DateTime @updatedAt - - @@map("users") -} -``` - -### API Controllers -```typescript -/** - * EN: User management controller - * VI: Controller quản lý người dùng - */ -export class UserController { - /** - * EN: Get user by ID - * VI: Lấy thông tin người dùng theo ID - * - * GET /api/users/:id - */ - async getById(req: Request, res: Response) { - try { - // EN: Extract user ID from params - // VI: Lấy ID người dùng từ params - const { id } = req.params; - - // EN: Fetch user from database - // VI: Lấy người dùng từ database - const user = await this.userService.findById(id); - - if (!user) { - return res.status(404).json({ - success: false, - error: { - code: 'USER_NOT_FOUND', - message: 'User not found / Không tìm thấy người dùng', - }, - }); - } - - return res.json({ - success: true, - data: user, - }); - } catch (error) { - logger.error('Failed to get user', { error, userId: req.params.id }); - return res.status(500).json({ - success: false, - error: { - code: 'INTERNAL_ERROR', - message: 'Internal server error / Lỗi máy chủ nội bộ', - }, - }); - } - } -} -``` - -### Middleware -```typescript -/** - * EN: Authentication middleware to verify JWT tokens - * VI: Middleware xác thực để kiểm tra JWT token - */ -export function authMiddleware( - req: Request, - res: Response, - next: NextFunction -) { - // EN: Extract token from Authorization header - // VI: Lấy token từ header Authorization - const authHeader = req.headers.authorization; - const token = authHeader?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ - success: false, - error: { - code: 'NO_TOKEN', - message: 'Authentication required / Yêu cầu xác thực', - }, - }); - } - - try { - // EN: Verify token and extract payload - // VI: Xác minh token và lấy payload - const payload = jwt.verify(token, JWT_SECRET); - req.user = payload; - next(); - } catch (error) { - return res.status(401).json({ - success: false, - error: { - code: 'INVALID_TOKEN', - message: 'Invalid or expired token / Token không hợp lệ hoặc hết hạn', - }, - }); - } -} -``` - -## Best Practices - -### 1. Comment Placement -- Place bilingual comments together (EN first, then VI) -- Keep comments close to the code they describe -- Use JSDoc format for functions and classes - -### 2. Comment Content -- **DO**: Explain WHY, not WHAT (code shows what) -- **DO**: Document complex logic and business rules -- **DO**: Include parameter descriptions and return types -- **DO**: Document error conditions and exceptions -- **DON'T**: State the obvious -- **DON'T**: Write redundant comments - -### 3. Language Guidelines -- **English**: Use clear, concise technical English -- **Vietnamese**: Use proper Vietnamese technical terms -- Keep translations accurate and natural -- Use consistent terminology across codebase - -### 4. Special Cases - -#### TODO Comments -```typescript -// TODO EN: Implement caching for better performance -// TODO VI: Triển khai caching để cải thiện hiệu suất -``` - -#### FIXME Comments -```typescript -// FIXME EN: This causes memory leak, needs refactoring -// FIXME VI: Đoạn này gây rò rỉ bộ nhớ, cần refactor -``` - -#### WARNING Comments -```typescript -// WARNING EN: Do not modify this without updating the database schema -// WARNING VI: Không sửa đổi phần này mà không cập nhật schema database -``` - -### 5. Documentation Priority - -**High Priority** (Always document): -- Public APIs and exported functions -- Complex algorithms and business logic -- Security-critical code -- Configuration and environment setup -- Error handling strategies - -**Medium Priority** (Document when helpful): -- Helper functions with non-obvious behavior -- Data transformations -- Integration points with external services - -**Low Priority** (Optional): -- Simple getters/setters -- Self-explanatory code -- Standard CRUD operations - - -## Integration with Project Rules - -When commenting code in this project: -- Follow the code organization from `project-rules` skill -- Use consistent terminology with project documentation -- Align with the API response format standards -- Document according to the testing standards -- Include security considerations where relevant - -## Common Mistakes - -1. **Stating the Obvious**: Comments that repeat what code says - ```typescript - // ❌ BAD: States the obvious - // EN: Set x to 5 - // VI: Gán x bằng 5 - const x = 5; - - // ✅ GOOD: Explains why - // EN: Default timeout in seconds for API calls - // VI: Timeout mặc định (giây) cho các API call - const x = 5; - ``` - -2. **Missing Translation**: Incomplete bilingual comments - ```typescript - // ❌ BAD: Missing Vietnamese - // EN: Calculate discount - const discount = price * 0.1; - - // ✅ GOOD: Both languages - // EN: Calculate 10% discount for members - // VI: Tính giảm giá 10% cho thành viên - const discount = price * 0.1; - ``` - -3. **Outdated Comments**: Comments not matching code - ```typescript - // ❌ BAD: Comment says email, code uses phone - // EN: Validate email format - // VI: Xác thực định dạng email - validatePhone(phone); - - // ✅ GOOD: Keep comments in sync with code - // EN: Validate phone number format - // VI: Xác thực định dạng số điện thoại - validatePhone(phone); - ``` - -4. **No JSDoc for Public APIs**: Missing documentation for exports - ```typescript - // ❌ BAD: Exported function without JSDoc - export function processOrder(order: Order) { ... } - - // ✅ GOOD: Full JSDoc for exported functions - /** - * EN: Process order and update inventory - * VI: Xử lý đơn hàng và cập nhật kho - * @param order - Order to process / Đơn hàng cần xử lý - * @returns ProcessingResult / Kết quả xử lý - */ - export function processOrder(order: Order): ProcessingResult { ... } - ``` - -## Quick Reference - -### Function Comment Template -```typescript -/** - * EN: [Brief description in English] - * VI: [Mô tả ngắn gọn bằng tiếng Việt] - * - * @param paramName - EN description / VI mô tả - * @returns EN description / VI mô tả - * @throws ErrorType EN when / VI khi nào - */ -``` - -### Inline Comment Template -```typescript -// EN: [English explanation] -// VI: [Giải thích tiếng Việt] -``` - -### Complex Block Template -```typescript -// EN: Step N: [What this block does] -// VI: Bước N: [Block này làm gì] -``` - -## Resources - -- [Project Rules](../project-rules/SKILL.md) - Code organization standards -- [Documentation](../documentation/SKILL.md) - Documentation patterns -- [API Design](../api-design/SKILL.md) - API documentation standards -- [Testing Patterns](../testing-patterns/SKILL.md) - Test documentation diff --git a/.agent/rules/configuration-management.md b/.agent/rules/configuration-management.md deleted file mode 100644 index a06eaaff..00000000 --- a/.agent/rules/configuration-management.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -trigger: always_on ---- - -# Configuration Management Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing feature flags and feature toggles -- Managing environment-specific configurations -- Implementing dynamic configuration reloading -- Managing secrets and sensitive configuration -- Implementing configuration validation -- Handling configuration versioning -- Building configuration services -- Implementing A/B testing with feature flags -- Managing configuration across multiple environments - -## Core Concepts - -### Configuration Types - -1. **Static Configuration**: Environment variables, config files -2. **Dynamic Configuration**: Feature flags, runtime configs -3. **Secrets**: Sensitive data (API keys, passwords) -4. **Feature Flags**: Runtime feature toggles - -### Configuration Sources - -- Environment variables -- Configuration files (JSON, YAML) -- Configuration service (external) -- Feature flag service -- Secrets manager (Kubernetes Secrets, Vault) diff --git a/.agent/rules/data-consistency-patterns.md b/.agent/rules/data-consistency-patterns.md deleted file mode 100644 index 46a651bd..00000000 --- a/.agent/rules/data-consistency-patterns.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -trigger: always_on ---- - -# Data Consistency Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing distributed transactions across multiple services -- Handling eventual consistency in microservices -- Implementing Saga patterns for distributed workflows -- Designing compensation strategies for failed transactions -- Implementing idempotent operations -- Managing data synchronization across services -- Handling conflict resolution -- Implementing optimistic locking strategies -- Building event sourcing systems -- Ensuring data integrity in distributed systems - -## Core Concepts - -### ACID vs BASE - -**ACID (Traditional Databases):** -- Atomicity: All or nothing -- Consistency: Data always valid -- Isolation: Concurrent transactions isolated -- Durability: Committed changes persist - -**BASE (Distributed Systems):** -- Basic Availability: System available most of the time -- Soft state: State may change over time -- Eventual consistency: Consistency achieved eventually - -### Consistency Models - -| Model | Description | Use Case | -|-------|-------------|----------| -| **Strong** | All nodes see same data at same time | Financial transactions | -| **Weak** | No guarantees about when consistency occurs | Caching | -| **Eventual** | System becomes consistent over time | Read models, analytics | -| **Causal** | Related operations maintain order | User sessions | - -### Distributed Transaction Challenges - -- Network partitions -- Service failures -- Clock synchronization -- Partial failures -- Two-Phase Commit (2PC) limitations - -## Saga Pattern - -### Orchestration vs Choreography - -| Approach | Central Control | Resilience | Complexity | Best For | -|----------|-----------------|------------|------------|----------| -| **Orchestration** | Yes (single coordinator) | Lower (SPOF) | Easier to debug | Complex workflows | -| **Choreography** | No (event-driven) | Higher | Harder to trace | Simple flows, loose coupling | - -### Saga Execution Flow - -``` -Execute: Step1 -> Step2 -> Step3 -> Complete - | | | - v v v -Compensate: <- Step2.undo <- Step1.undo (on failure) -``` - -### Key Saga Interfaces - -```typescript -interface SagaStep { - name: string; - execute: () => Promise; - compensate: (context: any) => Promise; - retry?: number; -} - -interface SagaContext { - sagaId: string; - steps: SagaStep[]; - currentStep: number; - data: Record; - status: 'pending' | 'running' | 'completed' | 'compensating' | 'failed'; -} -``` - -See [./references/REFERENCE.md](./references/REFERENCE.md) for full Saga orchestrator implementation. - -## Idempotency Pattern - -Ensures operations can be safely retried without side effects. - -### Key Concepts - -- **Idempotency Key**: Unique identifier for each operation -- **Result Storage**: Cache results to return on duplicates -- **TTL**: Time-to-live for idempotency records - -### Pattern - -```typescript -const key = `${operation}:${userId}:${hash(requestData)}`; -await idempotencyHandler.execute(key, () => operation()); -``` - -See [./references/REFERENCE.md](./references/REFERENCE.md) for full implementation. - -## Optimistic Locking - -Prevents lost updates in concurrent scenarios using version fields. - -### Pattern - -```typescript -await prisma.entity.update({ - where: { id, version: currentVersion }, - data: { ...updates, version: { increment: 1 } } -}); -``` - -### Prisma Schema - -```prisma -model Entity { - id String @id @default(cuid()) - version Int @default(1) - // ... other fields -} -``` - -See [./references/REFERENCE.md](./references/REFERENCE.md) for full optimistic locking service. - -## CQRS Pattern - -Command Query Responsibility Segregation separates read and write operations. - -### Architecture - -``` -Write Path: Command -> Write Model -> Event -> Event Store -Read Path: Query -> Read Model (denormalized, optimized for reads) -``` - -### Key Benefits - -- Optimized read models for query performance -- Independent scaling of read/write operations -- Eventual consistency between models - -See [./references/REFERENCE.md](./references/REFERENCE.md) for implementation details. - -## Conflict Resolution Strategies - -| Strategy | Description | Use Case | -|----------|-------------|----------| -| **Last Write Wins** | Latest timestamp wins | Simple scenarios | -| **First Write Wins** | Earliest timestamp wins | Preserving original data | -| **Merge** | Combine both versions | Non-conflicting fields | -| **Manual** | Store for human review | Critical data conflicts | - -## Outbox Pattern - -Ensures reliable event publishing by storing events in the same transaction as business data. - -### Flow - -1. Execute business operation in transaction -2. Store event in outbox table (same transaction) -3. Background processor publishes and marks as sent - -See [./references/REFERENCE.md](./references/REFERENCE.md) for implementation. - -## Best Practices - -### Saga Pattern -- Design compensations for every step -- Make steps idempotent for retries -- Set timeouts for saga execution -- Monitor saga execution and compensation -- Choose orchestration for complex workflows, choreography for simple ones - -### Idempotency -- Generate keys from request data -- Store keys with results -- Set appropriate TTL for records -- Regularly clean expired records - -### Eventual Consistency -- Accept that consistency is eventual -- Use separate read models for queries -- Define resolution strategies upfront -- Monitor consistency lag - -### Optimistic Locking -- Add version fields to entities -- Implement retry with exponential backoff -- Handle conflicts gracefully -- Use for read-heavy workloads - -## Common Mistakes - -### No Compensation Logic -```typescript -// BAD: No compensation -steps: [{ execute: () => createOrder() }] - -// GOOD: Always define compensation -steps: [{ - execute: () => createOrder(), - compensate: (ctx) => cancelOrder(ctx.orderId) -}] -``` - -### Missing Idempotency -```typescript -// BAD: Creates duplicate on retry -await createPayment(orderId); - -// GOOD: Idempotent check -const existing = await findPayment(idempotencyKey); -if (existing) return existing; -await createPayment(orderId); -``` - -### Ignoring Partial Failures -```typescript -// BAD: No error handling -await step1(); await step2(); await step3(); - -// GOOD: Saga orchestration -await sagaOrchestrator.execute(context); -``` - -### No Version Field -```prisma -// GOOD: Add version for optimistic locking -model Entity { - version Int @default(1) -} -``` - -## Quick Reference - -| Pattern | Use Case | Complexity | -|---------|----------|------------| -| **Saga (Orchestrated)** | Complex multi-step workflows | High | -| **Saga (Choreography)** | Simple event-driven flows | Medium | -| **Outbox Pattern** | Guaranteed event publishing | Medium | -| **Idempotency** | Retry-safe operations | Low | -| **Optimistic Lock** | Concurrent updates | Low | -| **CQRS** | Read/write optimization | High | -| **Dead Letter Queue** | Failed message handling | Medium | - -## Resources - -### Internal References -- [Detailed Code Examples](./references/REFERENCE.md) - Full implementations for all patterns -- [Event-Driven Architecture](../event-driven-architecture/SKILL.md) - Event patterns -- [Error Handling Patterns](../error-handling-patterns/SKILL.md) - Error handling -- [Database & Prisma](../database-prisma/SKILL.md) - Database patterns -- [Project Rules](../project-rules/SKILL.md) - GoodGo coding standards - -### External Resources -- [Saga Pattern](https://microservices.io/patterns/data/saga.html) - Saga pattern overview -- [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) - Event sourcing pattern -- [CQRS Pattern](https://martinfowler.com/bliki/CQRS.html) - CQRS pattern diff --git a/.agent/rules/database-prisma.md b/.agent/rules/database-prisma.md deleted file mode 100644 index afb32793..00000000 --- a/.agent/rules/database-prisma.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -trigger: always_on ---- - -# Prisma Database Patterns - -## When to Use This Skill - -Use this skill when: -- Setting up Prisma for a new service -- Creating or modifying database schemas -- Writing database migrations -- Implementing repository patterns -- Optimizing database queries -- Setting up database connections -- Implementing transactions -- Working with Neon PostgreSQL - -## Core Concepts - -### Architecture -- Repository pattern for data access -- Prisma as ORM for type safety -- Neon PostgreSQL as primary database -- Connection pooling for performance -- Transaction support for data consistency - -## Key Patterns - -### Prisma Setup - -```bash -npm install @prisma/client prisma -npx prisma init -``` - -### Schema Definition - -```prisma -model User { - id String @id @default(cuid()) - email String @unique - name String? - role Role @default(USER) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - posts Post[] - - @@index([email]) - @@index([createdAt]) - @@map("users") -} -``` - -### Database Connection - -```typescript -import { PrismaClient } from '@prisma/client'; - -const globalForPrisma = global as unknown as { prisma: PrismaClient | undefined }; - -export const prisma = globalForPrisma.prisma ?? new PrismaClient({ - log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'], -}); - -if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; -``` - -### Repository Pattern - -```typescript -export class UserRepository { - async findById(id: string) { - return prisma.user.findUnique({ where: { id }, include: { profile: true } }); - } - - async findAll({ page = 1, limit = 10, search }: QueryOptions) { - const where = search ? { OR: [ - { email: { contains: search, mode: 'insensitive' } }, - { name: { contains: search, mode: 'insensitive' } } - ]} : {}; - - const [data, total] = await Promise.all([ - prisma.user.findMany({ where, skip: (page - 1) * limit, take: limit }), - prisma.user.count({ where }) - ]); - return { data, total }; - } - - async create(data: CreateUserDto) { - return prisma.user.create({ data }); - } -} -``` - -### Transactions - -```typescript -await prisma.$transaction(async (tx) => { - await tx.account.update({ where: { id: from }, data: { balance: { decrement: amount } } }); - await tx.account.update({ where: { id: to }, data: { balance: { increment: amount } } }); -}, { maxWait: 5000, timeout: 10000 }); -``` - -## Best Practices - -- **Schema Design**: Use appropriate field types, add indexes for frequently queried fields -- **Performance**: Use select to fetch only needed fields, implement pagination -- **Security**: Never expose sensitive fields, use parameterized queries -- **Maintenance**: Keep migrations small and focused, test before production - -## Common Mistakes - -1. **N+1 Query Problem**: Fetching related data in a loop - ```typescript - // BAD: N+1 queries - for (const user of users) { await prisma.post.findMany({ where: { authorId: user.id } }); } - // GOOD: Include relations - await prisma.user.findMany({ include: { posts: true } }); - ``` - -2. **No Indexes**: Missing indexes on frequently queried columns - ```prisma - // GOOD: Add index - @@index([createdAt]) - ``` - -3. **Raw Queries Without Parameters**: SQL injection risk - ```typescript - // BAD: prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`); - // GOOD: prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`; - ``` - -4. **Not Using Transactions**: Data inconsistency risk - ```typescript - // GOOD: Use transaction for related operations - await prisma.$transaction([update1, update2]); - ``` - -5. **Exposing Internal IDs**: Leaking database structure - ```prisma - // GOOD: Use CUID or UUID - model User { id String @id @default(cuid()) } - ``` - -## Quick Reference - -| Operation | Command | -|-----------|---------| -| **Create migration** | `npx prisma migrate dev --name ` | -| **Apply migrations** | `npx prisma migrate deploy` | -| **Reset database** | `npx prisma migrate reset` | -| **Generate client** | `npx prisma generate` | -| **Open Studio** | `npx prisma studio` | -| **Seed database** | `npx prisma db seed` | - -**Common Query Patterns:** -```typescript -await prisma.user.findUnique({ where: { id } }); // Find unique -await prisma.user.findMany({ include: { posts: true } }); // With relations -await prisma.user.findMany({ select: { id: true } }); // Select fields -await prisma.user.findMany({ skip: 10, take: 10 }); // Pagination -await prisma.$transaction(async (tx) => { ... }); // Transaction -``` - -**Schema Field Types:** -| Type | PostgreSQL | Description | -|------|------------|-------------| -| `String` | TEXT | Variable-length string | -| `Int` | INTEGER | 32-bit integer | -| `BigInt` | BIGINT | 64-bit integer | -| `Float` | DOUBLE PRECISION | Floating point | -| `Boolean` | BOOLEAN | True/false | -| `DateTime` | TIMESTAMP | Date and time | -| `Json` | JSONB | JSON data | - -## Resources - -- [Prisma Documentation](https://www.prisma.io/docs) - Official Prisma docs -- [Prisma Schema Reference](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference) -- [Detailed Code Examples](./references/REFERENCE.md) -- [Repository Pattern](../repository-pattern/SKILL.md) - Data access patterns -- [Caching Patterns](../caching-patterns/SKILL.md) - Query caching -- [Testing Patterns](../testing-patterns/SKILL.md) - Database mocking diff --git a/.agent/rules/deployment-kubernetes.md b/.agent/rules/deployment-kubernetes.md deleted file mode 100644 index 80aa611d..00000000 --- a/.agent/rules/deployment-kubernetes.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -trigger: always_on ---- - -# Kubernetes Deployment Patterns - -## When to Use This Skill - -Use this skill when: -- Deploying services to staging/production environments -- Creating or updating Kubernetes manifests -- Configuring autoscaling (HPA/VPA) -- Setting up ingress and load balancing -- Managing secrets and configmaps -- Troubleshooting deployment issues -- Implementing health checks and probes -- Setting up monitoring and logging - -## Core Concepts - -### Deployment Strategy -- Rolling updates for zero-downtime deployments -- Resource limits and requests for stability -- Health checks (liveness/readiness probes) -- Horizontal Pod Autoscaler (HPA) for auto-scaling -- ConfigMaps for configuration, Secrets for sensitive data - -## Key Patterns - -### Deployment Manifest - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: auth-service - namespace: goodgo -spec: - replicas: 3 - selector: - matchLabels: - app: auth-service - template: - spec: - containers: - - name: auth-service - image: goodgo/auth-service:v1.0.0 - resources: - requests: { memory: "256Mi", cpu: "250m" } - limits: { memory: "512Mi", cpu: "500m" } - livenessProbe: - httpGet: { path: /health, port: 3000 } - initialDelaySeconds: 30 - readinessProbe: - httpGet: { path: /ready, port: 3000 } - initialDelaySeconds: 5 - env: - - name: DATABASE_URL - valueFrom: - secretKeyRef: { name: db-secrets, key: url } -``` - -### HPA Configuration - -```yaml -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: auth-service - minReplicas: 2 - maxReplicas: 10 - metrics: - - type: Resource - resource: - name: cpu - target: { type: Utilization, averageUtilization: 70 } -``` - -### Ingress - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - tls: - - hosts: [api.goodgo.com] - secretName: api-tls-secret - rules: - - host: api.goodgo.com - http: - paths: - - path: /auth - pathType: Prefix - backend: - service: { name: auth-service, port: { number: 80 } } -``` - -## Best Practices - -- **Resource Management**: Always set resource requests and limits, use HPA for scaling -- **Configuration**: Use ConfigMaps for config, Secrets for sensitive data -- **Health Checks**: Implement both liveness and readiness probes -- **Deployment**: Use rolling updates, set maxSurge/maxUnavailable appropriately -- **Security**: Run as non-root, use network policies, update base images regularly -- **Monitoring**: Expose metrics endpoint, set up alerts - -## Common Mistakes - -1. **No Resource Limits**: Pods consuming all node resources - ```yaml - # GOOD: Set limits - resources: - requests: { memory: "256Mi", cpu: "250m" } - limits: { memory: "512Mi", cpu: "500m" } - ``` - -2. **Missing Health Checks**: K8s can't detect unhealthy pods - ```yaml - # GOOD: Add probes - livenessProbe: - httpGet: { path: /health, port: 3000 } - readinessProbe: - httpGet: { path: /ready, port: 3000 } - ``` - -3. **Hardcoded Secrets**: Exposing sensitive data - ```yaml - # BAD: value: "secret123" - # GOOD: valueFrom: secretKeyRef: { name: secrets, key: password } - ``` - -4. **Using `latest` Tag**: Unpredictable deployments - ```yaml - # BAD: image: app:latest - # GOOD: image: app:v1.2.3 - ``` - -## Quick Reference - -| Resource | Command | -|----------|---------| -| **Apply manifests** | `kubectl apply -f kubernetes/` | -| **Get pods** | `kubectl get pods -n goodgo` | -| **Get logs** | `kubectl logs -f deployment/app -n goodgo` | -| **Scale** | `kubectl scale deployment/app --replicas=5` | -| **Rollback** | `kubectl rollout undo deployment/app` | -| **Port forward** | `kubectl port-forward svc/app 3000:80` | -| **Exec into pod** | `kubectl exec -it pod-name -- /bin/sh` | - -**Resource Sizing Guidelines:** -| Service Type | Memory Request | Memory Limit | CPU Request | CPU Limit | -|--------------|----------------|--------------|-------------|-----------| -| Microservice | 256Mi | 512Mi | 250m | 500m | -| API Gateway | 512Mi | 1Gi | 500m | 1000m | -| Database | 1Gi | 2Gi | 500m | 1000m | - -**Health Check Defaults:** -```yaml -livenessProbe: - initialDelaySeconds: 30 # Wait for app startup - periodSeconds: 10 # Check every 10s - failureThreshold: 3 # Restart after 3 failures - -readinessProbe: - initialDelaySeconds: 5 # Start checking early - periodSeconds: 5 # Check frequently - failureThreshold: 3 # Remove from LB after 3 failures -``` - -## Resources - -- [Kubernetes Documentation](https://kubernetes.io/docs/) - Official K8s docs -- [Helm](https://helm.sh/docs/) - K8s package manager -- [Detailed Manifests](./references/REFERENCE.md) -- [Infrastructure as Code](../infrastructure-as-code/SKILL.md) - Terraform patterns -- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Health checks -- [Service Discovery](../service-discovery-registry/SKILL.md) - K8s DNS patterns diff --git a/.agent/rules/documentation.md b/.agent/rules/documentation.md deleted file mode 100644 index 2107e287..00000000 --- a/.agent/rules/documentation.md +++ /dev/null @@ -1,444 +0,0 @@ ---- -trigger: always_on ---- - -# Documentation Writing Guidelines - -## Documentation Structure - -``` -docs/ -├── en/ # English documentation -│ ├── guides/ # How-to guides -│ │ ├── getting-started.md -│ │ ├── development.md -│ │ ├── deployment.md -│ │ └── local-development.md -│ ├── architecture/ # System design docs -│ │ ├── system-design.md -│ │ └── service-communication.md -│ ├── api/ # API documentation -│ │ └── openapi/ -│ └── runbooks/ # Operational guides -│ ├── incident-response.md -│ └── rollback-procedure.md -├── vi/ # Vietnamese documentation (mirror structure) -└── README.md # Documentation index -``` - -## Where to Put Documentation - -### Project-Level Documentation -- **Location**: `docs/en/` and `docs/vi/` -- **Examples**: Getting started, deployment guides, architecture -- **Format**: Markdown with bilingual support - -### Service/Package Documentation -- **Location**: `services/[service-name]/README.md` or `packages/[package-name]/README.md` -- **Content**: Service-specific setup, API endpoints, configuration -- **Format**: Single README with bilingual sections - -### Deployment Documentation -- **Location**: `deployments/[environment]/README.md` -- **Content**: Environment-specific deployment instructions -- **Format**: Technical, operations-focused - -### Infrastructure Documentation -- **Location**: `infra/[component]/README.md` -- **Content**: Infrastructure component configuration and usage -- **Examples**: `infra/traefik/README.md`, `infra/observability/README.md` - -## Bilingual Documentation Rules - -### Format Options - -**Option 1: Side-by-side (Recommended for short content)** -```markdown -# Service Name / Tên Dịch Vụ - -This is a description. -Đây là mô tả. -``` - -**Option 2: Separate files (Recommended for long content)** -``` -docs/ -├── en/ -│ └── guides/ -│ └── deployment.md -└── vi/ - └── guides/ - └── deployment.md -``` - -**Option 3: Sections (For mixed content)** -```markdown -# English Section - -Content in English... - ---- - -# Phần Tiếng Việt - -Nội dung bằng tiếng Việt... -``` - -### When to Use Each Format - -- **Side-by-side**: README files, short guides, configuration docs -- **Separate files**: Long guides (>200 lines), architecture docs, runbooks -- **Sections**: API documentation, technical specifications - -## Documentation Templates - -### Service README Template - -```markdown -# Service Name / Tên Dịch Vụ - -> **EN**: Brief description in English -> **VI**: Mô tả ngắn gọn bằng tiếng Việt - -## Features / Tính Năng - -- Feature 1 / Tính năng 1 -- Feature 2 / Tính năng 2 - -## Prerequisites / Yêu Cầu - -- Node.js 20+ -- PostgreSQL (Neon) -- Redis - -## Quick Start / Bắt Đầu Nhanh - -```bash -# Install dependencies / Cài đặt dependencies -pnpm install - -# Setup environment / Thiết lập môi trường -cp .env.example .env - -# Start service / Khởi động service -pnpm dev -``` - -## Configuration / Cấu Hình - -| Variable | Description / Mô Tả | Default | Required | -|----------|---------------------|---------|----------| -| PORT | Server port / Cổng server | 5000 | No | - -## API Endpoints - -See [API Documentation](../../docs/api/openapi/service-name.yaml) - -## Development / Phát Triển - -[Development instructions...] - -## Testing / Kiểm Thử - -```bash -pnpm test -``` - -## Deployment / Triển Khai - -See [Deployment Guide](../../docs/en/guides/deployment.md) -``` - -### Guide Template (docs/en/guides/) - -```markdown -# Guide Title - -**Last Updated**: 2024-01-01 -**Difficulty**: Beginner/Intermediate/Advanced - -## Overview - -Brief overview of what this guide covers. - -## Prerequisites - -- Requirement 1 -- Requirement 2 - -## Step-by-Step Instructions - -### Step 1: Title - -Description and commands... - -```bash -command here -``` - -### Step 2: Title - -Description and commands... - -## Troubleshooting - -### Issue 1 - -**Problem**: Description -**Solution**: Steps to fix - -## Next Steps - -- Link to related guide -- Link to another resource - -## Resources - -- [Related Doc](../path/to/doc.md) -- [External Link](https://example.com) -``` - -### Architecture Document Template - -```markdown -# Component Architecture - -## Overview - -High-level description of the component. - -## Architecture Diagram - -```mermaid -graph TD - A[Component A] --> B[Component B] - B --> C[Component C] -``` - -## Components - -### Component Name - -**Purpose**: What it does -**Technology**: Tech stack -**Dependencies**: What it depends on - -## Data Flow - -1. Step 1 -2. Step 2 -3. Step 3 - -## Design Decisions - -### Decision 1 - -**Context**: Why this decision was needed -**Decision**: What was decided -**Consequences**: Impact of the decision - -## Deployment - -How this component is deployed. - -## Monitoring - -How to monitor this component. -``` - -## Writing Style Guidelines - -### Technical Writing Principles - -1. **Clear and Concise**: Use simple language, avoid jargon -2. **Action-Oriented**: Start with verbs (Install, Configure, Deploy) -3. **Structured**: Use headings, lists, and tables -4. **Examples**: Provide code examples and commands -5. **Visual**: Use diagrams where helpful - -### Code Examples - -```markdown -# Good: With context and explanation -Install dependencies using pnpm: - -```bash -pnpm install -``` - -# Bad: No context -```bash -pnpm install -``` -``` - -### Commands - -- Always show the full command -- Include comments for clarity -- Show expected output when helpful - -```bash -# Good -docker-compose up -d -# Expected output: Creating network, Starting containers... - -# Bad -docker-compose up -``` - -### Links - -- Use relative links for internal docs -- Use descriptive link text (not "click here") - -```markdown -# Good -See the [Deployment Guide](../guides/deployment.md) for details. - -# Bad -Click [here](../guides/deployment.md) for more info. -``` - -## Documentation Checklist - -### Before Writing - -- [ ] Determine correct location (docs/ vs service README) -- [ ] Choose bilingual format (side-by-side vs separate) -- [ ] Review existing docs for consistency - -### While Writing - -- [ ] Use clear, concise language -- [ ] Include code examples -- [ ] Add diagrams where helpful -- [ ] Provide troubleshooting section -- [ ] Link to related documentation - -### After Writing - -- [ ] Test all commands and code examples -- [ ] Check all links work -- [ ] Ensure bilingual consistency -- [ ] Update documentation index (docs/README.md) -- [ ] Request review from team - -## Common Mistakes to Avoid - -### ❌ Don't - -- Write documentation in only one language -- Put detailed guides in service README (use docs/) -- Use absolute paths in links -- Assume prior knowledge -- Skip code examples -- Forget to update when code changes - -### ✅ Do - -- Maintain bilingual documentation -- Use appropriate location (docs/ vs README) -- Use relative links -- Explain prerequisites -- Provide working examples -- Keep docs up-to-date with code - -## Documentation Maintenance - -### When to Update Documentation - -- New feature added -- API changes -- Configuration changes -- Deployment process changes -- Bug fixes affecting usage -- Architecture changes - -### Version Documentation - -For major changes, consider: -- Adding "Last Updated" date -- Creating versioned docs (v1/, v2/) -- Maintaining changelog - -## Tools and Resources - -### Markdown Tools - -- **Mermaid**: For diagrams -- **Tables Generator**: For complex tables -- **Markdown Linter**: For consistency - -### Documentation Testing - -```bash -# Check for broken links -find docs -name "*.md" -exec markdown-link-check {} \; - -# Lint markdown files -markdownlint docs/**/*.md -``` - -## Examples from Project - -### Good Documentation Examples - -- `docs/en/guides/getting-started.md` - Clear step-by-step guide -- `services/_template/README.md` - Comprehensive service README -- `deployments/local/README.md` - Operations-focused deployment guide - -### Documentation Locations Reference - -| Content Type | Location | Format | -|--------------|----------|--------| -| Getting Started | `docs/en/guides/getting-started.md` | Separate files | -| Service Setup | `services/[name]/README.md` | Side-by-side | -| Deployment | `docs/en/guides/deployment.md` | Separate files | -| Architecture | `docs/en/architecture/` | Separate files | -| API Specs | `docs/en/api/openapi/` | OpenAPI YAML | -| Runbooks | `docs/en/runbooks/` | Separate files | -| Infrastructure | `infra/[component]/README.md` | Side-by-side | -| Environment Config | `deployments/[env]/README.md` | Technical only | - -## Quick Reference - -### File Naming - -- Use kebab-case: `getting-started.md` -- Be descriptive: `local-development.md` not `dev.md` -- Match EN and VI filenames - -### Heading Levels - -```markdown -# H1: Document Title (only one per file) -## H2: Major Sections -### H3: Subsections -#### H4: Details (use sparingly) -``` - -### Bilingual Patterns - -```markdown -# Pattern 1: Inline -Description / Mô tả - -# Pattern 2: After slash -PORT=5000 # Server port / Cổng server - -# Pattern 3: Table -| Variable | Description / Mô Tả | - -# Pattern 4: Code comments -# EN: Install dependencies -# VI: Cài đặt dependencies -pnpm install -``` - -## Resources - -- [Project Rules](../project-rules/SKILL.md) - Project structure and standards -- [Comment Code](../comment-code/SKILL.md) - Code commenting standards -- [API Design](../api-design/SKILL.md) - API documentation -- [Testing Patterns](../testing-patterns/SKILL.md) - Test documentation diff --git a/.agent/rules/error-handling-patterns.md b/.agent/rules/error-handling-patterns.md deleted file mode 100644 index 1825055b..00000000 --- a/.agent/rules/error-handling-patterns.md +++ /dev/null @@ -1,363 +0,0 @@ ---- -trigger: always_on ---- - -# Error Handling Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing error handling in services, controllers, or repositories -- Creating custom error classes for specific error scenarios -- Standardizing error responses across APIs -- Handling exceptions from external services or database operations -- Implementing error middleware and global error handlers -- Debugging error scenarios and improving error messages -- Distinguishing between operational and programming errors - -## Core Concepts - -### Error Types - -1. **Operational Errors**: Expected errors that occur during normal operation - - Examples: Validation errors, authentication failures, resource not found - - Should be handled gracefully and return appropriate HTTP status codes - - Safe to expose error details to clients (with caution) - -2. **Programming Errors**: Unexpected errors due to bugs in code - - Examples: Null pointer exceptions, type errors, logic bugs - - Should be logged with full details for debugging - - Should return generic error messages to clients (hide implementation details) - -### Error Code System - -The platform uses a centralized error code system (`ErrorCode` enum) that: -- Provides unique identifiers for each error type -- Maps to HTTP status codes consistently -- Enables error tracking and analytics -- Supports internationalization - -Error codes follow the pattern: `{CATEGORY}_{NUMBER}` -- `AUTH_001` - Authentication errors -- `VALIDATION_001` - Validation errors -- `RESOURCE_001` - Resource errors -- `DB_001` - Database errors - -## Patterns - -### Base Error Class: HttpError - -All custom errors extend the `HttpError` base class: - -```typescript -export class HttpError extends Error { - public readonly statusCode: number; - public readonly errorCode: string; - public readonly isOperational: boolean; - public readonly details?: any; - - constructor( - message: string, - statusCode: number = 500, - errorCode: string = 'INTERNAL_ERROR', - isOperational: boolean = true, - details?: any - ) { - super(message); - this.statusCode = statusCode; - this.errorCode = errorCode; - this.isOperational = isOperational; - this.details = details; - Error.captureStackTrace(this, this.constructor); - } - - toApiResponse() { - return { - success: false, - error: { - code: this.errorCode, - message: this.message, - ...(this.details && { details: this.details }), - }, - timestamp: new Date().toISOString(), - }; - } -} -``` - -### Standard Error Classes - -Use these predefined error classes for common scenarios: - -**Resource Errors:** -- `NotFoundError` - 404: Resource not found -- `ConflictError` - 409: Resource conflict (e.g., duplicate) - -**Validation Errors:** -- `ValidationError` - 422: Input validation failed -- `BadRequestError` - 400: Invalid request - -**Authentication/Authorization:** -- `UnauthorizedError` - 401: Authentication required -- `ForbiddenError` - 403: Access denied - -**System Errors:** -- `InternalServerError` - 500: Internal server error (programming error) -- `ServiceUnavailableError` - 503: Service temporarily unavailable -- `DatabaseError` - 500: Database operation failed -- `ExternalServiceError` - 502: External service error - -**Rate Limiting:** -- `RateLimitError` - 429: Too many requests - -### Error Code Enum - -Centralized error codes in `ErrorCode` enum: - -```typescript -export enum ErrorCode { - // Authentication & Authorization - UNAUTHORIZED = 'AUTH_001', - FORBIDDEN = 'AUTH_002', - INVALID_TOKEN = 'AUTH_003', - TOKEN_EXPIRED = 'AUTH_004', - - // Validation - VALIDATION_ERROR = 'VALIDATION_001', - INVALID_FORMAT = 'VALIDATION_002', - - // Resources - NOT_FOUND = 'RESOURCE_001', - ALREADY_EXISTS = 'RESOURCE_002', - CONFLICT = 'RESOURCE_003', - - // Database - DATABASE_ERROR = 'DB_001', - CONSTRAINT_VIOLATION = 'DB_004', - - // System - INTERNAL_ERROR = 'SYS_001', - RATE_LIMIT_EXCEEDED = 'SYS_003', -} -``` - -### Using Errors in Services - -```typescript -import { NotFoundError, ConflictError } from '../errors/http-error'; -import { ErrorCode } from '../errors/error-codes'; - -export class UserService { - async getUserById(id: string) { - const user = await this.repository.findById(id); - - if (!user) { - throw new NotFoundError('User', { id }); - } - - return user; - } - - async createUser(data: CreateUserInput) { - const existing = await this.repository.findByEmail(data.email); - - if (existing) { - throw new ConflictError('User with this email already exists'); - } - - return await this.repository.create(data); - } -} -``` - -### Error Middleware Pattern - -Global error handler middleware processes all errors: - -```typescript -export const errorHandler = ( - err: any, - req: express.Request, - res: express.Response, - _next: express.NextFunction -): void => { - let statusCode = 500; - let errorCode = ErrorCode.INTERNAL_ERROR; - let message = 'Internal server error'; - let isOperational = false; - - // Handle HttpError instances - if (err instanceof HttpError) { - statusCode = err.statusCode; - errorCode = err.errorCode as ErrorCode; - message = err.message; - isOperational = err.isOperational; - } - // Handle Prisma errors - else if (err.code === 'P2002') { - statusCode = 409; - errorCode = ErrorCode.CONSTRAINT_VIOLATION; - message = 'Resource already exists'; - isOperational = true; - } - // Handle Zod validation errors - else if (err.name === 'ZodError') { - statusCode = 422; - errorCode = ErrorCode.VALIDATION_ERROR; - message = 'Validation failed'; - // Extract validation details - } - - // Log error - if (!isOperational || statusCode >= 500) { - logger.error('Unhandled error', { error: err, statusCode, errorCode }); - } else { - logger.warn('Operational error', { error: err, statusCode, errorCode }); - } - - // Send response - const response = { - success: false, - error: { - code: errorCode, - message: isProduction && statusCode >= 500 - ? 'Internal server error' - : message, - }, - timestamp: new Date().toISOString(), - }; - - res.status(statusCode).json(response); -}; -``` - -### Async Error Wrapper - -Wrap async route handlers to catch promise rejections: - -```typescript -export const asyncHandler = (fn: Function) => { - return (req: express.Request, res: express.Response, next: express.NextFunction) => { - Promise.resolve(fn(req, res, next)).catch(next); - }; -}; - -// Usage -router.get('/users/:id', asyncHandler(async (req, res) => { - const user = await userService.getUserById(req.params.id); - res.json({ success: true, data: user }); -})); -``` - -### Error Response Format - -Standardized error response format: - -```typescript -{ - success: false, - error: { - code: "RESOURCE_001", - message: "User not found", - details?: { - // Optional additional details (not in production for 5xx errors) - } - }, - timestamp: "2024-01-01T00:00:00.000Z" -} -``` - -## Best Practices - -1. **Use Specific Error Classes**: Use the most specific error class available -2. **Include Context**: Provide helpful error messages with context -3. **Mark Operational Errors**: Set `isOperational: true` for expected errors -4. **Don't Expose Internal Details**: Hide implementation details in production -5. **Log Appropriately**: Use `logger.error()` for programming errors, `logger.warn()` for operational errors -6. **Handle Database Errors**: Map Prisma errors to appropriate HTTP errors -7. **Use Error Codes**: Always use `ErrorCode` enum for consistency -8. **Validate Early**: Validate input early to catch errors before processing - -## Common Mistakes - -1. **Not Using Error Classes**: Using generic `Error` instead of specific error classes -2. **Exposing Stack Traces**: Including stack traces in production responses -3. **Ignoring Errors**: Not handling errors in async operations -4. **Generic Error Messages**: Using vague error messages without context -5. **Not Logging**: Forgetting to log errors for debugging -6. **Wrong HTTP Status Codes**: Using incorrect status codes for error types -7. **Not Using Error Middleware**: Handling errors manually instead of using middleware - -## Troubleshooting - -### Error Not Caught by Middleware - -**Problem**: Error not being caught by error middleware -**Solution**: Ensure error middleware is added last, after all routes. Use `asyncHandler` for async route handlers. - -### Generic Error Messages in Production - -**Problem**: Generic "Internal server error" shown even for operational errors -**Solution**: Check `isOperational` flag is set correctly. Verify error middleware handles all error types. - -### Error Code Not Found - -**Problem**: Error code not in `ErrorCode` enum -**Solution**: Add error code to enum following naming convention. Update `ERROR_CODE_TO_STATUS` mapping. - -### Stack Traces Exposed - -**Problem**: Stack traces visible in API responses -**Solution**: Ensure production environment checks are in place. Use error middleware to filter stack traces. - -## Quick Reference - -| Error Class | Status | When to Use | -|-------------|--------|-------------| -| `NotFoundError` | 404 | Resource not found | -| `BadRequestError` | 400 | Invalid request | -| `ValidationError` | 422 | Input validation failed | -| `UnauthorizedError` | 401 | Auth required | -| `ForbiddenError` | 403 | Access denied | -| `ConflictError` | 409 | Duplicate/conflict | -| `RateLimitError` | 429 | Too many requests | -| `InternalServerError` | 500 | Server error | - -**Error Response Format:** -```typescript -{ - success: false, - error: { - code: "RESOURCE_001", - message: "User not found", - details?: { /* validation details */ } - }, - timestamp: "2024-01-01T00:00:00.000Z" -} -``` - -**Common Error Codes:** -| Code | Category | Description | -|------|----------|-------------| -| `AUTH_001` | Auth | Unauthorized | -| `AUTH_003` | Auth | Invalid token | -| `VALIDATION_001` | Validation | Validation failed | -| `RESOURCE_001` | Resource | Not found | -| `RESOURCE_002` | Resource | Already exists | -| `DB_001` | Database | Database error | -| `SYS_001` | System | Internal error | - -**Essential Imports:** -```typescript -import { NotFoundError, ValidationError, ConflictError } from '../errors/http-error'; -import { ErrorCode } from '../errors/error-codes'; -import { asyncHandler } from '../middlewares/async.middleware'; -``` - -## Resources - -- [Error Classes](../../services/iam-service/src/errors/http-error.ts) - Base error classes -- [Error Codes](../../services/iam-service/src/errors/error-codes.ts) - Error code definitions -- [Error Middleware](../../services/iam-service/src/middlewares/error.middleware.ts) - Global error handler -- [API Design](../api-design/SKILL.md) - API response formats -- [Security](../security/SKILL.md) - Security error handling diff --git a/.agent/rules/event-driven-architecture.md b/.agent/rules/event-driven-architecture.md deleted file mode 100644 index dbc9fa4f..00000000 --- a/.agent/rules/event-driven-architecture.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -trigger: always_on ---- - -# Event-Driven Architecture Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing asynchronous communication between services -- Decoupling services for better scalability -- Publishing domain events for downstream consumers -- Consuming events from other services -- Implementing event sourcing patterns -- Implementing CQRS (Command Query Responsibility Segregation) -- Exposing event streams via HTTP (SSE/WebSocket) -- Handling eventual consistency across services -- Building reactive systems that respond to changes -- Integrating with Apache Kafka message broker - -## Core Concepts - -### Event-Driven vs Request-Response - -| Aspect | Request-Response | Event-Driven | -|--------|------------------|--------------| -| Communication | Synchronous | Asynchronous | -| Coupling | Tight | Loose | -| Blocking | Yes | No | -| Consistency | Immediate | Eventual | -| Infrastructure | Traefik API Gateway | Kafka | - -### Kafka Fundamentals - -**Topics**: Named streams of events (e.g., `user.created`, `order.placed`) -- Organized by domain and action -- Divided into partitions for parallelism - -**Partitions**: Physical division of topics -- Enables horizontal scaling -- Maintains ordering per partition key -- Multiple consumers can process different partitions - -**Consumer Groups**: Group of consumers working together -- Each partition consumed by only one consumer in group -- Enables parallel processing -- Automatically rebalances on consumer join/leave - -**Producers**: Services that publish events to topics - -**Consumers**: Services that subscribe to topics and process events - -### Event Structure - -```typescript -interface BaseEvent { - eventId: string; // Unique event identifier - eventType: string; // Event type (e.g., "user.created") - eventVersion: string; // Schema version (e.g., "1.0.0") - timestamp: string; // ISO 8601 timestamp - source: string; // Service that published the event - correlationId?: string; // Request correlation ID - traceId?: string; // Distributed tracing ID - data: unknown; // Event payload -} -``` - -### Event Naming Conventions - -**Event Type Format**: `{domain}.{action}.v{version}` -- `user.created.v1` -- `order.placed.v1` -- `payment.processed.v2` - -**Topic Naming**: `{domain}.{entity}.{action}` -- `user.created` -- `order.placed` -- `payment.processed` - -## Key Patterns - -### 1. Event Publishing - -```typescript -// Fire-and-forget with error logging -eventPublisher.publish('user.created', event, { partitionKey: user.id }) - .catch(err => logger.error('Failed to publish', { err })); -``` - -### 2. Event Consuming - -```typescript -consumer.on('user.created', { - handle: async (event) => { - await processEvent(event); - }, -}); -await consumer.start(['user.created']); -``` - -### 3. Outbox Pattern (Transactional) - -Store events in database within same transaction, then publish asynchronously: -```typescript -await prisma.$transaction(async (tx) => { - const user = await tx.user.create({ data }); - await outboxService.addToOutbox('user.created', userData, 'user.created'); - return user; -}); -``` - -### 4. Dead Letter Queue (DLQ) - -After max retries, send failed events to DLQ topic for manual inspection: -```typescript -after maxRetries → send to topic.dlq -``` - -### 5. Idempotency - -Consumers must handle duplicate events: -```typescript -if (await this.isProcessed(event.eventId)) return; -await processEvent(event); -await this.markProcessed(event.eventId); -``` - -## Best Practices - -### Partition Key Selection -- Use entity ID for ordering guarantees (same entity → same partition) -- Use correlation ID for request tracing -- Use user ID for user-scoped events -- Avoid high-cardinality keys (distributes evenly) - -### Event Ordering Guarantees -- Kafka guarantees ordering **per partition** -- Use partition key to ensure related events go to same partition -- Events in different partitions have no ordering guarantee -- Don't rely on global ordering across all events - -### Event Size Limits -- Recommended: < 1MB per event -- Kafka default: 1MB (configurable) -- For large payloads: Store data elsewhere, send reference in event - -### Performance Optimization -- **Batch Publishing**: Group multiple events for better throughput -- **Async Publishing**: Don't block request handlers -- **Consumer Parallelism**: Use multiple partitions and consumers -- **Connection Pooling**: Reuse Kafka client instances -- **Compression**: Enable compression for better network usage - -## Common Mistakes - -1. **Blocking on Publish**: Slowing down request handlers - ```typescript - // BAD: Await in request handler - await eventPublisher.publish('user.created', event); - res.json({ success: true }); - - // GOOD: Fire and forget with error logging - eventPublisher.publish('user.created', event) - .catch(err => logger.error('Failed to publish', { err })); - res.json({ success: true }); - ``` - -2. **No Idempotency**: Duplicate event processing issues - ```typescript - // BAD: No duplicate check - async handle(event) { - await createUser(event.data); - } - - // GOOD: Check for duplicates - async handle(event) { - if (await this.isProcessed(event.eventId)) return; - await createUser(event.data); - await this.markProcessed(event.eventId); - } - ``` - -3. **Missing Partition Key**: Events for same entity out of order - ```typescript - // BAD: No partition key - await publish('user.updated', event); - - // GOOD: Use entity ID as partition key - await publish('user.updated', event, { partitionKey: userId }); - ``` - -4. **No Dead Letter Queue**: Lost events on failure - ```typescript - // GOOD: Always implement DLQ for failed events - after maxRetries → send to topic.dlq - ``` - -5. **Breaking Schema Changes**: Use versioning strategy instead - -6. **Global Ordering Expectations**: Understand partition ordering only - -## Quick Reference - -| Concept | Description | -|---------|-------------| -| **Topic** | Named stream of events (e.g., `user.created`) | -| **Partition** | Division of topic for parallelism | -| **Consumer Group** | Consumers sharing workload | -| **Offset** | Position in partition | - -**Event Structure:** -```typescript -{ - eventId: "uuid", // Unique identifier - eventType: "user.created", // Event type - eventVersion: "1.0.0", // Schema version - timestamp: "ISO-8601", // When published - source: "auth-service", // Publisher service - correlationId: "uuid", // Request trace ID - data: { ... } // Event payload -} -``` - -**Topic Naming:** -``` -{domain}.{action} -user.created -order.placed -payment.processed -``` - -**Essential Commands:** -```bash -# List topics -kafka-topics --list --bootstrap-server localhost:9092 - -# Create topic -kafka-topics --create --topic user.created --partitions 3 - -# Consume from beginning -kafka-console-consumer --topic user.created --from-beginning -``` - -**KafkaJS Quick Setup:** -```typescript -import { Kafka } from 'kafkajs'; - -const kafka = new Kafka({ brokers: ['localhost:9092'], clientId: 'my-app' }); -const producer = kafka.producer(); -const consumer = kafka.consumer({ groupId: 'my-group' }); -``` - -**Environment Variables:** -```bash -KAFKA_BROKERS=localhost:9092 -KAFKA_CLIENT_ID=my-service -KAFKA_CONSUMER_GROUP_ID=my-service-consumers -SCHEMA_REGISTRY_URL=http://localhost:8081 -``` - -## Resources - -- [Detailed Reference](./references/REFERENCE.md) - Full code examples and implementation details -- [KafkaJS Documentation](https://kafka.js.org/) - Node.js Kafka client -- [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/index.html) - Schema versioning -- [Kafka Best Practices](https://kafka.apache.org/documentation/#best_practices) - Official Kafka documentation -- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker, retry patterns -- [Error Handling Patterns](../error-handling-patterns/SKILL.md) - Error handling best practices -- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Logging, metrics, tracing -- [Project Rules](../project-rules/SKILL.md) - GoodGo coding standards diff --git a/.agent/rules/infrastructure-as-code.md b/.agent/rules/infrastructure-as-code.md deleted file mode 100644 index d2c6110b..00000000 --- a/.agent/rules/infrastructure-as-code.md +++ /dev/null @@ -1,415 +0,0 @@ ---- -trigger: always_on ---- - -# Infrastructure as Code Patterns - -## When to Use This Skill - -Use this skill when: -- Managing infrastructure with code -- Implementing Terraform modules -- Setting up GitOps workflows -- Creating Kubernetes operators -- Testing infrastructure changes -- Managing multi-environment infrastructure -- Versioning infrastructure -- Automating infrastructure provisioning - -## Core Concepts - -### Infrastructure as Code Benefits - -1. **Version Control**: Track infrastructure changes -2. **Reproducibility**: Consistent environments -3. **Automation**: Reduce manual errors -4. **Testing**: Test infrastructure changes -5. **Collaboration**: Team can review changes - -### IaC Tools - -- **Terraform**: Infrastructure provisioning -- **Kubernetes Manifests**: K8s resource definitions -- **Helm**: K8s package manager -- **ArgoCD/Flux**: GitOps tools - -## Terraform Patterns - -### Module Structure - -``` -infra/terraform/ -├── modules/ -│ ├── kubernetes-cluster/ -│ │ ├── main.tf -│ │ ├── variables.tf -│ │ └── outputs.tf -│ ├── postgresql/ -│ │ ├── main.tf -│ │ ├── variables.tf -│ │ └── outputs.tf -│ └── redis/ -│ ├── main.tf -│ ├── variables.tf -│ └── outputs.tf -├── environments/ -│ ├── staging/ -│ │ ├── main.tf -│ │ └── terraform.tfvars -│ └── production/ -│ ├── main.tf -│ └── terraform.tfvars -└── shared/ - └── backend.tf -``` - -### Terraform Module Example - -```hcl -# infra/terraform/modules/postgresql/main.tf -# EN: PostgreSQL module -# VI: Module PostgreSQL -variable "database_name" { - description = "Database name" - type = string -} - -variable "environment" { - description = "Environment name" - type = string -} - -resource "google_sql_database_instance" "postgres" { - name = "${var.database_name}-${var.environment}" - database_version = "POSTGRES_15" - region = "us-central1" - - settings { - tier = "db-f1-micro" - - backup_configuration { - enabled = true - start_time = "03:00" - } - } -} - -resource "google_sql_database" "database" { - name = var.database_name - instance = google_sql_database_instance.postgres.name -} - -output "connection_name" { - value = google_sql_database_instance.postgres.connection_name -} - -output "database_url" { - value = "postgresql://user:pass@${google_sql_database_instance.postgres.ip_address}/${var.database_name}" - sensitive = true -} -``` - -### Using Modules - -```hcl -# infra/terraform/environments/staging/main.tf -# EN: Use PostgreSQL module -# VI: Sử dụng module PostgreSQL -module "postgresql" { - source = "../../modules/postgresql" - - database_name = "goodgo_staging" - environment = "staging" -} - -output "database_url" { - value = module.postgresql.database_url - sensitive = true -} -``` - -## GitOps Patterns - -### ArgoCD Setup - -```yaml -# infra/gitops/argocd/applications/user-service.yaml -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: user-service - namespace: argocd -spec: - project: default - source: - repoURL: https://github.com/goodgo/platform - targetRevision: main - path: deployments/production/kubernetes/user-service - destination: - server: https://kubernetes.default.svc - namespace: production - syncPolicy: - automated: - prune: true - selfHeal: true - syncOptions: - - CreateNamespace=true -``` - -### Flux Setup - -```yaml -# infra/gitops/flux/kustomizations/user-service.yaml -apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 -kind: Kustomization -metadata: - name: user-service - namespace: flux-system -spec: - interval: 5m - path: ./deployments/production/kubernetes/user-service - prune: true - sourceRef: - kind: GitRepository - name: platform-repo - validation: client -``` - -## Infrastructure Testing - -### Terraform Testing - -```bash -#!/bin/bash -# scripts/infra/test-terraform.sh -# EN: Test Terraform changes -# VI: Test các thay đổi Terraform - -cd infra/terraform/environments/staging - -# EN: Validate Terraform -# VI: Validate Terraform -terraform init -terraform validate - -# EN: Plan changes -# VI: Plan changes -terraform plan -out=tfplan - -# EN: Review plan -# VI: Review plan -terraform show tfplan -``` - -### Infrastructure Validation - -```typescript -// scripts/infra/validate-k8s.ts -// EN: Validate Kubernetes manifests -// VI: Validate Kubernetes manifests -import { execSync } from 'child_process'; - -function validateKubernetesManifests(path: string): boolean { - try { - execSync(`kubectl apply --dry-run=client -f ${path}`, { - stdio: 'inherit', - }); - console.log('Kubernetes manifests are valid'); - return true; - } catch (error) { - console.error('Kubernetes validation failed', error); - return false; - } -} -``` - -## Environment Management - -### Environment Configuration - -```hcl -# infra/terraform/environments/staging/terraform.tfvars -environment = "staging" -cluster_name = "goodgo-staging" -node_count = 3 -instance_type = "t3.medium" - -# infra/terraform/environments/production/terraform.tfvars -environment = "production" -cluster_name = "goodgo-production" -node_count = 5 -instance_type = "t3.large" -``` - -### Multi-Environment Module - -```hcl -# infra/terraform/modules/service/main.tf -variable "environment" { - type = string -} - -variable "replicas" { - type = map(number) - default = { - staging = 2 - production = 5 - } -} - -resource "kubernetes_deployment" "service" { - metadata { - name = "${var.service_name}-${var.environment}" - } - - spec { - replicas = var.replicas[var.environment] - # ... - } -} -``` - -## Kubernetes Operators - -### Custom Resource Definition - -```yaml -# infra/operators/database-operator/crd.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: databases.example.com -spec: - group: example.com - versions: - - name: v1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - databaseName: - type: string - environment: - type: string -``` - -## Best Practices - -1. **Version Control**: Keep all infrastructure in version control -2. **Modules**: Create reusable Terraform modules -3. **Testing**: Test infrastructure changes before applying -4. **GitOps**: Use GitOps for Kubernetes deployments -5. **Environment Isolation**: Separate environments completely -6. **State Management**: Use remote state backends -7. **Secrets**: Never commit secrets, use secrets managers - -## Common Mistakes - -1. **Committing Secrets**: Exposed credentials - ```hcl - # ❌ BAD: Hardcoded secrets - password = "my-secret-password" - - # ✅ GOOD: Use variables + secrets manager - password = var.db_password # From env or secrets manager - ``` - -2. **No Remote State**: State conflicts in team - ```hcl - # ❌ BAD: Local state - # (no backend config) - - # ✅ GOOD: Remote state - terraform { - backend "s3" { - bucket = "terraform-state" - key = "staging/terraform.tfstate" - } - } - ``` - -3. **No State Locking**: Concurrent modifications - ```hcl - # ✅ Enable state locking - backend "s3" { - dynamodb_table = "terraform-locks" - } - ``` - -4. **Applying Without Plan Review**: Unexpected changes - ```bash - # ❌ BAD: Direct apply - terraform apply - - # ✅ GOOD: Plan first, review, then apply - terraform plan -out=tfplan - terraform show tfplan # Review - terraform apply tfplan - ``` - -## Quick Reference - -| Tool | Purpose | State | -|------|---------|-------| -| **Terraform** | Cloud resources | Stateful | -| **Kubernetes** | Container orchestration | Declarative | -| **Helm** | K8s package manager | Template | -| **ArgoCD/Flux** | GitOps deployment | Git-synced | - -**Terraform Commands:** -```bash -terraform init # Initialize -terraform validate # Validate syntax -terraform plan # Preview changes -terraform apply # Apply changes -terraform destroy # Remove resources -terraform state list # List state resources -``` - -**Module Structure:** -``` -modules/service/ -├── main.tf # Resources -├── variables.tf # Input variables -├── outputs.tf # Output values -└── README.md # Documentation -``` - -**Environment Pattern:** -``` -environments/ -├── staging/ -│ ├── main.tf -│ └── terraform.tfvars -└── production/ - ├── main.tf - └── terraform.tfvars -``` - -**GitOps Workflow:** -``` -Git Push → CI Validates → ArgoCD Syncs → K8s Applied - │ - Auto-heal if drift -``` - -**Best Practices Checklist:** -- [ ] Remote state backend configured -- [ ] State locking enabled -- [ ] No secrets in code -- [ ] Modules for reusable components -- [ ] Environment-specific tfvars -- [ ] PR review for all changes - -## Resources - -- [Terraform Documentation](https://www.terraform.io/docs) -- [ArgoCD Documentation](https://argo-cd.readthedocs.io/) -- [Flux Documentation](https://fluxcd.io/docs/) -- [Deployment Kubernetes](../deployment-kubernetes/SKILL.md) - K8s patterns -- [Project Rules](../project-rules/SKILL.md) - GoodGo standards diff --git a/.agent/rules/inter-service-communication.md b/.agent/rules/inter-service-communication.md deleted file mode 100644 index 7d32f6a7..00000000 --- a/.agent/rules/inter-service-communication.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -trigger: always_on ---- - -# Inter-Service Communication Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing service-to-service communication -- Choosing between REST, gRPC, or GraphQL protocols -- Setting up gRPC services and clients -- Implementing GraphQL services and resolvers -- Implementing service-to-service authentication -- Building resilient service clients with circuit breakers -- Managing connection pooling for service clients -- Implementing request/response interceptors -- Handling service discovery for internal calls - -## Protocol Comparison - -| Protocol | Use Case | Latency | Complexity | Best For | -|----------|----------|---------|------------|----------| -| **REST** | External APIs, CRUD | Medium | Low | Browser clients, simple APIs | -| **gRPC** | Internal high-perf | Low | High | Service-to-service, streaming | -| **GraphQL** | Flexible queries | Medium | Medium | Mobile apps, complex data | - -### Protocol Selection Guidelines - -``` -External/Public API → REST -Internal service-to-service → gRPC (performance) or REST (simplicity) -Complex data fetching → GraphQL -Real-time streaming → gRPC or WebSocket -``` - -## HTTP/REST Client Pattern - -```typescript -import axios from 'axios'; - -const client = axios.create({ - baseURL: process.env.USER_SERVICE_URL, - timeout: 5000, - headers: { - 'Content-Type': 'application/json', - 'x-service-auth': process.env.INTERNAL_API_KEY, - }, -}); - -// Add correlation ID for tracing -client.interceptors.request.use((config) => { - config.headers['x-correlation-id'] = generateCorrelationId(); - return config; -}); -``` - -## gRPC Pattern - -### Proto Definition -```protobuf -syntax = "proto3"; -package goodgo.user.v1; - -service UserService { - rpc GetUser(GetUserRequest) returns (GetUserResponse); - rpc StreamUserUpdates(StreamRequest) returns (stream UserUpdate); -} - -message GetUserRequest { string user_id = 1; } -message GetUserResponse { User user = 1; } -``` - -### Client Usage -```typescript -const client = new GrpcClient({ - protoPath: './proto/user_service.proto', - packageName: 'goodgo.user.v1', - serviceName: 'UserService', - serverUrl: 'localhost:50051', -}); - -const user = await client.call('getUser', { user_id: '123' }); -``` - -## GraphQL Pattern - -### Client Usage -```typescript -const client = new GraphQLClient(process.env.USER_SERVICE_GRAPHQL_URL, { - headers: { 'x-service-auth': process.env.INTERNAL_API_KEY }, -}); - -const GET_USER = `query GetUser($id: ID!) { user(id: $id) { id email name } }`; -const data = await client.request(GET_USER, { id: '123' }); -``` - -## Service Authentication - -### Internal Auth Middleware -```typescript -export const internalAuthMiddleware = (req, res, next) => { - const token = req.headers['x-service-auth']; - - if (token !== process.env.INTERNAL_API_KEY) { - return res.status(403).json({ - success: false, - error: { code: 'INVALID_SERVICE_AUTH', message: 'Invalid authentication' } - }); - } - - next(); -}; -``` - -### Essential Headers - -| Header | Purpose | -|--------|---------| -| `x-service-auth` | Internal authentication token | -| `x-correlation-id` | Request tracing across services | -| `x-request-id` | Unique request identification | - -## Best Practices - -### Performance Optimization -- **Connection Pooling**: Reuse HTTP connections -- **Keep-Alive**: Enable persistent connections -- **Compression**: Use gzip for HTTP responses -- **Timeouts**: Always set appropriate timeouts -- **Circuit Breaker**: Prevent cascade failures - -### Security -- **Service Authentication**: Always authenticate internal calls -- **TLS/mTLS**: Use TLS for all communication -- **Secrets Management**: Use environment variables -- **Rate Limiting**: Implement for service clients - -### Observability -- **Logging**: Log all calls with correlation IDs -- **Metrics**: Track duration, success rate, errors -- **Tracing**: Add distributed tracing -- **Health Checks**: Monitor service health - -## Common Mistakes - -1. **No Service Authentication** - ```typescript - // BAD: No auth header - await client.get('/api/users'); - - // GOOD: Include service auth - await client.get('/api/users', { - headers: { 'x-service-auth': process.env.INTERNAL_API_KEY } - }); - ``` - -2. **Missing Timeouts** - ```typescript - // BAD: No timeout - await axios.get(url); - - // GOOD: Set timeout - await axios.get(url, { timeout: 5000 }); - ``` - -3. **No Circuit Breaker** - ```typescript - // GOOD: Use circuit breaker for external calls - await circuitBreaker.fire(() => serviceClient.get('/api/users')); - ``` - -4. **Hardcoded Service URLs** - ```typescript - // BAD - const url = 'http://user-service:5000'; - - // GOOD - const url = process.env.USER_SERVICE_URL; - ``` - -## Quick Reference - -**HTTP Client Setup:** -```typescript -const client = axios.create({ - baseURL: process.env.SERVICE_URL, - timeout: 5000, - headers: { 'x-service-auth': process.env.INTERNAL_API_KEY } -}); -``` - -**gRPC Client Setup:** -```typescript -const client = new GrpcClient({ - protoPath: './proto/service.proto', - serviceName: 'UserService', - serverUrl: 'localhost:50051' -}); -``` - -**GraphQL Client Setup:** -```typescript -const client = new GraphQLClient(endpoint, { - headers: { 'x-service-auth': process.env.INTERNAL_API_KEY } -}); -``` - -**Error Classes:** -```typescript -ServiceUnavailableError // Service is down -ServiceTimeoutError // Request timeout -ServiceError // Generic service error -``` - -## Resources - -- [Detailed Reference](./references/REFERENCE.md) - Full code examples and implementation details -- [gRPC Documentation](https://grpc.io/docs/) - Official gRPC documentation -- [GraphQL Documentation](https://graphql.org/learn/) - GraphQL learning resources -- [Protocol Buffers](https://developers.google.com/protocol-buffers) - Protocol Buffer guide -- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker, retry patterns -- [Security](../security/SKILL.md) - Authentication, authorization patterns -- [API Design](../api-design/SKILL.md) - RESTful API patterns -- [Project Rules](../project-rules/SKILL.md) - GoodGo coding standards diff --git a/.agent/rules/microservices-development-process.md b/.agent/rules/microservices-development-process.md deleted file mode 100644 index 5a665910..00000000 --- a/.agent/rules/microservices-development-process.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -trigger: always_on ---- - - -# Microservices Development Process - -## When to Use This Skill - -Use this skill when: -- Creating a new microservice from scratch -- Migrating or refactoring an existing service -- Planning service implementation with multiple phases -- Ensuring comprehensive coverage of all development aspects -- Need structured approach to service development - -## Development Process Overview - -The microservices development process follows these phases: -1. **Planning & Impact Analysis** - Define scope, impact, dependencies -2. **Foundation Setup** - Service structure, configs, infrastructure -3. **Core Implementation** - Business logic, APIs, data layer -4. **Integration** - Routes, middleware, external services -5. **Testing** - Unit, integration, E2E tests -6. **Documentation** - API docs, README, guides -7. **Cleanup & Verification** - Remove temporary files, verify completeness -8. **Deployment** - Staging deployment, production deployment - -### Process Flow Diagram - -```mermaid -graph TD - Start([Start: New Service Requirements]) --> Phase1[Phase 1: Planning & Impact Analysis] - Phase1 --> ImpactCheck{Impact Analysis
Complete?} - ImpactCheck -->|No| Phase1 - ImpactCheck -->|Yes| Phase2[Phase 2: Foundation Setup] - - Phase2 --> FoundationCheck{Service Starts
& Health Check Passes?} - FoundationCheck -->|No| Phase2 - FoundationCheck -->|Yes| Phase3[Phase 3: Core Implementation] - - Phase3 --> ImplementationCheck{Business Logic
Implemented?} - ImplementationCheck -->|No| Phase3 - ImplementationCheck -->|Yes| Phase4[Phase 4: Integration] - - Phase4 --> IntegrationCheck{Routes & Middleware
Working?} - IntegrationCheck -->|No| Phase4 - IntegrationCheck -->|Yes| Phase5[Phase 5: Testing] - - Phase5 --> TestCheck{Tests Pass
& Coverage Met?} - TestCheck -->|No| Phase5 - TestCheck -->|Yes| Phase6[Phase 6: Documentation] - - Phase6 --> DocCheck{Docs
Complete?} - DocCheck -->|No| Phase6 - DocCheck -->|Yes| Phase7[Phase 7: Cleanup & Verification] - - Phase7 --> VerificationCheck{All Checks
Pass?} - VerificationCheck -->|No| Phase7 - VerificationCheck -->|Yes| Phase8[Phase 8: Deployment] - - Phase8 --> DeployCheck{Staging
Deployed?} - DeployCheck -->|No| Phase8 - DeployCheck -->|Yes| Production{Deploy to
Production?} - Production -->|Yes| ProdDeploy[Production Deployment] - Production -->|No| Complete([Complete]) - ProdDeploy --> Complete - - style Phase1 fill:#e1f5ff - style Phase2 fill:#fff4e1 - style Phase3 fill:#f0e1ff - style Phase4 fill:#e1ffe1 - style Phase5 fill:#ffe1e1 - style Phase6 fill:#e1ffff - style Phase7 fill:#fff0e1 - style Phase8 fill:#ffe1f5 - style Complete fill:#d4edda -``` - -### Detailed Phase Flow - -```mermaid -graph LR - subgraph Planning["Phase 1: Planning"] - P1A[Define Scope] --> P1B[Impact Analysis] - P1B --> P1C[Dependencies Map] - P1C --> P1D[Acceptance Criteria] - end - - subgraph Foundation["Phase 2: Foundation"] - F2A[Copy Template] --> F2B[Configure Package] - F2B --> F2C[Setup Database] - F2C --> F2D[Configure Docker] - F2D --> F2E[Setup Traefik] - end - - subgraph Implementation["Phase 3: Implementation"] - I3A[DTOs] --> I3B[Repository] - I3B --> I3C[Service] - I3C --> I3D[Controller] - I3D --> I3E[Module] - end - - subgraph Integration["Phase 4: Integration"] - IN4A[Register Routes] --> IN4B[Setup Middleware] - IN4B --> IN4C[External Services] - IN4C --> IN4D[Health Checks] - end - - subgraph Testing["Phase 5: Testing"] - T5A[Unit Tests] --> T5B[Integration Tests] - T5B --> T5C[E2E Tests] - T5C --> T5D[Coverage Check] - end - - subgraph Documentation["Phase 6: Documentation"] - D6A[README] --> D6B[API Docs] - D6B --> D6C[Architecture Docs] - end - - subgraph Cleanup["Phase 7: Cleanup"] - C7A[Remove Temp Files] --> C7B[Update References] - C7B --> C7C[Verify Everything] - end - - subgraph Deployment["Phase 8: Deployment"] - DEP8A[Staging] --> DEP8B[Verification] - DEP8B --> DEP8C[Production] - end - - Planning --> Foundation - Foundation --> Implementation - Implementation --> Integration - Integration --> Testing - Testing --> Documentation - Documentation --> Cleanup - Cleanup --> Deployment - - style Planning fill:#e1f5ff - style Foundation fill:#fff4e1 - style Implementation fill:#f0e1ff - style Integration fill:#e1ffe1 - style Testing fill:#ffe1e1 - style Documentation fill:#e1ffff - style Cleanup fill:#fff0e1 - style Deployment fill:#ffe1f5 -``` - -## Phase 1: Planning & Impact Analysis - -### Scope Definition - -Define clearly: -- **Service Purpose**: What business capability does it provide? -- **API Surface**: What endpoints are needed? -- **Data Models**: What data structures are required? -- **Dependencies**: What services/packages does it depend on? -- **Breaking Changes**: Any backward compatibility concerns? - -### Impact Analysis Checklist - -Before starting implementation, identify all affected areas: - -**Files to Create:** -- [ ] Service directory: `services/service-name/` -- [ ] Prisma schema: `services/service-name/prisma/schema.prisma` -- [ ] Dockerfile: `services/service-name/Dockerfile` -- [ ] Service README: `services/service-name/README.md` - -**Files to Update:** -- [ ] Root `package.json` workspace config -- [ ] `deployments/local/docker-compose.yml` - Add service -- [ ] `infra/traefik/dynamic/routes.yml` - Add routes -- [ ] `.github/workflows/ci-*.yml` - Add CI workflow (if needed) -- [ ] Documentation: `docs/en/guides/`, `docs/vi/guides/` -- [ ] Scripts: `scripts/db/*.sh`, `scripts/dev/*.sh` (if service-specific) - -**Infrastructure Changes:** -- [ ] Database: New schema/tables -- [ ] Redis: New cache keys/patterns (if needed) -- [ ] Traefik: New routes and services -- [ ] Observability: New service metrics/traces - -**Dependencies:** -- [ ] External: Database, Redis, third-party APIs -- [ ] Internal: Shared packages (@goodgo/logger, @goodgo/types, etc.) -- [ ] Other Services: List dependent services - -## Phase 2: Foundation Setup - -### Service Structure Creation - -**Template Usage:** -```bash -cp -r services/_template services/new-service-name -cd services/new-service-name -# Update package.json name to @goodgo/new-service-name -``` - -**Required Files:** -- Service structure from template -- `package.json` with correct name and dependencies -- `src/config/app.config.ts` - Configuration with Zod validation -- `.env.example` - Environment variables template -- `prisma/schema.prisma` - Database schema -- `Dockerfile` - Container configuration -- `jest.config.ts` - Test configuration - -### Database Setup - -```bash -# Create initial migration -cd services/service-name -pnpm prisma migrate dev --name init -pnpm prisma generate -``` - -### Docker & Infrastructure - -**Docker Compose Integration:** -Add service to `deployments/local/docker-compose.yml` with: -- Build context and dockerfile -- Environment variables -- Traefik labels for routing -- Health check configuration - -**Traefik Routes:** -Update `infra/traefik/dynamic/routes.yml` with: -- Router rules (PathPrefix) -- Service configuration -- Middleware chain (CORS, rate-limit, auth) - -### Acceptance Criteria for Phase 2 - -- [ ] Service directory created from template -- [ ] `package.json` configured correctly -- [ ] Environment variables defined -- [ ] Prisma schema created and migration run -- [ ] Service starts: `pnpm dev` (health check passes) -- [ ] Docker build succeeds -- [ ] Service accessible via Traefik -- [ ] No TypeScript errors: `pnpm typecheck` - -## Phase 3: Core Implementation - -### Module Structure - -Each feature module follows this pattern: -``` -modules/feature-name/ -├── feature.controller.ts # HTTP handlers -├── feature.service.ts # Business logic -├── feature.repository.ts # Data access -├── feature.dto.ts # Validation schemas (Zod) -├── feature.module.ts # Module registration -└── index.ts # Public exports -``` - -### Implementation Flow - -```mermaid -graph TD - Start[Start Implementation] --> DTOs[1. Create DTOs
Zod Validation Schemas] - DTOs --> Repo[2. Create Repository
Prisma Data Access] - Repo --> Service[3. Create Service
Business Logic] - Service --> Controller[4. Create Controller
HTTP Handlers] - Controller --> Module[5. Create Module
Wire Up Components] - Module --> Test[Manual Testing] - Test --> Pass{Tests Pass?} - Pass -->|No| Repo - Pass -->|Yes| Next[Next Feature Module] - - style DTOs fill:#e1f5ff - style Repo fill:#fff4e1 - style Service fill:#f0e1ff - style Controller fill:#e1ffe1 - style Module fill:#ffe1e1 -``` - -### Implementation Order - -1. **DTOs** - Zod schemas for request/response validation -2. **Repository** - Prisma-based data access, CRUD operations -3. **Service** - Business logic, error handling, validation -4. **Controller** - HTTP request handling, standardized responses -5. **Module** - Wire up components, export router - -### Code Patterns - -**Repository:** Extend base Repository, use Prisma client for data access -**Service:** Inject repository, implement business logic, use logger -**Controller:** Handle HTTP requests, validate with DTOs, call services -**Module:** Wire up dependencies, export router - -### Acceptance Criteria for Phase 3 - -- [ ] All DTOs defined with Zod validation -- [ ] Repository methods implemented -- [ ] Service business logic implemented -- [ ] Controllers handle requests correctly -- [ ] Modules configured properly -- [ ] No TypeScript errors -- [ ] Manual API testing successful - -## Phase 4: Integration - -### Route Registration - -Update `src/routes/index.ts`: -- Import feature modules -- Create router instances -- Register routes with path prefixes -- Mount to main app with `/api/v1/service-name` prefix - -### Middleware Setup - -**Required Middlewares (in order):** -1. Correlation middleware -2. Logging middleware -3. Metrics middleware -4. CORS middleware -5. Rate limiting middleware -6. Authentication middleware (if needed) -7. Error middleware (always last) - -### External Service Integration - -- HTTP clients: Use `@goodgo/http-client` for external APIs -- Redis caching: Implement cache patterns for frequently accessed data -- Error handling: Handle external service failures gracefully - -### Acceptance Criteria for Phase 4 - -- [ ] All routes registered and accessible -- [ ] Middlewares applied in correct order -- [ ] Error handling works for all scenarios -- [ ] External services integrated (if any) -- [ ] Caching implemented (if needed) -- [ ] Health check endpoint works: `/health` - -## Phase 5: Testing - -### Test Structure - -**Unit Tests:** Next to source files (`*.test.ts`), mock all dependencies -**Integration Tests:** `src/__tests__/`, test component interactions -**E2E Tests:** `src/__tests__/*.e2e.ts`, test full API workflows - -### Test Coverage Targets - -- Minimum: 70% coverage (branches, functions, lines, statements) -- Critical paths: 90%+ coverage -- Repositories: 80%+ coverage -- Services: 80%+ coverage -- Controllers: 70%+ coverage - diff --git a/.agent/rules/middleware-patterns.md b/.agent/rules/middleware-patterns.md deleted file mode 100644 index f3fa1f38..00000000 --- a/.agent/rules/middleware-patterns.md +++ /dev/null @@ -1,314 +0,0 @@ ---- -trigger: always_on ---- - -# Middleware Patterns - -## When to Use This Skill - -Use this skill when: -- Creating custom Express middleware -- Organizing middleware chains and ordering -- Implementing authentication/authorization middleware -- Creating request/response transformation middleware -- Handling cross-cutting concerns (logging, metrics, validation) -- Implementing async middleware patterns -- Testing middleware implementations - -## Core Concepts - -### Middleware Function Signature - -Express middleware functions have this signature: - -```typescript -(req: Request, res: Response, next: NextFunction) => void | Promise -``` - -### Middleware Types - -1. **Application-level**: Applied to all routes (`app.use()`) -2. **Router-level**: Applied to specific routes (`router.use()`) -3. **Route-level**: Applied to specific route handlers - -### Middleware Execution Order - -**Critical**: Middleware order matters! Execution flows top-to-bottom: - -``` -Request → Middleware 1 → Middleware 2 → ... → Route Handler → Response -``` - -## Patterns - -### Middleware Chain Order - -Standard middleware order in GoodGo services: - -```typescript -// 1. Security (Helmet, CORS) -app.use(helmet()); -app.use(cors({ ... })); - -// 2. Rate Limiting -app.use('/api', rateLimitMiddleware); - -// 3. Correlation ID (early for tracing) -app.use(correlationMiddleware()); - -// 4. Body Parsing -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.use(cookieParser()); - -// 5. Request Logging -app.use(loggerMiddleware); - -// 6. Metrics -app.use(metricsMiddleware); - -// 7. Routes -app.use(createRouter()); - -// 8. Error Handling (ALWAYS LAST) -app.use(notFoundHandler); -app.use(errorHandler); -``` - -### Correlation Middleware Pattern - -Adds correlation ID for request tracing: - -```typescript -export const correlationMiddleware = () => { - return (req: Request, res: Response, next: NextFunction) => { - const correlationId = req.headers['x-correlation-id'] || generateId(); - req.correlationId = correlationId; - res.setHeader('x-correlation-id', correlationId); - next(); - }; -}; -``` - -### Authentication Middleware Pattern - -Verifies JWT tokens and attaches user to request: - -```typescript -export const authenticate = () => { - return async (req: Request, res: Response, next: NextFunction) => { - try { - const token = extractToken(req); - if (!token) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - const payload = await jwtService.verify(token); - req.user = payload; - next(); - } catch (error) { - return res.status(401).json({ error: 'Invalid token' }); - } - }; -}; -``` - -### Validation Middleware Pattern - -Validates request data using Zod: - -```typescript -export const validateDto = (schema: AnyZodObject, property: 'body' | 'query' | 'params' = 'body') => { - return (req: Request, res: Response, next: NextFunction) => { - try { - const validatedData = schema.parse(req[property]); - (req as any)[property] = validatedData; - next(); - } catch (error) { - if (error instanceof ZodError) { - return res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - details: error.errors, - }, - }); - } - next(error); - } - }; -}; -``` - -### Conditional Middleware - -Apply middleware conditionally: - -```typescript -const conditionalAuth = (options: { optional?: boolean } = {}) => { - return async (req: Request, res: Response, next: NextFunction) => { - try { - const token = extractToken(req); - if (token) { - const payload = await jwtService.verify(token); - req.user = payload; - } else if (!options.optional) { - return res.status(401).json({ error: 'Unauthorized' }); - } - next(); - } catch (error) { - if (!options.optional) { - return res.status(401).json({ error: 'Invalid token' }); - } - next(); - } - }; -}; -``` - -### Async Middleware Pattern - -Handle async operations properly: - -```typescript -export const asyncMiddleware = (fn: Function) => { - return (req: Request, res: Response, next: NextFunction) => { - Promise.resolve(fn(req, res, next)).catch(next); - }; -}; - -// Usage -app.get('/users', asyncMiddleware(async (req, res) => { - const users = await userService.findAll(); - res.json({ success: true, data: users }); -})); -``` - -### Request/Response Transformation - -Transform request or response data: - -```typescript -export const transformResponse = () => { - return (req: Request, res: Response, next: NextFunction) => { - const originalJson = res.json.bind(res); - - res.json = function(data: unknown) { - const transformed = { - success: true, - data, - timestamp: new Date().toISOString(), - }; - return originalJson(transformed); - }; - - next(); - }; -}; -``` - -### Logging Middleware Pattern - -Log request details: - -```typescript -export const requestLogger = (req: Request, res: Response, next: NextFunction) => { - const startTime = Date.now(); - - res.on('finish', () => { - const duration = Date.now() - startTime; - logger.info('Request completed', { - method: req.method, - url: req.url, - statusCode: res.statusCode, - duration, - correlationId: req.correlationId, - }); - }); - - next(); -}; -``` - -## Best Practices - -1. **Order Matters**: Place middleware in correct order (security → correlation → parsing → logging → routes → errors) -2. **Error Handling**: Always handle errors and call `next(error)` for error middleware -3. **Async Support**: Wrap async middleware properly to catch promise rejections -4. **Early Returns**: Use early returns for validation failures (don't call `next()`) -5. **Request Extension**: Use TypeScript declaration merging to extend Request type -6. **Conditional Logic**: Use middleware factories for conditional middleware -7. **Reusability**: Create reusable middleware functions -8. **Performance**: Keep middleware lightweight, avoid heavy operations - -## Common Mistakes - -1. **Wrong Order**: Placing middleware in incorrect order (e.g., error handler before routes) -2. **Not Calling Next**: Forgetting to call `next()` or `next(error)` -3. **Async Errors**: Not handling promise rejections in async middleware -4. **Early Return Issues**: Calling `next()` after sending response -5. **Type Safety**: Not extending Express Request type properly -6. **Performance**: Doing heavy operations in middleware - -## Troubleshooting - -### Middleware Not Executing - -**Problem**: Middleware not being called -**Solution**: Check middleware order, ensure it's added before routes. Verify `next()` is called. - -### Async Errors Not Caught - -**Problem**: Unhandled promise rejections in async middleware -**Solution**: Use `asyncHandler` wrapper or wrap async code in try-catch with `next(error)`. - -## Quick Reference - -**Middleware Order (Critical):** -``` -1. Security (helmet, cors) -2. Rate Limiting -3. Correlation ID -4. Body Parsing (json, urlencoded) -5. Request Logging -6. Metrics -7. Routes -8. Error Handling (LAST) -``` - -| Middleware Type | Signature | When to Use | -|-----------------|-----------|-------------| -| **Standard** | `(req, res, next) => void` | Sync operations | -| **Async** | `async (req, res, next) => Promise` | Async operations | -| **Error** | `(err, req, res, next) => void` | Error handling | - -**Common Patterns:** -```typescript -// Async wrapper -const asyncHandler = (fn) => (req, res, next) => - Promise.resolve(fn(req, res, next)).catch(next); - -// Conditional middleware -const conditionalMiddleware = (condition, middleware) => - condition ? middleware : (req, res, next) => next(); - -// Factory pattern -const authenticate = (options = {}) => async (req, res, next) => { /* ... */ }; -``` - -**Essential Imports:** -```typescript -import { Request, Response, NextFunction } from 'express'; -import { z, ZodError } from 'zod'; -import { logger } from '@goodgo/logger'; -``` - -## Resources - -- [Express Middleware](https://expressjs.com/en/guide/writing-middleware.html) - Express middleware guide -- [Error Handling](../error-handling-patterns/SKILL.md) - Error middleware patterns -- [Security](../security/SKILL.md) - Auth middleware patterns -- [API Design](../api-design/SKILL.md) - Request/response patterns -- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker middleware -- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Logging middleware -- [Project Rules](../project-rules/SKILL.md) - GoodGo standards diff --git a/.agent/rules/observability-monitoring.md b/.agent/rules/observability-monitoring.md deleted file mode 100644 index 5666d221..00000000 --- a/.agent/rules/observability-monitoring.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -trigger: always_on ---- - -# Observability & Monitoring Patterns - -## When to Use This Skill - -Use this skill when: -- Setting up logging infrastructure -- Implementing metrics collection -- Adding distributed tracing -- Creating health check endpoints -- Setting up monitoring dashboards -- Debugging production issues -- Implementing alerting rules -- Analyzing performance bottlenecks - -## Core Concepts - -### Three Pillars of Observability -1. **Logs**: Event records for debugging -2. **Metrics**: Numerical measurements over time -3. **Traces**: Request flow across services - -### Tech Stack -- **Logging**: Winston, Pino -- **Metrics**: Prometheus + Grafana -- **Tracing**: OpenTelemetry + Jaeger -- **APM**: DataDog or New Relic (optional) - -## Key Patterns - -### Structured Logging - -```typescript -import winston from 'winston'; - -export const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: winston.format.combine( - winston.format.timestamp(), - winston.format.json() - ), - defaultMeta: { service: process.env.SERVICE_NAME } -}); - -// Request logging middleware -export const requestLogger = (req, res, next) => { - const start = Date.now(); - res.on('finish', () => { - logger.info('HTTP Request', { - method: req.method, url: req.url, - status: res.statusCode, duration: Date.now() - start, - correlationId: req.headers['x-correlation-id'] - }); - }); - next(); -}; -``` - -### Metrics Collection - -```typescript -import { Counter, Histogram, Gauge } from 'prom-client'; - -export const httpRequestDuration = new Histogram({ - name: 'http_request_duration_seconds', - help: 'Duration of HTTP requests in seconds', - labelNames: ['method', 'route', 'status'], - buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10] -}); - -export const httpRequestTotal = new Counter({ - name: 'http_requests_total', - help: 'Total number of HTTP requests', - labelNames: ['method', 'route', 'status'] -}); -``` - -### Distributed Tracing - -```typescript -import { NodeSDK } from '@opentelemetry/sdk-node'; -import { trace, SpanStatusCode } from '@opentelemetry/api'; - -export const tracedOperation = async (name: string, fn: Function) => { - const span = trace.getTracer('app').startSpan(name); - try { - const result = await fn(); - span.setStatus({ code: SpanStatusCode.OK }); - return result; - } catch (error) { - span.setStatus({ code: SpanStatusCode.ERROR, message: error.message }); - throw error; - } finally { - span.end(); - } -}; -``` - -### Health Checks - -```typescript -// Liveness - is the service running? -app.get('/health/live', (req, res) => res.json({ status: 'ok' })); - -// Readiness - is the service ready for traffic? -app.get('/health/ready', async (req, res) => { - const dbOk = await prisma.$queryRaw`SELECT 1`.then(() => true).catch(() => false); - const redisOk = await redis.ping().then(() => true).catch(() => false); - const ready = dbOk && redisOk; - res.status(ready ? 200 : 503).json({ ready, db: dbOk, redis: redisOk }); -}); -``` - -## Best Practices - -- **Logging**: Use structured JSON format with correlation IDs -- **Metrics**: Use standard types (Counter, Gauge, Histogram) with low-cardinality labels -- **Tracing**: Add traces for critical operations, sample appropriately -- **Alerting**: Alert on symptoms, include runbook links, avoid alert fatigue -- **Security**: Never log sensitive data (passwords, tokens, PII) - -## Common Mistakes - -1. **Logging Sensitive Data**: Exposing PII in logs - ```typescript - // BAD: logger.info('User login', { email, password, token }); - // GOOD: logger.info('User login', { email, userId }); - ``` - -2. **High Cardinality Labels**: Too many metric label values - ```typescript - // BAD: httpRequests.labels(method, route, userId).inc(); - // GOOD: httpRequests.labels(method, route, statusCode).inc(); - ``` - -3. **No Correlation IDs**: Can't trace requests across services - ```typescript - // GOOD: Include correlationId in all logs - logger.info('Processing', { correlationId: req.headers['x-correlation-id'] }); - ``` - -4. **Wrong Log Levels**: Using ERROR for non-errors - ```typescript - // BAD: logger.error('User not found'); - // GOOD: logger.info('User not found', { userId }); - ``` - -5. **No Health Checks**: Service status unknown - ```typescript - // GOOD: Implement both endpoints - app.get('/health/live', livenessCheck); - app.get('/health/ready', readinessCheck); - ``` - -## Quick Reference - -| Pillar | Tool | Endpoint | -|--------|------|----------| -| **Logs** | Winston/Loki | stdout -> Loki | -| **Metrics** | Prometheus | `/metrics` | -| **Traces** | Jaeger | `http://jaeger:14268` | - -**Log Levels:** -| Level | When to Use | -|-------|-------------| -| `error` | Errors requiring attention | -| `warn` | Potential issues, degradation | -| `info` | Business events, state changes | -| `debug` | Development details | - -**Essential Metrics:** -```typescript -rate(http_requests_total[5m]) // Request rate -rate(http_requests_total{status=~"5.."}[5m]) // Error rate -histogram_quantile(0.95, http_request_duration_bucket) // Latency p95 -``` - -**Health Check Endpoints:** -| Endpoint | Purpose | Used By | -|----------|---------|---------| -| `/health/live` | Is process running? | K8s liveness probe | -| `/health/ready` | Ready for traffic? | K8s readiness probe | -| `/health` | Full status | Monitoring | - -## Resources - -- [OpenTelemetry](https://opentelemetry.io/docs/) - Distributed tracing standard -- [Prometheus](https://prometheus.io/docs/) - Metrics and alerting -- [Grafana](https://grafana.com/docs/) - Visualization -- [Detailed Code Examples](./references/REFERENCE.md) -- [Deployment Kubernetes](../deployment-kubernetes/SKILL.md) - K8s health probes -- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker metrics diff --git a/.agent/rules/performance-optimization.md b/.agent/rules/performance-optimization.md deleted file mode 100644 index 4aaf612e..00000000 --- a/.agent/rules/performance-optimization.md +++ /dev/null @@ -1,457 +0,0 @@ ---- -trigger: always_on ---- - -# Performance Optimization Patterns - -## When to Use This Skill - -Use this skill when: -- Optimizing database queries -- Detecting and fixing memory leaks -- Profiling application performance -- Optimizing connection pooling -- Improving caching strategies -- Identifyings N+1 query problems -- Optimizing batch operations -- Improving application startup time -- Reducing memory usage -- Optimizing API response times - -## Core Concepts - -### Performance Metrics - -1. **Response Time**: Time to process request -2. **Throughput**: Requests processed per second -3. **Memory Usage**: Memory consumption -4. **CPU Usage**: CPU utilization -5. **Database Query Time**: Query execution time - -### Optimization Areas - -- Database queries -- Memory management -- Connection pooling -- Caching -- Batch processing -- Lazy loading - -## Database Query Optimization - -### Query Analysis - -```typescript -// src/core/db/query-analyzer.ts -// EN: Query performance analyzer -// VI: Phân tích hiệu suất query -import { PrismaClient } from '@prisma/client'; -import { logger } from '@goodgo/logger'; - -export class QueryAnalyzer { - constructor(private prisma: PrismaClient) {} - - /** - * EN: Analyze query performance - * VI: Phân tích hiệu suất query - */ - async analyzeQuery(query: string): Promise { - const result = await this.prisma.$queryRawUnsafe(`EXPLAIN ANALYZE ${query}`); - logger.info('Query analysis', { query, result }); - return result; - } - - /** - * EN: Check for missing indexes - * VI: Kiểm tra indexes bị thiếu - */ - async checkIndexes(tableName: string): Promise { - const indexes = await this.prisma.$queryRawUnsafe(` - SELECT * FROM pg_indexes WHERE tablename = '${tableName}' - `); - return indexes; - } -} -``` - -### N+1 Query Prevention - -```typescript -// EN: Bad: N+1 queries -// VI: Xấu: N+1 queries -async function getUsersWithOrdersBad(): Promise { - const users = await userRepository.findAll(); - for (const user of users) { - user.orders = await orderRepository.findByUserId(user.id); // N+1! - } - return users; -} - -// EN: Good: Use include/join -// VI: Tốt: Sử dụng include/join -async function getUsersWithOrdersGood(): Promise { - return await userRepository.findAll({ - include: { - orders: true, // EN: Single query with join / VI: Single query với join - }, - }); -} -``` - -### Batch Operations - -```typescript -// src/core/db/batch-operations.ts -// EN: Batch database operations -// VI: Batch database operations -export class BatchOperations { - /** - * EN: Batch create operations - * VI: Batch create operations - */ - async batchCreate(items: T[], batchSize: number = 100): Promise { - const results: T[] = []; - - for (let i = 0; i < items.length; i += batchSize) { - const batch = items.slice(i, i + batchSize); - const batchResults = await prisma.$transaction( - batch.map((item) => prisma.item.create({ data: item })) - ); - results.push(...batchResults); - } - - return results; - } - - /** - * EN: Batch update operations - * VI: Batch update operations - */ - async batchUpdate( - updates: Array<{ id: string; data: any }>, - batchSize: number = 100 - ): Promise { - for (let i = 0; i < updates.length; i += batchSize) { - const batch = updates.slice(i, i + batchSize); - await prisma.$transaction( - batch.map(({ id, data }) => prisma.item.update({ where: { id }, data })) - ); - } - } -} -``` - -## Memory Leak Detection - -### Memory Profiling - -```typescript -// src/core/performance/memory-profiler.ts -// EN: Memory leak detection -// VI: Phát hiện memory leak -import { performance } from 'perf_hooks'; -import { logger } from '@goodgo/logger'; - -export class MemoryProfiler { - private baseline?: NodeJS.MemoryUsage; - private interval?: NodeJS.Timeout; - - /** - * EN: Start memory profiling - * VI: Bắt đầu memory profiling - */ - start(): void { - this.baseline = process.memoryUsage(); - - this.interval = setInterval(() => { - const current = process.memoryUsage(); - const delta = { - heapUsed: current.heapUsed - (this.baseline?.heapUsed || 0), - heapTotal: current.heapTotal - (this.baseline?.heapTotal || 0), - external: current.external - (this.baseline?.external || 0), - }; - - logger.info('Memory usage', { - current: { - heapUsed: `${(current.heapUsed / 1024 / 1024).toFixed(2)} MB`, - heapTotal: `${(current.heapTotal / 1024 / 1024).toFixed(2)} MB`, - }, - delta: { - heapUsed: `${(delta.heapUsed / 1024 / 1024).toFixed(2)} MB`, - }, - }); - - // EN: Alert if memory growth is excessive - // VI: Cảnh báo nếu memory tăng quá mức - if (delta.heapUsed > 100 * 1024 * 1024) { - // 100MB growth - logger.warn('Potential memory leak detected', { delta }); - } - }, 60000); // EN: Check every minute / VI: Kiểm tra mỗi phút - } - - stop(): void { - if (this.interval) { - clearInterval(this.interval); - } - } - - /** - * EN: Force garbage collection (if --expose-gc flag is set) - * VI: Ép garbage collection (nếu --expose-gc flag được set) - */ - forceGC(): void { - if (global.gc) { - global.gc(); - } - } -} -``` - -## Connection Pooling - -### Database Connection Pool - -```typescript -// src/config/database.config.ts -// EN: Optimize Prisma connection pool -// VI: Tối ưu Prisma connection pool -export const prisma = new PrismaClient({ - datasources: { - db: { - url: process.env.DATABASE_URL, - }, - }, - log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'], -}); - -// EN: Connection pool configuration in DATABASE_URL -// VI: Cấu hình connection pool trong DATABASE_URL -// DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=10" -``` - -### HTTP Connection Pool - -```typescript -// src/core/clients/http-pool.config.ts -// EN: HTTP connection pooling with keep-alive -// VI: HTTP connection pooling với keep-alive -import axios from 'axios'; -import https from 'https'; - -const httpAgent = new https.Agent({ - keepAlive: true, - keepAliveMsecs: 1000, - maxSockets: 50, - maxFreeSockets: 10, - timeout: 60000, -}); - -export const httpClient = axios.create({ - httpAgent, - httpsAgent: httpAgent, - timeout: 30000, -}); -``` - -## Performance Monitoring - -```typescript -// src/core/performance/performance-monitor.ts -// EN: Performance monitoring middleware -// VI: Middleware giám sát hiệu suất -import { Request, Response, NextFunction } from 'express'; -import { performance } from 'perf_hooks'; -import { logger } from '@goodgo/logger'; - -export function performanceMonitor(req: Request, res: Response, next: NextFunction) { - const start = performance.now(); - const startMemory = process.memoryUsage(); - - res.on('finish', () => { - const duration = performance.now() - start; - const endMemory = process.memoryUsage(); - - logger.info('Request performance', { - method: req.method, - path: req.path, - status: res.statusCode, - duration: `${duration.toFixed(2)}ms`, - memoryDelta: `${((endMemory.heapUsed - startMemory.heapUsed) / 1024 / 1024).toFixed(2)} MB`, - }); - - // EN: Alert on slow requests - // VI: Cảnh báo trên requests chậm - if (duration > 1000) { - logger.warn('Slow request detected', { - path: req.path, - duration: `${duration.toFixed(2)}ms`, - }); - } - }); - - next(); -} -``` - -## Caching Optimization - -```typescript -// src/core/cache/cache-optimizer.ts -// EN: Cache optimization strategies -// VI: Chiến lược tối ưu cache -export class CacheOptimizer { - /** - * EN: Preload frequently accessed data - * VI: Tải trước dữ liệu thường truy cập - */ - async preloadHotData(keys: string[]): Promise { - for (const key of keys) { - // EN: Fetch and cache data - // VI: Fetch và cache dữ liệu - await cacheService.getOrSet(key, async () => { - return await this.fetchData(key); - }, 3600); - } - } - - /** - * EN: Batch cache operations - * VI: Batch cache operations - */ - async batchGet(keys: string[]): Promise> { - const results = new Map(); - const missing: string[] = []; - - // EN: Check cache for all keys - // VI: Kiểm tra cache cho tất cả keys - for (const key of keys) { - const cached = await cacheService.get(key); - if (cached) { - results.set(key, cached); - } else { - missing.push(key); - } - } - - // EN: Fetch missing data - // VI: Fetch dữ liệu thiếu - if (missing.length > 0) { - const data = await this.fetchBatch(missing); - for (const [key, value] of Object.entries(data)) { - results.set(key, value); - await cacheService.set(key, value, 3600); - } - } - - return results; - } -} -``` - -## Best Practices - -1. **Database**: Use indexes, avoid N+1 queries, use batch operations -2. **Memory**: Monitor memory usage, detect leaks, use streaming for large data -3. **Caching**: Cache frequently accessed data, use appropriate TTLs -4. **Connection Pooling**: Configure pool sizes appropriately -5. **Profiling**: Profile regularly to identify bottlenecks -6. **Monitoring**: Monitor performance metrics continuously - -## Common Mistakes - -1. **N+1 Queries**: Fetching related data in loops - ```typescript - // ❌ BAD: N+1 - for (const user of users) { - user.orders = await orderRepo.findByUserId(user.id); - } - - // ✅ GOOD: Use include - const users = await userRepo.findAll({ include: { orders: true } }); - ``` - -2. **No Connection Pooling**: Creating new connections per request - ```typescript - // ❌ BAD: No pooling - const client = new PrismaClient(); // per request - - // ✅ GOOD: Reuse singleton - import { prisma } from './db'; // shared instance - ``` - -3. **Missing Indexes**: Slow queries on frequently searched columns - ```prisma - // ✅ Add indexes in schema - @@index([createdAt]) - @@index([userId, status]) - ``` - -4. **Unbounded Queries**: Fetching all data without limits - ```typescript - // ❌ BAD - const all = await repo.findAll(); - - // ✅ GOOD - const page = await repo.findAll({ take: 100, skip: offset }); - ``` - -## Quick Reference - -| Metric | Target | Alert Threshold | -|--------|--------|-----------------| -| **Response Time (p95)** | <200ms | >500ms | -| **Memory Usage** | <70% | >85% | -| **CPU Usage** | <60% | >80% | -| **Query Time** | <50ms | >200ms | - -**Profiling Commands:** -```bash -# CPU profiling -node --prof app.js -node --prof-process isolate-*.log > profile.txt - -# Memory snapshot -node --inspect app.js # Use Chrome DevTools - -# Clinic.js -npx clinic doctor -- node app.js -npx clinic flame -- node app.js -``` - -**Prisma Query Optimization:** -```typescript -// Select only needed fields -await prisma.user.findMany({ select: { id: true, email: true } }); - -// Use include for relations -await prisma.user.findMany({ include: { posts: true } }); - -// Batch operations -await prisma.$transaction([...operations]); - -// Raw query for complex cases -await prisma.$queryRaw`SELECT ... FROM ...`; -``` - -**Connection Pool Config:** -```bash -# In DATABASE_URL -?connection_limit=20&pool_timeout=10 -``` - -**Performance Checklist:** -- [ ] Add database indexes for frequent queries -- [ ] Use pagination for list endpoints -- [ ] Implement caching for hot data -- [ ] Enable connection pooling -- [ ] Profile and monitor regularly -- [ ] Use batch operations for bulk updates - -## Resources - -- [Node.js Performance](https://nodejs.org/en/docs/guides/simple-profiling/) -- [Prisma Performance](https://www.prisma.io/docs/guides/performance-and-optimization) -- [Caching Patterns](../caching-patterns/SKILL.md) - Caching strategies -- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Monitoring patterns -- [Database & Prisma](../database-prisma/SKILL.md) - Database patterns diff --git a/.agent/rules/project-rules.md b/.agent/rules/project-rules.md deleted file mode 100644 index 24b268ae..00000000 --- a/.agent/rules/project-rules.md +++ /dev/null @@ -1,329 +0,0 @@ ---- -trigger: always_on ---- - -# GoodGo Project Rules - -## Architecture - -**Monorepo Structure:** -- **Apps**: Next.js (web) + Flutter (mobile) -- **Services**: Node.js/TypeScript microservices (Express) -- **Packages**: Shared libraries (logger, types, http-client, auth-sdk, tracing) -- **Infrastructure**: Traefik (API Gateway), Redis, Neon PostgreSQL, Observability -- **Deployments**: Local (Docker Compose), Staging/Production (Kubernetes) - -**Template Location**: `services/_template/` - Use as starting point for new services - -## Tech Stack - -**Frontend:** -- Next.js 14+ (App Router), TypeScript, Tailwind CSS, Zustand -- Flutter 3.x with Provider pattern -- Use `@goodgo/types` and `@goodgo/http-client` - -**Backend:** -- Node.js 20+, TypeScript 5+, Express -- Prisma ORM + Neon PostgreSQL -- Zod validation, `@goodgo/logger`, `@goodgo/tracing`, `@goodgo/auth-sdk` - -**Infrastructure:** -- Traefik (path-based routing), Redis (cache), Prometheus + Grafana + Loki - -## Project Structure - -**Service:** `src/{config,modules,middlewares,routes,main.ts}` + `prisma/` + `Dockerfile` -**Package:** `src/index.ts` + `package.json` + `tsconfig.json` + `README.md` -**App:** `src/{app,services/api,stores}` + `Dockerfile` - -## Naming Conventions - -- **Services/Packages**: `kebab-case` (e.g., `auth-service`, `http-client`) -- **Files**: `kebab-case.type.ts` (e.g., `user.controller.ts`) -- **Components**: `PascalCase.tsx` (React), `snake_case.dart` (Flutter) -- **Classes**: `PascalCase`, **Functions**: `camelCase`, **Constants**: `UPPER_SNAKE_CASE` -- **Package Names**: `@goodgo/package-name` - -## Workflows - -**New Service:** -1. Copy `services/_template/` -2. Update `package.json` name to `@goodgo/service-name` -3. Add to `deployments/local/docker-compose.yml` with Traefik labels -4. Configure Prisma schema if needed -5. Add health check endpoint - -**New Package:** -1. Create in `packages/`, export from `src/index.ts` -2. Add to `pnpm-workspace.yaml` -3. Use TypeScript strict mode - -**Dependencies:** -```bash -pnpm --filter @goodgo/service-name add package-name -pnpm --filter @goodgo/service-name add @goodgo/logger # workspace -pnpm --filter @goodgo/service-name add -D @types/pkg # dev -``` - -**Database:** -```bash -pnpm --filter @goodgo/service-name prisma migrate dev -pnpm --filter @goodgo/service-name prisma generate -``` - -## Code Standards - -**TypeScript:** -- Strict mode, no `any` (use `unknown`) -- Zod for runtime validation -- Export shared types from `@goodgo/types` - -**API Responses:** -```typescript -// Success: { success: true, data: any } -// Error: { success: false, error: { code, message, details? } } -``` - -**Logging:** -```typescript -import { logger } from '@goodgo/logger'; -logger.info('Message', { context }); -logger.error('Error', { error, context }); -``` - -**Environment:** -- Use `.env.example` template, never commit `.env` -- Validate with Zod at startup -- Document all vars in README - -## Testing - -- **Unit**: Place tests next to source (`*.test.ts`), use Jest, mock dependencies, >80% coverage -- **Integration**: Test API endpoints, use test database, cleanup after -- **Commands**: `pnpm test`, `pnpm --filter @goodgo/service-name test`, `pnpm test:coverage` - -## Docker - -**Multi-stage Build Pattern:** -```dockerfile -FROM node:20-alpine AS builder -# ... build stage -FROM node:20-alpine -# ... production stage with non-root user -``` - -**Image Naming:** `goodgo/service-name:version` (semantic versioning) - -## Git Workflow - -**Branches:** `feature/`, `fix/`, `hotfix/`, `release/` - -**Commits:** Conventional Commits format -``` -type(scope): subject -``` -Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` - -**PRs:** Use template, link issues, ensure CI passes, squash merge to main - -## CI/CD - -**GitHub Actions:** PR (lint, test, build) → `develop` (staging) → `main` (production) - -**Deployment Checklist:** Tests pass, no lint errors, env vars set, migrations applied, docs updated, monitoring configured - -## Security - -**Auth:** JWT (15min access, 7d refresh), httpOnly cookies, use `@goodgo/auth-sdk` -**Authorization:** RBAC, check permissions at service level, middleware for routes -**Data:** bcrypt (cost 12), HTTPS, sanitize inputs, Zod validation -**Secrets:** Environment variables, Kubernetes secrets, never hardcode, rotate regularly - -## Performance - -**Backend:** Redis caching, connection pooling, pagination, database indexes, rate limiting -**Frontend:** Next.js Image optimization, code splitting, lazy loading, React.memo, bundle optimization -**Database:** Prisma optimization, indexes, transactions, soft deletes - -## Observability - -**Metrics:** Prometheus (request count, duration, errors), set alerts -**Logging:** `@goodgo/logger` with trace IDs, levels (error, warn, info, debug), Loki aggregation -**Tracing:** OpenTelemetry via `@goodgo/tracing`, trace cross-service requests - -## Documentation - -**Code:** JSDoc for public APIs, inline comments for complex logic, README per service/package -**API:** OpenAPI/Swagger specs in `docs/api/openapi/`, document endpoints with examples -**Architecture:** System design in `docs/architecture/`, service communication, data flows, ADRs - -## Architecture Patterns - -**Modular Structure:** Controller → Service → Repository pattern -**DTO Validation:** Zod schemas with type inference -**Error Handling:** Custom error classes, global error middleware -**Dependency Injection:** Constructor injection for testability - -**Example Module:** -```typescript -// DTO with Zod -export const CreateFeatureDto = z.object({ - name: z.string().min(1), - email: z.string().email() -}); -export type CreateFeatureDto = z.infer; - -// Controller -export class FeatureController { - constructor(private service: FeatureService) {} - async create(req: Request, res: Response, next: NextFunction) { - try { - const dto = CreateFeatureDto.parse(req.body); - const result = await this.service.create(dto); - res.json({ success: true, data: result }); - } catch (error) { next(error); } - } -} - -// Service -export class FeatureService { - constructor(private repository: FeatureRepository) {} - async create(dto: CreateFeatureDto) { - return this.repository.create(dto); - } -} - -// Repository -export class FeatureRepository extends BaseRepository { - async create(data: CreateFeatureDto) { - return this.prisma.feature.create({ data }); - } -} -``` - -## Deployment & Traefik - -**Service Registration:** -Services are deployed via `deployments/local/docker-compose.yml` and auto-discovered by Traefik: - -```yaml -services: - my-service: - build: - context: ../.. - dockerfile: services/my-service/Dockerfile - labels: - - "traefik.enable=true" - - "traefik.http.routers.my-service.rule=PathPrefix(`/api/v1/my-service`)" - - "traefik.http.services.my-service.loadbalancer.server.port=5002" - - "traefik.http.services.my-service.loadbalancer.healthcheck.path=/health/live" -``` - -**Traefik Configuration:** -- **Location**: `infra/traefik/` (platform-level, not per-service) -- **Static Config**: `traefik.yml` - Entry points, providers, dashboard -- **Dynamic Config**: `dynamic/middlewares.yml`, `dynamic/routes.yml` -- **Dashboard**: http://localhost:8080 - -**Access Points:** -- API: `http://localhost/api/v1/service-name` -- Health: `http://localhost/api/v1/service-name/health` -- Docs: `http://localhost/api/v1/service-name/api-docs` - -## Troubleshooting - -**Common Issues:** -- Port conflicts: Check `deployments/local/docker-compose.yml` -- Database: Verify `DATABASE_URL` in `.env.local` -- Module not found: Run `pnpm install` -- Type errors: Run `pnpm --filter @goodgo/service-name prisma generate` - -**Debug:** -```bash -cd deployments/local -docker-compose logs -f service-name -docker-compose ps -docker-compose up -d --build -``` - -## Common Mistakes - -1. **Wrong Directory Structure**: Placing files in wrong locations - ``` - # ❌ BAD: Controllers outside modules - src/controllers/user.controller.ts - - # ✅ GOOD: Controllers inside modules - src/modules/user/user.controller.ts - ``` - -2. **Inconsistent Naming**: Mixing naming conventions - ```typescript - // ❌ BAD: Mixed naming - UserService.ts - user_repository.ts - userController.ts - - // ✅ GOOD: Consistent kebab-case - user.service.ts - user.repository.ts - user.controller.ts - ``` - -3. **Wrong Response Format**: Not following API response standard - ```typescript - // ❌ BAD: Inconsistent format - res.json({ user: data }); - res.json({ error: "Not found" }); - - // ✅ GOOD: Standard format - res.json({ success: true, data: user }); - res.json({ success: false, error: { code: 'NOT_FOUND', message: 'User not found' } }); - ``` - -4. **Missing Bilingual Comments**: Only one language - ```typescript - // ❌ BAD: English only - // Initialize database - - // ✅ GOOD: Bilingual - // EN: Initialize database connection - // VI: Khởi tạo kết nối database - ``` - -## Quick Reference - -| Category | Pattern/Standard | -|----------|-----------------| -| **Service structure** | `src/{config,modules,middlewares,routes,main.ts}` | -| **File naming** | `kebab-case.type.ts` (e.g., `user.controller.ts`) | -| **Package naming** | `@goodgo/package-name` | -| **API response** | `{ success: true, data }` / `{ success: false, error: { code, message } }` | -| **Password hashing** | bcrypt, cost 12 | -| **JWT tokens** | Access: 15min, Refresh: 7 days | -| **Coverage target** | >80% for unit tests | -| **Commits** | `type(scope): subject` (conventional commits) | - -**Common Commands:** -```bash -# Add dependency -pnpm --filter @goodgo/service-name add package-name - -# Run migrations -pnpm --filter @goodgo/service-name prisma migrate dev - -# Run tests -pnpm --filter @goodgo/service-name test - -# Start dev server -pnpm --filter @goodgo/service-name dev -``` - -## Resources - -- [Architecture Docs](../../docs/architecture/) -- [API Specs](../../docs/api/openapi/) -- [Development Guide](../../docs/guides/development.md) -- [Deployment Guide](../../docs/guides/deployment.md) -- [Neon Database Guide](../../docs/guides/neon-database.md) -- [Contributing Guide](../../CONTRIBUTING.md) diff --git a/.agent/rules/repository-pattern.md b/.agent/rules/repository-pattern.md deleted file mode 100644 index b3199739..00000000 --- a/.agent/rules/repository-pattern.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -trigger: always_on ---- - -# Repository Pattern - -## When to Use This Skill - -Use this skill when: -- Implementing data access layers for new modules -- Extending BaseRepository for specific entity types -- Writing custom database queries -- Handling database transactions -- Optimizing database queries and operations -- Testing repository implementations -- Organizing data access code - -## Core Concepts - -### Repository Pattern Benefits - -1. **Abstraction**: Separates business logic from data access -2. **Testability**: Easy to mock repositories for testing -3. **Maintainability**: Centralized database operations -4. **Consistency**: Standardized CRUD operations -5. **Type Safety**: TypeScript generics provide type safety - -### BaseRepository Class - -The `BaseRepository` abstract class provides common database operations that can be extended: - -- `findById(id)` - Find entity by ID -- `findByUnique(field, value)` - Find by unique field -- `findAll(options)` - Find all with filtering, pagination, sorting -- `create(data)` - Create new entity -- `update(id, data)` - Update entity -- `delete(id)` - Delete entity -- `count(where)` - Count entities -- `exists(id)` - Check if entity exists -- `transaction(callback)` - Execute transaction - -## Patterns - -### Extending BaseRepository - -```typescript -import { PrismaClient, User } from '@prisma/client'; -import { BaseRepository } from '../modules/common/repository'; - -export class UserRepository extends BaseRepository { - constructor(prisma: PrismaClient) { - super(prisma, 'user'); - } - - // Add custom methods - async findByEmail(email: string): Promise { - return this.prisma.user.findUnique({ - where: { email }, - }); - } - - async findByUsername(username: string): Promise { - return this.prisma.user.findUnique({ - where: { username }, - }); - } -} -``` - -### Custom Query Methods - -Add domain-specific query methods: - -```typescript -export class UserRepository extends BaseRepository { - // Find user with related data - async findWithPermissions(userId: string): Promise { - return this.prisma.user.findUnique({ - where: { id: userId }, - include: { - userRoles: { - include: { role: true }, - }, - userPermissions: { - include: { permission: true }, - }, - }, - }); - } - - // Complex query with filtering - async findActiveUsers(organizationId?: string): Promise { - return this.prisma.user.findMany({ - where: { - isActive: true, - ...(organizationId && { organizationId }), - }, - orderBy: { createdAt: 'desc' }, - }); - } -} -``` - -### Using Repository Interface - -Implement the `IRepository` interface for type safety: - -```typescript -import { IRepository } from '../modules/common/repository'; - -export class UserRepository - extends BaseRepository - implements IRepository { - - // Implementation... -} -``` - -### Error Handling - -BaseRepository handles errors automatically: - -```typescript -async findById(id: string): Promise { - try { - // Database operation - const entity = await this.prisma.user.findUnique({ where: { id } }); - return entity; - } catch (error: any) { - logger.error(`Failed to find ${this.modelName} by ID`, { error, id }); - throw new DatabaseError(`Failed to find ${this.modelName}`, { id, originalError: error }); - } -} -``` - -### Transactions - -Use repository transaction method for multiple operations: - -```typescript -await repository.transaction(async (tx) => { - const user = await tx.user.create({ data: userData }); - await tx.userProfile.create({ data: { userId: user.id, ...profileData } }); - return user; -}); -``` - -### Query Options - -Use Prisma query options in findAll: - -```typescript -// Pagination -const users = await userRepository.findAll({ - skip: (page - 1) * limit, - take: limit, -}); - -// Filtering -const activeUsers = await userRepository.findAll({ - where: { isActive: true }, -}); - -// Sorting -const recentUsers = await userRepository.findAll({ - orderBy: { createdAt: 'desc' }, -}); - -// Including relations -const usersWithRoles = await userRepository.findAll({ - include: { userRoles: true }, -}); -``` - -## Best Practices - -1. **Extend BaseRepository**: Always extend BaseRepository instead of implementing from scratch -2. **Custom Methods**: Add domain-specific query methods in repository subclasses -3. **Type Safety**: Use TypeScript generics for type safety -4. **Error Handling**: Let BaseRepository handle common errors, handle domain-specific errors in custom methods -5. **Logging**: BaseRepository handles logging automatically -6. **Transactions**: Use repository transaction method for multi-step operations -7. **Query Optimization**: Use Prisma query options (select, include) to optimize queries -8. **Single Responsibility**: Each repository handles one entity type -9. **Dependency Injection**: Inject PrismaClient in constructor for testability - -## Common Mistakes - -1. **Not Extending BaseRepository**: Implementing CRUD from scratch instead of extending -2. **Business Logic in Repository**: Putting business logic in repository instead of service layer -3. **Exposing Prisma Client**: Exposing raw Prisma client instead of using repository methods -4. **Missing Error Handling**: Not handling errors in custom query methods -5. **Over-fetching Data**: Using `include` unnecessarily, fetching too much data -6. **No Type Safety**: Not using TypeScript generics properly -7. **Transaction Mistakes**: Not using repository transaction method for related operations - -## Troubleshooting - -### Type Errors with Prisma - -**Problem**: TypeScript errors when using Prisma client methods -**Solution**: Ensure Prisma client is generated: `pnpm prisma generate`. Use proper type assertions if needed. - -### Transaction Rollback Issues - -**Problem**: Transaction not rolling back on error -**Solution**: Ensure all operations in transaction callback use the transaction client (`tx`) parameter, not the main Prisma client. - -### Performance Issues - -**Problem**: Slow queries or N+1 query problems -**Solution**: Use `include` to fetch related data in single query. Use `select` to limit fields. Add database indexes. - -## Quick Reference - -**BaseRepository Methods:** -| Method | Returns | Description | -|--------|---------|-------------| -| `findById(id)` | `T \| null` | Find by primary key | -| `findByUnique(field, value)` | `T \| null` | Find by unique field | -| `findAll(options)` | `T[]` | Find with filters | -| `create(data)` | `T` | Create entity | -| `update(id, data)` | `T` | Update entity | -| `delete(id)` | `void` | Delete entity | -| `count(where)` | `number` | Count entities | -| `exists(id)` | `boolean` | Check existence | -| `transaction(cb)` | `R` | Execute transaction | - -**Query Options:** -```typescript -// Pagination -{ skip: 0, take: 10 } - -// Filtering -{ where: { isActive: true } } - -// Sorting -{ orderBy: { createdAt: 'desc' } } - -// Relations -{ include: { roles: true } } - -// Field selection -{ select: { id: true, email: true } } -``` - -**Repository Template:** -```typescript -export class EntityRepository extends BaseRepository { - constructor(prisma: PrismaClient) { - super(prisma, 'entity'); - } - - // Custom query methods - async findByField(value: string): Promise { - return this.prisma.entity.findUnique({ where: { field: value } }); - } -} -``` - -**Essential Imports:** -```typescript -import { PrismaClient } from '@prisma/client'; -import { BaseRepository, IRepository } from '../modules/common/repository'; -import { DatabaseError } from '../errors/http-error'; -``` - -## Resources - -- [BaseRepository](../../services/iam-service/src/modules/common/repository.ts) - Base repository implementation -- [User Repository](../../services/iam-service/src/repositories/user.repository.ts) - Example repository -- [Database Prisma](../database-prisma/SKILL.md) - Prisma ORM patterns -- [Error Handling](../error-handling-patterns/SKILL.md) - Error handling in repositories diff --git a/.agent/rules/resilience-patterns.md b/.agent/rules/resilience-patterns.md deleted file mode 100644 index c75015a6..00000000 --- a/.agent/rules/resilience-patterns.md +++ /dev/null @@ -1,421 +0,0 @@ ---- -trigger: always_on ---- - -# Resilience Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing circuit breaker patterns for external services -- Adding retry logic for transient failures -- Setting timeout handling for long-running operations -- Implementing graceful degradation strategies -- Handling external service failures -- Improving system fault tolerance - -## Core Concepts - -### Resilience Patterns - -1. **Circuit Breaker**: Prevents cascading failures by stopping calls to failing services -2. **Retry**: Automatically retries failed operations with backoff -3. **Timeout**: Sets maximum time limits for operations -4. **Bulkhead**: Isolates failures to prevent spread -5. **Graceful Degradation**: Provides fallback behavior when services fail - -## Patterns - -### Circuit Breaker Pattern - -Protects against cascading failures: - -```typescript -import CircuitBreaker from 'opossum'; -import { logger } from '@goodgo/logger'; - -export const createCircuitBreaker = ( - action: (...args: TArgs) => Promise, - name: string, - options: Partial = {} -): CircuitBreaker => { - const breaker = new CircuitBreaker(action, { - timeout: 3000, - errorThresholdPercentage: 50, - resetTimeout: 30000, - ...options, - name, - }); - - breaker.on('open', () => { - logger.warn(`Circuit Breaker OPEN: ${name}`); - }); - - breaker.on('halfOpen', () => { - logger.info(`Circuit Breaker HALF-OPEN: ${name}`); - }); - - breaker.on('close', () => { - logger.info(`Circuit Breaker CLOSED: ${name}`); - }); - - return breaker; -}; - -// Usage -const externalApiBreaker = createCircuitBreaker( - async (data) => await externalApi.call(data), - 'external-api' -); - -try { - const result = await externalApiBreaker.fire(requestData); -} catch (error) { - // Handle circuit breaker error or fallback -} -``` - -### Retry Pattern - -Retry transient failures with exponential backoff: - -```typescript -async function retryWithBackoff( - fn: () => Promise, - maxRetries: number = 3, - baseDelay: number = 1000 -): Promise { - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - return await fn(); - } catch (error) { - if (attempt === maxRetries) throw error; - - const delay = baseDelay * Math.pow(2, attempt); - await new Promise(resolve => setTimeout(resolve, delay)); - } - } - throw new Error('Retry exhausted'); -} -``` - -### Timeout Pattern - -Set maximum time limits: - -```typescript -async function withTimeout( - promise: Promise, - timeoutMs: number -): Promise { - const timeout = new Promise((_, reject) => { - setTimeout(() => reject(new Error('Operation timeout')), timeoutMs); - }); - - return Promise.race([promise, timeout]); -} - -// Usage -try { - const result = await withTimeout( - externalService.call(), - 5000 // 5 second timeout - ); -} catch (error) { - if (error.message === 'Operation timeout') { - // Handle timeout - } -} -``` - -### Graceful Degradation - -Provide fallback behavior: - -```typescript -async function getDataWithFallback() { - try { - return await primaryDataSource.get(); - } catch (error) { - logger.warn('Primary source failed, using fallback', { error }); - return await fallbackDataSource.get(); - } -} -``` - -### Bulkhead Pattern - -Isolate failures to prevent spread: - -```typescript -import PQueue from 'p-queue'; - -// Create separate queues for different operations -const externalApiQueue = new PQueue({ - concurrency: 10, // Max 10 concurrent calls - timeout: 30000 // 30 second timeout per operation -}); - -const databaseQueue = new PQueue({ - concurrency: 20 -}); - -// Usage - operations are isolated -async function fetchExternalData(id: string) { - return externalApiQueue.add(async () => { - return await externalApi.getData(id); - }); -} - -async function queryDatabase(query: string) { - return databaseQueue.add(async () => { - return await database.execute(query); - }); -} -``` - -### Combined Resilience Service - -```typescript -// src/core/resilience/resilience.service.ts -import CircuitBreaker from 'opossum'; -import { logger } from '@goodgo/logger'; - -interface ResilienceOptions { - timeout?: number; - maxRetries?: number; - circuitBreaker?: boolean; - fallback?: () => Promise; -} - -export class ResilienceService { - async execute( - operation: () => Promise, - name: string, - options: ResilienceOptions = {} - ): Promise { - const { - timeout = 5000, - maxRetries = 3, - circuitBreaker = true, - fallback - } = options; - - let fn = operation; - - // Wrap with timeout - fn = () => this.withTimeout(operation(), timeout); - - // Wrap with retry - fn = () => this.retryWithBackoff(fn, maxRetries); - - // Wrap with circuit breaker - if (circuitBreaker) { - const breaker = this.createCircuitBreaker(fn, name); - try { - return await breaker.fire(); - } catch (error) { - if (fallback) { - logger.warn(`${name}: Using fallback`, { error: error.message }); - return await fallback(); - } - throw error; - } - } - - try { - return await fn(); - } catch (error) { - if (fallback) { - return await fallback(); - } - throw error; - } - } - - private withTimeout(promise: Promise, ms: number): Promise { - const timeout = new Promise((_, reject) => { - setTimeout(() => reject(new Error('Operation timeout')), ms); - }); - return Promise.race([promise, timeout]); - } - - private async retryWithBackoff( - fn: () => Promise, - maxRetries: number - ): Promise { - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - return await fn(); - } catch (error) { - if (attempt === maxRetries) throw error; - const delay = 1000 * Math.pow(2, attempt); - await new Promise(resolve => setTimeout(resolve, delay)); - } - } - throw new Error('Retry exhausted'); - } - - private createCircuitBreaker( - fn: () => Promise, - name: string - ): CircuitBreaker<[], T> { - return new CircuitBreaker(fn, { - timeout: 3000, - errorThresholdPercentage: 50, - resetTimeout: 30000, - name - }); - } -} - -// Usage -const resilience = new ResilienceService(); - -const result = await resilience.execute( - () => externalApi.fetchUser(userId), - 'fetch-user', - { - timeout: 3000, - maxRetries: 2, - fallback: () => Promise.resolve({ id: userId, name: 'Unknown' }) - } -); -``` - -### Health Check with Resilience - -```typescript -// src/health/health.controller.ts -export class HealthController { - async checkDependencies(): Promise { - const checks = await Promise.allSettled([ - this.checkDatabase(), - this.checkRedis(), - this.checkExternalApi() - ]); - - const results = { - database: checks[0].status === 'fulfilled' ? 'healthy' : 'unhealthy', - redis: checks[1].status === 'fulfilled' ? 'healthy' : 'unhealthy', - externalApi: checks[2].status === 'fulfilled' ? 'healthy' : 'degraded' - }; - - // Service is healthy even if external API is down (graceful degradation) - const isHealthy = results.database === 'healthy' && results.redis === 'healthy'; - - return { - status: isHealthy ? 'healthy' : 'unhealthy', - dependencies: results - }; - } -} -``` - -## Best Practices - -1. **Circuit Breaker**: Use for external service calls -2. **Retry**: Retry only transient failures (network, timeout) -3. **Timeout**: Set appropriate timeouts for all external calls -4. **Fallback**: Always provide fallback behavior -5. **Monitoring**: Monitor circuit breaker states and retry rates -6. **Logging**: Log all resilience actions for debugging - -## Common Mistakes - -1. **Retrying Non-Retryable Errors**: Retrying 4xx errors (client errors) - ```typescript - // ❌ BAD: Retry all errors - catch (error) { - await retry(operation); - } - - // ✅ GOOD: Only retry transient errors - catch (error) { - if (isTransientError(error)) { - await retry(operation); - } else { - throw error; - } - } - ``` - -2. **No Timeout**: Missing timeouts on external calls - ```typescript - // ❌ BAD: No timeout - const data = await externalApi.fetch(); - - // ✅ GOOD: With timeout - const data = await withTimeout(externalApi.fetch(), 5000); - ``` - -3. **No Fallback**: No graceful degradation strategy - ```typescript - // ❌ BAD: Service crashes if dependency fails - const user = await userService.get(id); - - // ✅ GOOD: Fallback to cached/default data - const user = await userService.get(id).catch(() => cachedUser); - ``` - -4. **Too Many Retries**: Excessive retries causing performance issues - ```typescript - // ❌ BAD: Too many retries with short delay - retry(fn, { maxRetries: 10, delay: 100 }); - - // ✅ GOOD: Limited retries with exponential backoff - retry(fn, { maxRetries: 3, baseDelay: 1000, exponential: true }); - ``` - -5. **Circuit Breaker Misconfiguration**: Wrong thresholds - ```typescript - // ❌ BAD: Circuit opens too easily or never - { errorThresholdPercentage: 5 } // Opens after 5% errors - { errorThresholdPercentage: 99 } // Almost never opens - - // ✅ GOOD: Balanced threshold - { errorThresholdPercentage: 50, resetTimeout: 30000 } - ``` - -## Quick Reference - -| Pattern | Use Case | Key Config | -|---------|----------|------------| -| **Circuit Breaker** | External API calls | threshold: 50%, reset: 30s | -| **Retry** | Transient failures | max: 3, exponential backoff | -| **Timeout** | All external calls | 3-5s for API, 30s for batch | -| **Bulkhead** | Resource isolation | 10-20 concurrent ops | -| **Fallback** | Critical operations | Cache, default, or degraded | - -**Opossum Circuit Breaker States:** -``` -CLOSED → (errors exceed threshold) → OPEN -OPEN → (reset timeout expires) → HALF-OPEN -HALF-OPEN → (success) → CLOSED -HALF-OPEN → (failure) → OPEN -``` - -**Retry Delays (Exponential Backoff):** -``` -Attempt 1: 1s -Attempt 2: 2s -Attempt 3: 4s -Attempt 4: 8s -``` - -**Essential Imports:** -```typescript -import CircuitBreaker from 'opossum'; -import PQueue from 'p-queue'; -import { logger } from '@goodgo/logger'; -``` - -## Resources - -- [Opossum Documentation](https://nodeshift.dev/opossum/) - Circuit breaker library -- [Microsoft Resilience Patterns](https://docs.microsoft.com/en-us/azure/architecture/patterns/category/resiliency) -- [API Gateway Advanced](../api-gateway-advanced/SKILL.md) - Gateway circuit breaker -- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Health checks, metrics -- [Event-Driven Architecture](../event-driven-architecture/SKILL.md) - Event retry patterns -- [Error Handling Patterns](../error-handling-patterns/SKILL.md) - Error handling -- [Project Rules](../project-rules/SKILL.md) - GoodGo standards diff --git a/.agent/rules/security.md b/.agent/rules/security.md deleted file mode 100644 index cd05dd2d..00000000 --- a/.agent/rules/security.md +++ /dev/null @@ -1,305 +0,0 @@ ---- -trigger: always_on ---- - - -# Security Patterns for GoodGo Microservices - -## When to Use This Skill - -Use this skill when: -- Implementing authentication and authorization in any service -- Protecting sensitive data (PII, credentials, tokens) -- Validating user inputs and file uploads -- Implementing rate limiting and DDoS protection -- Setting up audit logging and security monitoring -- Encrypting data at rest and in transit -- Managing secrets and credentials -- Implementing security testing -- Handling security incidents -- Designing secure API endpoints - -## Core Security Principles - -1. **Defense in Depth**: Multiple layers of security controls -2. **Least Privilege**: Grant minimum required permissions -3. **Fail Secure**: Default to deny access -4. **Separation of Duties**: Critical operations require multiple approvals -5. **Audit Everything**: Log all security-relevant events -6. **Encrypt Sensitive Data**: PII, tokens, credentials must be encrypted -7. **Validate All Inputs**: Never trust user input -8. **Principle of Least Exposure**: Minimize attack surface -9. **Secure by Default**: Security built-in, not bolted on -10. **Assume Breach**: Design for detection and response - -## Authentication & Authorization - -### JWT Token Validation - -Extract tokens from Authorization header or cookies, verify with `jwtService.verifyAccessToken()`, and attach user payload to request. Return 401 for missing/invalid tokens. - -```typescript -// Key pattern - see references/REFERENCE.md for full implementation -const authHeader = req.headers.authorization; -const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : req.cookies?.access_token; -const payload = await jwtService.verifyAccessToken(token); -req.user = { id: payload.sub, roles: payload.roles || [], permissions: payload.permissions || [] }; -``` - -### Role-Based Authorization (RBAC) - -Use `requireRole()` middleware for role checks and `requirePermission()` for fine-grained access control with `resource:action` format. - -```typescript -// Usage pattern -router.post('/api/v1/users', authenticate(), requirePermission('users', 'create'), userController.create); -router.delete('/api/v1/admin', authenticate(), requireRole('admin', 'superadmin'), adminController.delete); -``` - -### Resource Ownership - -Validate users can only access their own resources using `requireOwnership()` middleware. - -## Data Protection - -### Encryption - -- Use AES-256-GCM for encrypting PII at rest -- Store encrypted data as `iv:tag:ciphertext` format -- Require 32+ character ENCRYPTION_KEY - -### Password Hashing - -- Always use bcrypt with cost factor 12 in production -- Never log passwords - sanitize before logging - -### Token Storage - -- Hash tokens with SHA-256 before database storage -- Use `crypto.randomBytes(32)` for secure token generation - -## Input Validation - -### Zod Schema Validation - -Always validate inputs with Zod schemas before processing: - -```typescript -const CreateUserDto = z.object({ - email: z.string().email(), - password: z.string().min(8).regex(/[A-Z]/).regex(/[a-z]/).regex(/[0-9]/).regex(/[^A-Za-z0-9]/), - phone: z.string().regex(/^\+[1-9]\d{1,14}$/).optional(), - name: z.string().min(1).max(255) -}); - -// In controller -const dto = CreateUserDto.parse(req.body); -``` - -### File Upload Validation - -- Check file size (max 10MB default) -- Validate MIME type against whitelist -- Verify content with `file-type` library to prevent MIME spoofing - -### SQL Injection Prevention - -Always use Prisma parameterized queries - never string concatenation for queries. - -## Rate Limiting - -Configure Redis-backed rate limiting for all endpoints: - -| Limiter Type | Window | Max Requests | Use Case | -|-------------|--------|--------------|----------| -| Standard | 15 min | 100 | General API endpoints | -| Strict | 1 hour | 10 | Sensitive operations | -| Login | 15 min | 5 | Authentication endpoints | - -```typescript -router.post('/api/v1/auth/login', loginLimiter, authController.login); -``` - -## Security Headers - -Use Helmet middleware with CSP, HSTS, and additional headers: -- `X-Content-Type-Options: nosniff` -- `X-Frame-Options: DENY` -- `X-XSS-Protection: 1; mode=block` -- `Referrer-Policy: strict-origin-when-cross-origin` - -## CORS Configuration - -- Whitelist allowed origins from environment variables -- Enable credentials for authenticated requests -- Set `maxAge: 86400` (24 hours) for preflight caching - -## Secrets Management - -- Never hardcode secrets - use environment variables -- Validate secrets with Zod schema at startup -- Use secret managers in production (AWS Secrets Manager, Vault, etc.) -- Rotate secrets quarterly - -```typescript -const secretsSchema = z.object({ - JWT_SECRET: z.string().min(32), - DATABASE_URL: z.string().url(), - ENCRYPTION_KEY: z.string().min(32).optional() -}); -``` - -## Audit Logging - -Log all security-relevant events with sanitized details: - -```typescript -await auditService.logSecurityEvent('LOGIN_SUCCESS', user.id, { email: user.email }, req); -await auditService.logSecurityEvent('PERMISSION_DENIED', user.id, { resource, action }, req); -``` - -Sanitize sensitive fields: password, token, secret, ssn, creditCard. - -## Error Handling - -- Log full errors internally -- Never expose user existence (use generic "Invalid credentials") -- Show stack traces only in development -- Return sanitized error codes in production - -## Security Testing - -Write tests for: -- SQL injection prevention -- XSS attack prevention -- Authentication enforcement -- Authorization enforcement -- Rate limiting effectiveness - -## Best Practices - -- All endpoints require authentication (except public) -- Authorization checks at every protected route -- Input validation with Zod on all user input -- Rate limiting on all endpoints -- Error messages sanitized in production -- PII encrypted at rest with AES-256-GCM -- Passwords hashed with bcrypt (cost 12+) -- Tokens hashed before database storage -- HTTPS enforced (TLS 1.2+) -- Security headers via Helmet -- Audit logging for security events -- Dependencies scanned for vulnerabilities -- File uploads validated (size, type, content) - -## Common Mistakes - -### 1. Weak Password Hashing - -```typescript -// BAD: Low cost factor -const hash = await bcrypt.hash(password, 8); - -// GOOD: Use cost 12 -const hash = await bcrypt.hash(password, 12); -``` - -### 2. Hardcoded Secrets - -```typescript -// BAD -const JWT_SECRET = "my-secret-key"; - -// GOOD -const JWT_SECRET = process.env.JWT_SECRET; -if (!JWT_SECRET) throw new Error('JWT_SECRET required'); -``` - -### 3. Missing Input Validation - -```typescript -// BAD -const user = await prisma.user.findUnique({ where: { id: req.params.id } }); - -// GOOD -const { id } = z.object({ id: z.string().cuid() }).parse(req.params); -const user = await prisma.user.findUnique({ where: { id } }); -``` - -### 4. Logging Sensitive Data - -```typescript -// BAD -logger.info('User login', { email, password }); - -// GOOD -logger.info('User login', { email, password: '[REDACTED]' }); -``` - -### 5. Exposing User Existence - -```typescript -// BAD -if (!user) throw new Error('User not found'); - -// GOOD -if (!user || !await bcrypt.compare(password, user.passwordHash)) { - throw new Error('Invalid credentials'); -} -``` - -## Quick Reference - -| Security Area | Implementation | -|--------------|----------------| -| **Password hashing** | `bcrypt.hash(password, 12)` | -| **JWT Access Token** | 15 minutes expiry | -| **JWT Refresh Token** | 7 days expiry | -| **Rate limiting** | Standard: 100/15min, Strict: 10/hour, Login: 5/15min | -| **Encryption** | AES-256-GCM for PII | -| **Input validation** | Zod schemas, always parse before use | -| **SQL injection** | Use Prisma (parameterized by default) | -| **Security headers** | helmet middleware | -| **CORS** | Whitelist origins, credentials: true | - -**Essential Imports:** - -```typescript -import bcrypt from 'bcrypt'; -import helmet from 'helmet'; -import rateLimit from 'express-rate-limit'; -import { z } from 'zod'; -import { jwtService } from '@goodgo/auth-sdk'; -``` - -## Security Checklist - -Before deploying any service: - -- [ ] All endpoints require authentication (except public) -- [ ] Authorization checks implemented (RBAC/ABAC) -- [ ] Input validation with Zod schemas -- [ ] Rate limiting configured -- [ ] Error messages sanitized (no info disclosure) -- [ ] PII encrypted at rest -- [ ] Passwords hashed with bcrypt (cost 12+) -- [ ] Tokens hashed before storing -- [ ] Secrets in environment variables (never hardcoded) -- [ ] HTTPS enforced (TLS 1.2+) -- [ ] CORS configured correctly -- [ ] Security headers set (helmet) -- [ ] Audit logging enabled -- [ ] SQL injection prevented (use Prisma) -- [ ] XSS prevention (input sanitization) -- [ ] File upload validation -- [ ] Security tests passing -- [ ] Dependencies scanned for vulnerabilities -- [ ] Secrets rotation plan in place - -## Resources - -- [Detailed Code Examples](./references/REFERENCE.md) - Full implementation examples -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) -- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/) -- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) -- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html) diff --git a/.agent/rules/service-discovery-registry.md b/.agent/rules/service-discovery-registry.md deleted file mode 100644 index b6adddad..00000000 --- a/.agent/rules/service-discovery-registry.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -trigger: always_on ---- - -# Service Discovery & Registry Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing authentication and authorization in any service -- Protecting sensitive data (PII, credentials, tokens) -- Validating user inputs and file uploads -- Implementing rate limiting and DDoS protection -- Setting up audit logging and security monitoring -- Encrypting data at rest and in transit -- Managing secrets and credentials -- Implementing security testing -- Handling security incidents -- Designing secure API endpoints - diff --git a/.agent/rules/service-layer-patterns.md b/.agent/rules/service-layer-patterns.md deleted file mode 100644 index a821de9d..00000000 --- a/.agent/rules/service-layer-patterns.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -trigger: always_on ---- - -# Service Layer Patterns - -## When to Use This Skill - -Use this skill when: -- Implementing business logic in services -- Organizing service layer code -- Using dependency injection patterns -- Composing multiple services together -- Separating concerns between controllers, services, and repositories -- Handling business rules and validations -- Implementing service composition patterns - -## Core Concepts - -### Service Layer Responsibilities - -The service layer: -- Contains business logic -- Orchestrates repository calls -- Validates business rules -- Handles cross-cutting concerns (caching, logging) -- Coordinates multiple repositories -- Independent of HTTP transport layer - -### Dependency Injection - -Services use constructor injection for dependencies: - -```typescript -export class UserService { - constructor( - private userRepository: UserRepository, - private cacheService: CacheService - ) {} -} -``` - -## Patterns - -### Basic Service Pattern - -```typescript -import { logger } from '@goodgo/logger'; -import { userRepository } from '../repositories/user.repository'; -import { NotFoundError } from '../errors/http-error'; - -export class UserService { - async getUserById(id: string) { - logger.info('Fetching user by ID', { id }); - - const user = await userRepository.findById(id); - if (!user) { - throw new NotFoundError('User', { id }); - } - - return user; - } -} -``` - -### Service with Caching - -```typescript -export class UserService { - constructor( - private repository: UserRepository, - private cacheService: CacheService - ) {} - - async getUserById(id: string) { - const cacheKey = cacheService.keys.user(id); - - // Try cache first - const cached = await this.cacheService.get(cacheKey); - if (cached) return cached; - - // Cache miss - fetch from repository - const user = await this.repository.findById(id); - if (!user) { - throw new NotFoundError('User'); - } - - // Cache for 5 minutes - await this.cacheService.set(cacheKey, user, 300); - return user; - } -} -``` - -### Service Composition - -Services can depend on other services: - -```typescript -export class AccessRequestService { - constructor( - private accessRequestRepository: AccessRequestRepository, - private userService: UserService, - private rbacService: RBACService - ) {} - - async createRequest(userId: string, data: CreateRequestDto) { - // Use other services - const user = await this.userService.getUserById(userId); - const hasPermission = await this.rbacService.checkPermission(userId, 'CREATE_REQUEST'); - - if (!hasPermission) { - throw new ForbiddenError('Insufficient permissions'); - } - - return await this.accessRequestRepository.create({ ...data, userId }); - } -} -``` - -### Business Logic Validation - -Services validate business rules: - -```typescript -export class UserService { - async createUser(data: CreateUserInput) { - // Business rule: Check if email exists - const existing = await this.repository.findByEmail(data.email); - if (existing) { - throw new ConflictError('User with this email already exists'); - } - - // Business rule: Validate organization - if (data.organizationId) { - const org = await this.orgRepository.findById(data.organizationId); - if (!org) { - throw new NotFoundError('Organization'); - } - } - - return await this.repository.create(data); - } -} -``` - -### Service Module Pattern - -Organize services into modules: - -```typescript -export class FeatureModule { - private controller: FeatureController; - private service: FeatureService; - private router: Router; - - constructor() { - const repository = new FeatureRepository(prisma); - this.service = new FeatureService(repository); - this.controller = new FeatureController(this.service); - this.router = this.createRouter(); - } - - getRouter(): Router { - return this.router; - } - - private createRouter(): Router { - const router = Router(); - router.get('/', asyncHandler(this.controller.findAll.bind(this.controller))); - router.post('/', asyncHandler(this.controller.create.bind(this.controller))); - return router; - } -} -``` - -## Best Practices - -1. **Single Responsibility**: Each service handles one domain area -2. **Dependency Injection**: Use constructor injection for testability -3. **Business Logic Only**: Keep HTTP concerns in controllers -4. **Use Repositories**: Don't access database directly -5. **Error Handling**: Throw appropriate domain errors -6. **Logging**: Log important operations and errors -7. **Caching**: Implement caching in services, not repositories -8. **Composition**: Compose services for complex operations - -## Common Mistakes - -1. **HTTP in Services**: Using `req`/`res` in services -2. **Direct Database Access**: Accessing Prisma directly instead of repositories -3. **Too Many Responsibilities**: Service doing too many things -4. **No Error Handling**: Not throwing appropriate errors -5. **Business Logic in Controllers**: Moving business logic to controllers - -## Quick Reference - -**Service Responsibilities:** -| Layer | Responsibility | Example | -|-------|----------------|---------| -| **Controller** | HTTP handling, validation | Parse request, send response | -| **Service** | Business logic | Validate rules, orchestrate | -| **Repository** | Data access | CRUD operations | - -**Service Template:** -```typescript -export class EntityService { - constructor( - private repository: EntityRepository, - private cache?: CacheService - ) {} - - async findById(id: string): Promise { - const entity = await this.repository.findById(id); - if (!entity) throw new NotFoundError('Entity'); - return entity; - } - - async create(data: CreateDto): Promise { - // Business validation - await this.validateBusinessRules(data); - return this.repository.create(data); - } -} -``` - -**Dependency Injection Pattern:** -```typescript -// Module setup -const repository = new EntityRepository(prisma); -const service = new EntityService(repository, cacheService); -const controller = new EntityController(service); -``` - -**Common Patterns:** -| Pattern | When to Use | -|---------|-------------| -| **Caching** | Frequently accessed data | -| **Composition** | Complex operations across domains | -| **Validation** | Business rule enforcement | -| **Logging** | Audit and debugging | - -## Resources - -- [Feature Service](../../services/iam-service/src/modules/feature/feature.service.ts) - Example service implementation -- [Repository Pattern](../repository-pattern/SKILL.md) - Repository patterns -- [Caching Patterns](../caching-patterns/SKILL.md) - Caching in services -- [Error Handling](../error-handling-patterns/SKILL.md) - Error handling patterns diff --git a/.agent/rules/testing-patterns.md b/.agent/rules/testing-patterns.md deleted file mode 100644 index a2b14dc3..00000000 --- a/.agent/rules/testing-patterns.md +++ /dev/null @@ -1,179 +0,0 @@ ---- -trigger: always_on ---- - -# Testing Patterns for GoodGo Microservices - -## When to Use This Skill - -Use this skill when: -- Writing unit tests for services, controllers, or repositories -- Creating integration tests for middleware chains -- Building E2E tests for API endpoints -- Setting up Jest configuration for a new service -- Mocking external dependencies (Prisma, Redis, Auth SDK) -- Debugging test failures -- Improving test coverage - -## Core Concepts - -### Test Types - -| Type | Location | Speed | Dependencies | -|------|----------|-------|--------------| -| **Unit** | `*.test.ts` (next to source) | <1s | All mocked | -| **Integration** | `__tests__/` | 1-5s | Partial mocking | -| **E2E** | `__tests__/*.e2e.ts` | 5-10s | Test DB, mocked external | - -## Key Patterns - -### Jest Configuration - -```typescript -// jest.config.ts -export default { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/__tests__/**/*.test.ts', '**/__tests__/**/*.e2e.ts'], - coverageThreshold: { global: { branches: 70, functions: 70, lines: 70 } }, - setupFilesAfterEnv: ['/src/__tests__/setupTests.ts'], -}; -``` - -### Unit Test Pattern - -```typescript -describe('FeatureService', () => { - let service: FeatureService; - let mockRepository: any; - - beforeEach(() => { - mockRepository = { findById: jest.fn(), create: jest.fn() }; - service = new FeatureService(mockRepository); - }); - - it('should return feature when found', async () => { - mockRepository.findById.mockResolvedValue({ id: '1', name: 'Test' }); - const result = await service.findById('1'); - expect(result).toEqual({ id: '1', name: 'Test' }); - }); -}); -``` - -### E2E Test Pattern - -```typescript -describe('POST /api/features', () => { - it('should create a new feature', async () => { - const response = await request - .post('/api/features') - .set('Authorization', 'Bearer valid-token') - .send({ name: 'New Feature' }) - .expect(201); - - expect(response.body).toMatchObject({ success: true, data: { name: 'New Feature' } }); - }); -}); -``` - -### Mock Prisma - -```typescript -import { mockDeep } from 'jest-mock-extended'; -import { PrismaClient } from '@prisma/client'; - -export const prismaMock = mockDeep(); -jest.mock('../prisma', () => ({ default: prismaMock })); - -// Usage -prismaMock.user.create.mockResolvedValue({ id: '1', email: 'test@example.com' }); -``` - -### Test Factory - -```typescript -export class TestFactory { - static createUser(overrides = {}) { - return { id: 'test-user-1', email: 'test@example.com', ...overrides }; - } - static createAuthToken(userId: string) { - return jwt.sign({ userId }, 'test-secret'); - } -} -``` - -## Best Practices - -- Each test is independent and isolated -- Tests follow AAA pattern (Arrange-Act-Assert) -- Mock external dependencies -- Test edge cases and error scenarios -- Keep tests simple and focused -- Use descriptive test names -- Maintain >70% code coverage - -## Common Mistakes - -1. **Testing Implementation Details**: Tests break on refactoring - ```typescript - // BAD: expect(service['privateMethod']()).toBe(true); - // GOOD: expect(await service.processOrder(order)).toEqual({ success: true }); - ``` - -2. **Shared Mutable State**: Tests affect each other - ```typescript - // BAD: let counter = 0; (shared across tests) - // GOOD: beforeEach(() => { counter = 0; }); - ``` - -3. **Not Mocking External Services**: Tests are slow and flaky - ```typescript - // BAD: await fetch('https://api.example.com/data'); - // GOOD: jest.spyOn(httpClient, 'get').mockResolvedValue({ data: mockData }); - ``` - -4. **Missing Edge Cases**: Only testing happy path - ```typescript - // GOOD: Include error cases - test('creates user', async () => { ... }); - test('throws on duplicate email', async () => { ... }); - test('validates email format', async () => { ... }); - ``` - -## Quick Reference - -**Coverage Targets:** -- Global: 70%+ (branches, functions, lines) -- Critical paths: 90%+ -- Repositories/Services: 80%+ - -**Essential Commands:** -```bash -pnpm test # Run all tests -pnpm test:watch # Watch mode -pnpm test:coverage # With coverage -pnpm test -- --runInBand # Sequential (for debugging) -pnpm test -- UserService # Run specific test file -``` - -**Mock Imports:** -```typescript -import { mockDeep } from 'jest-mock-extended'; -import { prismaMock } from '../__mocks__/prisma'; -import supertest from 'supertest'; -``` - -**Debug Tips:** -1. Use `test.only()` to run single test -2. Use `--detectOpenHandles` for async issues -3. Use `--runInBand` for sequential execution -4. Add `console.log()` statements temporarily - -## Resources - -- [Jest Documentation](https://jestjs.io/docs/getting-started) - Testing framework -- [Supertest](https://github.com/ladjs/supertest) - HTTP assertions -- [jest-mock-extended](https://github.com/marchaos/jest-mock-extended) - TypeScript mocks -- [Detailed Code Examples](./references/REFERENCE.md) -- [Database Prisma](../database-prisma/SKILL.md) - Database mocking patterns -- [Error Handling](../error-handling-patterns/SKILL.md) - Error testing