From 6abea5b18b1efc563bc0b6d521684d9d17705be4 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sun, 4 Jan 2026 09:52:46 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20B=E1=BB=95=20sung=20nhi=E1=BB=81u=20t?= =?UTF-8?q?=C3=A0i=20li=E1=BB=87u=20quy=20t=E1=BA=AFc=20m=E1=BB=9Bi=20v?= =?UTF-8?q?=E1=BB=81=20thi=E1=BA=BFt=20k=E1=BA=BF,=20ki=E1=BA=BFn=20tr?= =?UTF-8?q?=C3=BAc=20v=C3=A0=20ph=C3=A1t=20tri=E1=BB=83n,=20=C4=91?= =?UTF-8?q?=E1=BB=93ng=20th=E1=BB=9Di=20lo=E1=BA=A1i=20b=E1=BB=8F=20c?= =?UTF-8?q?=C3=A1c=20quy=20t=E1=BA=AFc=20c=C5=A9=20v=E1=BB=81=20ki?= =?UTF-8?q?=E1=BA=BFn=20tr=C3=BAc=20v=C3=A0=20=C4=91=E1=BA=B7t=20t=C3=AAn.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agent/rules/api-design.md | 171 +++++++ .agent/rules/api-gateway-advanced.md | 182 +++++++ .agent/rules/api-versioning-strategy.md | 432 ++++++++++++++++ .agent/rules/architecture.md | 28 - .agent/rules/caching-patterns.md | 325 ++++++++++++ .agent/rules/cicd-advanced-patterns.md | 484 ++++++++++++++++++ .agent/rules/comment-code.md | 430 ++++++++++++++++ .agent/rules/configuration-management.md | 35 ++ .agent/rules/data-consistency-patterns.md | 269 ++++++++++ .agent/rules/database-prisma.md | 184 +++++++ .agent/rules/deployment-kubernetes.md | 183 +++++++ .agent/rules/documentation.md | 444 ++++++++++++++++ .agent/rules/error-handling-patterns.md | 363 +++++++++++++ .agent/rules/event-driven-architecture.md | 270 ++++++++++ .agent/rules/infrastructure-as-code.md | 415 +++++++++++++++ .agent/rules/inter-service-communication.md | 225 ++++++++ .../microservices-development-process.md | 356 +++++++++++++ .agent/rules/middleware-patterns.md | 314 ++++++++++++ .agent/rules/naming-conventions.md | 19 - .agent/rules/observability-monitoring.md | 195 +++++++ .agent/rules/performance-optimization.md | 457 +++++++++++++++++ .agent/rules/project-rules.md | 329 ++++++++++++ .agent/rules/repository-pattern.md | 273 ++++++++++ .agent/rules/resilience-patterns.md | 421 +++++++++++++++ .agent/rules/security.md | 305 +++++++++++ .agent/rules/service-discovery-registry.md | 20 + .agent/rules/service-layer-patterns.md | 248 +++++++++ .agent/rules/testing-patterns.md | 179 +++++++ 28 files changed, 7509 insertions(+), 47 deletions(-) create mode 100644 .agent/rules/api-design.md create mode 100644 .agent/rules/api-gateway-advanced.md create mode 100644 .agent/rules/api-versioning-strategy.md delete mode 100644 .agent/rules/architecture.md create mode 100644 .agent/rules/caching-patterns.md create mode 100644 .agent/rules/cicd-advanced-patterns.md create mode 100644 .agent/rules/comment-code.md create mode 100644 .agent/rules/configuration-management.md create mode 100644 .agent/rules/data-consistency-patterns.md create mode 100644 .agent/rules/database-prisma.md create mode 100644 .agent/rules/deployment-kubernetes.md create mode 100644 .agent/rules/documentation.md create mode 100644 .agent/rules/error-handling-patterns.md create mode 100644 .agent/rules/event-driven-architecture.md create mode 100644 .agent/rules/infrastructure-as-code.md create mode 100644 .agent/rules/inter-service-communication.md create mode 100644 .agent/rules/microservices-development-process.md create mode 100644 .agent/rules/middleware-patterns.md delete mode 100644 .agent/rules/naming-conventions.md create mode 100644 .agent/rules/observability-monitoring.md create mode 100644 .agent/rules/performance-optimization.md create mode 100644 .agent/rules/project-rules.md create mode 100644 .agent/rules/repository-pattern.md create mode 100644 .agent/rules/resilience-patterns.md create mode 100644 .agent/rules/security.md create mode 100644 .agent/rules/service-discovery-registry.md create mode 100644 .agent/rules/service-layer-patterns.md create mode 100644 .agent/rules/testing-patterns.md diff --git a/.agent/rules/api-design.md b/.agent/rules/api-design.md new file mode 100644 index 00000000..cd8ada4a --- /dev/null +++ b/.agent/rules/api-design.md @@ -0,0 +1,171 @@ +--- +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 new file mode 100644 index 00000000..a60afcb9 --- /dev/null +++ b/.agent/rules/api-gateway-advanced.md @@ -0,0 +1,182 @@ +--- +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 new file mode 100644 index 00000000..eeb29f63 --- /dev/null +++ b/.agent/rules/api-versioning-strategy.md @@ -0,0 +1,432 @@ +--- +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/architecture.md b/.agent/rules/architecture.md deleted file mode 100644 index 0e0b0393..00000000 --- a/.agent/rules/architecture.md +++ /dev/null @@ -1,28 +0,0 @@ -# Architecture Rules - -## 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/`: Always use this as the starting point for creating new services. - -## Technology Stack -### Frontend -- Next.js 14+ (App Router), TypeScript, Tailwind CSS, Zustand -- Flutter 3.x with Provider pattern -- Prefer `@goodgo/types` and `@goodgo/http-client` - -### Backend -- Node.js 20+, TypeScript 5+, Express -- Prisma ORM + Neon PostgreSQL -- Zod for validation -- Standard packages: `@goodgo/logger`, `@goodgo/tracing`, `@goodgo/auth-sdk` - -### Infrastructure -- Traefik: Path-based routing -- Redis: Caching -- Monitoring: Prometheus, Grafana, Loki diff --git a/.agent/rules/caching-patterns.md b/.agent/rules/caching-patterns.md new file mode 100644 index 00000000..04db6676 --- /dev/null +++ b/.agent/rules/caching-patterns.md @@ -0,0 +1,325 @@ +--- +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 new file mode 100644 index 00000000..df52f254 --- /dev/null +++ b/.agent/rules/cicd-advanced-patterns.md @@ -0,0 +1,484 @@ +--- +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 new file mode 100644 index 00000000..5242d043 --- /dev/null +++ b/.agent/rules/comment-code.md @@ -0,0 +1,430 @@ +--- +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 new file mode 100644 index 00000000..a06eaaff --- /dev/null +++ b/.agent/rules/configuration-management.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 00000000..46a651bd --- /dev/null +++ b/.agent/rules/data-consistency-patterns.md @@ -0,0 +1,269 @@ +--- +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 new file mode 100644 index 00000000..afb32793 --- /dev/null +++ b/.agent/rules/database-prisma.md @@ -0,0 +1,184 @@ +--- +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 new file mode 100644 index 00000000..80aa611d --- /dev/null +++ b/.agent/rules/deployment-kubernetes.md @@ -0,0 +1,183 @@ +--- +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 new file mode 100644 index 00000000..2107e287 --- /dev/null +++ b/.agent/rules/documentation.md @@ -0,0 +1,444 @@ +--- +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 new file mode 100644 index 00000000..1825055b --- /dev/null +++ b/.agent/rules/error-handling-patterns.md @@ -0,0 +1,363 @@ +--- +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 new file mode 100644 index 00000000..dbc9fa4f --- /dev/null +++ b/.agent/rules/event-driven-architecture.md @@ -0,0 +1,270 @@ +--- +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 new file mode 100644 index 00000000..d2c6110b --- /dev/null +++ b/.agent/rules/infrastructure-as-code.md @@ -0,0 +1,415 @@ +--- +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 new file mode 100644 index 00000000..7d32f6a7 --- /dev/null +++ b/.agent/rules/inter-service-communication.md @@ -0,0 +1,225 @@ +--- +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 new file mode 100644 index 00000000..5a665910 --- /dev/null +++ b/.agent/rules/microservices-development-process.md @@ -0,0 +1,356 @@ +--- +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 new file mode 100644 index 00000000..f3fa1f38 --- /dev/null +++ b/.agent/rules/middleware-patterns.md @@ -0,0 +1,314 @@ +--- +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/naming-conventions.md b/.agent/rules/naming-conventions.md deleted file mode 100644 index 0678afb1..00000000 --- a/.agent/rules/naming-conventions.md +++ /dev/null @@ -1,19 +0,0 @@ -# Naming Conventions - -## General Rules -- **Services/Packages**: `kebab-case` (e.g., `auth-service`, `http-client`) -- **Files**: `kebab-case.type.ts` (e.g., `user.controller.ts`, `auth.middleware.ts`) -- **Components**: - - React/Next.js: `PascalCase.tsx` - - Flutter: `snake_case.dart` -- **Classes**: `PascalCase` -- **Functions**: `camelCase` -- **Constants**: `UPPER_SNAKE_CASE` -- **Package Names**: `@goodgo/package-name` - -## Specific Examples -- Controller: `user.controller.ts` -- Service: `user.service.ts` -- Repository: `user.repository.ts` -- Middleware: `auth.middleware.ts` -- DTO: `create-user.dto.ts` diff --git a/.agent/rules/observability-monitoring.md b/.agent/rules/observability-monitoring.md new file mode 100644 index 00000000..5666d221 --- /dev/null +++ b/.agent/rules/observability-monitoring.md @@ -0,0 +1,195 @@ +--- +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 new file mode 100644 index 00000000..4aaf612e --- /dev/null +++ b/.agent/rules/performance-optimization.md @@ -0,0 +1,457 @@ +--- +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 new file mode 100644 index 00000000..24b268ae --- /dev/null +++ b/.agent/rules/project-rules.md @@ -0,0 +1,329 @@ +--- +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 new file mode 100644 index 00000000..b3199739 --- /dev/null +++ b/.agent/rules/repository-pattern.md @@ -0,0 +1,273 @@ +--- +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 new file mode 100644 index 00000000..c75015a6 --- /dev/null +++ b/.agent/rules/resilience-patterns.md @@ -0,0 +1,421 @@ +--- +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 new file mode 100644 index 00000000..cd05dd2d --- /dev/null +++ b/.agent/rules/security.md @@ -0,0 +1,305 @@ +--- +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 new file mode 100644 index 00000000..b6adddad --- /dev/null +++ b/.agent/rules/service-discovery-registry.md @@ -0,0 +1,20 @@ +--- +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 new file mode 100644 index 00000000..a821de9d --- /dev/null +++ b/.agent/rules/service-layer-patterns.md @@ -0,0 +1,248 @@ +--- +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 new file mode 100644 index 00000000..a2b14dc3 --- /dev/null +++ b/.agent/rules/testing-patterns.md @@ -0,0 +1,179 @@ +--- +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