feat: Bổ sung nhiều tài liệu quy tắc mới về thiết kế, kiến trúc và phát triển, đồng thời loại bỏ các quy tắc cũ về kiến trúc và đặt tên.

This commit is contained in:
Ho Ngoc Hai
2026-01-04 09:52:46 +07:00
parent 0d48dedc08
commit 6abea5b18b
28 changed files with 7509 additions and 47 deletions

171
.agent/rules/api-design.md Normal file
View File

@@ -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<T> {
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

View File

@@ -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

View File

@@ -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<void> {
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: </api/v2/users>; 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];
```

View File

@@ -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

View File

@@ -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>('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<string[]> {
const cacheKey = cacheService.keys.userPermissions(userId);
// Try cache first
const cached = await cacheService.get<string[]>(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<Data | null> {
try {
// Try cache first
const cached = await cacheService.get<Data>(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<T>(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

View File

@@ -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<boolean> {
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

View File

@@ -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<string> {
// 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<UserPayload> {
// 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 (
<div className="user-card">
{/* EN: Display user avatar / VI: Hiển thị avatar người dùng */}
<img src={user.avatar} alt={user.name} />
{/* EN: User information section / VI: Phần thông tin người dùng */}
<div className="user-info">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</div>
);
}
```
### 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

View File

@@ -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)

View File

@@ -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<any>;
compensate: (context: any) => Promise<void>;
retry?: number;
}
interface SagaContext {
sagaId: string;
steps: SagaStep[];
currentStep: number;
data: Record<string, any>;
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

View File

@@ -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 <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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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<br/>Complete?}
ImpactCheck -->|No| Phase1
ImpactCheck -->|Yes| Phase2[Phase 2: Foundation Setup]
Phase2 --> FoundationCheck{Service Starts<br/>& Health Check Passes?}
FoundationCheck -->|No| Phase2
FoundationCheck -->|Yes| Phase3[Phase 3: Core Implementation]
Phase3 --> ImplementationCheck{Business Logic<br/>Implemented?}
ImplementationCheck -->|No| Phase3
ImplementationCheck -->|Yes| Phase4[Phase 4: Integration]
Phase4 --> IntegrationCheck{Routes & Middleware<br/>Working?}
IntegrationCheck -->|No| Phase4
IntegrationCheck -->|Yes| Phase5[Phase 5: Testing]
Phase5 --> TestCheck{Tests Pass<br/>& Coverage Met?}
TestCheck -->|No| Phase5
TestCheck -->|Yes| Phase6[Phase 6: Documentation]
Phase6 --> DocCheck{Docs<br/>Complete?}
DocCheck -->|No| Phase6
DocCheck -->|Yes| Phase7[Phase 7: Cleanup & Verification]
Phase7 --> VerificationCheck{All Checks<br/>Pass?}
VerificationCheck -->|No| Phase7
VerificationCheck -->|Yes| Phase8[Phase 8: Deployment]
Phase8 --> DeployCheck{Staging<br/>Deployed?}
DeployCheck -->|No| Phase8
DeployCheck -->|Yes| Production{Deploy to<br/>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<br/>Zod Validation Schemas]
DTOs --> Repo[2. Create Repository<br/>Prisma Data Access]
Repo --> Service[3. Create Service<br/>Business Logic]
Service --> Controller[4. Create Controller<br/>HTTP Handlers]
Controller --> Module[5. Create Module<br/>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

View File

@@ -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<void>
```
### 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<void>` | 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

View File

@@ -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`

View File

@@ -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

View File

@@ -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<any> {
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<any> {
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<User[]> {
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<User[]> {
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<T>(items: T[], batchSize: number = 100): Promise<T[]> {
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<void> {
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<void> {
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<Map<string, any>> {
const results = new Map<string, any>();
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

View File

@@ -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<typeof CreateFeatureDto>;
// 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<Feature> {
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)

View File

@@ -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<User, CreateUserInput, UpdateUserInput> {
constructor(prisma: PrismaClient) {
super(prisma, 'user');
}
// Add custom methods
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { email },
});
}
async findByUsername(username: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { username },
});
}
}
```
### Custom Query Methods
Add domain-specific query methods:
```typescript
export class UserRepository extends BaseRepository<User, CreateUserInput, UpdateUserInput> {
// Find user with related data
async findWithPermissions(userId: string): Promise<User | null> {
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<User[]> {
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<User, CreateUserInput, UpdateUserInput>
implements IRepository<User, CreateUserInput, UpdateUserInput> {
// Implementation...
}
```
### Error Handling
BaseRepository handles errors automatically:
```typescript
async findById(id: string): Promise<T | null> {
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<Entity, CreateDto, UpdateDto> {
constructor(prisma: PrismaClient) {
super(prisma, 'entity');
}
// Custom query methods
async findByField(value: string): Promise<Entity | null> {
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

View File

@@ -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 = <TArgs extends any[], TResult>(
action: (...args: TArgs) => Promise<TResult>,
name: string,
options: Partial<CircuitBreaker.Options> = {}
): CircuitBreaker<TArgs, TResult> => {
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<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
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<T>(
promise: Promise<T>,
timeoutMs: number
): Promise<T> {
const timeout = new Promise<never>((_, 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<any>;
}
export class ResilienceService {
async execute<T>(
operation: () => Promise<T>,
name: string,
options: ResilienceOptions = {}
): Promise<T> {
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<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Operation timeout')), ms);
});
return Promise.race([promise, timeout]);
}
private async retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries: number
): Promise<T> {
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<T>(
fn: () => Promise<T>,
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<HealthStatus> {
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

305
.agent/rules/security.md Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -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<User>(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<Entity> {
const entity = await this.repository.findById(id);
if (!entity) throw new NotFoundError('Entity');
return entity;
}
async create(data: CreateDto): Promise<Entity> {
// 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

View File

@@ -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: ['<rootDir>/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<PrismaClient>();
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