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:
171
.agent/rules/api-design.md
Normal file
171
.agent/rules/api-design.md
Normal 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
|
||||
182
.agent/rules/api-gateway-advanced.md
Normal file
182
.agent/rules/api-gateway-advanced.md
Normal 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
|
||||
432
.agent/rules/api-versioning-strategy.md
Normal file
432
.agent/rules/api-versioning-strategy.md
Normal 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];
|
||||
```
|
||||
@@ -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
|
||||
325
.agent/rules/caching-patterns.md
Normal file
325
.agent/rules/caching-patterns.md
Normal 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
|
||||
484
.agent/rules/cicd-advanced-patterns.md
Normal file
484
.agent/rules/cicd-advanced-patterns.md
Normal 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
|
||||
430
.agent/rules/comment-code.md
Normal file
430
.agent/rules/comment-code.md
Normal 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
|
||||
35
.agent/rules/configuration-management.md
Normal file
35
.agent/rules/configuration-management.md
Normal 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)
|
||||
269
.agent/rules/data-consistency-patterns.md
Normal file
269
.agent/rules/data-consistency-patterns.md
Normal 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
|
||||
184
.agent/rules/database-prisma.md
Normal file
184
.agent/rules/database-prisma.md
Normal 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
|
||||
183
.agent/rules/deployment-kubernetes.md
Normal file
183
.agent/rules/deployment-kubernetes.md
Normal 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
|
||||
444
.agent/rules/documentation.md
Normal file
444
.agent/rules/documentation.md
Normal 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
|
||||
363
.agent/rules/error-handling-patterns.md
Normal file
363
.agent/rules/error-handling-patterns.md
Normal 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
|
||||
270
.agent/rules/event-driven-architecture.md
Normal file
270
.agent/rules/event-driven-architecture.md
Normal 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
|
||||
415
.agent/rules/infrastructure-as-code.md
Normal file
415
.agent/rules/infrastructure-as-code.md
Normal 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
|
||||
225
.agent/rules/inter-service-communication.md
Normal file
225
.agent/rules/inter-service-communication.md
Normal 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
|
||||
356
.agent/rules/microservices-development-process.md
Normal file
356
.agent/rules/microservices-development-process.md
Normal 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
|
||||
|
||||
314
.agent/rules/middleware-patterns.md
Normal file
314
.agent/rules/middleware-patterns.md
Normal 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
|
||||
@@ -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`
|
||||
195
.agent/rules/observability-monitoring.md
Normal file
195
.agent/rules/observability-monitoring.md
Normal 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
|
||||
457
.agent/rules/performance-optimization.md
Normal file
457
.agent/rules/performance-optimization.md
Normal 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
|
||||
329
.agent/rules/project-rules.md
Normal file
329
.agent/rules/project-rules.md
Normal 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)
|
||||
273
.agent/rules/repository-pattern.md
Normal file
273
.agent/rules/repository-pattern.md
Normal 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
|
||||
421
.agent/rules/resilience-patterns.md
Normal file
421
.agent/rules/resilience-patterns.md
Normal 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
305
.agent/rules/security.md
Normal 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)
|
||||
20
.agent/rules/service-discovery-registry.md
Normal file
20
.agent/rules/service-discovery-registry.md
Normal 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
|
||||
|
||||
248
.agent/rules/service-layer-patterns.md
Normal file
248
.agent/rules/service-layer-patterns.md
Normal 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
|
||||
179
.agent/rules/testing-patterns.md
Normal file
179
.agent/rules/testing-patterns.md
Normal 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
|
||||
Reference in New Issue
Block a user