chore: Remove obsolete skill documentation files
- Deleted multiple outdated skill documentation files, including those related to API design, API gateway patterns, versioning strategies, caching patterns, CI/CD advanced patterns, bilingual code comments, configuration management, data consistency patterns, Prisma database patterns, Kubernetes deployment patterns, and more. - This cleanup streamlines the documentation repository, ensuring that only relevant and up-to-date information is maintained for current development practices.
This commit is contained in:
@@ -1,602 +0,0 @@
|
||||
---
|
||||
name: api-design
|
||||
description: RESTful API design standards for GoodGo microservices. Use when creating new API endpoints, designing DTOs, implementing controllers, writing OpenAPI documentation, or standardizing API responses.
|
||||
---
|
||||
|
||||
# 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
|
||||
- Designing resource relationships
|
||||
|
||||
## 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
|
||||
|
||||
## Request/Response Flow
|
||||
|
||||
The following diagram illustrates how a request flows through the API layers:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware
|
||||
participant Controller
|
||||
participant Service
|
||||
participant Repository
|
||||
participant Database
|
||||
|
||||
Client->>Middleware: HTTP Request
|
||||
Middleware->>Middleware: Authentication<br/>Rate Limiting<br/>Validation
|
||||
Middleware->>Controller: Validated Request
|
||||
Controller->>Controller: Parse DTO<br/>Extract Params
|
||||
Controller->>Service: Business Logic Call
|
||||
Service->>Repository: Data Access Call
|
||||
Repository->>Database: Query Execution
|
||||
Database-->>Repository: Data Result
|
||||
Repository-->>Service: Entity/Entities
|
||||
Service-->>Controller: Business Result
|
||||
Controller->>Controller: Transform to DTO<br/>Format Response
|
||||
Controller-->>Middleware: Response Object
|
||||
Middleware-->>Client: HTTP Response
|
||||
```
|
||||
|
||||
## API Structure Hierarchy
|
||||
|
||||
The following diagram shows the hierarchical structure of RESTful API endpoints:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[API Base URL<br/>https://api.goodgo.com] --> B[Version<br/>/v1]
|
||||
B --> C[Resource Collection<br/>/users]
|
||||
B --> D[Resource Collection<br/>/orders]
|
||||
B --> E[Resource Collection<br/>/products]
|
||||
|
||||
C --> F[Resource Instance<br/>/users/:id]
|
||||
C --> G[Sub-Resource<br/>/users/:id/orders]
|
||||
|
||||
F --> H[GET /users/:id<br/>Retrieve user]
|
||||
F --> I[PUT /users/:id<br/>Update user]
|
||||
F --> J[DELETE /users/:id<br/>Delete user]
|
||||
|
||||
C --> K[GET /users<br/>List users]
|
||||
C --> L[POST /users<br/>Create user]
|
||||
|
||||
G --> M[GET /users/:id/orders<br/>List user orders]
|
||||
G --> N[POST /users/:id/orders<br/>Create order]
|
||||
|
||||
style A fill:#e1f5ff
|
||||
style B fill:#b3e5fc
|
||||
style C fill:#81d4fa
|
||||
style D fill:#81d4fa
|
||||
style E fill:#81d4fa
|
||||
style F fill:#4fc3f7
|
||||
style G fill:#4fc3f7
|
||||
```
|
||||
|
||||
## URL Structure
|
||||
|
||||
```
|
||||
https://api.goodgo.com/v1/{resource}/{id}/{sub-resource}
|
||||
|
||||
Examples:
|
||||
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
|
||||
POST /v1/users/123/orders # Create order for user
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
### Success Response
|
||||
|
||||
```typescript
|
||||
interface SuccessResponse<T> {
|
||||
success: true;
|
||||
data: T;
|
||||
metadata?: {
|
||||
timestamp: string;
|
||||
version: string;
|
||||
requestId: string;
|
||||
};
|
||||
pagination?: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Example
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "123",
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe"
|
||||
},
|
||||
"metadata": {
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"version": "1.0.0",
|
||||
"requestId": "req_abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```typescript
|
||||
interface ErrorResponse {
|
||||
success: false;
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: any;
|
||||
field?: string;
|
||||
stack?: string; // Only in development
|
||||
};
|
||||
metadata?: {
|
||||
timestamp: string;
|
||||
requestId: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Example
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Invalid email format",
|
||||
"field": "email",
|
||||
"details": {
|
||||
"provided": "invalid-email",
|
||||
"expected": "valid email address"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Status Codes
|
||||
|
||||
```typescript
|
||||
// Success codes
|
||||
200 OK // GET, PUT, PATCH success
|
||||
201 Created // POST success with resource creation
|
||||
204 No Content // DELETE success
|
||||
|
||||
// Client errors
|
||||
400 Bad Request // Invalid request data
|
||||
401 Unauthorized // Missing/invalid authentication
|
||||
403 Forbidden // Valid auth but no permission
|
||||
404 Not Found // Resource doesn't exist
|
||||
409 Conflict // Resource conflict (duplicate)
|
||||
422 Unprocessable // Validation errors
|
||||
|
||||
// Server errors
|
||||
500 Internal Error // Unexpected server error
|
||||
502 Bad Gateway // External service error
|
||||
503 Service Unavailable // Service temporarily down
|
||||
504 Gateway Timeout // External service timeout
|
||||
```
|
||||
|
||||
## DTOs (Data Transfer Objects)
|
||||
|
||||
### Request DTOs
|
||||
|
||||
```typescript
|
||||
// create.dto.ts
|
||||
import { IsEmail, IsNotEmpty, IsOptional, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@MinLength(6)
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// update.dto.ts
|
||||
export class UpdateUserDto {
|
||||
@IsEmail()
|
||||
@IsOptional()
|
||||
email?: string;
|
||||
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
// query.dto.ts
|
||||
export class QueryUsersDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number = 10;
|
||||
|
||||
@IsOptional()
|
||||
search?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['createdAt', 'name', 'email'])
|
||||
sortBy?: string = 'createdAt';
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['asc', 'desc'])
|
||||
order?: 'asc' | 'desc' = 'desc';
|
||||
}
|
||||
```
|
||||
|
||||
### Response DTOs
|
||||
|
||||
```typescript
|
||||
// user.response.dto.ts
|
||||
export class UserResponseDto {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
role: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
// Hide sensitive data
|
||||
static fromEntity(user: User): UserResponseDto {
|
||||
const { password, ...data } = user;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// paginated.response.dto.ts
|
||||
export class PaginatedResponseDto<T> {
|
||||
data: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Controller Implementation
|
||||
|
||||
```typescript
|
||||
// user.controller.ts
|
||||
@Controller('users')
|
||||
@ApiTags('Users')
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List users' })
|
||||
@ApiQuery({ type: QueryUsersDto })
|
||||
@ApiResponse({ status: 200, type: PaginatedResponseDto })
|
||||
async list(@Query() query: QueryUsersDto): Promise<ResponseDto> {
|
||||
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')
|
||||
@ApiOperation({ summary: 'Get user by ID' })
|
||||
@ApiParam({ name: 'id', type: 'string' })
|
||||
@ApiResponse({ status: 200, type: UserResponseDto })
|
||||
@ApiResponse({ status: 404, description: 'User not found' })
|
||||
async getById(@Param('id') id: string): Promise<ResponseDto> {
|
||||
const user = await this.userService.findById(id);
|
||||
|
||||
if (!user) {
|
||||
throw new HttpException(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: 'USER_NOT_FOUND',
|
||||
message: `User with ID ${id} not found`
|
||||
}
|
||||
},
|
||||
HttpStatus.NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: UserResponseDto.fromEntity(user)
|
||||
};
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create user' })
|
||||
@ApiBody({ type: CreateUserDto })
|
||||
@ApiResponse({ status: 201, type: UserResponseDto })
|
||||
async create(@Body() dto: CreateUserDto): Promise<ResponseDto> {
|
||||
const user = await this.userService.create(dto);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: UserResponseDto.fromEntity(user)
|
||||
};
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: 'Update user' })
|
||||
@UseGuards(AuthGuard)
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateUserDto
|
||||
): Promise<ResponseDto> {
|
||||
const user = await this.userService.update(id, dto);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: UserResponseDto.fromEntity(user)
|
||||
};
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete user' })
|
||||
@UseGuards(AuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
async delete(@Param('id') id: string): Promise<ResponseDto> {
|
||||
await this.userService.delete(id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { deleted: true }
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OpenAPI/Swagger Documentation
|
||||
|
||||
```yaml
|
||||
# openapi/user-service.yaml
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: User Service API
|
||||
version: 1.0.0
|
||||
description: User management endpoints
|
||||
servers:
|
||||
- url: https://api.goodgo.com/v1
|
||||
paths:
|
||||
/users:
|
||||
get:
|
||||
summary: List users
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
responses:
|
||||
'200':
|
||||
description: List of users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserListResponse'
|
||||
post:
|
||||
summary: Create user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateUserRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: User created
|
||||
'400':
|
||||
description: Validation error
|
||||
```
|
||||
|
||||
## Pagination Pattern
|
||||
|
||||
```typescript
|
||||
// pagination.service.ts
|
||||
export class PaginationService {
|
||||
paginate<T>(
|
||||
query: any,
|
||||
options: {
|
||||
page: number;
|
||||
limit: number;
|
||||
sortBy?: string;
|
||||
order?: 'asc' | 'desc';
|
||||
}
|
||||
) {
|
||||
const skip = (options.page - 1) * options.limit;
|
||||
|
||||
return {
|
||||
skip,
|
||||
take: options.limit,
|
||||
orderBy: options.sortBy ? {
|
||||
[options.sortBy]: options.order || 'desc'
|
||||
} : undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The following diagram illustrates the error handling flow in the API:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Request Received] --> B{Validation<br/>Middleware}
|
||||
B -->|Invalid| C[ValidationError]
|
||||
B -->|Valid| D[Controller]
|
||||
|
||||
D --> E{Business Logic}
|
||||
E -->|Not Found| F[NotFoundError]
|
||||
E -->|Unauthorized| G[UnauthorizedError]
|
||||
E -->|Forbidden| H[ForbiddenError]
|
||||
E -->|Conflict| I[ConflictError]
|
||||
E -->|Success| J[Return Success Response]
|
||||
|
||||
E -->|Unexpected Error| K[Generic Error]
|
||||
|
||||
C --> L[Error Handler<br/>Middleware]
|
||||
F --> L
|
||||
G --> L
|
||||
H --> L
|
||||
I --> L
|
||||
K --> L
|
||||
|
||||
L --> M{Error Type?}
|
||||
M -->|ValidationError| N[400 Bad Request<br/>VALIDATION_ERROR]
|
||||
M -->|UnauthorizedError| O[401 Unauthorized<br/>UNAUTHORIZED]
|
||||
M -->|ForbiddenError| P[403 Forbidden<br/>FORBIDDEN]
|
||||
M -->|NotFoundError| Q[404 Not Found<br/>NOT_FOUND]
|
||||
M -->|ConflictError| R[409 Conflict<br/>CONFLICT]
|
||||
M -->|Unknown Error| S{Development<br/>Mode?}
|
||||
|
||||
S -->|Yes| T[500 Internal Error<br/>INTERNAL_ERROR<br/>+ Stack Trace]
|
||||
S -->|No| U[500 Internal Error<br/>INTERNAL_ERROR<br/>Generic Message]
|
||||
|
||||
N --> V[Format Error Response<br/>success: false<br/>error: code, message, details]
|
||||
O --> V
|
||||
P --> V
|
||||
Q --> V
|
||||
R --> V
|
||||
T --> V
|
||||
U --> V
|
||||
|
||||
V --> W[Log Error<br/>Server-Side]
|
||||
W --> X[Send HTTP Response]
|
||||
J --> X
|
||||
|
||||
style A fill:#e1f5ff
|
||||
style J fill:#c8e6c9
|
||||
style L fill:#fff9c4
|
||||
style V fill:#ffccbc
|
||||
style X fill:#e1f5ff
|
||||
```
|
||||
|
||||
### Error Handler Implementation
|
||||
|
||||
```typescript
|
||||
// error.middleware.ts
|
||||
export function errorHandler(
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// Known errors
|
||||
if (err instanceof ValidationError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: err.message,
|
||||
details: err.errors
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (err instanceof UnauthorizedError) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'UNAUTHORIZED',
|
||||
message: 'Authentication required'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Unknown errors
|
||||
logger.error('Unhandled error:', err);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: isDev ? err.message : 'Internal server error',
|
||||
stack: isDev ? err.stack : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Resource Naming**
|
||||
- Use plural nouns (`/users` not `/user`)
|
||||
- Use kebab-case for multi-word resources
|
||||
- Keep URLs as short as possible
|
||||
|
||||
2. **Versioning**
|
||||
- Include version in URL (`/v1/users`)
|
||||
- Maintain backward compatibility
|
||||
- Deprecate old versions gracefully
|
||||
|
||||
3. **Security**
|
||||
- Always use HTTPS
|
||||
- Implement rate limiting
|
||||
- Validate all inputs
|
||||
- Use proper authentication/authorization
|
||||
|
||||
4. **Performance**
|
||||
- Implement pagination for lists
|
||||
- Use field filtering when possible
|
||||
- Cache responses appropriately
|
||||
- Compress responses (gzip)
|
||||
|
||||
5. **Documentation**
|
||||
- Keep OpenAPI spec up to date
|
||||
- Include examples in documentation
|
||||
- Document error responses
|
||||
- Version your documentation
|
||||
@@ -1,204 +0,0 @@
|
||||
---
|
||||
name: api-gateway-advanced
|
||||
description: Advanced API Gateway patterns for GoodGo microservices including API composition, request/response transformation, service mesh integration, advanced routing, and gateway-level resilience.
|
||||
---
|
||||
|
||||
# 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 with Traefik
|
||||
- Implementing advanced routing strategies
|
||||
- Adding gateway-level circuit breakers
|
||||
- Implementing API versioning at gateway
|
||||
|
||||
## API Gateway Architecture
|
||||
|
||||
The API Gateway serves as the single entry point for all client requests, handling routing, composition, transformation, and resilience patterns.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Client[Client Application] --> Gateway[API Gateway]
|
||||
|
||||
subgraph Gateway["API Gateway Components"]
|
||||
Router[Request Router]
|
||||
Auth[Authentication/Authorization]
|
||||
RateLimit[Rate Limiting]
|
||||
CircuitBreaker[Circuit Breaker]
|
||||
Cache[Gateway Cache]
|
||||
Transformer[Request/Response Transformer]
|
||||
Composition[API Composition Engine]
|
||||
end
|
||||
|
||||
Gateway --> Router
|
||||
Router --> Auth
|
||||
Auth --> RateLimit
|
||||
RateLimit --> CircuitBreaker
|
||||
CircuitBreaker --> Cache
|
||||
Cache --> Transformer
|
||||
Transformer --> Composition
|
||||
|
||||
Composition --> Service1[User Service]
|
||||
Composition --> Service2[Order Service]
|
||||
Composition --> Service3[Payment Service]
|
||||
Composition --> Service4[Other Services]
|
||||
|
||||
CircuitBreaker -.-> Service1
|
||||
CircuitBreaker -.-> Service2
|
||||
CircuitBreaker -.-> Service3
|
||||
CircuitBreaker -.-> Service4
|
||||
|
||||
Cache --> Redis[(Redis Cache)]
|
||||
|
||||
style Gateway fill:#e1f5ff
|
||||
style Composition fill:#fff4e1
|
||||
style CircuitBreaker fill:#ffe1e1
|
||||
```
|
||||
|
||||
## Request Routing Flow
|
||||
|
||||
Requests flow through the gateway middleware chain in a specific order, ensuring proper handling at each stage.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant RateLimit
|
||||
participant Auth
|
||||
participant CircuitBreaker
|
||||
participant Cache
|
||||
participant Transformer
|
||||
participant Service
|
||||
|
||||
Client->>Gateway: HTTP Request
|
||||
Gateway->>RateLimit: Check Rate Limit
|
||||
RateLimit-->>Gateway: Allowed
|
||||
|
||||
Gateway->>Auth: Validate Token
|
||||
Auth-->>Gateway: Authenticated
|
||||
|
||||
Gateway->>CircuitBreaker: Check Circuit State
|
||||
alt Circuit Open
|
||||
CircuitBreaker-->>Gateway: Service Unavailable
|
||||
Gateway-->>Client: 503 Error
|
||||
else Circuit Closed/Half-Open
|
||||
Gateway->>Cache: Check Cache
|
||||
alt Cache Hit
|
||||
Cache-->>Gateway: Cached Response
|
||||
Gateway->>Transformer: Transform Response
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway-->>Client: Response
|
||||
else Cache Miss
|
||||
Gateway->>Transformer: Transform Request
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway->>Service: Forward Request
|
||||
Service-->>Gateway: Response
|
||||
Gateway->>Cache: Store in Cache
|
||||
Gateway->>Transformer: Transform Response
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway-->>Client: Response
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## API Composition Patterns
|
||||
|
||||
API composition enables the gateway to aggregate data from multiple services, reducing client round trips and improving performance.
|
||||
|
||||
### Fan-Out / Fan-In Pattern (Parallel Aggregation)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Client[Client Request] --> Gateway[API Gateway]
|
||||
|
||||
subgraph Gateway["API Composition"]
|
||||
Comp[Composition Handler]
|
||||
end
|
||||
|
||||
Gateway --> Comp
|
||||
|
||||
Comp -->|Parallel Calls| S1[User Service]
|
||||
Comp -->|Parallel Calls| S2[Order Service]
|
||||
Comp -->|Parallel Calls| S3[Payment Service]
|
||||
|
||||
S1 -->|Response| Comp
|
||||
S2 -->|Response| Comp
|
||||
S3 -->|Response| Comp
|
||||
|
||||
Comp -->|Aggregated Response| Client
|
||||
|
||||
style Comp fill:#fff4e1
|
||||
style S1 fill:#e1ffe1
|
||||
style S2 fill:#e1ffe1
|
||||
style S3 fill:#e1ffe1
|
||||
```
|
||||
|
||||
### Chaining Pattern (Sequential Calls with Compensation)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant OrderService
|
||||
participant PaymentService
|
||||
|
||||
Client->>Gateway: Create Order Request
|
||||
Gateway->>OrderService: POST /orders
|
||||
OrderService-->>Gateway: Order Created
|
||||
|
||||
Gateway->>PaymentService: POST /payments
|
||||
alt Payment Success
|
||||
PaymentService-->>Gateway: Payment Processed
|
||||
Gateway-->>Client: Success Response
|
||||
else Payment Failed
|
||||
PaymentService-->>Gateway: Payment Error
|
||||
Gateway->>OrderService: DELETE /orders/:id (Compensate)
|
||||
OrderService-->>Gateway: Order Deleted
|
||||
Gateway-->>Client: Error Response
|
||||
end
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### API Composition
|
||||
|
||||
```typescript
|
||||
// Aggregate multiple service responses
|
||||
const [user, orders, payments] = await Promise.all([
|
||||
userClient.get(`/users/${userId}`),
|
||||
orderClient.get(`/orders?userId=${userId}`),
|
||||
paymentClient.get(`/payments?userId=${userId}`),
|
||||
]);
|
||||
```
|
||||
|
||||
### Request/Response Transformation
|
||||
|
||||
```typescript
|
||||
// Transform at gateway level
|
||||
transformer.addRule({
|
||||
path: '/api/v1/users',
|
||||
requestTransform: (req) => {
|
||||
if (!req.query.page) req.query.page = '1';
|
||||
return req;
|
||||
},
|
||||
responseTransform: (res, data) => {
|
||||
return { success: true, data };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use API composition for aggregating related data
|
||||
2. Cache at gateway for frequently accessed data
|
||||
3. Implement circuit breaker at gateway level
|
||||
4. Keep transformations simple and testable
|
||||
|
||||
## Resources
|
||||
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
- [Middleware Patterns](./middleware-patterns.md)
|
||||
- Skill Source: `.cursor/skills/api-gateway-advanced/SKILL.md`
|
||||
@@ -1,404 +0,0 @@
|
||||
---
|
||||
name: api-versioning-strategy
|
||||
description: API versioning strategies for GoodGo microservices including semantic versioning, backward compatibility patterns, API deprecation, version negotiation, and breaking changes handling.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
## Version Negotiation
|
||||
|
||||
Version negotiation allows clients to request a specific API version through headers while maintaining clean URLs. The middleware extracts the version from the `Accept` header and routes to the appropriate handler.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware as Version Negotiation<br/>Middleware
|
||||
participant Controller as Version-Aware<br/>Controller
|
||||
participant Service
|
||||
|
||||
Client->>Middleware: Request with Accept header<br/>Accept: application/vnd.goodgo.v1+json
|
||||
Middleware->>Middleware: Extract version from header
|
||||
alt Version specified
|
||||
Middleware->>Middleware: Parse version number
|
||||
alt Version supported
|
||||
Middleware->>Controller: Set req.apiVersion = 1
|
||||
Controller->>Controller: Check version
|
||||
Controller->>Service: Call service method
|
||||
Service-->>Controller: Return data
|
||||
Controller->>Controller: Format response for v1
|
||||
Controller-->>Client: v1 response format
|
||||
else Version not supported
|
||||
Middleware-->>Client: 400 Unsupported Version
|
||||
end
|
||||
else No version specified
|
||||
Middleware->>Controller: Set req.apiVersion = latest (2)
|
||||
Controller->>Service: Call service method
|
||||
Service-->>Controller: Return data
|
||||
Controller->>Controller: Format response for v2
|
||||
Controller-->>Client: v2 response format (default)
|
||||
end
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
// src/middlewares/version-negotiation.middleware.ts
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export function versionNegotiation(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
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;
|
||||
|
||||
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 {
|
||||
req.apiVersion = 2; // Default to latest
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
```
|
||||
|
||||
## API Deprecation Timeline
|
||||
|
||||
API deprecation follows a structured timeline to give consumers adequate time to migrate. The lifecycle progresses through active, deprecated, sunset, and removed phases.
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title API Version Lifecycle Timeline
|
||||
dateFormat YYYY-MM-DD
|
||||
section Version 1
|
||||
Active (v1 only) :active, v1-active, 2024-01-01, 2024-06-01
|
||||
Deprecated (v1 + v2) :crit, v1-deprecated, 2024-06-01, 2024-12-31
|
||||
Sunset Period :v1-sunset, 2024-12-31, 2025-01-31
|
||||
Removed :v1-removed, 2025-01-31, 1d
|
||||
section Version 2
|
||||
Development :v2-dev, 2024-03-01, 2024-06-01
|
||||
Active (v1 + v2) :active, v2-active, 2024-06-01, 2025-12-31
|
||||
```
|
||||
|
||||
### Deprecation Phases
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active: Version Released
|
||||
Active --> Deprecated: New Version Released<br/>Add Deprecation Headers
|
||||
Deprecated --> Sunset: Sunset Date Reached<br/>Stop Accepting New Requests
|
||||
Sunset --> Removed: Grace Period Ended<br/>Remove Routes
|
||||
Removed --> [*]
|
||||
|
||||
note right of Active
|
||||
- Version fully supported
|
||||
- No warnings
|
||||
- All features available
|
||||
end note
|
||||
|
||||
note right of Deprecated
|
||||
- Deprecation header set
|
||||
- Warning headers added
|
||||
- Migration guide provided
|
||||
- Still functional
|
||||
end note
|
||||
|
||||
note right of Sunset
|
||||
- Read-only mode
|
||||
- No new requests accepted
|
||||
- Existing requests honored
|
||||
end note
|
||||
```
|
||||
|
||||
### Deprecation Headers
|
||||
|
||||
```typescript
|
||||
// src/middlewares/deprecation.middleware.ts
|
||||
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();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Flow
|
||||
|
||||
Breaking changes require a careful 3-phase migration strategy to ensure zero downtime and smooth client transitions.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Breaking Change Identified]) --> Phase1[Phase 1: Support Both Versions]
|
||||
|
||||
Phase1 --> DeployV2[Deploy v2 alongside v1]
|
||||
DeployV2 --> Monitor1[Monitor v1 and v2 usage]
|
||||
Monitor1 --> Wait1[Wait for client adoption]
|
||||
Wait1 --> Phase2{Sufficient<br/>v2 adoption?}
|
||||
|
||||
Phase2 -->|No| Wait1
|
||||
Phase2 -->|Yes| Phase2Start[Phase 2: Deprecate v1]
|
||||
|
||||
Phase2Start --> AddHeaders[Add deprecation headers to v1]
|
||||
AddHeaders --> NotifyClients[Notify clients via<br/>deprecation warnings]
|
||||
NotifyClients --> ProvideGuide[Provide migration guide]
|
||||
ProvideGuide --> Monitor2[Monitor migration progress]
|
||||
Monitor2 --> Wait2[Wait until sunset date]
|
||||
Wait2 --> Phase3{Sunset date<br/>reached?}
|
||||
|
||||
Phase3 -->|No| Monitor2
|
||||
Phase3 -->|Yes| Phase3Start[Phase 3: Remove v1]
|
||||
|
||||
Phase3Start --> StopAccepting[Stop accepting new v1 requests]
|
||||
StopAccepting --> GracePeriod[Grace period for<br/>existing requests]
|
||||
GracePeriod --> RemoveRoutes[Remove v1 routes]
|
||||
RemoveRoutes --> End([Migration Complete])
|
||||
|
||||
style Phase1 fill:#e1f5ff
|
||||
style Phase2Start fill:#fff4e1
|
||||
style Phase3Start fill:#ffe1e1
|
||||
style End fill:#e1ffe1
|
||||
```
|
||||
|
||||
### Implementation Strategy
|
||||
|
||||
```typescript
|
||||
// src/core/api/migration.strategy.ts
|
||||
export class MigrationStrategy {
|
||||
/**
|
||||
* Phase 1: Support both versions
|
||||
*/
|
||||
phase1SupportBoth(): void {
|
||||
router.use('/v1', v1Router);
|
||||
router.use('/v2', v2Router);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 2: Deprecate v1
|
||||
*/
|
||||
phase2DeprecateV1(): void {
|
||||
router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router);
|
||||
router.use('/v2', v2Router);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 3: Remove v1
|
||||
*/
|
||||
phase3RemoveV1(): void {
|
||||
router.use('/v2', v2Router);
|
||||
// v1 routes removed after sunset date
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## URL Path Versioning
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
// src/routes/index.ts
|
||||
import { Router } from 'express';
|
||||
import v1Router from './v1';
|
||||
import v2Router from './v2';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/v1', v1Router);
|
||||
router.use('/v2', v2Router);
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
## 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
|
||||
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();
|
||||
}
|
||||
```
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
### Compatibility Layer
|
||||
|
||||
```typescript
|
||||
// src/core/api/compatibility.adapter.ts
|
||||
export class CompatibilityAdapter {
|
||||
adaptV1ToV2(v1Data: any): any {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
...v1Data,
|
||||
profile: null, // Add default for new field
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
version: '2.0.0',
|
||||
adapted: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
adaptV2RequestToV1(v2Request: any): any {
|
||||
return {
|
||||
email: v2Request.email,
|
||||
name: v2Request.name,
|
||||
// Ignore new fields
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 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"
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [API Design](./api-design.md) - API design patterns
|
||||
- [Middleware Patterns](./middleware-patterns.md) - Middleware patterns
|
||||
- [Project Rules](./project-rules.md) - GoodGo standards
|
||||
- Skill Source: `.cursor/skills/api-versioning-strategy/SKILL.md`
|
||||
@@ -1,369 +0,0 @@
|
||||
---
|
||||
name: caching-patterns
|
||||
description: Caching strategies and patterns for GoodGo microservices including multi-layer cache, Redis caching, cache key naming, TTL strategies, cache invalidation, and cache-aside patterns.
|
||||
---
|
||||
|
||||
# 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 to balance speed and capacity:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Application["Application Layer"]
|
||||
App[Application Code]
|
||||
end
|
||||
|
||||
subgraph L1Layer["L1 Cache - Memory (NodeCache)"]
|
||||
L1[In-Memory Cache]
|
||||
L1Props["• Speed: < 1ms<br/>• Capacity: 10k keys<br/>• TTL: 60s-5min<br/>• Scope: Per-instance"]
|
||||
end
|
||||
|
||||
subgraph L2Layer["L2 Cache - Redis (Distributed)"]
|
||||
L2[Redis Cache]
|
||||
L2Props["• Speed: < 5ms<br/>• Capacity: Large<br/>• TTL: Configurable<br/>• Scope: Shared"]
|
||||
end
|
||||
|
||||
subgraph DataLayer["Data Source"]
|
||||
DB[(Database)]
|
||||
API[External API]
|
||||
end
|
||||
|
||||
App -->|Check First| L1
|
||||
L1 -->|Miss| L2
|
||||
L2 -->|Miss| DB
|
||||
L2 -->|Miss| API
|
||||
DB -->|Store| L2
|
||||
API -->|Store| L2
|
||||
L2 -->|Warm| L1
|
||||
|
||||
L1 -.-> L1Props
|
||||
L2 -.-> L2Props
|
||||
|
||||
style L1 fill:#e1f5ff
|
||||
style L2 fill:#fff4e1
|
||||
style DB fill:#ffe1e1
|
||||
style API fill:#ffe1e1
|
||||
```
|
||||
|
||||
**Layer Characteristics:**
|
||||
|
||||
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
|
||||
|
||||
The cache lookup follows a multi-layer approach, checking L1 first, then L2, and finally the data source.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Request Data]) --> CheckL1{Check L1 Cache<br/>Memory}
|
||||
CheckL1 -->|Hit| ReturnL1[Return Data<br/>from L1]
|
||||
CheckL1 -->|Miss| CheckL2{Check L2 Cache<br/>Redis}
|
||||
CheckL2 -->|Hit| StoreL1[Store in L1<br/>Warm Cache]
|
||||
StoreL1 --> ReturnL2[Return Data<br/>from L2]
|
||||
CheckL2 -->|Miss| FetchSource[Fetch from<br/>Data Source]
|
||||
FetchSource --> StoreBoth[Store in L1 & L2]
|
||||
StoreBoth --> ReturnSource[Return Data<br/>from Source]
|
||||
|
||||
ReturnL1 --> End([End])
|
||||
ReturnL2 --> End
|
||||
ReturnSource --> End
|
||||
|
||||
style CheckL1 fill:#e1f5ff
|
||||
style CheckL2 fill:#fff4e1
|
||||
style FetchSource fill:#ffe1e1
|
||||
style ReturnL1 fill:#e1ffe1
|
||||
style ReturnL2 fill:#e1ffe1
|
||||
style ReturnSource fill:#e1ffe1
|
||||
```
|
||||
|
||||
## 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 to prevent serving stale data. The platform supports multiple invalidation strategies:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Data Changed]) --> ChooseStrategy{Choose<br/>Invalidation<br/>Strategy}
|
||||
|
||||
ChooseStrategy -->|Single Key| SingleKey[Single Key<br/>Invalidation]
|
||||
SingleKey --> DelL1[Delete from L1]
|
||||
DelL1 --> DelL2[Delete from L2]
|
||||
DelL2 --> Done1([Complete])
|
||||
|
||||
ChooseStrategy -->|Pattern Match| PatternMatch[Pattern-Based<br/>Invalidation]
|
||||
PatternMatch --> FindKeys[Find Matching Keys<br/>user:123:*]
|
||||
FindKeys --> DelManyL1[Delete from L1<br/>All Matching]
|
||||
DelManyL1 --> DelManyL2[Delete from L2<br/>All Matching]
|
||||
DelManyL2 --> Done2([Complete])
|
||||
|
||||
ChooseStrategy -->|Multiple Keys| MultipleKeys[Multiple Keys<br/>Invalidation]
|
||||
MultipleKeys --> ListKeys[List Keys to Delete<br/>user:123<br/>user:123:permissions<br/>user:123:roles]
|
||||
ListKeys --> BatchDelL1[Batch Delete from L1]
|
||||
BatchDelL1 --> BatchDelL2[Batch Delete from L2]
|
||||
BatchDelL2 --> Done3([Complete])
|
||||
|
||||
style SingleKey fill:#e1f5ff
|
||||
style PatternMatch fill:#fff4e1
|
||||
style MultipleKeys fill:#ffe1e1
|
||||
style Done1 fill:#e1ffe1
|
||||
style Done2 fill:#e1ffe1
|
||||
style Done3 fill:#e1ffe1
|
||||
```
|
||||
|
||||
**Implementation Examples:**
|
||||
|
||||
```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)
|
||||
|
||||
## 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
|
||||
@@ -1,544 +0,0 @@
|
||||
---
|
||||
name: cicd-advanced-patterns
|
||||
description: Advanced CI/CD patterns for GoodGo microservices including blue-green deployments, canary releases, automated rollback, deployment verification, and progressive delivery.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
Blue-green deployment maintains two identical production environments (blue and green). At any time, only one environment serves live traffic. The new version is deployed to the idle environment, verified, and then traffic is switched.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Deployment Triggered]) --> DeployGreen[Deploy to Green Environment]
|
||||
DeployGreen --> WaitRollout[Wait for Rollout Complete]
|
||||
WaitRollout --> RunSmokeTests[Run Smoke Tests]
|
||||
RunSmokeTests --> TestsPassed{Tests Passed?}
|
||||
|
||||
TestsPassed -->|Yes| SwitchTraffic[Switch Service Selector to Green]
|
||||
TestsPassed -->|No| RollbackToBlue[Rollback: Keep Blue Active]
|
||||
|
||||
SwitchTraffic --> MonitorHealth[Monitor Health Metrics]
|
||||
MonitorHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| Complete([Deployment Complete])
|
||||
HealthOK -->|No| AutoRollback[Auto Rollback to Blue]
|
||||
|
||||
AutoRollback --> Complete
|
||||
RollbackToBlue --> Fail([Deployment Failed])
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail fill:#f8d7da
|
||||
style TestsPassed fill:#fff3cd
|
||||
style HealthOK fill:#fff3cd
|
||||
```
|
||||
|
||||
### 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 # Switch to green after verification
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 5000
|
||||
```
|
||||
|
||||
## Canary Deployment
|
||||
|
||||
Canary deployment gradually rolls out changes to a small subset of users before making them available to everyone. This allows for real-world testing with minimal risk.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Canary Deployment Started]) --> DeployCanary[Deploy Canary Version<br/>1 Replica]
|
||||
DeployCanary --> Route10[Route 10% Traffic to Canary]
|
||||
Route10 --> Wait10[Wait 5-10 minutes]
|
||||
Wait10 --> Check10{Health & Metrics OK?}
|
||||
|
||||
Check10 -->|No| RollbackCanary[Rollback: Route 0% to Canary]
|
||||
Check10 -->|Yes| Route25[Route 25% Traffic to Canary]
|
||||
|
||||
Route25 --> Wait25[Wait 5-10 minutes]
|
||||
Wait25 --> Check25{Health & Metrics OK?}
|
||||
|
||||
Check25 -->|No| RollbackCanary
|
||||
Check25 -->|Yes| Route50[Route 50% Traffic to Canary]
|
||||
|
||||
Route50 --> Wait50[Wait 5-10 minutes]
|
||||
Wait50 --> Check50{Health & Metrics OK?}
|
||||
|
||||
Check50 -->|No| RollbackCanary
|
||||
Check50 -->|Yes| Route75[Route 75% Traffic to Canary]
|
||||
|
||||
Route75 --> Wait75[Wait 5-10 minutes]
|
||||
Wait75 --> Check75{Health & Metrics OK?}
|
||||
|
||||
Check75 -->|No| RollbackCanary
|
||||
Check75 -->|Yes| Route100[Route 100% Traffic to Canary]
|
||||
|
||||
Route100 --> PromoteCanary[Promote Canary to Stable]
|
||||
PromoteCanary --> Complete([Canary Complete])
|
||||
|
||||
RollbackCanary --> Fail([Canary Failed])
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail fill:#f8d7da
|
||||
style Check10 fill:#fff3cd
|
||||
style Check25 fill:#fff3cd
|
||||
style Check50 fill:#fff3cd
|
||||
style Check75 fill:#fff3cd
|
||||
```
|
||||
|
||||
### 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 # Start with 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 # 10% traffic to canary
|
||||
```
|
||||
|
||||
## Automated Rollback
|
||||
|
||||
Automated rollback mechanisms detect deployment failures and automatically revert to the previous stable version, minimizing downtime and impact.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Deployment Completed]) --> RunSmokeTests[Run Smoke Tests]
|
||||
RunSmokeTests --> SmokePassed{Smoke Tests Pass?}
|
||||
|
||||
SmokePassed -->|No| GetPreviousRev[Get Previous Revision]
|
||||
GetPreviousRev --> RollbackDeploy[Rollback Deployment]
|
||||
RollbackDeploy --> VerifyRollback[Verify Rollback Success]
|
||||
VerifyRollback --> RollbackComplete([Rollback Complete])
|
||||
|
||||
SmokePassed -->|Yes| MonitorHealth[Monitor Health Metrics]
|
||||
MonitorHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| MonitorErrors[Monitor Error Rates]
|
||||
HealthOK -->|No| GetPreviousRev
|
||||
|
||||
MonitorErrors --> ErrorRateOK{Error Rate < Threshold?}
|
||||
|
||||
ErrorRateOK -->|Yes| MonitorPerformance[Monitor Performance]
|
||||
ErrorRateOK -->|No| GetPreviousRev
|
||||
|
||||
MonitorPerformance --> PerfOK{Performance OK?}
|
||||
|
||||
PerfOK -->|Yes| DeploymentSuccess([Deployment Successful])
|
||||
PerfOK -->|No| GetPreviousRev
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style DeploymentSuccess fill:#d4edda
|
||||
style RollbackComplete fill:#f8d7da
|
||||
style SmokePassed fill:#fff3cd
|
||||
style HealthOK fill:#fff3cd
|
||||
style ErrorRateOK fill:#fff3cd
|
||||
style PerfOK fill:#fff3cd
|
||||
```
|
||||
|
||||
### Rollback Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/deployment/rollback.sh
|
||||
# Automated rollback to previous version
|
||||
|
||||
SERVICE_NAME=$1
|
||||
NAMESPACE=${2:-production}
|
||||
|
||||
# Get previous deployment revision
|
||||
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"
|
||||
|
||||
# Rollback deployment
|
||||
kubectl rollout undo deployment/$SERVICE_NAME -n $NAMESPACE --to-revision=$PREVIOUS_REVISION
|
||||
|
||||
# Wait for 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
|
||||
// Smoke tests for deployment verification
|
||||
import axios from 'axios';
|
||||
|
||||
const SERVICE_URL = process.env.SERVICE_URL || 'http://localhost';
|
||||
|
||||
async function runSmokeTests(): Promise<boolean> {
|
||||
try {
|
||||
// Health check
|
||||
const healthResponse = await axios.get(`${SERVICE_URL}/health`);
|
||||
if (healthResponse.status !== 200) {
|
||||
console.error('Health check failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Basic functionality test
|
||||
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
|
||||
# Comprehensive health checks
|
||||
|
||||
SERVICE_NAME=$1
|
||||
NAMESPACE=${2:-production}
|
||||
|
||||
echo "Running health checks for $SERVICE_NAME"
|
||||
|
||||
# Check pods are 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
|
||||
|
||||
# Check 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
|
||||
|
||||
# Check 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
|
||||
|
||||
Deployment gates add checkpoints in the CI/CD pipeline that must pass before proceeding to the next stage.
|
||||
|
||||
```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/service
|
||||
|
||||
# Rollback
|
||||
kubectl rollout undo deployment/service
|
||||
|
||||
# Canary traffic split (Istio)
|
||||
kubectl apply -f virtualservice-canary.yaml
|
||||
```
|
||||
|
||||
**GitHub Actions Triggers:**
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [main] # Deploy to prod
|
||||
tags: ['v*'] # Release
|
||||
pull_request:
|
||||
branches: [main] # PR checks
|
||||
```
|
||||
|
||||
**Deployment Gates:**
|
||||
```
|
||||
Build → Test → Security Scan → Deploy Staging
|
||||
→ Smoke Tests → Manual Approval → Deploy Prod
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
||||
- [Istio Traffic Management](https://istio.io/latest/docs/tasks/traffic-management/)
|
||||
- [Deployment Kubernetes](./deployment-kubernetes.md) - K8s deployment patterns
|
||||
- [Testing Patterns](./testing-patterns.md) - Testing strategies
|
||||
- [Project Rules](./project-rules.md) - GoodGo coding standards
|
||||
- Skill Source: `.cursor/skills/cicd-advanced-patterns/SKILL.md`
|
||||
@@ -1,489 +0,0 @@
|
||||
---
|
||||
name: comment-code
|
||||
description: Add bilingual code comments in Vietnamese and English for better documentation. Use when adding comments to code, documenting functions/classes, or when user requests Vietnamese/English documentation.
|
||||
---
|
||||
|
||||
# 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 Structure
|
||||
|
||||
The following diagram illustrates the structure and hierarchy of comment types used in the GoodGo codebase:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph CommentTypes["Comment Types"]
|
||||
SingleLine["Single-line Comments<br/>// EN: ...<br/>// VI: ..."]
|
||||
MultiLine["Multi-line Comments<br/>/* EN: ...<br/>VI: ... */"]
|
||||
JSDoc["JSDoc Comments<br/>/** EN: ...<br/>VI: ... */"]
|
||||
Prisma["Prisma Comments<br/>/// EN: ...<br/>VI: ..."]
|
||||
end
|
||||
|
||||
subgraph Contexts["Code Contexts"]
|
||||
Functions["Functions"]
|
||||
Classes["Classes"]
|
||||
Interfaces["Interfaces/Types"]
|
||||
Components["React Components"]
|
||||
Controllers["API Controllers"]
|
||||
Middleware["Middleware"]
|
||||
Schema["Prisma Schema"]
|
||||
Config["Configuration"]
|
||||
end
|
||||
|
||||
subgraph SpecialTypes["Special Comment Types"]
|
||||
TODO["TODO Comments"]
|
||||
FIXME["FIXME Comments"]
|
||||
WARNING["WARNING Comments"]
|
||||
NOTE["NOTE Comments"]
|
||||
end
|
||||
|
||||
subgraph Format["Bilingual Format"]
|
||||
EN["English (EN)<br/>First"]
|
||||
VI["Vietnamese (VI)<br/>Second"]
|
||||
end
|
||||
|
||||
JSDoc --> Functions
|
||||
JSDoc --> Classes
|
||||
JSDoc --> Interfaces
|
||||
JSDoc --> Components
|
||||
JSDoc --> Controllers
|
||||
JSDoc --> Middleware
|
||||
|
||||
SingleLine --> Config
|
||||
SingleLine --> SpecialTypes
|
||||
|
||||
MultiLine --> Functions
|
||||
MultiLine --> Classes
|
||||
|
||||
Prisma --> Schema
|
||||
|
||||
Format --> CommentTypes
|
||||
Format --> Contexts
|
||||
Format --> SpecialTypes
|
||||
|
||||
style CommentTypes fill:#e1f5ff
|
||||
style Contexts fill:#fff4e1
|
||||
style SpecialTypes fill:#ffe1f5
|
||||
style Format fill:#e1ffe1
|
||||
```
|
||||
|
||||
## Documentation Flow
|
||||
|
||||
The following diagram shows the decision flow for adding comments to code:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start: Writing Code]) --> CheckType{What type of<br/>code element?}
|
||||
|
||||
CheckType -->|Public API| HighPriority[High Priority:<br/>Always Document]
|
||||
CheckType -->|Complex Logic| HighPriority
|
||||
CheckType -->|Security Code| HighPriority
|
||||
CheckType -->|Config/Setup| HighPriority
|
||||
CheckType -->|Error Handling| HighPriority
|
||||
|
||||
CheckType -->|Helper Function| MediumPriority[Medium Priority:<br/>Document if Helpful]
|
||||
CheckType -->|Data Transform| MediumPriority
|
||||
CheckType -->|External Integration| MediumPriority
|
||||
|
||||
CheckType -->|Simple Getter/Setter| LowPriority[Low Priority:<br/>Optional]
|
||||
CheckType -->|Self-explanatory| LowPriority
|
||||
CheckType -->|Standard CRUD| LowPriority
|
||||
|
||||
HighPriority --> ChooseFormat{Choose Comment<br/>Format}
|
||||
MediumPriority --> ChooseFormat
|
||||
LowPriority --> ChooseFormat
|
||||
|
||||
ChooseFormat -->|Function/Class| UseJSDoc[Use JSDoc Format<br/>/** EN: ...<br/>VI: ... */]
|
||||
ChooseFormat -->|Brief Explanation| UseSingleLine[Use Single-line<br/>// EN: ...<br/>// VI: ...]
|
||||
ChooseFormat -->|Multi-step Process| UseMultiLine[Use Multi-line<br/>/* EN: ...<br/>VI: ... */]
|
||||
ChooseFormat -->|Prisma Schema| UsePrisma[Use Prisma Format<br/>/// EN: ...<br/>VI: ...]
|
||||
|
||||
UseJSDoc --> AddParams[Add @param tags<br/>Add @returns tag<br/>Add @throws if needed]
|
||||
UseSingleLine --> WriteBilingual[Write Bilingual:<br/>EN first, VI second]
|
||||
UseMultiLine --> WriteBilingual
|
||||
UsePrisma --> WriteBilingual
|
||||
|
||||
AddParams --> WriteBilingual
|
||||
WriteBilingual --> CheckSpecial{Special<br/>Comment Type?}
|
||||
|
||||
CheckSpecial -->|Future Work| AddTODO[Add TODO prefix]
|
||||
CheckSpecial -->|Needs Fix| AddFIXME[Add FIXME prefix]
|
||||
CheckSpecial -->|Important Warning| AddWARNING[Add WARNING prefix]
|
||||
CheckSpecial -->|Important Note| AddNOTE[Add NOTE prefix]
|
||||
CheckSpecial -->|No| End([Done])
|
||||
|
||||
AddTODO --> End
|
||||
AddFIXME --> End
|
||||
AddWARNING --> End
|
||||
AddNOTE --> End
|
||||
|
||||
style HighPriority fill:#ffcccc
|
||||
style MediumPriority fill:#ffffcc
|
||||
style LowPriority fill:#ccffcc
|
||||
style UseJSDoc fill:#cce5ff
|
||||
style UseSingleLine fill:#cce5ff
|
||||
style UseMultiLine fill:#cce5ff
|
||||
style UsePrisma fill:#cce5ff
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## 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ì]
|
||||
```
|
||||
@@ -1,131 +0,0 @@
|
||||
---
|
||||
name: configuration-management
|
||||
description: Configuration management patterns for GoodGo microservices including feature flags, dynamic configuration reloading, environment-specific configurations, and secrets management.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Configuration Loading Flow
|
||||
|
||||
The configuration loading process fetches configuration from multiple sources, validates it, and supports dynamic reloading:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Application Startup]) --> LoadConfig[Load Configuration]
|
||||
LoadConfig --> FetchSource{Fetch from Source}
|
||||
|
||||
FetchSource --> |Environment Variables| EnvVars[Read Env Vars]
|
||||
FetchSource --> |Config Files| ConfigFiles[Read JSON/YAML]
|
||||
FetchSource --> |Database| Database[Query Config Table]
|
||||
FetchSource --> |External Service| ExternalService[Call Config API]
|
||||
|
||||
EnvVars --> Validate[Validate with Zod Schema]
|
||||
ConfigFiles --> Validate
|
||||
Database --> Validate
|
||||
ExternalService --> Validate
|
||||
|
||||
Validate --> |Valid| StoreConfig[Store in Memory Map]
|
||||
Validate --> |Invalid| LogError[Log Validation Error]
|
||||
LogError --> ThrowError[Throw Error]
|
||||
ThrowError --> End([Application Fails to Start])
|
||||
|
||||
StoreConfig --> CheckChange{Value Changed?}
|
||||
CheckChange --> |Yes| EmitEvent[Emit 'config-changed' Event]
|
||||
CheckChange --> |No| SkipEvent[Skip Event]
|
||||
|
||||
EmitEvent --> Ready[Configuration Ready]
|
||||
SkipEvent --> Ready
|
||||
Ready --> End
|
||||
|
||||
Ready --> AutoReload{Auto-Reload Enabled?}
|
||||
AutoReload --> |Yes| SetInterval[Set Interval Timer]
|
||||
AutoReload --> |No| End
|
||||
SetInterval --> Wait[Wait Interval]
|
||||
Wait --> LoadConfig
|
||||
```
|
||||
|
||||
### Feature Flag Evaluation Flow
|
||||
|
||||
Feature flags support multiple evaluation strategies including global flags, user-specific flags, and percentage-based rollouts:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Check Feature Flag]) --> GetFlag[Get Flag by Key]
|
||||
GetFlag --> FlagExists{Flag Exists?}
|
||||
|
||||
FlagExists --> |No| ReturnFalse[Return false]
|
||||
ReturnFalse --> End([End])
|
||||
|
||||
FlagExists --> |Yes| CheckEnabled{Flag Enabled?}
|
||||
CheckEnabled --> |No| ReturnFalse
|
||||
|
||||
CheckEnabled --> |Yes| HasUserId{User ID Provided?}
|
||||
HasUserId --> |No| ReturnTrue[Return true]
|
||||
ReturnTrue --> End
|
||||
|
||||
HasUserId --> |Yes| CheckUserSpecific{User-Specific Flag?}
|
||||
CheckUserSpecific --> |Yes| MatchUser{User ID Matches?}
|
||||
MatchUser --> |Yes| ReturnTrue
|
||||
MatchUser --> |No| CheckPercentage
|
||||
|
||||
CheckUserSpecific --> |No| CheckPercentage{Percentage Rollout?}
|
||||
CheckPercentage --> |No| ReturnTrue
|
||||
|
||||
CheckPercentage --> |Yes| HashUser[Hash User ID]
|
||||
HashUser --> CalcHash[Calculate Hash % 100]
|
||||
CalcHash --> CompareHash{Hash < Percentage?}
|
||||
|
||||
CompareHash --> |Yes| ReturnTrue
|
||||
CompareHash --> |No| ReturnFalse
|
||||
```
|
||||
|
||||
### Feature Flags
|
||||
|
||||
```typescript
|
||||
// Check if feature is enabled
|
||||
const enabled = await featureFlagService.isEnabled('new-feature', userId);
|
||||
|
||||
if (enabled) {
|
||||
// Use new feature
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Configuration
|
||||
|
||||
```typescript
|
||||
// Load and auto-reload configuration
|
||||
await configService.load();
|
||||
configService.startAutoReload(60000); // Reload every minute
|
||||
|
||||
const value = configService.get('config-key', 'default-value');
|
||||
```
|
||||
|
||||
### Configuration Validation
|
||||
|
||||
```typescript
|
||||
// Validate with Zod
|
||||
const config = validateConfig(process.env);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always validate configuration at startup
|
||||
2. Provide sensible defaults
|
||||
3. Never commit secrets to code
|
||||
4. Use feature flags for gradual rollouts
|
||||
|
||||
## Resources
|
||||
|
||||
- [Feature Flags Pattern](https://martinfowler.com/articles/feature-toggles.html)
|
||||
- Skill Source: `.cursor/skills/configuration-management/SKILL.md`
|
||||
@@ -1,363 +0,0 @@
|
||||
---
|
||||
name: data-consistency-patterns
|
||||
description: Data consistency patterns for distributed microservices including Saga patterns, distributed transactions, eventual consistency, compensation, and idempotency. Use when handling distributed transactions, implementing eventual consistency, or managing data synchronization across services.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### ACID vs BASE
|
||||
|
||||
**ACID (Traditional):** Atomicity, Consistency, Isolation, Durability
|
||||
|
||||
**BASE (Distributed):** Basic Availability, Soft state, Eventual consistency
|
||||
|
||||
### Consistency Models
|
||||
|
||||
- **Strong Consistency**: All nodes see same data at same time
|
||||
- **Eventual Consistency**: System becomes consistent over time
|
||||
- **Weak Consistency**: No guarantees about when consistency occurs
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Saga Orchestrator Pattern
|
||||
|
||||
#### Saga Orchestration Flow
|
||||
|
||||
The following diagram illustrates how a Saga orchestrator executes steps sequentially and handles compensation on failure:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Orchestrator
|
||||
participant Step1 as Step 1: Create Order
|
||||
participant Step2 as Step 2: Reserve Inventory
|
||||
participant Step3 as Step 3: Process Payment
|
||||
|
||||
Client->>Orchestrator: Execute Saga
|
||||
Orchestrator->>Step1: Execute Step 1
|
||||
Step1-->>Orchestrator: Success (Order Created)
|
||||
Orchestrator->>Step2: Execute Step 2
|
||||
Step2-->>Orchestrator: Success (Inventory Reserved)
|
||||
Orchestrator->>Step3: Execute Step 3
|
||||
Step3-->>Orchestrator: Failure (Payment Failed)
|
||||
Orchestrator->>Step2: Compensate Step 2
|
||||
Step2-->>Orchestrator: Compensation Complete
|
||||
Orchestrator->>Step1: Compensate Step 1
|
||||
Step1-->>Orchestrator: Compensation Complete
|
||||
Orchestrator-->>Client: Saga Failed (Compensated)
|
||||
```
|
||||
|
||||
#### Compensation Flow
|
||||
|
||||
When a step fails, the orchestrator compensates all previously completed steps in reverse order:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Saga Execution Starts]) --> Step1[Execute Step 1]
|
||||
Step1 -->|Success| Step2[Execute Step 2]
|
||||
Step1 -->|Failure| Fail1[Saga Failed<br/>No Compensation Needed]
|
||||
|
||||
Step2 -->|Success| Step3[Execute Step 3]
|
||||
Step2 -->|Failure| Comp1[Compensate Step 1]
|
||||
|
||||
Step3 -->|Success| Complete([Saga Completed])
|
||||
Step3 -->|Failure| Comp2[Compensate Step 2]
|
||||
|
||||
Comp2 --> Comp1
|
||||
Comp1 --> Fail2[Saga Failed<br/>All Steps Compensated]
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail1 fill:#f8d7da
|
||||
style Fail2 fill:#f8d7da
|
||||
style Comp1 fill:#fff3cd
|
||||
style Comp2 fill:#fff3cd
|
||||
```
|
||||
|
||||
#### Eventual Consistency Flow
|
||||
|
||||
This diagram shows how data becomes consistent across services over time through event propagation:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph ServiceA[Service A: Write Model]
|
||||
Write[Write Operation] --> EventStore[Event Store]
|
||||
EventStore --> Publish[Publish Event]
|
||||
end
|
||||
|
||||
subgraph EventBus[Event Bus]
|
||||
Publish --> Queue[Event Queue]
|
||||
end
|
||||
|
||||
subgraph ServiceB[Service B: Read Model]
|
||||
Queue --> Consume[Consume Event]
|
||||
Consume --> Update[Update Read Model]
|
||||
Update --> Consistent[Eventually Consistent]
|
||||
end
|
||||
|
||||
subgraph ServiceC[Service C: Read Model]
|
||||
Queue --> Consume2[Consume Event]
|
||||
Consume2 --> Update2[Update Read Model]
|
||||
Update2 --> Consistent2[Eventually Consistent]
|
||||
end
|
||||
|
||||
style Write fill:#e1f5ff
|
||||
style Consistent fill:#d4edda
|
||||
style Consistent2 fill:#d4edda
|
||||
style Queue fill:#fff3cd
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Centralized orchestrator coordinates steps
|
||||
const saga = new SagaOrchestrator();
|
||||
await saga.execute({
|
||||
sagaId: 'saga_123',
|
||||
steps: [
|
||||
{ name: 'create-order', execute: createOrder, compensate: cancelOrder },
|
||||
{ name: 'reserve-inventory', execute: reserveInventory, compensate: releaseInventory },
|
||||
{ name: 'process-payment', execute: chargePayment, compensate: refundPayment },
|
||||
],
|
||||
data: {},
|
||||
status: 'pending',
|
||||
});
|
||||
```
|
||||
|
||||
### Saga Choreography Pattern
|
||||
|
||||
In choreography, services react to events without a central coordinator:
|
||||
|
||||
```typescript
|
||||
// Services react to events
|
||||
eventConsumer.on('order.created', async (event) => {
|
||||
await inventoryService.reserve(event.data.items);
|
||||
await eventPublisher.publish('inventory.reserved', {...});
|
||||
});
|
||||
```
|
||||
|
||||
#### Saga Choreography Flow
|
||||
|
||||
The following diagram shows how services coordinate through events in a choreography pattern:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant OrderService
|
||||
participant EventBus
|
||||
participant InventoryService
|
||||
participant PaymentService
|
||||
participant NotificationService
|
||||
|
||||
OrderService->>EventBus: Publish order.created
|
||||
EventBus->>InventoryService: order.created event
|
||||
InventoryService->>InventoryService: Reserve Inventory
|
||||
InventoryService->>EventBus: Publish inventory.reserved
|
||||
EventBus->>PaymentService: inventory.reserved event
|
||||
PaymentService->>PaymentService: Process Payment
|
||||
PaymentService->>EventBus: Publish payment.processed
|
||||
EventBus->>NotificationService: payment.processed event
|
||||
NotificationService->>NotificationService: Send Confirmation
|
||||
|
||||
Note over InventoryService,PaymentService: If payment fails,<br/>compensation events are published
|
||||
PaymentService->>EventBus: Publish payment.failed
|
||||
EventBus->>InventoryService: payment.failed event
|
||||
InventoryService->>InventoryService: Release Inventory
|
||||
EventBus->>OrderService: payment.failed event
|
||||
OrderService->>OrderService: Cancel Order
|
||||
```
|
||||
|
||||
### Idempotency
|
||||
|
||||
Idempotency ensures operations can be safely retried without side effects:
|
||||
|
||||
```typescript
|
||||
// Execute operation with idempotency check
|
||||
await idempotencyHandler.execute(
|
||||
idempotencyKey,
|
||||
async () => await userService.create(data)
|
||||
);
|
||||
```
|
||||
|
||||
#### Idempotency Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Request[Client Request] --> Check{Idempotency Key<br/>Exists?}
|
||||
Check -->|Yes| Return[Return Cached Result]
|
||||
Check -->|No| Execute[Execute Operation]
|
||||
Execute --> Store[Store Result with Key]
|
||||
Store --> Return2[Return Result]
|
||||
|
||||
Return --> Client[Client Response]
|
||||
Return2 --> Client
|
||||
|
||||
style Check fill:#fff3cd
|
||||
style Return fill:#d4edda
|
||||
style Return2 fill:#d4edda
|
||||
```
|
||||
|
||||
### Optimistic Locking
|
||||
|
||||
Optimistic locking prevents lost updates using version fields:
|
||||
|
||||
```typescript
|
||||
// Update with version check
|
||||
await optimisticLockService.updateWithLock(
|
||||
repository,
|
||||
id,
|
||||
(current) => ({ ...current, name: newName })
|
||||
);
|
||||
```
|
||||
|
||||
#### Optimistic Locking Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client1
|
||||
participant Client2
|
||||
participant Service
|
||||
participant DB[(Database)]
|
||||
|
||||
Client1->>Service: Read Entity (version=1)
|
||||
Service->>DB: SELECT * WHERE id=123
|
||||
DB-->>Service: Entity (version=1)
|
||||
Service-->>Client1: Entity Data
|
||||
|
||||
Client2->>Service: Read Entity (version=1)
|
||||
Service->>DB: SELECT * WHERE id=123
|
||||
DB-->>Service: Entity (version=1)
|
||||
Service-->>Client2: Entity Data
|
||||
|
||||
Client1->>Service: Update (version=1)
|
||||
Service->>DB: UPDATE WHERE id=123 AND version=1
|
||||
DB-->>Service: Success (version=2)
|
||||
|
||||
Client2->>Service: Update (version=1)
|
||||
Service->>DB: UPDATE WHERE id=123 AND version=1
|
||||
DB-->>Service: Conflict (version=2 exists)
|
||||
Service-->>Client2: OptimisticLockError
|
||||
Note over Client2: Retry with new version
|
||||
```
|
||||
|
||||
### CQRS Pattern
|
||||
|
||||
Command Query Responsibility Segregation separates read and write operations for optimized performance and scalability.
|
||||
|
||||
#### CQRS Architecture Flow
|
||||
|
||||
The following diagram illustrates how CQRS separates write and read paths:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph WritePath[Write Path]
|
||||
Command[Command] --> WriteModel[Write Model<br/>Normalized]
|
||||
WriteModel --> Event[Domain Event]
|
||||
Event --> EventStore[(Event Store)]
|
||||
end
|
||||
|
||||
subgraph ReadPath[Read Path]
|
||||
Query[Query] --> ReadModel[Read Model<br/>Denormalized]
|
||||
ReadModel --> Response[Query Response]
|
||||
end
|
||||
|
||||
subgraph Sync[Eventual Consistency]
|
||||
EventStore --> EventHandler[Event Handler]
|
||||
EventHandler --> Projection[Projection]
|
||||
Projection --> ReadModel
|
||||
end
|
||||
|
||||
style WritePath fill:#e1f5ff
|
||||
style ReadPath fill:#d4edda
|
||||
style Sync fill:#fff3cd
|
||||
style EventStore fill:#f8d7da
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Write path: Command handler
|
||||
await commandHandler.handle({
|
||||
type: 'CREATE_ORDER',
|
||||
data: { userId, items }
|
||||
});
|
||||
|
||||
// Read path: Optimized query
|
||||
const orders = await readModel.findOrdersByUser(userId);
|
||||
```
|
||||
|
||||
### Outbox Pattern
|
||||
|
||||
The Outbox pattern ensures reliable event publishing by storing events in the same database transaction as business data.
|
||||
|
||||
#### Outbox Pattern Flow
|
||||
|
||||
This diagram shows how the Outbox pattern guarantees event delivery:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Service
|
||||
participant DB[(Database)]
|
||||
participant OutboxTable[(Outbox Table)]
|
||||
participant Processor[Outbox Processor]
|
||||
participant EventBus[Event Bus]
|
||||
|
||||
Client->>Service: Business Operation
|
||||
Service->>DB: Begin Transaction
|
||||
Service->>DB: Update Business Data
|
||||
Service->>OutboxTable: Insert Event (same transaction)
|
||||
Service->>DB: Commit Transaction
|
||||
|
||||
Note over Service,OutboxTable: Event stored atomically<br/>with business data
|
||||
|
||||
Processor->>OutboxTable: Poll for Unpublished Events
|
||||
OutboxTable-->>Processor: Return Events
|
||||
Processor->>EventBus: Publish Event
|
||||
EventBus-->>Processor: Publish Confirmed
|
||||
Processor->>OutboxTable: Mark as Published
|
||||
|
||||
Note over Processor,EventBus: Background processor<br/>ensures delivery
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Execute business operation and store event in same transaction
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Business operation
|
||||
await tx.order.create({ data: orderData });
|
||||
|
||||
// Store event in outbox (same transaction)
|
||||
await tx.outboxEvent.create({
|
||||
data: {
|
||||
eventType: 'order.created',
|
||||
payload: orderData,
|
||||
status: 'pending'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Background processor publishes events from outbox
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Design Compensations**: Every step needs compensation
|
||||
2. **Idempotent Steps**: Make steps idempotent for retries
|
||||
3. **Conflict Resolution**: Define resolution strategies
|
||||
4. **Monitoring**: Track saga execution and consistency lag
|
||||
5. **Read Models**: Use separate read models for queries (CQRS)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Saga Pattern](https://microservices.io/patterns/data/saga.html)
|
||||
- [Event-Driven Architecture](./event-driven-architecture.md)
|
||||
- [Error Handling Patterns](./error-handling-patterns.md)
|
||||
- Skill Source: `.cursor/skills/data-consistency-patterns/SKILL.md`
|
||||
@@ -1,571 +0,0 @@
|
||||
---
|
||||
name: database-prisma
|
||||
description: Prisma ORM and database patterns for GoodGo microservices. Use when working with databases, creating Prisma schemas, writing migrations, implementing repositories, or optimizing queries.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
## Prisma Setup
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install @prisma/client prisma
|
||||
npm install --save-dev @types/node
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```typescript
|
||||
// prisma/schema.prisma
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// Base model with common fields
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String?
|
||||
password String
|
||||
role Role @default(USER)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
posts Post[]
|
||||
profile Profile?
|
||||
|
||||
// Indexes for performance
|
||||
@@index([email])
|
||||
@@index([createdAt])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
content String?
|
||||
published Boolean @default(false)
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([authorId])
|
||||
@@index([published, createdAt])
|
||||
@@map("posts")
|
||||
}
|
||||
|
||||
model Profile {
|
||||
id String @id @default(cuid())
|
||||
bio String?
|
||||
avatar String?
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@map("profiles")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
USER
|
||||
ADMIN
|
||||
MODERATOR
|
||||
}
|
||||
```
|
||||
|
||||
### Schema Relationships
|
||||
|
||||
The following diagram illustrates the relationships between User, Post, and Profile models:
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
User ||--o{ Post : "has many"
|
||||
User ||--o| Profile : "has one"
|
||||
|
||||
User {
|
||||
string id PK
|
||||
string email UK
|
||||
string name
|
||||
string password
|
||||
enum role
|
||||
datetime createdAt
|
||||
datetime updatedAt
|
||||
}
|
||||
|
||||
Post {
|
||||
string id PK
|
||||
string title
|
||||
string content
|
||||
boolean published
|
||||
string authorId FK
|
||||
datetime createdAt
|
||||
datetime updatedAt
|
||||
}
|
||||
|
||||
Profile {
|
||||
string id PK
|
||||
string bio
|
||||
string avatar
|
||||
string userId FK,UK
|
||||
}
|
||||
```
|
||||
|
||||
## Database Connection
|
||||
|
||||
```typescript
|
||||
// src/lib/prisma.ts
|
||||
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', 'warn']
|
||||
: ['error'],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
|
||||
// Middleware for soft delete
|
||||
prisma.$use(async (params, next) => {
|
||||
if (params.model && params.action === 'delete') {
|
||||
return next({
|
||||
...params,
|
||||
action: 'update',
|
||||
args: {
|
||||
...params.args,
|
||||
data: { deletedAt: new Date() }
|
||||
}
|
||||
});
|
||||
}
|
||||
return next(params);
|
||||
});
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
```typescript
|
||||
// src/repositories/base.repository.ts
|
||||
export abstract class BaseRepository<T> {
|
||||
constructor(protected prisma: PrismaClient) {}
|
||||
|
||||
abstract findById(id: string): Promise<T | null>;
|
||||
abstract findAll(options?: any): Promise<T[]>;
|
||||
abstract create(data: any): Promise<T>;
|
||||
abstract update(id: string, data: any): Promise<T>;
|
||||
abstract delete(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
// src/repositories/user.repository.ts
|
||||
export class UserRepository extends BaseRepository<User> {
|
||||
async findById(id: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: { profile: true }
|
||||
});
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(options: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
sortBy?: string;
|
||||
order?: 'asc' | 'desc';
|
||||
} = {}): Promise<{ data: User[]; total: number }> {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search,
|
||||
sortBy = 'createdAt',
|
||||
order = 'desc'
|
||||
} = options;
|
||||
|
||||
const where = search ? {
|
||||
OR: [
|
||||
{ email: { contains: search, mode: 'insensitive' } },
|
||||
{ name: { contains: search, mode: 'insensitive' } }
|
||||
]
|
||||
} : {};
|
||||
|
||||
const [data, total] = await Promise.all([
|
||||
this.prisma.user.findMany({
|
||||
where,
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
orderBy: { [sortBy]: order },
|
||||
include: { profile: true }
|
||||
}),
|
||||
this.prisma.user.count({ where })
|
||||
]);
|
||||
|
||||
return { data, total };
|
||||
}
|
||||
|
||||
async create(data: CreateUserDto): Promise<User> {
|
||||
return this.prisma.user.create({
|
||||
data: {
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
name: data.name,
|
||||
profile: data.bio ? {
|
||||
create: { bio: data.bio }
|
||||
} : undefined
|
||||
},
|
||||
include: { profile: true }
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: string, data: UpdateUserDto): Promise<User> {
|
||||
return this.prisma.user.update({
|
||||
where: { id },
|
||||
data,
|
||||
include: { profile: true }
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.prisma.user.delete({
|
||||
where: { id }
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transactions
|
||||
|
||||
```typescript
|
||||
// Transaction example
|
||||
export class TransferService {
|
||||
async transferFunds(
|
||||
fromAccountId: string,
|
||||
toAccountId: string,
|
||||
amount: number
|
||||
) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// Check balance
|
||||
const fromAccount = await tx.account.findUnique({
|
||||
where: { id: fromAccountId }
|
||||
});
|
||||
|
||||
if (!fromAccount || fromAccount.balance < amount) {
|
||||
throw new Error('Insufficient funds');
|
||||
}
|
||||
|
||||
// Deduct from sender
|
||||
const updatedFrom = await tx.account.update({
|
||||
where: { id: fromAccountId },
|
||||
data: { balance: { decrement: amount } }
|
||||
});
|
||||
|
||||
// Add to receiver
|
||||
const updatedTo = await tx.account.update({
|
||||
where: { id: toAccountId },
|
||||
data: { balance: { increment: amount } }
|
||||
});
|
||||
|
||||
// Create transaction record
|
||||
const transaction = await tx.transaction.create({
|
||||
data: {
|
||||
fromAccountId,
|
||||
toAccountId,
|
||||
amount,
|
||||
type: 'TRANSFER',
|
||||
status: 'COMPLETED'
|
||||
}
|
||||
});
|
||||
|
||||
return transaction;
|
||||
}, {
|
||||
maxWait: 5000,
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
### Migration Workflow
|
||||
|
||||
The following diagram shows the typical migration workflow from development to production:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Migration]) --> EditSchema[Edit schema.prisma]
|
||||
EditSchema --> CreateMigration[Run: prisma migrate dev]
|
||||
CreateMigration --> GenerateSQL[Prisma generates SQL]
|
||||
GenerateSQL --> ReviewSQL{Review SQL?}
|
||||
ReviewSQL -->|Yes| CheckSQL[Check migration SQL]
|
||||
ReviewSQL -->|No| ApplyDev[Apply to dev database]
|
||||
CheckSQL --> ApplyDev
|
||||
ApplyDev --> GenerateClient[Generate Prisma Client]
|
||||
GenerateClient --> TestDev[Test in development]
|
||||
TestDev --> TestPass{Tests pass?}
|
||||
TestPass -->|No| FixIssues[Fix issues]
|
||||
FixIssues --> EditSchema
|
||||
TestPass -->|Yes| CommitMigration[Commit migration files]
|
||||
CommitMigration --> DeployProd[Deploy to production]
|
||||
DeployProd --> RunDeploy[Run: prisma migrate deploy]
|
||||
RunDeploy --> End([Migration Complete])
|
||||
|
||||
style Start fill:#e1f5e1
|
||||
style End fill:#e1f5e1
|
||||
style TestPass fill:#fff4e1
|
||||
style ReviewSQL fill:#fff4e1
|
||||
```
|
||||
|
||||
```bash
|
||||
# Create migration
|
||||
npx prisma migrate dev --name add_user_table
|
||||
|
||||
# Apply migrations
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Reset database
|
||||
npx prisma migrate reset
|
||||
|
||||
# Generate Prisma Client
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
### Migration Files
|
||||
|
||||
```sql
|
||||
-- migrations/20240101000000_add_user_table/migration.sql
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"password" TEXT NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'USER',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
CREATE INDEX "users_createdAt_idx" ON "users"("createdAt");
|
||||
```
|
||||
|
||||
## Query Optimization
|
||||
|
||||
### Query Execution Flow
|
||||
|
||||
The following sequence diagram illustrates how Prisma queries flow from the application layer to the database:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant Repo as Repository
|
||||
participant Client as Prisma Client
|
||||
participant Pool as Connection Pool
|
||||
participant DB as PostgreSQL
|
||||
|
||||
App->>Repo: findById(id)
|
||||
Repo->>Client: prisma.user.findUnique()
|
||||
Client->>Client: Validate query
|
||||
Client->>Client: Generate SQL
|
||||
Client->>Pool: Request connection
|
||||
Pool->>DB: Execute SQL query
|
||||
DB-->>Pool: Return results
|
||||
Pool-->>Client: Return data
|
||||
Client->>Client: Transform to TypeScript types
|
||||
Client-->>Repo: Return typed result
|
||||
Repo-->>App: Return User | null
|
||||
|
||||
Note over App,DB: Prisma ensures type safety<br/>throughout the flow
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Optimized queries
|
||||
export class OptimizedUserRepository {
|
||||
// Select only needed fields
|
||||
async findUsersLight() {
|
||||
return this.prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Use pagination
|
||||
async findPaginated(cursor?: string) {
|
||||
return this.prisma.user.findMany({
|
||||
take: 10,
|
||||
skip: cursor ? 1 : 0,
|
||||
cursor: cursor ? { id: cursor } : undefined,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
|
||||
// Batch operations
|
||||
async createMany(users: CreateUserDto[]) {
|
||||
return this.prisma.user.createMany({
|
||||
data: users,
|
||||
skipDuplicates: true
|
||||
});
|
||||
}
|
||||
|
||||
// Use raw SQL for complex queries
|
||||
async getStatistics() {
|
||||
return this.prisma.$queryRaw`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN role = 'ADMIN' THEN 1 END) as admins,
|
||||
COUNT(CASE WHEN created_at > NOW() - INTERVAL '30 days' THEN 1 END) as new_users
|
||||
FROM users
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Seeding
|
||||
|
||||
```typescript
|
||||
// prisma/seed.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// Create admin user
|
||||
const adminPassword = await bcrypt.hash('admin123', 10);
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: 'admin@goodgo.com' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'admin@goodgo.com',
|
||||
name: 'Admin User',
|
||||
password: adminPassword,
|
||||
role: 'ADMIN'
|
||||
}
|
||||
});
|
||||
|
||||
// Create test users
|
||||
const testUsers = Array.from({ length: 10 }, (_, i) => ({
|
||||
email: `user${i}@example.com`,
|
||||
name: `Test User ${i}`,
|
||||
password: bcrypt.hashSync('password123', 10)
|
||||
}));
|
||||
|
||||
await prisma.user.createMany({
|
||||
data: testUsers,
|
||||
skipDuplicates: true
|
||||
});
|
||||
|
||||
console.log('Database seeded successfully');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(() => prisma.$disconnect());
|
||||
```
|
||||
|
||||
## Neon PostgreSQL Configuration
|
||||
|
||||
```typescript
|
||||
// .env
|
||||
DATABASE_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require"
|
||||
|
||||
// Connection pooling for serverless
|
||||
DIRECT_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require"
|
||||
```
|
||||
|
||||
## Testing with Prisma
|
||||
|
||||
```typescript
|
||||
// __tests__/user.repository.test.ts
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
jest.mock('../src/lib/prisma', () => ({
|
||||
__esModule: true,
|
||||
prisma: mockDeep<PrismaClient>()
|
||||
}));
|
||||
|
||||
describe('UserRepository', () => {
|
||||
beforeEach(() => {
|
||||
mockReset(prismaMock);
|
||||
});
|
||||
|
||||
it('should create user', async () => {
|
||||
const user = { id: '1', email: 'test@example.com' };
|
||||
prismaMock.user.create.mockResolvedValue(user);
|
||||
|
||||
const result = await repository.create({
|
||||
email: 'test@example.com',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Schema Design**
|
||||
- Use appropriate field types
|
||||
- Add indexes for frequently queried fields
|
||||
- Use relations instead of storing JSON
|
||||
- Implement soft deletes when needed
|
||||
|
||||
2. **Performance**
|
||||
- Use select to fetch only needed fields
|
||||
- Implement pagination for large datasets
|
||||
- Use connection pooling
|
||||
- Cache frequently accessed data
|
||||
|
||||
3. **Security**
|
||||
- Never expose sensitive fields
|
||||
- Use parameterized queries
|
||||
- Validate input before database operations
|
||||
- Implement row-level security
|
||||
|
||||
4. **Maintenance**
|
||||
- Keep migrations small and focused
|
||||
- Test migrations before production
|
||||
- Backup before major changes
|
||||
- Monitor query performance
|
||||
@@ -1,618 +0,0 @@
|
||||
---
|
||||
name: deployment-kubernetes
|
||||
description: Kubernetes deployment patterns for GoodGo microservices. Use when deploying to staging/production, creating K8s manifests, configuring HPA, setting up ingress, or troubleshooting K8s deployments.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
### Kubernetes Architecture
|
||||
|
||||
The following diagram illustrates the key Kubernetes components and their relationships in a typical GoodGo service deployment:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph External["External Traffic"]
|
||||
Client[Client Request]
|
||||
end
|
||||
|
||||
subgraph IngressLayer["Ingress Layer"]
|
||||
Ingress[Ingress<br/>api.goodgo.com]
|
||||
end
|
||||
|
||||
subgraph ServiceLayer["Service Layer"]
|
||||
Service[Service<br/>ClusterIP]
|
||||
end
|
||||
|
||||
subgraph DeploymentLayer["Deployment Layer"]
|
||||
Deployment[Deployment<br/>iam-service]
|
||||
HPA[HorizontalPodAutoscaler<br/>HPA]
|
||||
end
|
||||
|
||||
subgraph PodLayer["Pod Layer"]
|
||||
Pod1[Pod 1<br/>Container]
|
||||
Pod2[Pod 2<br/>Container]
|
||||
Pod3[Pod 3<br/>Container]
|
||||
end
|
||||
|
||||
subgraph ConfigLayer["Configuration Layer"]
|
||||
ConfigMap[ConfigMap<br/>app-config]
|
||||
Secret[Secret<br/>database-secrets]
|
||||
end
|
||||
|
||||
Client -->|HTTPS| Ingress
|
||||
Ingress -->|Route /auth| Service
|
||||
Service -->|Load Balance| Pod1
|
||||
Service -->|Load Balance| Pod2
|
||||
Service -->|Load Balance| Pod3
|
||||
|
||||
Deployment -->|Manages| Pod1
|
||||
Deployment -->|Manages| Pod2
|
||||
Deployment -->|Manages| Pod3
|
||||
|
||||
HPA -->|Scales| Deployment
|
||||
|
||||
Pod1 -->|Reads| ConfigMap
|
||||
Pod2 -->|Reads| ConfigMap
|
||||
Pod3 -->|Reads| ConfigMap
|
||||
|
||||
Pod1 -->|Reads| Secret
|
||||
Pod2 -->|Reads| Secret
|
||||
Pod3 -->|Reads| Secret
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### Pod Lifecycle
|
||||
|
||||
Pods go through various states during their lifecycle. Health checks (liveness and readiness probes) determine pod availability:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Pending: Pod Created
|
||||
|
||||
Pending --> ContainerCreating: Scheduler Assigned
|
||||
ContainerCreating --> Running: Containers Started
|
||||
|
||||
Running --> Running: Liveness Check Pass
|
||||
Running --> Restarting: Liveness Check Fail (3x)
|
||||
Restarting --> Running: Container Restarted
|
||||
|
||||
Running --> Ready: Readiness Check Pass
|
||||
Ready --> Running: Readiness Check Fail (3x)
|
||||
|
||||
Ready --> Terminating: Pod Deleted
|
||||
Terminating --> [*]: Cleanup Complete
|
||||
|
||||
note right of Ready
|
||||
Pod receives traffic
|
||||
from Service
|
||||
end note
|
||||
|
||||
note right of Running
|
||||
Liveness probe checks
|
||||
if container is alive
|
||||
end note
|
||||
|
||||
note right of Restarting
|
||||
Container restarted
|
||||
after 3 failures
|
||||
end note
|
||||
```
|
||||
|
||||
### Service Discovery Flow
|
||||
|
||||
Kubernetes provides built-in service discovery through DNS. The following diagram shows how requests flow from external clients to pods:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Ingress
|
||||
participant Service
|
||||
participant Pod1
|
||||
participant Pod2
|
||||
participant Pod3
|
||||
|
||||
Client->>Ingress: HTTPS Request<br/>api.goodgo.com/auth/login
|
||||
Ingress->>Ingress: TLS Termination
|
||||
Ingress->>Ingress: Path Routing<br/>/auth → iam-service
|
||||
|
||||
Ingress->>Service: HTTP Request<br/>iam-service:80
|
||||
Service->>Service: DNS Resolution<br/>iam-service.goodgo.svc.cluster.local
|
||||
|
||||
Service->>Service: Endpoint Selection<br/>Load Balancing
|
||||
|
||||
alt Pod1 Selected
|
||||
Service->>Pod1: Forward Request
|
||||
Pod1->>Pod1: Process Request
|
||||
Pod1->>Service: Response
|
||||
else Pod2 Selected
|
||||
Service->>Pod2: Forward Request
|
||||
Pod2->>Pod2: Process Request
|
||||
Pod2->>Service: Response
|
||||
else Pod3 Selected
|
||||
Service->>Pod3: Forward Request
|
||||
Pod3->>Pod3: Process Request
|
||||
Pod3->>Service: Response
|
||||
end
|
||||
|
||||
Service->>Ingress: Response
|
||||
Ingress->>Client: HTTPS Response
|
||||
```
|
||||
|
||||
## Service Deployment Manifest
|
||||
|
||||
```yaml
|
||||
# kubernetes/iam-service.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: goodgo
|
||||
labels:
|
||||
app: iam-service
|
||||
version: v1
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: iam-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: iam-service
|
||||
version: v1
|
||||
spec:
|
||||
containers:
|
||||
- name: iam-service
|
||||
image: goodgo/iam-service:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-secrets
|
||||
key: url
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: auth-secrets
|
||||
key: jwt-secret
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: redis-config
|
||||
key: url
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: goodgo
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: iam-service
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
## Horizontal Pod Autoscaler
|
||||
|
||||
```yaml
|
||||
# kubernetes/hpa.yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: iam-service-hpa
|
||||
namespace: goodgo
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: iam-service
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 0
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 100
|
||||
periodSeconds: 15
|
||||
```
|
||||
|
||||
## ConfigMap & Secrets
|
||||
|
||||
```yaml
|
||||
# kubernetes/configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
namespace: goodgo
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
LOG_LEVEL: "info"
|
||||
REDIS_URL: "redis://redis-service:6379"
|
||||
METRICS_ENABLED: "true"
|
||||
|
||||
---
|
||||
# kubernetes/secrets.yaml (example - use sealed-secrets in production)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: database-secrets
|
||||
namespace: goodgo
|
||||
type: Opaque
|
||||
stringData:
|
||||
url: "postgresql://user:pass@postgres:5432/db"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: auth-secrets
|
||||
namespace: goodgo
|
||||
type: Opaque
|
||||
stringData:
|
||||
jwt-secret: "your-secret-key"
|
||||
refresh-secret: "your-refresh-secret"
|
||||
```
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
```yaml
|
||||
# kubernetes/ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
namespace: goodgo
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- api.goodgo.com
|
||||
secretName: api-tls-secret
|
||||
rules:
|
||||
- host: api.goodgo.com
|
||||
http:
|
||||
paths:
|
||||
- path: /auth
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 80
|
||||
- path: /users
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: user-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
## Database Deployment (Development Only)
|
||||
|
||||
```yaml
|
||||
# kubernetes/postgres.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: goodgo
|
||||
spec:
|
||||
serviceName: postgres
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: goodgo
|
||||
- name: POSTGRES_USER
|
||||
value: postgres
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secret
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: postgres-storage
|
||||
mountPath: /var/lib/postgresql/data
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: postgres-storage
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
```
|
||||
|
||||
## Deployment Scripts
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/deploy-k8s.sh
|
||||
|
||||
# Set namespace
|
||||
NAMESPACE="goodgo"
|
||||
ENVIRONMENT="${1:-staging}"
|
||||
|
||||
# Create namespace if not exists
|
||||
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Apply configurations
|
||||
echo "Applying ConfigMaps..."
|
||||
kubectl apply -f kubernetes/configmap-$ENVIRONMENT.yaml
|
||||
|
||||
echo "Applying Secrets..."
|
||||
kubectl apply -f kubernetes/secrets-$ENVIRONMENT.yaml
|
||||
|
||||
echo "Deploying services..."
|
||||
kubectl apply -f kubernetes/iam-service.yaml
|
||||
kubectl apply -f kubernetes/user-service.yaml
|
||||
|
||||
echo "Configuring autoscaling..."
|
||||
kubectl apply -f kubernetes/hpa.yaml
|
||||
|
||||
echo "Setting up ingress..."
|
||||
kubectl apply -f kubernetes/ingress.yaml
|
||||
|
||||
# Wait for rollout
|
||||
kubectl rollout status deployment/iam-service -n $NAMESPACE
|
||||
kubectl rollout status deployment/user-service -n $NAMESPACE
|
||||
|
||||
echo "Deployment complete!"
|
||||
```
|
||||
|
||||
## Health Check Implementation
|
||||
|
||||
```typescript
|
||||
// src/modules/health/health.controller.ts
|
||||
export class HealthController {
|
||||
constructor(
|
||||
private prisma: PrismaClient,
|
||||
private redis: Redis
|
||||
) {}
|
||||
|
||||
// Liveness probe - is the service alive?
|
||||
async liveness(req: Request, res: Response) {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
}
|
||||
|
||||
// Readiness probe - is the service ready to accept traffic?
|
||||
async readiness(req: Request, res: Response) {
|
||||
try {
|
||||
// Check database connection
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
|
||||
// Check Redis connection
|
||||
await this.redis.ping();
|
||||
|
||||
res.status(200).json({
|
||||
status: 'ready',
|
||||
checks: {
|
||||
database: 'ok',
|
||||
redis: 'ok'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(503).json({
|
||||
status: 'not ready',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring with Prometheus
|
||||
|
||||
```yaml
|
||||
# kubernetes/servicemonitor.yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: iam-service-monitor
|
||||
namespace: goodgo
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: iam-service
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Deploy to staging
|
||||
kubectl apply -f kubernetes/ -n goodgo-staging
|
||||
|
||||
# Check deployment status
|
||||
kubectl get deployments -n goodgo
|
||||
kubectl get pods -n goodgo
|
||||
kubectl get svc -n goodgo
|
||||
|
||||
# View logs
|
||||
kubectl logs -f deployment/iam-service -n goodgo
|
||||
kubectl logs -f pod-name -n goodgo --tail=100
|
||||
|
||||
# Scale manually
|
||||
kubectl scale deployment iam-service --replicas=5 -n goodgo
|
||||
|
||||
# Update image
|
||||
kubectl set image deployment/iam-service iam-service=goodgo/iam-service:v1.2.3 -n goodgo
|
||||
|
||||
# Rollback
|
||||
kubectl rollout undo deployment/iam-service -n goodgo
|
||||
|
||||
# Port forward for debugging
|
||||
kubectl port-forward service/iam-service 3000:80 -n goodgo
|
||||
|
||||
# Execute command in pod
|
||||
kubectl exec -it pod-name -n goodgo -- /bin/sh
|
||||
|
||||
# View HPA status
|
||||
kubectl get hpa -n goodgo
|
||||
kubectl describe hpa iam-service-hpa -n goodgo
|
||||
|
||||
# View resource usage
|
||||
kubectl top nodes
|
||||
kubectl top pods -n goodgo
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pod Not Starting
|
||||
|
||||
```bash
|
||||
# Check pod status
|
||||
kubectl describe pod pod-name -n goodgo
|
||||
|
||||
# Check events
|
||||
kubectl get events -n goodgo --sort-by='.lastTimestamp'
|
||||
|
||||
# Check logs
|
||||
kubectl logs pod-name -n goodgo --previous
|
||||
```
|
||||
|
||||
### ImagePullBackOff
|
||||
|
||||
```bash
|
||||
# Check image name and tag
|
||||
kubectl describe pod pod-name -n goodgo | grep -i image
|
||||
|
||||
# Check image pull secrets
|
||||
kubectl get secrets -n goodgo
|
||||
```
|
||||
|
||||
### CrashLoopBackOff
|
||||
|
||||
```bash
|
||||
# Check logs of crashed container
|
||||
kubectl logs pod-name -n goodgo --previous
|
||||
|
||||
# Check resource limits
|
||||
kubectl describe pod pod-name -n goodgo | grep -A 5 Limits
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Resource Management**
|
||||
- Always set resource requests and limits
|
||||
- Monitor actual usage and adjust accordingly
|
||||
- Use HPA for automatic scaling
|
||||
|
||||
2. **Configuration**
|
||||
- Use ConfigMaps for non-sensitive config
|
||||
- Use Secrets for sensitive data
|
||||
- Never hardcode configuration in images
|
||||
|
||||
3. **Health Checks**
|
||||
- Implement both liveness and readiness probes
|
||||
- Set appropriate timeouts and thresholds
|
||||
- Include dependency checks in readiness probe
|
||||
|
||||
4. **Deployment**
|
||||
- Use rolling updates for zero-downtime
|
||||
- Set maxSurge and maxUnavailable appropriately
|
||||
- Test deployments in staging first
|
||||
|
||||
5. **Security**
|
||||
- Run containers as non-root user
|
||||
- Use network policies to restrict traffic
|
||||
- Regularly update base images
|
||||
- Use sealed-secrets or external secret manager
|
||||
|
||||
6. **Monitoring**
|
||||
- Expose metrics endpoint
|
||||
- Set up alerts for critical issues
|
||||
- Monitor resource usage and performance
|
||||
@@ -1,507 +0,0 @@
|
||||
---
|
||||
name: documentation
|
||||
description: Guidelines for writing technical documentation in the GoodGo project. Use when creating or updating README files, guides, architecture docs, or API documentation. Ensures bilingual (EN/VI) consistency and proper structure.
|
||||
---
|
||||
|
||||
# Documentation Writing Guidelines
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
The project follows a structured documentation hierarchy organized by location and content type:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
### Documentation Structure Diagram
|
||||
|
||||
The following diagram illustrates where different types of documentation should be placed:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start[Documentation Need] --> TypeDecision{Documentation Type?}
|
||||
|
||||
TypeDecision -->|Project-Level| ProjectDocs[Project-Level Documentation]
|
||||
TypeDecision -->|Service/Package| ServiceDocs[Service/Package Documentation]
|
||||
TypeDecision -->|Deployment| DeployDocs[Deployment Documentation]
|
||||
TypeDecision -->|Infrastructure| InfraDocs[Infrastructure Documentation]
|
||||
|
||||
ProjectDocs --> ProjectLoc["docs/en/<br/>docs/vi/"]
|
||||
ProjectLoc --> ProjectSub{Content Type?}
|
||||
ProjectSub -->|Guides| GuidesLoc["guides/<br/>(getting-started.md,<br/>deployment.md)"]
|
||||
ProjectSub -->|Architecture| ArchLoc["architecture/<br/>(system-design.md)"]
|
||||
ProjectSub -->|API Specs| APILoc["api/openapi/<br/>(*.yaml)"]
|
||||
ProjectSub -->|Runbooks| RunbookLoc["runbooks/<br/>(incident-response.md)"]
|
||||
|
||||
ServiceDocs --> ServiceLoc["services/[name]/README.md<br/>packages/[name]/README.md"]
|
||||
ServiceLoc --> ServiceFormat[Format: Side-by-side bilingual]
|
||||
|
||||
DeployDocs --> DeployLoc["deployments/[env]/README.md"]
|
||||
DeployLoc --> DeployFormat[Format: Technical, operations-focused]
|
||||
|
||||
InfraDocs --> InfraLoc["infra/[component]/README.md"]
|
||||
InfraLoc --> InfraFormat[Format: Side-by-side bilingual]
|
||||
|
||||
style ProjectDocs fill:#e1f5ff
|
||||
style ServiceDocs fill:#fff4e1
|
||||
style DeployDocs fill:#ffe1f5
|
||||
style InfraDocs fill:#e1ffe1
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
### Bilingual Format Decision Flow
|
||||
|
||||
Use the following decision tree to choose the appropriate bilingual format:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Creating Documentation] --> CheckLength{Content Length?}
|
||||
|
||||
CheckLength -->|Short<br/>< 200 lines| CheckLocation{Document Location?}
|
||||
CheckLength -->|Long<br/>> 200 lines| SeparateFiles[Use Separate Files Format]
|
||||
|
||||
CheckLocation -->|README files<br/>Service/Package docs<br/>Infrastructure docs| SideBySide[Use Side-by-side Format]
|
||||
CheckLocation -->|docs/guides/<br/>Short configuration docs| SideBySide
|
||||
|
||||
CheckLength -->|Medium| CheckType{Content Type?}
|
||||
CheckType -->|API Documentation<br/>Technical Specifications| Sections[Use Sections Format]
|
||||
CheckType -->|Mixed Content| Sections
|
||||
|
||||
SeparateFiles --> SeparateAction["Create docs/en/[path]/file.md<br/>Create docs/vi/[path]/file.md<br/>(Mirror structure)"]
|
||||
SideBySide --> SideBySideAction["Single file with<br/>EN / VI inline<br/>Example: 'Title / Tiêu Đề'"]
|
||||
Sections --> SectionsAction["Single file with<br/>--- separator<br/>EN section then VI section"]
|
||||
|
||||
SeparateAction --> Done[Documentation Complete]
|
||||
SideBySideAction --> Done
|
||||
SectionsAction --> Done
|
||||
|
||||
style SideBySide fill:#e1f5ff
|
||||
style SeparateFiles fill:#fff4e1
|
||||
style Sections fill:#ffe1f5
|
||||
style Done fill:#e1ffe1
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
@@ -1,460 +0,0 @@
|
||||
---
|
||||
name: error-handling-patterns
|
||||
description: Error handling patterns and conventions for GoodGo microservices. Use when implementing error handling, creating custom error classes, handling exceptions, standardizing error responses, or debugging error scenarios.
|
||||
---
|
||||
|
||||
# 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 Propagation Flow
|
||||
|
||||
The following diagram illustrates how errors propagate through the application layers:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Request] --> B[Controller]
|
||||
B --> C[Service Layer]
|
||||
C --> D[Repository Layer]
|
||||
D --> E{Error Occurs?}
|
||||
E -->|Yes| F[Throw HttpError]
|
||||
E -->|No| G[Return Data]
|
||||
F --> H[Error Middleware]
|
||||
H --> I{Error Type?}
|
||||
I -->|HttpError| J[Extract Status/Code]
|
||||
I -->|Prisma Error| K[Map to HttpError]
|
||||
I -->|Zod Error| L[Map to ValidationError]
|
||||
I -->|Unknown| M[Map to InternalServerError]
|
||||
J --> N[Log Error]
|
||||
K --> N
|
||||
L --> N
|
||||
M --> N
|
||||
N --> O{Is Operational?}
|
||||
O -->|Yes| P[Log as Warning]
|
||||
O -->|No| Q[Log as Error]
|
||||
P --> R[Format Response]
|
||||
Q --> R
|
||||
R --> S{Is Production?}
|
||||
S -->|Yes & 5xx| T[Generic Message]
|
||||
S -->|No or < 5xx| U[Detailed Message]
|
||||
T --> V[Send Response]
|
||||
U --> V
|
||||
G --> V
|
||||
```
|
||||
|
||||
### Error Hierarchy Structure
|
||||
|
||||
The error class hierarchy shows the relationship between different error types:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Error {
|
||||
<<built-in>>
|
||||
+message: string
|
||||
+stack: string
|
||||
}
|
||||
class HttpError {
|
||||
+statusCode: number
|
||||
+errorCode: string
|
||||
+isOperational: boolean
|
||||
+details?: any
|
||||
+toApiResponse()
|
||||
}
|
||||
class NotFoundError {
|
||||
+statusCode: 404
|
||||
}
|
||||
class BadRequestError {
|
||||
+statusCode: 400
|
||||
}
|
||||
class ValidationError {
|
||||
+statusCode: 422
|
||||
}
|
||||
class UnauthorizedError {
|
||||
+statusCode: 401
|
||||
}
|
||||
class ForbiddenError {
|
||||
+statusCode: 403
|
||||
}
|
||||
class ConflictError {
|
||||
+statusCode: 409
|
||||
}
|
||||
class RateLimitError {
|
||||
+statusCode: 429
|
||||
}
|
||||
class InternalServerError {
|
||||
+statusCode: 500
|
||||
}
|
||||
class ServiceUnavailableError {
|
||||
+statusCode: 503
|
||||
}
|
||||
class DatabaseError {
|
||||
+statusCode: 500
|
||||
}
|
||||
class ExternalServiceError {
|
||||
+statusCode: 502
|
||||
}
|
||||
|
||||
Error <|-- HttpError
|
||||
HttpError <|-- NotFoundError
|
||||
HttpError <|-- BadRequestError
|
||||
HttpError <|-- ValidationError
|
||||
HttpError <|-- UnauthorizedError
|
||||
HttpError <|-- ForbiddenError
|
||||
HttpError <|-- ConflictError
|
||||
HttpError <|-- RateLimitError
|
||||
HttpError <|-- InternalServerError
|
||||
HttpError <|-- ServiceUnavailableError
|
||||
HttpError <|-- DatabaseError
|
||||
HttpError <|-- ExternalServiceError
|
||||
```
|
||||
|
||||
### Error Handling Decision Tree
|
||||
|
||||
Use this decision tree to determine which error class to use:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Error Occurs] --> B{Error Type?}
|
||||
B -->|Resource Not Found| C[NotFoundError<br/>404]
|
||||
B -->|Invalid Input| D{Validation?}
|
||||
B -->|Authentication| E{Type?}
|
||||
B -->|Resource Conflict| F[ConflictError<br/>409]
|
||||
B -->|Rate Limit| G[RateLimitError<br/>429]
|
||||
B -->|Database| H[DatabaseError<br/>500]
|
||||
B -->|External Service| I[ExternalServiceError<br/>502]
|
||||
B -->|Service Unavailable| J[ServiceUnavailableError<br/>503]
|
||||
B -->|Unknown/Programming| K[InternalServerError<br/>500]
|
||||
D -->|Schema Validation| L[ValidationError<br/>422]
|
||||
D -->|Bad Request Format| M[BadRequestError<br/>400]
|
||||
E -->|No Token/Invalid| N[UnauthorizedError<br/>401]
|
||||
E -->|No Permission| O[ForbiddenError<br/>403]
|
||||
C --> P[Set isOperational: true]
|
||||
L --> P
|
||||
M --> P
|
||||
N --> P
|
||||
O --> P
|
||||
F --> P
|
||||
G --> P
|
||||
H --> Q{Is Operational?}
|
||||
I --> Q
|
||||
J --> Q
|
||||
K --> R[Set isOperational: false]
|
||||
Q -->|Yes| P
|
||||
Q -->|No| R
|
||||
P --> S[Include Error Code]
|
||||
R --> S
|
||||
S --> T[Add Context Details]
|
||||
T --> U[Throw Error]
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
## 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
|
||||
@@ -1,452 +0,0 @@
|
||||
---
|
||||
name: event-driven-architecture
|
||||
description: Event-driven architecture patterns with Apache Kafka for GoodGo microservices. Use when implementing async communication, event publishing/consuming, event sourcing, CQRS, or integrating event streams with HTTP endpoints.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
**Request-Response (Synchronous):**
|
||||
- Client waits for response
|
||||
- Tight coupling between services
|
||||
- Blocking operations
|
||||
- Immediate consistency
|
||||
- Use Traefik API Gateway for HTTP/REST
|
||||
|
||||
**Event-Driven (Asynchronous):**
|
||||
- Fire-and-forget publishing
|
||||
- Loose coupling between services
|
||||
- Non-blocking operations
|
||||
- Eventual consistency
|
||||
- Use Kafka for message broker
|
||||
|
||||
### Kafka Fundamentals
|
||||
|
||||
- **Topics**: Named streams of events (e.g., `user.created`, `order.placed`)
|
||||
- **Partitions**: Physical division of topics for parallelism and scaling
|
||||
- **Consumer Groups**: Groups of consumers that work together to process events
|
||||
- **Producers**: Services that publish events to topics
|
||||
- **Consumers**: Services that subscribe to topics and process events
|
||||
|
||||
#### Consumer Groups Architecture
|
||||
|
||||
The following diagram illustrates how consumer groups distribute work across partitions:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Topic["Topic: user.created"]
|
||||
P0["Partition 0"]
|
||||
P1["Partition 1"]
|
||||
P2["Partition 2"]
|
||||
end
|
||||
|
||||
subgraph ConsumerGroup["Consumer Group: notification-service"]
|
||||
C1["Consumer 1"]
|
||||
C2["Consumer 2"]
|
||||
end
|
||||
|
||||
subgraph ConsumerGroup2["Consumer Group: analytics-service"]
|
||||
C3["Consumer 3"]
|
||||
C4["Consumer 4"]
|
||||
C5["Consumer 5"]
|
||||
end
|
||||
|
||||
P0 --> C1
|
||||
P1 --> C2
|
||||
P2 --> C1
|
||||
|
||||
P0 --> C3
|
||||
P1 --> C4
|
||||
P2 --> C5
|
||||
|
||||
style Topic fill:#e1f5ff
|
||||
style ConsumerGroup fill:#fff4e1
|
||||
style ConsumerGroup2 fill:#e8f5e9
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Each partition is consumed by only one consumer per consumer group
|
||||
- Multiple consumer groups can independently consume from the same topic
|
||||
- Consumers in a group automatically rebalance when members join or leave
|
||||
- More partitions enable better parallelism within a consumer group
|
||||
|
||||
### Traefik Integration
|
||||
|
||||
Traefik serves dual purpose:
|
||||
- **API Gateway**: Routes synchronous HTTP/REST requests
|
||||
- **Event Streaming Gateway**: Routes SSE/WebSocket connections to event streaming endpoints
|
||||
|
||||
Services publish events to Kafka, then expose SSE/WebSocket endpoints that consume from Kafka for HTTP clients.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Event Publishing
|
||||
|
||||
```typescript
|
||||
// src/core/events/event-publisher.ts
|
||||
import { producer } from '../config/kafka.config';
|
||||
import { logger } from '@goodgo/logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class EventPublisher {
|
||||
async publish<T extends BaseEvent>(
|
||||
topic: string,
|
||||
event: Omit<T, 'eventId' | 'timestamp' | 'source'>,
|
||||
options?: { partitionKey?: string }
|
||||
): Promise<void> {
|
||||
const fullEvent: T = {
|
||||
...event,
|
||||
eventId: uuidv4(),
|
||||
timestamp: new Date().toISOString(),
|
||||
source: this.serviceName,
|
||||
} as T;
|
||||
|
||||
await producer.send({
|
||||
topic,
|
||||
messages: [{
|
||||
key: options?.partitionKey || fullEvent.eventId,
|
||||
value: JSON.stringify(fullEvent),
|
||||
headers: {
|
||||
'event-type': event.eventType,
|
||||
'event-version': event.eventVersion,
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Event Publishing Flow
|
||||
|
||||
The following sequence diagram shows how events are published from a service to Kafka:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Layer
|
||||
participant Publisher as EventPublisher
|
||||
participant Kafka as Kafka Broker
|
||||
participant Topic as Topic Partition
|
||||
|
||||
Service->>Publisher: publish(topic, event, options)
|
||||
activate Publisher
|
||||
Publisher->>Publisher: Generate eventId
|
||||
Publisher->>Publisher: Add timestamp & source
|
||||
Publisher->>Publisher: Determine partition key
|
||||
Publisher->>Kafka: send({ topic, messages })
|
||||
activate Kafka
|
||||
Kafka->>Topic: Route to partition
|
||||
activate Topic
|
||||
Topic-->>Kafka: Acknowledge
|
||||
deactivate Topic
|
||||
Kafka-->>Publisher: Success
|
||||
deactivate Kafka
|
||||
Publisher-->>Service: Complete (fire-and-forget)
|
||||
deactivate Publisher
|
||||
Note over Service,Publisher: Non-blocking operation
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Publishing is asynchronous and non-blocking
|
||||
- Partition key determines which partition receives the event
|
||||
- Events are acknowledged by Kafka before completion
|
||||
- Fire-and-forget pattern prevents blocking request handlers
|
||||
|
||||
### Event Consuming
|
||||
|
||||
```typescript
|
||||
// src/core/events/event-consumer.ts
|
||||
import { kafka } from '../config/kafka.config';
|
||||
|
||||
export class EventConsumer {
|
||||
private handlers: Map<string, EventHandler[]> = new Map();
|
||||
|
||||
on<T extends BaseEvent>(eventType: string, handler: EventHandler<T>): void {
|
||||
if (!this.handlers.has(eventType)) {
|
||||
this.handlers.set(eventType, []);
|
||||
}
|
||||
this.handlers.get(eventType)!.push(handler);
|
||||
}
|
||||
|
||||
async start(topics: string[]): Promise<void> {
|
||||
await this.consumer.connect();
|
||||
await this.consumer.subscribe({ topics, fromBeginning: false });
|
||||
|
||||
await this.consumer.run({
|
||||
eachMessage: async ({ topic, partition, message }) => {
|
||||
const event: BaseEvent = JSON.parse(message.value?.toString() || '{}');
|
||||
const handlers = this.handlers.get(event.eventType) || [];
|
||||
await Promise.all(handlers.map(h => h.handle(event)));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Outbox Pattern for Transactional Publishing
|
||||
|
||||
The Outbox pattern ensures transactional consistency by storing events in the database within the same transaction as business data, then publishing them asynchronously.
|
||||
|
||||
#### Outbox Pattern Flow
|
||||
|
||||
The following sequence diagram illustrates the outbox pattern workflow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Layer
|
||||
participant DB as Database
|
||||
participant Outbox as Outbox Table
|
||||
participant Processor as Outbox Processor
|
||||
participant Publisher as EventPublisher
|
||||
participant Kafka as Kafka Broker
|
||||
|
||||
Service->>DB: Begin Transaction
|
||||
activate DB
|
||||
Service->>DB: Create business entity
|
||||
Service->>Outbox: Insert event (status: PENDING)
|
||||
Outbox-->>DB: Stored
|
||||
Service->>DB: Commit Transaction
|
||||
deactivate DB
|
||||
Note over Service,DB: Event stored atomically with business data
|
||||
|
||||
loop Polling Interval
|
||||
Processor->>Outbox: Find PENDING events
|
||||
Outbox-->>Processor: Return events
|
||||
Processor->>Publisher: publish(event)
|
||||
activate Publisher
|
||||
Publisher->>Kafka: Send to topic
|
||||
Kafka-->>Publisher: Acknowledge
|
||||
Publisher-->>Processor: Success
|
||||
deactivate Publisher
|
||||
Processor->>Outbox: Update status to PUBLISHED
|
||||
end
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Events are stored in the database within the same transaction as business data
|
||||
- A separate background process (Outbox Processor) publishes events to Kafka
|
||||
- Ensures at-least-once delivery guarantee
|
||||
- Prevents lost events if Kafka is temporarily unavailable
|
||||
|
||||
```typescript
|
||||
// Store event in database within transaction
|
||||
await prisma.outboxEvent.create({
|
||||
data: {
|
||||
eventType: 'user.created',
|
||||
eventData: userData,
|
||||
topic: 'user.created',
|
||||
status: 'PENDING',
|
||||
},
|
||||
});
|
||||
|
||||
// Separate process publishes from outbox to Kafka
|
||||
async function processOutbox() {
|
||||
const events = await prisma.outboxEvent.findMany({
|
||||
where: { status: 'PENDING' },
|
||||
});
|
||||
|
||||
for (const event of events) {
|
||||
await eventPublisher.publish(event.topic, event.eventData);
|
||||
await prisma.outboxEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { status: 'PUBLISHED' },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SSE Endpoint for Event Streaming
|
||||
|
||||
```typescript
|
||||
// src/modules/events/events.controller.ts
|
||||
async streamEvents(req: Request, res: Response): Promise<void> {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const topic = req.query.topic as string;
|
||||
const consumer = kafka.consumer({ groupId: `sse-${Date.now()}` });
|
||||
|
||||
await consumer.connect();
|
||||
await consumer.subscribe({ topic, fromBeginning: false });
|
||||
|
||||
await consumer.run({
|
||||
eachMessage: async ({ message }) => {
|
||||
const event = JSON.parse(message.value?.toString() || '{}');
|
||||
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
||||
},
|
||||
});
|
||||
|
||||
req.on('close', async () => {
|
||||
await consumer.disconnect();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Event Structure
|
||||
|
||||
```typescript
|
||||
interface BaseEvent {
|
||||
eventId: string;
|
||||
eventType: string;
|
||||
eventVersion: string;
|
||||
timestamp: string;
|
||||
source: string;
|
||||
correlationId?: string;
|
||||
traceId?: string;
|
||||
data: unknown;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Event Naming Conventions
|
||||
|
||||
- **Event Type**: `{domain}.{action}.v{version}` (e.g., `user.created.v1`)
|
||||
- **Topic**: `{domain}.{entity}.{action}` (e.g., `user.created`)
|
||||
- Use lowercase with dots as separators
|
||||
- Keep names descriptive and consistent
|
||||
|
||||
### 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
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Implement Dead Letter Queue (DLQ) for failed events
|
||||
- Use retry with exponential backoff
|
||||
- Log all event processing failures
|
||||
- Monitor consumer lag and DLQ size
|
||||
|
||||
### Observability
|
||||
|
||||
- Log all published and consumed events
|
||||
- Track metrics: events published/consumed, processing duration, consumer lag
|
||||
- Add distributed tracing to event flows
|
||||
- Include correlation IDs for request tracking
|
||||
|
||||
## Infrastructure Setup
|
||||
|
||||
### Docker Compose (Local)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:7.4.0
|
||||
ports:
|
||||
- "9092:9092"
|
||||
environment:
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
||||
|
||||
schema-registry:
|
||||
image: confluentinc/cp-schema-registry:7.4.0
|
||||
ports:
|
||||
- "8081:8081"
|
||||
environment:
|
||||
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: kafka:9092
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Testing
|
||||
|
||||
```typescript
|
||||
import { EventPublisher } from '../event-publisher';
|
||||
import { producer } from '../../config/kafka.config';
|
||||
|
||||
jest.mock('../../config/kafka.config');
|
||||
|
||||
describe('EventPublisher', () => {
|
||||
it('should publish event successfully', async () => {
|
||||
const publisher = new EventPublisher();
|
||||
const mockSend = jest.fn().mockResolvedValue({});
|
||||
(producer.send as jest.Mock) = mockSend;
|
||||
|
||||
await publisher.publish('user.created', {
|
||||
eventType: 'user.created',
|
||||
eventVersion: '1.0.0',
|
||||
data: { userId: '123' },
|
||||
});
|
||||
|
||||
expect(mockSend).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Testing with Test Containers
|
||||
|
||||
```typescript
|
||||
import { KafkaContainer } from '@testcontainers/kafka';
|
||||
|
||||
describe('Event Flow E2E', () => {
|
||||
let kafkaContainer: StartedKafkaContainer;
|
||||
|
||||
beforeAll(async () => {
|
||||
kafkaContainer = await new KafkaContainer().start();
|
||||
process.env.KAFKA_BROKERS = kafkaContainer.getBootstrapServer();
|
||||
});
|
||||
|
||||
it('should publish and consume event', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### User Created Event Flow
|
||||
|
||||
1. Auth Service creates user in database
|
||||
2. Publishes `user.created` event to Kafka
|
||||
3. Notification Service consumes event and sends welcome email
|
||||
4. Analytics Service consumes event and updates metrics
|
||||
|
||||
### Order Processing with Multiple Consumers
|
||||
|
||||
1. Order Service publishes `order.placed` event
|
||||
2. Payment Service processes payment
|
||||
3. Inventory Service reserves items
|
||||
4. Notification Service sends confirmation
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [Resilience Patterns](./resilience-patterns.md) - Circuit breaker, retry patterns
|
||||
- [Error Handling Patterns](./error-handling-patterns.md) - Error handling best practices
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Logging, metrics, tracing
|
||||
- [Middleware Patterns](./middleware-patterns.md) - SSE endpoint middleware
|
||||
- [Project Rules](./project-rules.md) - GoodGo coding standards
|
||||
|
||||
## Resources
|
||||
|
||||
- [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
|
||||
- Skill Source: `.cursor/skills/event-driven-architecture/SKILL.md`
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
name: infrastructure-as-code
|
||||
description: Infrastructure as Code patterns for GoodGo platform including Terraform modules, Kubernetes operators, infrastructure testing, GitOps workflows, and multi-environment management.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
## Infrastructure as Code Workflow
|
||||
|
||||
The following diagram illustrates the complete IaC workflow from code changes to infrastructure deployment:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Developer writes IaC code] --> B[Commit to Git repository]
|
||||
B --> C{Code Review}
|
||||
C -->|Rejected| D[Fix issues]
|
||||
D --> B
|
||||
C -->|Approved| E[Merge to branch]
|
||||
E --> F[CI/CD Pipeline triggers]
|
||||
F --> G{Terraform or<br/>Kubernetes?}
|
||||
G -->|Terraform| H[Terraform Workflow]
|
||||
G -->|Kubernetes| I[GitOps Workflow]
|
||||
H --> J[terraform init]
|
||||
J --> K[terraform validate]
|
||||
K --> L[terraform plan]
|
||||
L --> M{Plan review}
|
||||
M -->|Issues found| D
|
||||
M -->|Approved| N[terraform apply]
|
||||
N --> O[Infrastructure updated]
|
||||
I --> P[GitOps tool detects changes]
|
||||
P --> Q[Sync to Kubernetes cluster]
|
||||
Q --> O
|
||||
O --> R[Health checks]
|
||||
R --> S{Deployment successful?}
|
||||
S -->|No| T[Rollback]
|
||||
S -->|Yes| U[Monitor infrastructure]
|
||||
```
|
||||
|
||||
## GitOps Flow
|
||||
|
||||
GitOps enables automated synchronization of Kubernetes manifests from Git to clusters:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Dev as Developer
|
||||
participant Git as Git Repository
|
||||
participant ArgoCD as ArgoCD/Flux
|
||||
participant K8s as Kubernetes Cluster
|
||||
|
||||
Dev->>Git: Push manifest changes
|
||||
Git->>ArgoCD: Detect changes (poll/webhook)
|
||||
ArgoCD->>Git: Fetch latest manifests
|
||||
ArgoCD->>ArgoCD: Compare desired vs actual state
|
||||
alt Drift detected
|
||||
ArgoCD->>K8s: Apply changes (sync)
|
||||
K8s->>K8s: Update resources
|
||||
K8s->>ArgoCD: Status update
|
||||
else Auto-heal enabled
|
||||
ArgoCD->>K8s: Self-heal (correct drift)
|
||||
end
|
||||
ArgoCD->>Git: Update sync status
|
||||
```
|
||||
|
||||
## Terraform Execution Flow
|
||||
|
||||
The Terraform workflow ensures safe and predictable infrastructure changes:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[terraform init] --> B[Load providers & modules]
|
||||
B --> C[terraform validate]
|
||||
C --> D{Syntax valid?}
|
||||
D -->|No| E[Fix errors]
|
||||
E --> C
|
||||
D -->|Yes| F[terraform plan]
|
||||
F --> G[Read state]
|
||||
G --> H[Build dependency graph]
|
||||
H --> I[Calculate changes]
|
||||
I --> J[Generate plan]
|
||||
J --> K{Review plan}
|
||||
K -->|Issues| L[Adjust code]
|
||||
L --> F
|
||||
K -->|Approved| M[terraform apply]
|
||||
M --> N[Lock state]
|
||||
N --> O[Execute changes]
|
||||
O --> P{Success?}
|
||||
P -->|No| Q[Rollback]
|
||||
P -->|Yes| R[Update state]
|
||||
R --> S[Unlock state]
|
||||
S --> T[Save state to backend]
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Terraform Modules
|
||||
|
||||
Terraform modules enable reusable infrastructure components across environments:
|
||||
|
||||
```hcl
|
||||
# Reusable module
|
||||
module "postgresql" {
|
||||
source = "../../modules/postgresql"
|
||||
database_name = "goodgo"
|
||||
environment = "staging"
|
||||
}
|
||||
```
|
||||
|
||||
### Module Structure
|
||||
|
||||
The following diagram shows the typical Terraform module structure and how modules are composed:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Module: postgresql] --> B[variables.tf<br/>Input parameters]
|
||||
A --> C[main.tf<br/>Resource definitions]
|
||||
A --> D[outputs.tf<br/>Exported values]
|
||||
E[Environment: staging] --> F[main.tf]
|
||||
F --> G[Module: postgresql]
|
||||
F --> H[Module: redis]
|
||||
F --> I[Module: kubernetes-cluster]
|
||||
G --> J[Output: database_url]
|
||||
H --> K[Output: redis_url]
|
||||
I --> L[Output: cluster_endpoint]
|
||||
F --> M[terraform.tfvars<br/>Environment config]
|
||||
```
|
||||
|
||||
### GitOps with ArgoCD
|
||||
|
||||
GitOps tools like ArgoCD and Flux automatically sync Kubernetes manifests from Git repositories:
|
||||
|
||||
```yaml
|
||||
# Automated sync from Git
|
||||
spec:
|
||||
source:
|
||||
repoURL: https://github.com/goodgo/platform
|
||||
path: deployments/production/kubernetes
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
```
|
||||
|
||||
### Multi-Environment Management
|
||||
|
||||
Managing infrastructure across multiple environments requires clear separation and consistent patterns:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Git Repository"
|
||||
A[infra/terraform]
|
||||
end
|
||||
subgraph "Modules (Reusable)"
|
||||
B[modules/postgresql]
|
||||
C[modules/redis]
|
||||
D[modules/kubernetes-cluster]
|
||||
end
|
||||
subgraph "Environments"
|
||||
E[environments/staging]
|
||||
F[environments/production]
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
A --> D
|
||||
A --> E
|
||||
A --> F
|
||||
E --> B
|
||||
E --> C
|
||||
E --> D
|
||||
F --> B
|
||||
F --> C
|
||||
F --> D
|
||||
E --> G[terraform.tfvars<br/>staging config]
|
||||
F --> H[terraform.tfvars<br/>production config]
|
||||
G --> I[Remote State Backend<br/>staging/terraform.tfstate]
|
||||
H --> J[Remote State Backend<br/>production/terraform.tfstate]
|
||||
```
|
||||
|
||||
## Infrastructure Testing
|
||||
|
||||
Always validate infrastructure changes before applying them:
|
||||
|
||||
```bash
|
||||
# Validate Terraform syntax
|
||||
terraform init
|
||||
terraform validate
|
||||
|
||||
# Preview changes
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Review plan before applying
|
||||
terraform show tfplan
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Version Control**: Keep all infrastructure in version control
|
||||
2. **Modules**: Create reusable Terraform modules for common components
|
||||
3. **Testing**: Test infrastructure changes before applying to production
|
||||
4. **GitOps**: Use GitOps (ArgoCD/Flux) for Kubernetes deployments
|
||||
5. **Environment Isolation**: Separate environments completely with different state backends
|
||||
6. **State Management**: Use remote state backends (S3, GCS) with state locking
|
||||
7. **Secrets**: Never commit secrets - use environment variables or secrets managers
|
||||
|
||||
### Common Mistakes to Avoid
|
||||
|
||||
1. **Committing Secrets**: Never hardcode passwords or API keys
|
||||
2. **Local State Only**: Always use remote state backends for team collaboration
|
||||
3. **No State Locking**: Enable state locking to prevent concurrent modifications
|
||||
4. **Direct Apply**: Always review `terraform plan` output before applying
|
||||
|
||||
## Resources
|
||||
|
||||
- [Terraform Documentation](https://www.terraform.io/docs)
|
||||
- [Deployment Kubernetes](./deployment-kubernetes.md)
|
||||
- Skill Source: `.cursor/skills/infrastructure-as-code/SKILL.md`
|
||||
@@ -1,280 +0,0 @@
|
||||
---
|
||||
name: inter-service-communication
|
||||
description: Inter-service communication patterns for GoodGo microservices including gRPC, GraphQL, service-to-service authentication, protocol selection, and client patterns. Use when implementing service-to-service calls, choosing communication protocols, or building service clients.
|
||||
---
|
||||
|
||||
# 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
|
||||
- Optimizing inter-service communication performance
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Communication Protocol Options
|
||||
|
||||
**HTTP/REST:**
|
||||
- Human-readable, easy to debug
|
||||
- Browser-compatible
|
||||
- Standard HTTP semantics
|
||||
- JSON payloads
|
||||
- Good for external APIs
|
||||
|
||||
**gRPC:**
|
||||
- Binary protocol (Protocol Buffers)
|
||||
- High performance, low latency
|
||||
- Streaming support
|
||||
- Strong typing with .proto files
|
||||
- HTTP/2 based
|
||||
|
||||
**GraphQL:**
|
||||
- Flexible query language
|
||||
- Single endpoint
|
||||
- Client-controlled data fetching
|
||||
- Strong typing with schema
|
||||
|
||||
### Protocol Selection Guidelines
|
||||
|
||||
Choose protocol based on use case, performance requirements, team expertise, and ecosystem needs.
|
||||
|
||||
#### Protocol Selection Decision Tree
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Need Inter-Service Communication]) --> CheckExternal{External/Public API?}
|
||||
|
||||
CheckExternal -->|Yes| UseREST[Use REST]
|
||||
CheckExternal -->|No| CheckPerformance{High Performance<br/>Required?}
|
||||
|
||||
CheckPerformance -->|Yes| CheckStreaming{Need Streaming?}
|
||||
CheckPerformance -->|No| CheckFlexible{Need Flexible<br/>Queries?}
|
||||
|
||||
CheckStreaming -->|Yes| UseGRPC[Use gRPC]
|
||||
CheckStreaming -->|No| CheckLowLatency{Ultra Low<br/>Latency?}
|
||||
|
||||
CheckLowLatency -->|Yes| UseGRPC
|
||||
CheckLowLatency -->|No| UseREST
|
||||
|
||||
CheckFlexible -->|Yes| UseGraphQL[Use GraphQL]
|
||||
CheckFlexible -->|No| UseREST
|
||||
|
||||
UseREST --> RESTDesc["REST: External APIs<br/>Browser clients<br/>Simple CRUD"]
|
||||
UseGRPC --> GRPCDesc["gRPC: Internal services<br/>High performance<br/>Streaming support"]
|
||||
UseGraphQL --> GraphQLDesc["GraphQL: Complex queries<br/>Mobile apps<br/>Flexible data"]
|
||||
|
||||
style UseREST fill:#e1f5ff
|
||||
style UseGRPC fill:#fff4e1
|
||||
style UseGraphQL fill:#e8f5e9
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Service-to-Service Call Flow
|
||||
|
||||
The following diagram illustrates the complete flow of a service-to-service call, including authentication, interceptors, and error handling:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ClientService as Client Service
|
||||
participant ClientLib as Service Client<br/>(HTTP/gRPC/GraphQL)
|
||||
participant Interceptor as Request<br/>Interceptor
|
||||
participant Auth as Auth<br/>Middleware
|
||||
participant TargetService as Target Service
|
||||
participant Logger as Logger
|
||||
participant Metrics as Metrics
|
||||
|
||||
ClientService->>ClientLib: Make request
|
||||
ClientLib->>Interceptor: Add correlation ID<br/>Add service auth header<br/>Add request ID
|
||||
Interceptor->>Logger: Log request start
|
||||
Interceptor->>Metrics: Track request start
|
||||
Interceptor->>TargetService: HTTP/gRPC/GraphQL Request<br/>(with headers)
|
||||
|
||||
TargetService->>Auth: Validate x-service-auth
|
||||
alt Invalid Auth
|
||||
Auth-->>TargetService: 403 Forbidden
|
||||
TargetService-->>ClientLib: Error Response
|
||||
ClientLib->>Logger: Log error
|
||||
ClientLib->>Metrics: Track failure
|
||||
ClientLib-->>ClientService: ServiceError
|
||||
else Valid Auth
|
||||
Auth->>TargetService: Authenticated
|
||||
TargetService->>TargetService: Process request
|
||||
TargetService-->>ClientLib: Success Response
|
||||
ClientLib->>Logger: Log success<br/>(with correlation ID)
|
||||
ClientLib->>Metrics: Track success<br/>(duration, status)
|
||||
ClientLib-->>ClientService: Response Data
|
||||
end
|
||||
```
|
||||
|
||||
### HTTP/REST Service Client
|
||||
|
||||
```typescript
|
||||
// Base service client with circuit breaker and interceptors
|
||||
import { ServiceClient } from '../../core/clients/service-client';
|
||||
|
||||
const notificationClient = new ServiceClient({
|
||||
baseURL: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:5003',
|
||||
serviceName: 'notification-service',
|
||||
timeout: 5000,
|
||||
enableCircuitBreaker: true,
|
||||
});
|
||||
|
||||
// Usage
|
||||
await notificationClient.post('/api/v1/notifications', {
|
||||
userId,
|
||||
message,
|
||||
});
|
||||
```
|
||||
|
||||
### gRPC Service
|
||||
|
||||
```typescript
|
||||
// gRPC server implementation
|
||||
import { UserGrpcServer } from './user.grpc.service';
|
||||
|
||||
const grpcServer = new UserGrpcServer(userService);
|
||||
grpcServer.start(50051);
|
||||
|
||||
// gRPC client
|
||||
import { GrpcClient } from '../../core/clients/grpc-client';
|
||||
|
||||
const userGrpcClient = new GrpcClient({
|
||||
protoPath: './proto/user_service.proto',
|
||||
packageName: 'goodgo.user.v1',
|
||||
serviceName: 'UserService',
|
||||
serverUrl: 'localhost:50051',
|
||||
});
|
||||
|
||||
const user = await userGrpcClient.call('getUser', { user_id: '123' });
|
||||
```
|
||||
|
||||
### GraphQL Service
|
||||
|
||||
```typescript
|
||||
// GraphQL client
|
||||
import { GraphQLServiceClient } from '../../core/clients/graphql-client';
|
||||
|
||||
const userGraphQLClient = new GraphQLServiceClient({
|
||||
endpoint: 'http://user-service:5002/graphql',
|
||||
});
|
||||
|
||||
const GET_USER_QUERY = `
|
||||
query GetUser($id: ID!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const user = await userGraphQLClient.query(GET_USER_QUERY, { id: '123' });
|
||||
```
|
||||
|
||||
### Service-to-Service Authentication
|
||||
|
||||
The authentication flow ensures secure communication between services:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ClientService as Client Service
|
||||
participant ServiceClient as Service Client
|
||||
participant Env as Environment<br/>Variables
|
||||
participant TargetService as Target Service
|
||||
participant AuthMiddleware as Auth<br/>Middleware
|
||||
|
||||
ClientService->>ServiceClient: Create client instance
|
||||
ServiceClient->>Env: Read INTERNAL_API_KEY
|
||||
Env-->>ServiceClient: API Key
|
||||
|
||||
ClientService->>ServiceClient: Make request
|
||||
ServiceClient->>ServiceClient: Auto-add x-service-auth<br/>header with API key
|
||||
|
||||
ServiceClient->>TargetService: HTTP Request<br/>(x-service-auth: token)
|
||||
|
||||
TargetService->>AuthMiddleware: Extract x-service-auth
|
||||
AuthMiddleware->>AuthMiddleware: Compare with<br/>INTERNAL_API_KEY
|
||||
|
||||
alt Token Matches
|
||||
AuthMiddleware->>TargetService: Auth Success
|
||||
TargetService->>TargetService: Process request
|
||||
TargetService-->>ServiceClient: 200 OK + Data
|
||||
else Token Mismatch
|
||||
AuthMiddleware->>TargetService: Auth Failed
|
||||
TargetService-->>ServiceClient: 403 Forbidden<br/>(INVALID_SERVICE_AUTH)
|
||||
ServiceClient->>ServiceClient: Throw ServiceError
|
||||
ServiceClient-->>ClientService: Error Response
|
||||
end
|
||||
```
|
||||
|
||||
#### Implementation
|
||||
|
||||
```typescript
|
||||
// Internal auth middleware
|
||||
import { internalAuthMiddleware } from '../../middlewares/internal-auth.middleware';
|
||||
|
||||
router.use('/internal', internalAuthMiddleware);
|
||||
|
||||
// Client automatically adds auth header
|
||||
const client = new ServiceClient({
|
||||
baseURL: 'http://service:5000',
|
||||
serviceName: 'service',
|
||||
});
|
||||
// X-Service-Auth header is added automatically
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Protocol Selection
|
||||
|
||||
- **REST**: External APIs, browser clients, simple CRUD
|
||||
- **gRPC**: Internal services, high performance, streaming
|
||||
- **GraphQL**: Complex queries, mobile apps, flexible data
|
||||
|
||||
### Performance
|
||||
|
||||
- Use connection pooling
|
||||
- Enable HTTP keep-alive
|
||||
- Set appropriate timeouts
|
||||
- Implement circuit breakers
|
||||
|
||||
### Security
|
||||
|
||||
- Always authenticate internal calls
|
||||
- Use TLS/mTLS
|
||||
- Store secrets securely
|
||||
- Implement rate limiting
|
||||
|
||||
### Observability
|
||||
|
||||
- Log with correlation IDs
|
||||
- Track metrics (duration, success rate)
|
||||
- Add distributed tracing
|
||||
- Monitor service health
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
// Mock service client
|
||||
const mockClient = createMockServiceClient();
|
||||
mockClient.get.mockResolvedValue({ id: '123' });
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [gRPC Documentation](https://grpc.io/docs/)
|
||||
- [GraphQL Documentation](https://graphql.org/learn/)
|
||||
- [Protocol Buffers](https://developers.google.com/protocol-buffers)
|
||||
- [Resilience Patterns](./resilience-patterns.md)
|
||||
- [Security](./security.md)
|
||||
- Skill Source: `.cursor/skills/inter-service-communication/SKILL.md`
|
||||
@@ -1,660 +0,0 @@
|
||||
---
|
||||
name: microservices-development-process
|
||||
description: Standard development process for creating and maintaining microservices in GoodGo platform. Use when creating new services, migrating services, refactoring services, or planning service implementations.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
This diagram shows the complete 8-phase development process with decision points and feedback loops.
|
||||
|
||||
```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
|
||||
|
||||
This diagram breaks down the tasks within each phase and shows the sequential flow between phases.
|
||||
|
||||
```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
|
||||
|
||||
This diagram shows the step-by-step implementation order for each feature module within Phase 3.
|
||||
|
||||
```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
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
**Unit Tests:**
|
||||
- [ ] Repository tests: All CRUD operations
|
||||
- [ ] Service tests: Business logic, error handling
|
||||
- [ ] Controller tests: Request/response handling
|
||||
- [ ] DTO tests: Validation rules
|
||||
|
||||
**Integration Tests:**
|
||||
- [ ] Module integration: Controller → Service → Repository
|
||||
- [ ] Database operations: Real Prisma client with test DB
|
||||
- [ ] Middleware chain: Request flow through middlewares
|
||||
|
||||
**E2E Tests:**
|
||||
- [ ] API endpoints: Full request/response cycle
|
||||
- [ ] Authentication: Protected routes
|
||||
- [ ] Error scenarios: 400, 401, 403, 404, 500
|
||||
- [ ] Health checks: /health endpoint
|
||||
|
||||
### Acceptance Criteria for Phase 5
|
||||
|
||||
- [ ] All unit tests pass: `pnpm test`
|
||||
- [ ] Integration tests pass
|
||||
- [ ] E2E tests pass
|
||||
- [ ] Coverage meets thresholds: `pnpm test:coverage`
|
||||
- [ ] No test warnings or errors
|
||||
- [ ] Tests run in CI pipeline successfully
|
||||
|
||||
## Phase 6: Documentation
|
||||
|
||||
### Required Documentation
|
||||
|
||||
**Service README:**
|
||||
- Service overview (bilingual EN/VI)
|
||||
- Features list
|
||||
- Prerequisites
|
||||
- Quick start guide
|
||||
- Configuration reference (environment variables table)
|
||||
- API endpoints overview
|
||||
- Development guide
|
||||
- Testing instructions
|
||||
|
||||
**API Documentation:**
|
||||
- Swagger/OpenAPI spec: `src/docs/swagger.ts`
|
||||
- Document all endpoints
|
||||
- Request/response schemas
|
||||
- Examples
|
||||
|
||||
**Architecture Documentation (if complex):**
|
||||
- `ARCHITECTURE.en.md` / `ARCHITECTURE.vi.md`
|
||||
- System design, data flow, component interactions
|
||||
|
||||
### Documentation Checklist
|
||||
|
||||
- [ ] README is comprehensive and bilingual
|
||||
- [ ] Swagger docs accessible: `/api-docs`
|
||||
- [ ] All endpoints appear in Swagger
|
||||
- [ ] Examples are clear and accurate
|
||||
- [ ] Environment variables documented
|
||||
- [ ] Architecture docs created (if needed)
|
||||
|
||||
### Acceptance Criteria for Phase 6
|
||||
|
||||
- [ ] README is comprehensive and bilingual
|
||||
- [ ] Swagger docs accessible: `/api-docs`
|
||||
- [ ] All endpoints documented with examples
|
||||
- [ ] Documentation reviewed and accurate
|
||||
|
||||
## Phase 7: Cleanup & Verification
|
||||
|
||||
### Verification Process Flow
|
||||
|
||||
This diagram illustrates the cleanup and verification workflow for Phase 7, including the decision point for migrations and the comprehensive verification steps.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start[Start Cleanup] --> Remove[Remove Temporary Files]
|
||||
Remove --> Update{Is Migration?}
|
||||
Update -->|Yes| RefUpdate[Update References<br/>grep & replace]
|
||||
Update -->|No| Verify[Run Verification]
|
||||
RefUpdate --> Verify
|
||||
|
||||
Verify --> TypeCheck[TypeScript Check]
|
||||
TypeCheck --> LintCheck[Lint Check]
|
||||
LintCheck --> TestCheck[Test Check]
|
||||
TestCheck --> BuildCheck[Build Check]
|
||||
BuildCheck --> DockerCheck[Docker Build]
|
||||
DockerCheck --> HealthCheck[Health Check]
|
||||
HealthCheck --> TraefikCheck[Traefik Check]
|
||||
TraefikCheck --> AllPass{All Pass?}
|
||||
|
||||
AllPass -->|No| Fix[Fix Issues]
|
||||
Fix --> Verify
|
||||
AllPass -->|Yes| Complete[Phase Complete]
|
||||
|
||||
style Remove fill:#ffe1e1
|
||||
style RefUpdate fill:#fff4e1
|
||||
style Verify fill:#e1ffe1
|
||||
style Complete fill:#d4edda
|
||||
```
|
||||
|
||||
### Cleanup Checklist
|
||||
|
||||
**Remove Temporary Files:**
|
||||
- [ ] Remove backup directories (e.g., `service-name.backup/`)
|
||||
- [ ] Remove temporary status files (e.g., `*_STATUS.md`, `*_CHECKLIST.md`)
|
||||
- [ ] Remove debug/scratch files
|
||||
- [ ] Clean up unused imports
|
||||
- [ ] Remove commented-out code
|
||||
|
||||
**Reference Updates (for migrations/renames):**
|
||||
```bash
|
||||
# Find all references
|
||||
grep -r "old-service-name" . --exclude-dir=node_modules --exclude-dir=.git
|
||||
|
||||
# Update checklist:
|
||||
- [ ] Package names: `@goodgo/old-name` → `@goodgo/new-name`
|
||||
- [ ] Service paths: `services/old-name` → `services/new-name`
|
||||
- [ ] Docker images: `goodgo/old-name` → `goodgo/new-name`
|
||||
- [ ] Deployment names: `old-name` → `new-name`
|
||||
- [ ] Environment variables updated
|
||||
- [ ] CI/CD workflows updated
|
||||
- [ ] Scripts updated (if needed)
|
||||
- [ ] Documentation updated (except historical context)
|
||||
```
|
||||
|
||||
### Verification Steps
|
||||
|
||||
**Comprehensive Verification:**
|
||||
```bash
|
||||
# 1. Service starts successfully
|
||||
pnpm dev && curl http://localhost:5000/health
|
||||
|
||||
# 2. Type checking passes
|
||||
pnpm typecheck
|
||||
|
||||
# 3. Linting passes
|
||||
pnpm lint
|
||||
|
||||
# 4. Tests pass with coverage
|
||||
pnpm test && pnpm test:coverage
|
||||
|
||||
# 5. Build succeeds
|
||||
pnpm build
|
||||
|
||||
# 6. Docker build succeeds
|
||||
docker build -t service-name .
|
||||
|
||||
# 7. Service accessible via Traefik
|
||||
curl http://localhost/api/v1/service-name/health
|
||||
|
||||
# 8. No broken references (if migration)
|
||||
grep -r "old-reference" . --exclude-dir=node_modules
|
||||
```
|
||||
|
||||
### Final Verification Checklist
|
||||
|
||||
**Code Quality:**
|
||||
- [ ] No TypeScript errors
|
||||
- [ ] No linting errors
|
||||
- [ ] No unused imports/variables
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] Comments are clear (bilingual if needed)
|
||||
|
||||
**Functionality:**
|
||||
- [ ] Service starts without errors
|
||||
- [ ] Health check works
|
||||
- [ ] All API endpoints functional
|
||||
- [ ] Database operations work
|
||||
- [ ] External integrations work (if any)
|
||||
|
||||
**Testing:**
|
||||
- [ ] All tests pass
|
||||
- [ ] Coverage meets requirements
|
||||
- [ ] E2E tests verify full workflows
|
||||
|
||||
**Documentation:**
|
||||
- [ ] README is complete and accurate
|
||||
- [ ] API documentation is up-to-date
|
||||
- [ ] Code comments are helpful
|
||||
|
||||
**Infrastructure:**
|
||||
- [ ] Docker image builds
|
||||
- [ ] Service works in Docker Compose
|
||||
- [ ] Traefik routes configured correctly
|
||||
- [ ] Environment variables documented
|
||||
|
||||
**Cleanup:**
|
||||
- [ ] Temporary files removed
|
||||
- [ ] All references updated (if migration)
|
||||
- [ ] No orphaned files
|
||||
|
||||
### Acceptance Criteria for Phase 7
|
||||
|
||||
- [ ] All cleanup tasks completed
|
||||
- [ ] All verification steps pass
|
||||
- [ ] No broken references or links
|
||||
- [ ] Code is production-ready
|
||||
- [ ] Documentation is complete
|
||||
|
||||
## Phase 8: Deployment
|
||||
|
||||
### Staging Deployment
|
||||
|
||||
**Pre-deployment Checklist:**
|
||||
- [ ] Database migrations tested: `pnpm prisma migrate deploy`
|
||||
- [ ] Environment variables configured in staging
|
||||
- [ ] Kubernetes manifests reviewed
|
||||
- [ ] Secrets configured in Kubernetes
|
||||
- [ ] Health checks configured
|
||||
|
||||
**Deployment Steps:**
|
||||
```bash
|
||||
# 1. Build and push Docker image
|
||||
docker build -t goodgo/service-name:latest .
|
||||
docker push goodgo/service-name:latest
|
||||
|
||||
# 2. Apply Kubernetes configs
|
||||
kubectl apply -f deployments/staging/kubernetes/service-name.yaml
|
||||
kubectl apply -f deployments/staging/kubernetes/service-name-configmap.yaml
|
||||
|
||||
# 3. Wait for rollout
|
||||
kubectl rollout status deployment/service-name -n staging
|
||||
|
||||
# 4. Verify deployment
|
||||
kubectl get pods -n staging -l app=service-name
|
||||
curl https://staging-api.example.com/api/v1/service-name/health
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
**Pre-production Checklist:**
|
||||
- [ ] Staging tests passed
|
||||
- [ ] Database backup created
|
||||
- [ ] Rollback plan documented
|
||||
- [ ] Monitoring dashboards ready
|
||||
- [ ] Alerting configured
|
||||
|
||||
### Acceptance Criteria for Phase 8
|
||||
|
||||
- [ ] Service deployed to staging successfully
|
||||
- [ ] All staging tests pass
|
||||
- [ ] Monitoring shows healthy metrics
|
||||
- [ ] Production deployment completed (if applicable)
|
||||
- [ ] Post-deployment verification successful
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
### When to Rollback
|
||||
|
||||
- Critical errors in staging/production
|
||||
- Performance degradation
|
||||
- Data integrity issues
|
||||
- Security vulnerabilities discovered
|
||||
|
||||
### Rollback Steps
|
||||
|
||||
```bash
|
||||
# 1. Identify previous working version
|
||||
kubectl rollout history deployment/service-name -n staging
|
||||
|
||||
# 2. Rollback to previous version
|
||||
kubectl rollout undo deployment/service-name -n staging
|
||||
|
||||
# 3. Verify rollback
|
||||
kubectl rollout status deployment/service-name -n staging
|
||||
|
||||
# 4. Database rollback (if needed)
|
||||
# Revert migrations if schema changes were made
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
1. **Always Plan First**: Complete impact analysis before coding
|
||||
2. **Follow Phases**: Don't skip verification steps
|
||||
3. **Test Early**: Write tests alongside implementation
|
||||
4. **Document as You Go**: Don't leave documentation for the end
|
||||
5. **Clean Up Regularly**: Remove temporary files during development
|
||||
6. **Verify Comprehensively**: Use checklists to ensure nothing is missed
|
||||
7. **Plan for Rollback**: Always have a rollback strategy
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
1. **Skipping Impact Analysis**: Leads to missing updates in scripts/configs
|
||||
2. **No Verification Steps**: Misses broken references or incomplete implementation
|
||||
3. **Deferring Cleanup**: Accumulates technical debt
|
||||
4. **Incomplete Testing**: Missing edge cases and error scenarios
|
||||
5. **Poor Documentation**: Makes maintenance difficult
|
||||
6. **No Rollback Plan**: Difficult to recover from failures
|
||||
|
||||
## Resources
|
||||
|
||||
- [Project Rules](../project-rules/SKILL.md) - Architecture and conventions
|
||||
- [API Design](../api-design/SKILL.md) - API design patterns
|
||||
- [Testing Patterns](../testing-patterns/SKILL.md) - Testing best practices
|
||||
- [Documentation](../documentation/SKILL.md) - Documentation guidelines
|
||||
- [Database Prisma](../database-prisma/SKILL.md) - Prisma patterns
|
||||
- Service Template: `services/_template/`
|
||||
@@ -1,413 +0,0 @@
|
||||
---
|
||||
name: middleware-patterns
|
||||
description: Express middleware patterns and best practices for GoodGo microservices. Use when creating custom middleware, organizing middleware chains, handling request/response transformation, or implementing cross-cutting concerns.
|
||||
---
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
The following diagram illustrates the complete middleware chain flow in GoodGo services:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([HTTP Request]) --> Security["Security Middleware<br/>Helmet, CORS"]
|
||||
Security --> RateLimit["Rate Limiting<br/>Middleware"]
|
||||
RateLimit --> Correlation["Correlation ID<br/>Middleware"]
|
||||
Correlation --> BodyParsing["Body Parsing<br/>JSON, URLEncoded, Cookies"]
|
||||
BodyParsing --> Logging["Request Logging<br/>Middleware"]
|
||||
Logging --> Metrics["Metrics Collection<br/>Middleware"]
|
||||
Metrics --> Routes["Route Handlers"]
|
||||
Routes -->|Success| Response([HTTP Response])
|
||||
Routes -->|Error| ErrorHandler["Error Handler<br/>Middleware"]
|
||||
Routes -->|Not Found| NotFound["Not Found<br/>Handler"]
|
||||
ErrorHandler --> Response
|
||||
NotFound --> 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' });
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The following sequence diagram illustrates the authentication middleware flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant AuthMW as Authentication Middleware
|
||||
participant JWTService as JWT Service
|
||||
participant RouteHandler as Route Handler
|
||||
|
||||
Client->>AuthMW: HTTP Request with Token
|
||||
AuthMW->>AuthMW: Extract token from headers
|
||||
alt Token exists
|
||||
AuthMW->>JWTService: Verify token
|
||||
alt Token valid
|
||||
JWTService-->>AuthMW: Payload (user data)
|
||||
AuthMW->>AuthMW: Attach user to req.user
|
||||
AuthMW->>RouteHandler: next() - Continue
|
||||
RouteHandler->>Client: HTTP Response (200)
|
||||
else Token invalid
|
||||
JWTService-->>AuthMW: Verification error
|
||||
AuthMW->>Client: HTTP Response (401 Unauthorized)
|
||||
end
|
||||
else No token
|
||||
AuthMW->>Client: HTTP Response (401 Unauthorized)
|
||||
end
|
||||
```
|
||||
|
||||
### 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);
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The following sequence diagram shows the validation middleware flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant ValidateMW as Validation Middleware
|
||||
participant ZodSchema as Zod Schema
|
||||
participant RouteHandler as Route Handler
|
||||
|
||||
Client->>ValidateMW: HTTP Request with Data
|
||||
ValidateMW->>ValidateMW: Extract data from req[property]
|
||||
ValidateMW->>ZodSchema: schema.parse(data)
|
||||
alt Validation successful
|
||||
ZodSchema-->>ValidateMW: Validated data
|
||||
ValidateMW->>ValidateMW: Replace req[property] with validated data
|
||||
ValidateMW->>RouteHandler: next() - Continue
|
||||
RouteHandler->>Client: HTTP Response (200)
|
||||
else Validation failed
|
||||
ZodSchema-->>ValidateMW: ZodError with details
|
||||
ValidateMW->>ValidateMW: Format error response
|
||||
ValidateMW->>Client: HTTP Response (400 Validation Error)
|
||||
end
|
||||
```
|
||||
|
||||
### 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 });
|
||||
}));
|
||||
```
|
||||
|
||||
The following sequence diagram illustrates async middleware error handling:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant AsyncMW as Async Middleware Wrapper
|
||||
participant AsyncHandler as Async Route Handler
|
||||
participant ErrorHandler as Error Handler
|
||||
|
||||
Client->>AsyncMW: HTTP Request
|
||||
AsyncMW->>AsyncHandler: Execute async function
|
||||
alt Async operation succeeds
|
||||
AsyncHandler->>AsyncHandler: Process request
|
||||
AsyncHandler->>Client: HTTP Response (200)
|
||||
else Async operation fails
|
||||
AsyncHandler-->>AsyncMW: Promise rejection (Error)
|
||||
AsyncMW->>ErrorHandler: next(error)
|
||||
ErrorHandler->>ErrorHandler: Format error response
|
||||
ErrorHandler->>Client: HTTP Response (500 Error)
|
||||
end
|
||||
```
|
||||
|
||||
### 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: any) {
|
||||
const transformed = {
|
||||
success: true,
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
return originalJson(transformed);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The following sequence diagram shows how request and response transformation middleware works:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant TransformMW as Transform Middleware
|
||||
participant RouteHandler as Route Handler
|
||||
participant Response as Response Object
|
||||
|
||||
Client->>TransformMW: HTTP Request
|
||||
Note over TransformMW: Intercept res.json()
|
||||
TransformMW->>TransformMW: Store original res.json
|
||||
TransformMW->>TransformMW: Override res.json with transformation logic
|
||||
TransformMW->>RouteHandler: next() - Continue chain
|
||||
|
||||
RouteHandler->>RouteHandler: Process request, Generate data
|
||||
RouteHandler->>Response: res.json(rawData)
|
||||
|
||||
Note over Response: Transformed res.json executes
|
||||
Response->>Response: Wrap data: success, data, timestamp
|
||||
Response->>Client: HTTP Response (transformed)
|
||||
```
|
||||
|
||||
### 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();
|
||||
};
|
||||
```
|
||||
|
||||
The following sequence diagram illustrates how logging middleware tracks request lifecycle:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant LogMW as Logging Middleware
|
||||
participant RouteHandler as Route Handler
|
||||
participant Logger as Logger Service
|
||||
participant Response as Response Object
|
||||
|
||||
Client->>LogMW: HTTP Request
|
||||
LogMW->>LogMW: Record startTime = Date.now()
|
||||
LogMW->>RouteHandler: next() - Continue chain
|
||||
|
||||
RouteHandler->>RouteHandler: Process request
|
||||
RouteHandler->>Response: res.json(data) or res.send()
|
||||
|
||||
Response->>Response: Set statusCode, send response
|
||||
Response->>Response: Emit 'finish' event
|
||||
|
||||
Response->>LogMW: 'finish' event triggered
|
||||
LogMW->>LogMW: Calculate duration = Date.now() - startTime
|
||||
LogMW->>Logger: logger.info('Request completed', {<br/>method, url, statusCode,<br/>duration, correlationId})
|
||||
Logger->>Logger: Write structured log entry
|
||||
|
||||
Response->>Client: HTTP Response
|
||||
```
|
||||
|
||||
## 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)`.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Correlation Middleware](../../services/iam-service/src/middlewares/correlation.middleware.ts)
|
||||
- [Auth Middleware](../../services/iam-service/src/middlewares/auth.middleware.ts)
|
||||
- [Validation Middleware](../../services/iam-service/src/middlewares/validation.middleware.ts)
|
||||
- [Error Handling](../error-handling-patterns/SKILL.md) - Error middleware patterns
|
||||
@@ -1,658 +0,0 @@
|
||||
---
|
||||
name: observability-monitoring
|
||||
description: Observability and monitoring patterns for GoodGo microservices. Use when adding metrics, implementing logging, setting up tracing, creating health checks, or debugging production issues.
|
||||
---
|
||||
|
||||
# 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)
|
||||
|
||||
### Observability Stack Architecture
|
||||
|
||||
The observability stack consists of three pillars working together to provide comprehensive visibility into system behavior:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Application Layer"
|
||||
App[Microservice]
|
||||
end
|
||||
|
||||
subgraph "Three Pillars of Observability"
|
||||
Logs[Logs<br/>Winston/Pino]
|
||||
Metrics[Metrics<br/>Prometheus]
|
||||
Traces[Traces<br/>OpenTelemetry]
|
||||
end
|
||||
|
||||
subgraph "Aggregation & Storage"
|
||||
Loki[Loki<br/>Log Aggregation]
|
||||
Prom[Prometheus<br/>Metrics Storage]
|
||||
Jaeger[Jaeger<br/>Trace Storage]
|
||||
end
|
||||
|
||||
subgraph "Visualization & Alerting"
|
||||
Grafana[Grafana<br/>Dashboards]
|
||||
AlertManager[AlertManager<br/>Alerts]
|
||||
end
|
||||
|
||||
App -->|Structured Logs| Logs
|
||||
App -->|HTTP Metrics| Metrics
|
||||
App -->|Distributed Spans| Traces
|
||||
|
||||
Logs -->|Collect| Loki
|
||||
Metrics -->|Scrape /metrics| Prom
|
||||
Traces -->|Export| Jaeger
|
||||
|
||||
Loki -->|Query| Grafana
|
||||
Prom -->|Query| Grafana
|
||||
Prom -->|Alerts| AlertManager
|
||||
Jaeger -->|Query| Grafana
|
||||
|
||||
style App fill:#e1f5ff
|
||||
style Logs fill:#fff4e1
|
||||
style Metrics fill:#e1ffe1
|
||||
style Traces fill:#ffe1f5
|
||||
style Grafana fill:#e1e1ff
|
||||
```
|
||||
|
||||
## Structured Logging
|
||||
|
||||
```typescript
|
||||
// src/lib/logger.ts
|
||||
import winston from 'winston';
|
||||
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
export const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: logFormat,
|
||||
defaultMeta: {
|
||||
service: process.env.SERVICE_NAME || 'unknown',
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
},
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: process.env.NODE_ENV === 'development'
|
||||
? winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
: logFormat
|
||||
}),
|
||||
// Production: Send to log aggregation service
|
||||
...(process.env.NODE_ENV === 'production'
|
||||
? [new winston.transports.Http({
|
||||
host: 'logs.example.com',
|
||||
path: '/collect',
|
||||
ssl: true
|
||||
})]
|
||||
: [])
|
||||
]
|
||||
});
|
||||
|
||||
// Request logger middleware
|
||||
export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
|
||||
logger.info('HTTP Request', {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: res.statusCode,
|
||||
duration,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
correlationId: req.headers['x-correlation-id']
|
||||
});
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
```
|
||||
|
||||
### Logging Flow
|
||||
|
||||
The logging flow shows how requests are logged with correlation IDs and flow through the system:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Service as Microservice
|
||||
participant Logger as Winston/Pino Logger
|
||||
participant Aggregator as Log Aggregator<br/>(Loki)
|
||||
participant Dashboard as Grafana Dashboard
|
||||
|
||||
Client->>Service: HTTP Request<br/>(with x-correlation-id)
|
||||
Service->>Service: Generate/Extract<br/>Correlation ID
|
||||
Service->>Logger: Log Request Start<br/>{correlationId, method, url}
|
||||
Service->>Service: Process Request
|
||||
Service->>Logger: Log Business Event<br/>{correlationId, event, data}
|
||||
Service->>Client: HTTP Response<br/>(with x-correlation-id)
|
||||
Service->>Logger: Log Request End<br/>{correlationId, status, duration}
|
||||
|
||||
Logger->>Aggregator: Send Structured Logs<br/>(JSON format)
|
||||
Aggregator->>Dashboard: Index & Store Logs
|
||||
Dashboard->>Dashboard: Query by correlationId<br/>to trace request flow
|
||||
```
|
||||
|
||||
## Metrics Collection
|
||||
|
||||
```typescript
|
||||
// src/lib/metrics.ts
|
||||
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
|
||||
|
||||
export const register = new Registry();
|
||||
|
||||
// HTTP metrics
|
||||
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']
|
||||
});
|
||||
|
||||
// Business metrics
|
||||
export const userRegistrations = new Counter({
|
||||
name: 'user_registrations_total',
|
||||
help: 'Total number of user registrations',
|
||||
labelNames: ['type']
|
||||
});
|
||||
|
||||
export const activeUsers = new Gauge({
|
||||
name: 'active_users',
|
||||
help: 'Number of active users',
|
||||
labelNames: ['status']
|
||||
});
|
||||
|
||||
// Register metrics
|
||||
register.registerMetric(httpRequestDuration);
|
||||
register.registerMetric(httpRequestTotal);
|
||||
register.registerMetric(userRegistrations);
|
||||
register.registerMetric(activeUsers);
|
||||
|
||||
// Metrics middleware
|
||||
export const metricsMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = (Date.now() - start) / 1000;
|
||||
const route = req.route?.path || req.path;
|
||||
|
||||
httpRequestDuration
|
||||
.labels(req.method, route, res.statusCode.toString())
|
||||
.observe(duration);
|
||||
|
||||
httpRequestTotal
|
||||
.labels(req.method, route, res.statusCode.toString())
|
||||
.inc();
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Metrics endpoint
|
||||
export const metricsHandler = async (req: Request, res: Response) => {
|
||||
res.set('Content-Type', register.contentType);
|
||||
res.end(await register.metrics());
|
||||
};
|
||||
```
|
||||
|
||||
### Metrics Collection Flow
|
||||
|
||||
Metrics are collected from services and exposed to Prometheus for monitoring and alerting:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Service Instance"
|
||||
App[Application]
|
||||
Middleware[Metrics Middleware]
|
||||
Registry[Prometheus Registry]
|
||||
Endpoint[/metrics Endpoint]
|
||||
end
|
||||
|
||||
subgraph "Metrics Types"
|
||||
Counter[Counter<br/>http_requests_total]
|
||||
Gauge[Gauge<br/>active_users]
|
||||
Histogram[Histogram<br/>request_duration]
|
||||
end
|
||||
|
||||
subgraph "Collection"
|
||||
Prometheus[Prometheus<br/>Scraper]
|
||||
end
|
||||
|
||||
subgraph "Storage & Query"
|
||||
PromDB[(Prometheus<br/>Time Series DB)]
|
||||
end
|
||||
|
||||
subgraph "Visualization"
|
||||
Grafana[Grafana<br/>Dashboards]
|
||||
Alerts[AlertManager<br/>Rules]
|
||||
end
|
||||
|
||||
App -->|HTTP Request| Middleware
|
||||
Middleware -->|Record| Counter
|
||||
Middleware -->|Record| Histogram
|
||||
App -->|Update| Gauge
|
||||
|
||||
Counter --> Registry
|
||||
Gauge --> Registry
|
||||
Histogram --> Registry
|
||||
Registry --> Endpoint
|
||||
|
||||
Prometheus -->|Scrape every 15s| Endpoint
|
||||
Prometheus -->|Store| PromDB
|
||||
|
||||
PromDB -->|Query| Grafana
|
||||
PromDB -->|Evaluate| Alerts
|
||||
Alerts -->|Trigger| Grafana
|
||||
|
||||
style App fill:#e1f5ff
|
||||
style Prometheus fill:#ffe1e1
|
||||
style Grafana fill:#e1e1ff
|
||||
```
|
||||
|
||||
## Distributed Tracing
|
||||
|
||||
```typescript
|
||||
// src/lib/tracing.ts
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
|
||||
|
||||
export const initTracing = () => {
|
||||
const jaegerExporter = new JaegerExporter({
|
||||
endpoint: process.env.JAEGER_ENDPOINT || 'http://localhost:14268/api/traces',
|
||||
});
|
||||
|
||||
const sdk = new NodeSDK({
|
||||
resource: new Resource({
|
||||
[SemanticResourceAttributes.SERVICE_NAME]: process.env.SERVICE_NAME || 'unknown',
|
||||
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.SERVICE_VERSION || '1.0.0',
|
||||
}),
|
||||
traceExporter: jaegerExporter,
|
||||
instrumentations: [getNodeAutoInstrumentations()]
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
sdk.shutdown()
|
||||
.then(() => console.log('Tracing terminated'))
|
||||
.catch((error) => console.log('Error terminating tracing', error))
|
||||
.finally(() => process.exit(0));
|
||||
});
|
||||
};
|
||||
|
||||
// Custom span creation
|
||||
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
||||
|
||||
export const tracedOperation = async (name: string, fn: Function) => {
|
||||
const tracer = trace.getTracer('application');
|
||||
const span = tracer.startSpan(name);
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
span.setStatus({ code: SpanStatusCode.OK });
|
||||
return result;
|
||||
} catch (error) {
|
||||
span.setStatus({
|
||||
code: SpanStatusCode.ERROR,
|
||||
message: error.message
|
||||
});
|
||||
span.recordException(error);
|
||||
throw error;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Distributed Tracing Flow
|
||||
|
||||
Distributed tracing tracks requests across multiple services using OpenTelemetry:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway as API Gateway
|
||||
participant ServiceA as Service A<br/>(User Service)
|
||||
participant ServiceB as Service B<br/>(Order Service)
|
||||
participant DB as Database
|
||||
participant Jaeger as Jaeger<br/>Collector
|
||||
|
||||
Client->>Gateway: Request<br/>(Trace ID: abc123)
|
||||
Gateway->>Gateway: Create Root Span<br/>Span: gateway.request
|
||||
Gateway->>ServiceA: HTTP Call<br/>(Trace ID: abc123,<br/>Span ID: span-1)
|
||||
|
||||
ServiceA->>ServiceA: Create Child Span<br/>Span: user.getById
|
||||
ServiceA->>DB: Query User<br/>(Trace ID: abc123,<br/>Span ID: span-2)
|
||||
DB-->>ServiceA: User Data
|
||||
ServiceA->>ServiceA: End Span span-2
|
||||
ServiceA->>ServiceB: HTTP Call<br/>(Trace ID: abc123,<br/>Span ID: span-3)
|
||||
|
||||
ServiceB->>ServiceB: Create Child Span<br/>Span: order.getByUserId
|
||||
ServiceB->>DB: Query Orders<br/>(Trace ID: abc123,<br/>Span ID: span-4)
|
||||
DB-->>ServiceB: Orders Data
|
||||
ServiceB->>ServiceB: End Span span-4
|
||||
ServiceB->>ServiceB: End Span span-3
|
||||
ServiceB-->>ServiceA: Response
|
||||
ServiceA->>ServiceA: End Span span-1
|
||||
ServiceA-->>Gateway: Response
|
||||
Gateway->>Gateway: End Span gateway.request
|
||||
Gateway-->>Client: Final Response
|
||||
|
||||
ServiceA->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
ServiceB->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
Gateway->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
|
||||
Note over Jaeger: All spans linked by<br/>Trace ID: abc123
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
```typescript
|
||||
// src/modules/health/health.controller.ts
|
||||
export class HealthController {
|
||||
constructor(
|
||||
private prisma: PrismaClient,
|
||||
private redis: Redis
|
||||
) {}
|
||||
|
||||
// Liveness probe - is the service running?
|
||||
async liveness(req: Request, res: Response) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// Readiness probe - is the service ready for traffic?
|
||||
async readiness(req: Request, res: Response) {
|
||||
const checks = await this.runHealthChecks();
|
||||
const isHealthy = Object.values(checks).every(check => check.status === 'healthy');
|
||||
|
||||
res.status(isHealthy ? 200 : 503).json({
|
||||
status: isHealthy ? 'ready' : 'not ready',
|
||||
checks,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// Detailed health check
|
||||
async health(req: Request, res: Response) {
|
||||
const checks = await this.runHealthChecks();
|
||||
const isHealthy = Object.values(checks).every(check => check.status === 'healthy');
|
||||
|
||||
res.status(isHealthy ? 200 : 503).json({
|
||||
status: isHealthy ? 'healthy' : 'unhealthy',
|
||||
version: process.env.SERVICE_VERSION || '1.0.0',
|
||||
uptime: process.uptime(),
|
||||
checks,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
private async runHealthChecks() {
|
||||
const checks: Record<string, any> = {};
|
||||
|
||||
// Database check
|
||||
try {
|
||||
const start = Date.now();
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
checks.database = {
|
||||
status: 'healthy',
|
||||
responseTime: Date.now() - start
|
||||
};
|
||||
} catch (error) {
|
||||
checks.database = {
|
||||
status: 'unhealthy',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
|
||||
// Redis check
|
||||
try {
|
||||
const start = Date.now();
|
||||
await this.redis.ping();
|
||||
checks.redis = {
|
||||
status: 'healthy',
|
||||
responseTime: Date.now() - start
|
||||
};
|
||||
} catch (error) {
|
||||
checks.redis = {
|
||||
status: 'unhealthy',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
|
||||
// Memory check
|
||||
const memUsage = process.memoryUsage();
|
||||
checks.memory = {
|
||||
status: memUsage.heapUsed < 500 * 1024 * 1024 ? 'healthy' : 'warning',
|
||||
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024)
|
||||
};
|
||||
|
||||
return checks;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Tracking
|
||||
|
||||
```typescript
|
||||
// src/lib/error-tracking.ts
|
||||
import * as Sentry from '@sentry/node';
|
||||
|
||||
export const initErrorTracking = () => {
|
||||
if (process.env.SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.NODE_ENV,
|
||||
tracesSampleRate: 0.1,
|
||||
beforeSend(event, hint) {
|
||||
// Filter sensitive data
|
||||
if (event.request?.cookies) {
|
||||
delete event.request.cookies;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Error handler middleware
|
||||
export const errorHandler = (
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
// Log error
|
||||
logger.error('Unhandled error', {
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
correlationId: req.headers['x-correlation-id']
|
||||
});
|
||||
|
||||
// Report to Sentry
|
||||
Sentry.captureException(err, {
|
||||
tags: {
|
||||
service: process.env.SERVICE_NAME
|
||||
},
|
||||
user: {
|
||||
id: req.user?.id
|
||||
}
|
||||
});
|
||||
|
||||
// Send response
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: process.env.NODE_ENV === 'production'
|
||||
? 'Internal server error'
|
||||
: err.message
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
```typescript
|
||||
// src/middlewares/performance.middleware.ts
|
||||
export const performanceMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const start = process.hrtime.bigint();
|
||||
|
||||
res.on('finish', () => {
|
||||
const end = process.hrtime.bigint();
|
||||
const duration = Number(end - start) / 1000000; // Convert to milliseconds
|
||||
|
||||
// Log slow requests
|
||||
if (duration > 1000) {
|
||||
logger.warn('Slow request detected', {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
duration,
|
||||
threshold: 1000
|
||||
});
|
||||
}
|
||||
|
||||
// Add to response header
|
||||
res.set('X-Response-Time', `${duration}ms`);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
```
|
||||
|
||||
## Grafana Dashboard Config
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "Service Metrics",
|
||||
"panels": [
|
||||
{
|
||||
"title": "Request Rate",
|
||||
"targets": [{
|
||||
"expr": "rate(http_requests_total[5m])"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"title": "Request Duration",
|
||||
"targets": [{
|
||||
"expr": "histogram_quantile(0.95, http_request_duration_seconds)"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"title": "Error Rate",
|
||||
"targets": [{
|
||||
"expr": "rate(http_requests_total{status=~\"5..\"}[5m])"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"title": "Active Users",
|
||||
"targets": [{
|
||||
"expr": "active_users"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Alerting Rules
|
||||
|
||||
```yaml
|
||||
# prometheus/alerts.yml
|
||||
groups:
|
||||
- name: service_alerts
|
||||
rules:
|
||||
- alert: HighErrorRate
|
||||
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
|
||||
for: 5m
|
||||
annotations:
|
||||
summary: "High error rate detected"
|
||||
description: "Error rate is above 5% for 5 minutes"
|
||||
|
||||
- alert: HighLatency
|
||||
expr: histogram_quantile(0.95, http_request_duration_seconds) > 1
|
||||
for: 5m
|
||||
annotations:
|
||||
summary: "High latency detected"
|
||||
description: "95th percentile latency is above 1s"
|
||||
|
||||
- alert: ServiceDown
|
||||
expr: up{job="service"} == 0
|
||||
for: 1m
|
||||
annotations:
|
||||
summary: "Service is down"
|
||||
description: "Service has been down for 1 minute"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Logging**
|
||||
- Use structured logging (JSON format)
|
||||
- Include correlation IDs for request tracing
|
||||
- Log at appropriate levels (ERROR, WARN, INFO, DEBUG)
|
||||
- Avoid logging sensitive data
|
||||
|
||||
2. **Metrics**
|
||||
- Use standard metric types (Counter, Gauge, Histogram)
|
||||
- Keep cardinality low (avoid high-cardinality labels)
|
||||
- Define SLIs and SLOs for critical paths
|
||||
- Monitor business metrics, not just technical ones
|
||||
|
||||
3. **Tracing**
|
||||
- Add traces for critical operations
|
||||
- Include relevant context in spans
|
||||
- Sample appropriately to control costs
|
||||
- Use distributed tracing for microservices
|
||||
|
||||
4. **Alerting**
|
||||
- Alert on symptoms, not causes
|
||||
- Include runbook links in alerts
|
||||
- Avoid alert fatigue with proper thresholds
|
||||
- Test alerting rules regularly
|
||||
@@ -1,158 +0,0 @@
|
||||
---
|
||||
name: performance-optimization
|
||||
description: Performance optimization patterns for GoodGo microservices including database query optimization, memory leak detection, profiling, connection pooling, and caching strategies.
|
||||
---
|
||||
|
||||
# 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
|
||||
- Identifying N+1 query problems
|
||||
|
||||
## Performance Optimization Workflow
|
||||
|
||||
The performance optimization process follows a systematic approach to identify, analyze, and resolve performance bottlenecks.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Optimization]) --> Identify[Identify Performance Issue]
|
||||
Identify --> Monitor[Monitor Metrics]
|
||||
Monitor --> Profiling[Profile Application]
|
||||
Profiling --> Analyze[Analyze Results]
|
||||
Analyze --> IdentifyBottleneck{Identify Bottleneck}
|
||||
|
||||
IdentifyBottleneck -->|Database| OptimizeDB[Optimize Queries]
|
||||
IdentifyBottleneck -->|Memory| OptimizeMem[Fix Memory Leaks]
|
||||
IdentifyBottleneck -->|Network| OptimizeNet[Optimize Connections]
|
||||
IdentifyBottleneck -->|Cache| OptimizeCache[Improve Caching]
|
||||
|
||||
OptimizeDB --> Implement[Implement Optimization]
|
||||
OptimizeMem --> Implement
|
||||
OptimizeNet --> Implement
|
||||
OptimizeCache --> Implement
|
||||
|
||||
Implement --> Test[Test Changes]
|
||||
Test --> Verify{Performance Improved?}
|
||||
Verify -->|Yes| Monitor
|
||||
Verify -->|No| Analyze
|
||||
Monitor --> Threshold{Meets Targets?}
|
||||
Threshold -->|Yes| Complete([Optimization Complete])
|
||||
Threshold -->|No| Profiling
|
||||
```
|
||||
|
||||
## Query Optimization Flow
|
||||
|
||||
Database query optimization is a critical aspect of performance. This flow shows how to systematically optimize queries.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Query Performance Issue]) --> Analyze[Analyze Query Performance]
|
||||
Analyze --> CheckIndexes[Check Indexes]
|
||||
CheckIndexes --> Explain[Run EXPLAIN ANALYZE]
|
||||
Explain --> IdentifyIssues{Identify Issues}
|
||||
|
||||
IdentifyIssues -->|N+1 Queries| FixN1[Use Include/Join]
|
||||
IdentifyIssues -->|Missing Index| AddIndex[Add Database Index]
|
||||
IdentifyIssues -->|Slow Query| OptimizeQuery[Rewrite Query]
|
||||
IdentifyIssues -->|Unbounded| AddPagination[Add Pagination]
|
||||
|
||||
FixN1 --> VerifyQuery[Verify Query Performance]
|
||||
AddIndex --> VerifyQuery
|
||||
OptimizeQuery --> VerifyQuery
|
||||
AddPagination --> VerifyQuery
|
||||
|
||||
VerifyQuery --> TestQuery{Query Time < 50ms?}
|
||||
TestQuery -->|Yes| Complete([Optimization Complete])
|
||||
TestQuery -->|No| Analyze
|
||||
|
||||
style FixN1 fill:#e1f5e1
|
||||
style AddIndex fill:#e1f5e1
|
||||
style OptimizeQuery fill:#e1f5e1
|
||||
style AddPagination fill:#e1f5e1
|
||||
```
|
||||
|
||||
## Profiling Process
|
||||
|
||||
The profiling process helps identify performance bottlenecks through systematic data collection and analysis.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Profiling]) --> Setup[Setup Profiling Tools]
|
||||
Setup --> ChooseTool{Choose Profiling Type}
|
||||
|
||||
ChooseTool -->|CPU| CPUProf[CPU Profiling]
|
||||
ChooseTool -->|Memory| MemProf[Memory Profiling]
|
||||
ChooseTool -->|Database| DBProf[Database Query Profiling]
|
||||
|
||||
CPUProf --> CollectCPU[Collect CPU Metrics]
|
||||
MemProf --> CollectMem[Collect Memory Metrics]
|
||||
DBProf --> CollectDB[Collect Query Metrics]
|
||||
|
||||
CollectCPU --> Analyze[Analyze Profiling Data]
|
||||
CollectMem --> Analyze
|
||||
CollectDB --> Analyze
|
||||
|
||||
Analyze --> IdentifyHotspots[Identify Hotspots]
|
||||
IdentifyHotspots --> Prioritize[Prioritize Issues]
|
||||
Prioritize --> Optimize[Optimize Critical Paths]
|
||||
Optimize --> ReProfile[Re-run Profiling]
|
||||
ReProfile --> Compare{Performance Improved?}
|
||||
Compare -->|Yes| Complete([Profiling Complete])
|
||||
Compare -->|No| IdentifyHotspots
|
||||
|
||||
style CPUProf fill:#e3f2fd
|
||||
style MemProf fill:#e3f2fd
|
||||
style DBProf fill:#e3f2fd
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Database Query Optimization
|
||||
|
||||
```typescript
|
||||
// Avoid N+1 queries
|
||||
// Bad: Multiple queries
|
||||
for (const user of users) {
|
||||
user.orders = await orderRepository.findByUserId(user.id);
|
||||
}
|
||||
|
||||
// Good: Single query with join
|
||||
const users = await userRepository.findAll({
|
||||
include: { orders: true },
|
||||
});
|
||||
```
|
||||
|
||||
### Memory Profiling
|
||||
|
||||
```typescript
|
||||
// Monitor memory usage
|
||||
const profiler = new MemoryProfiler();
|
||||
profiler.start(); // Monitor every minute
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```typescript
|
||||
// Batch database operations
|
||||
await batchOperations.batchCreate(items, 100); // Process 100 at a time
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use indexes, avoid N+1 queries
|
||||
2. Monitor memory usage, detect leaks
|
||||
3. Cache frequently accessed data
|
||||
4. Configure connection pools appropriately
|
||||
5. Profile regularly to identify bottlenecks
|
||||
|
||||
## Resources
|
||||
|
||||
- [Caching Patterns](./caching-patterns.md)
|
||||
- [Observability & Monitoring](./observability-monitoring.md)
|
||||
- Skill Source: `.cursor/skills/performance-optimization/SKILL.md`
|
||||
@@ -1,400 +0,0 @@
|
||||
---
|
||||
name: project-rules
|
||||
description: GoodGo Microservices Platform coding standards and architecture patterns. Use when working with services, apps, packages, or infrastructure.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
### Monorepo Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph apps[Apps Layer]
|
||||
webAdmin[web-admin<br/>Next.js Admin]
|
||||
webClient[web-client<br/>Next.js Client]
|
||||
appAdmin[app-admin<br/>Flutter Admin]
|
||||
appClient[app-client<br/>Flutter Client]
|
||||
end
|
||||
|
||||
subgraph gateway[API Gateway]
|
||||
traefik[Traefik<br/>Path-based Routing]
|
||||
end
|
||||
|
||||
subgraph services[Services Layer]
|
||||
iamService[iam-service<br/>IAM Service]
|
||||
templateService[_template<br/>Service Template]
|
||||
otherServices[Other Services<br/>Node.js/TypeScript]
|
||||
end
|
||||
|
||||
subgraph packages[Shared Packages]
|
||||
loggerPackage[@goodgo/logger<br/>Centralized Logging]
|
||||
typesPackage[@goodgo/types<br/>TypeScript Types]
|
||||
httpClientPackage[@goodgo/http-client<br/>API Client]
|
||||
authSdkPackage[@goodgo/auth-sdk<br/>Auth Utilities]
|
||||
tracingPackage[@goodgo/tracing<br/>OpenTelemetry]
|
||||
configPackage[@goodgo/config<br/>Shared Configs]
|
||||
end
|
||||
|
||||
subgraph infrastructure[Infrastructure]
|
||||
postgres[Neon PostgreSQL<br/>Database]
|
||||
redis[Redis<br/>Cache]
|
||||
prometheus[Prometheus<br/>Metrics]
|
||||
grafana[Grafana<br/>Dashboards]
|
||||
loki[Loki<br/>Log Aggregation]
|
||||
end
|
||||
|
||||
subgraph deployments[Deployments]
|
||||
dockerCompose[Docker Compose<br/>Local Development]
|
||||
kubernetes[Kubernetes<br/>Staging/Production]
|
||||
end
|
||||
|
||||
webAdmin --> traefik
|
||||
webClient --> traefik
|
||||
appAdmin --> traefik
|
||||
appClient --> traefik
|
||||
traefik --> iamService
|
||||
traefik --> otherServices
|
||||
iamService --> packages
|
||||
otherServices --> packages
|
||||
iamService --> postgres
|
||||
otherServices --> postgres
|
||||
iamService --> redis
|
||||
otherServices --> redis
|
||||
services --> prometheus
|
||||
services --> loki
|
||||
prometheus --> grafana
|
||||
loki --> grafana
|
||||
services --> deployments
|
||||
```
|
||||
|
||||
## 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`
|
||||
|
||||
### Detailed Structure Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph service[Service Structure]
|
||||
serviceRoot[service-name/]
|
||||
serviceSrc[src/]
|
||||
serviceConfig[config/<br/>Configuration]
|
||||
serviceModules[modules/<br/>Feature Modules]
|
||||
serviceMiddlewares[middlewares/<br/>Express Middlewares]
|
||||
serviceRoutes[routes/<br/>Route Definitions]
|
||||
serviceMain[main.ts<br/>Entry Point]
|
||||
servicePrisma[prisma/<br/>Schema & Migrations]
|
||||
serviceDockerfile[Dockerfile<br/>Container Definition]
|
||||
servicePackageJson[package.json<br/>Dependencies]
|
||||
|
||||
serviceRoot --> serviceSrc
|
||||
serviceRoot --> servicePrisma
|
||||
serviceRoot --> serviceDockerfile
|
||||
serviceRoot --> servicePackageJson
|
||||
serviceSrc --> serviceConfig
|
||||
serviceSrc --> serviceModules
|
||||
serviceSrc --> serviceMiddlewares
|
||||
serviceSrc --> serviceRoutes
|
||||
serviceSrc --> serviceMain
|
||||
end
|
||||
|
||||
subgraph package[Package Structure]
|
||||
packageRoot[package-name/]
|
||||
packageSrc[src/]
|
||||
packageIndex[index.ts<br/>Main Export]
|
||||
packagePackageJson[package.json<br/>Package Metadata]
|
||||
packageTsconfig[tsconfig.json<br/>TypeScript Config]
|
||||
packageReadme[README.md<br/>Documentation]
|
||||
|
||||
packageRoot --> packageSrc
|
||||
packageRoot --> packagePackageJson
|
||||
packageRoot --> packageTsconfig
|
||||
packageRoot --> packageReadme
|
||||
packageSrc --> packageIndex
|
||||
end
|
||||
|
||||
subgraph app[App Structure - Next.js]
|
||||
appRoot[app-name/]
|
||||
appSrc[src/]
|
||||
appApp[app/<br/>Next.js App Router]
|
||||
appServicesApi[services/api/<br/>API Clients]
|
||||
appStores[stores/<br/>State Management]
|
||||
appDockerfile[Dockerfile<br/>Container Definition]
|
||||
appPackageJson[package.json<br/>Dependencies]
|
||||
|
||||
appRoot --> appSrc
|
||||
appRoot --> appDockerfile
|
||||
appRoot --> appPackageJson
|
||||
appSrc --> appApp
|
||||
appSrc --> appServicesApi
|
||||
appSrc --> appStores
|
||||
end
|
||||
|
||||
subgraph module[Module Structure inside modules/]
|
||||
moduleRoot[modules/feature-name/]
|
||||
moduleController[feature.controller.ts<br/>HTTP Handlers]
|
||||
moduleService[feature.service.ts<br/>Business Logic]
|
||||
moduleRepository[feature.repository.ts<br/>Data Access]
|
||||
moduleDto[feature.dto.ts<br/>Zod Schemas]
|
||||
moduleTypes[feature.types.ts<br/>TypeScript Types]
|
||||
moduleTest[feature.controller.test.ts<br/>Unit Tests]
|
||||
|
||||
moduleRoot --> moduleController
|
||||
moduleRoot --> moduleService
|
||||
moduleRoot --> moduleRepository
|
||||
moduleRoot --> moduleDto
|
||||
moduleRoot --> moduleTypes
|
||||
moduleRoot --> moduleTest
|
||||
moduleController --> moduleService
|
||||
moduleService --> moduleRepository
|
||||
end
|
||||
|
||||
serviceModules --> moduleRoot
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 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)
|
||||
@@ -1,334 +0,0 @@
|
||||
---
|
||||
name: repository-pattern
|
||||
description: Repository pattern implementation and best practices for GoodGo microservices. Use when implementing data access layers, extending BaseRepository, writing database queries, handling transactions, or optimizing database operations.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
### Repository Architecture
|
||||
|
||||
The repository pattern creates an abstraction layer between the service layer and data access layer, providing a clean separation of concerns.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Controller["Controller<br/>(HTTP Handler)"] --> Service["Service Layer<br/>(Business Logic)"]
|
||||
Service --> Repository["Repository<br/>(Data Access)"]
|
||||
Repository --> Prisma["Prisma Client<br/>(ORM)"]
|
||||
Prisma --> Database[("Database<br/>(PostgreSQL)")]
|
||||
|
||||
style Controller fill:#e1f5ff
|
||||
style Service fill:#fff4e1
|
||||
style Repository fill:#e8f5e9
|
||||
style Prisma fill:#f3e5f5
|
||||
style Database fill:#ffebee
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### Class Hierarchy
|
||||
|
||||
Repositories extend the `BaseRepository` abstract class, inheriting common CRUD operations while allowing custom query methods.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BaseRepository {
|
||||
<<abstract>>
|
||||
#prisma: PrismaClient
|
||||
#modelName: string
|
||||
+findById(id: string) Promise~T~null~
|
||||
+findByUnique(field: string, value: any) Promise~T~null~
|
||||
+findAll(options?: any) Promise~T[]~
|
||||
+create(data: CreateInput) Promise~T~
|
||||
+update(id: string, data: UpdateInput) Promise~T~
|
||||
+delete(id: string) Promise~boolean~
|
||||
+count(where?: any) Promise~number~
|
||||
+exists(id: string) Promise~boolean~
|
||||
+transaction(callback: Function) Promise~R~
|
||||
}
|
||||
|
||||
class IRepository {
|
||||
<<interface>>
|
||||
+findById(id: string) Promise~T~null~
|
||||
+findByUnique(field: string, value: any) Promise~T~null~
|
||||
+findAll(options?: any) Promise~T[]~
|
||||
+create(data: CreateInput) Promise~T~
|
||||
+update(id: string, data: UpdateInput) Promise~T~
|
||||
+delete(id: string) Promise~boolean~
|
||||
+count(where?: any) Promise~number~
|
||||
+exists(id: string) Promise~boolean~
|
||||
}
|
||||
|
||||
class UserRepository {
|
||||
+findByEmail(email: string) Promise~User~null~
|
||||
+findByUsername(username: string) Promise~User~null~
|
||||
+findWithPermissions(userId: string) Promise~User~null~
|
||||
+findActiveUsers(organizationId?: string) Promise~User[]~
|
||||
}
|
||||
|
||||
class ProductRepository {
|
||||
+findByCategory(categoryId: string) Promise~Product[]~
|
||||
+findActiveProducts() Promise~Product[]~
|
||||
}
|
||||
|
||||
BaseRepository <|-- UserRepository : extends
|
||||
BaseRepository <|-- ProductRepository : extends
|
||||
IRepository <|.. UserRepository : implements
|
||||
```
|
||||
|
||||
**Note**: Specific repositories like `UserRepository` and `ProductRepository` extend `BaseRepository` and can implement the `IRepository` interface for additional type safety. They inherit all base CRUD methods and add domain-specific query methods.
|
||||
|
||||
## 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;
|
||||
});
|
||||
```
|
||||
|
||||
**Transaction Flow**:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service
|
||||
participant Repository
|
||||
participant Prisma as Prisma Client
|
||||
participant DB as Database
|
||||
|
||||
Service->>Repository: transaction(callback)
|
||||
Repository->>Prisma: $transaction(callback)
|
||||
Prisma->>DB: BEGIN TRANSACTION
|
||||
|
||||
Note over Service,DB: All operations use transaction client (tx)
|
||||
|
||||
Service->>Repository: tx.user.create(data)
|
||||
Repository->>Prisma: tx.user.create(data)
|
||||
Prisma->>DB: INSERT INTO users ...
|
||||
DB-->>Prisma: User created
|
||||
Prisma-->>Repository: User entity
|
||||
Repository-->>Service: User entity
|
||||
|
||||
Service->>Repository: tx.userProfile.create(data)
|
||||
Repository->>Prisma: tx.userProfile.create(data)
|
||||
Prisma->>DB: INSERT INTO user_profiles ...
|
||||
DB-->>Prisma: Profile created
|
||||
Prisma-->>Repository: Profile entity
|
||||
Repository-->>Service: Profile entity
|
||||
|
||||
alt All operations succeed
|
||||
Prisma->>DB: COMMIT
|
||||
DB-->>Prisma: Transaction committed
|
||||
Prisma-->>Repository: Success result
|
||||
Repository-->>Service: Success result
|
||||
else Error occurs
|
||||
Prisma->>DB: ROLLBACK
|
||||
DB-->>Prisma: Transaction rolled back
|
||||
Prisma-->>Repository: Error thrown
|
||||
Repository-->>Service: DatabaseError thrown
|
||||
end
|
||||
```
|
||||
|
||||
**Important**: All operations within the transaction callback must use the transaction client (`tx`) parameter, not the main Prisma client, to ensure atomicity.
|
||||
|
||||
### 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.
|
||||
|
||||
## 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
|
||||
@@ -1,239 +0,0 @@
|
||||
---
|
||||
name: resilience-patterns
|
||||
description: Resilience patterns for GoodGo microservices including circuit breaker, retry strategies, timeout handling, and graceful degradation. Use when implementing fault tolerance, handling external service failures, or improving system reliability.
|
||||
---
|
||||
|
||||
# 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:
|
||||
|
||||
The circuit breaker has three states that transition based on error rates and timeouts:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> CLOSED: Initial State
|
||||
CLOSED --> OPEN: Errors exceed threshold<br/>(errorThresholdPercentage: 50%)
|
||||
OPEN --> HALF_OPEN: Reset timeout expires<br/>(resetTimeout: 30s)
|
||||
HALF_OPEN --> CLOSED: Request succeeds
|
||||
HALF_OPEN --> OPEN: Request fails
|
||||
CLOSED --> [*]: Normal operation
|
||||
OPEN --> [*]: Circuit open (rejecting requests)
|
||||
HALF_OPEN --> [*]: Testing recovery
|
||||
```
|
||||
|
||||
**Circuit Breaker States:**
|
||||
- **CLOSED**: Normal operation, requests pass through
|
||||
- **OPEN**: Circuit is open, requests are immediately rejected
|
||||
- **HALF-OPEN**: Testing if service has recovered, allows limited requests
|
||||
|
||||
```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:
|
||||
|
||||
The retry pattern attempts an operation multiple times with increasing delays between attempts:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Operation]) --> Attempt[Attempt Operation]
|
||||
Attempt --> Success{Success?}
|
||||
Success -->|Yes| Return([Return Result])
|
||||
Success -->|No| CheckRetries{Attempt < Max Retries?}
|
||||
CheckRetries -->|No| ThrowError([Throw Error])
|
||||
CheckRetries -->|Yes| CalculateDelay[Calculate Delay:<br/>baseDelay × 2^attempt]
|
||||
CalculateDelay --> Wait[Wait for Delay]
|
||||
Wait --> IncrementAttempt[Increment Attempt]
|
||||
IncrementAttempt --> Attempt
|
||||
|
||||
style Start fill:#e1f5e1
|
||||
style Return fill:#e1f5e1
|
||||
style ThrowError fill:#ffe1e1
|
||||
style CalculateDelay fill:#fff4e1
|
||||
```
|
||||
|
||||
**Exponential Backoff Example:**
|
||||
- Attempt 1: 1s delay
|
||||
- Attempt 2: 2s delay
|
||||
- Attempt 3: 4s delay
|
||||
- Attempt 4: 8s delay
|
||||
|
||||
```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:
|
||||
|
||||
The timeout pattern uses Promise.race to enforce maximum execution time:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant TimeoutWrapper
|
||||
participant Operation
|
||||
participant TimeoutTimer
|
||||
|
||||
Client->>TimeoutWrapper: Execute with timeout
|
||||
TimeoutWrapper->>Operation: Start operation
|
||||
TimeoutWrapper->>TimeoutTimer: Start timeout timer
|
||||
|
||||
alt Operation completes first
|
||||
Operation-->>TimeoutWrapper: Return result
|
||||
TimeoutWrapper-->>Client: Return result
|
||||
TimeoutWrapper->>TimeoutTimer: Cancel timer
|
||||
else Timeout expires first
|
||||
TimeoutTimer-->>TimeoutWrapper: Timeout error
|
||||
TimeoutWrapper->>Operation: (Operation may continue)
|
||||
TimeoutWrapper-->>Client: Reject with timeout error
|
||||
end
|
||||
```
|
||||
|
||||
**Timeout Behavior:**
|
||||
- Uses `Promise.race()` to compete operation vs timeout
|
||||
- First to resolve/reject wins
|
||||
- Operation may continue after timeout, but result is ignored
|
||||
|
||||
```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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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)
|
||||
2. **No Timeout**: Missing timeouts on external calls
|
||||
3. **No Fallback**: No graceful degradation strategy
|
||||
4. **Too Many Retries**: Excessive retries causing performance issues
|
||||
|
||||
## Resources
|
||||
|
||||
- [Circuit Breaker](../../services/iam-service/src/modules/common/circuit-breaker.ts) - Circuit breaker implementation
|
||||
@@ -1,925 +0,0 @@
|
||||
---
|
||||
name: security
|
||||
description: Security best practices and patterns for GoodGo microservices platform. Use when implementing authentication, authorization, data protection, input validation, rate limiting, secrets management, or security testing across all services.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
The following diagram illustrates the authentication flow when a client makes a request with a JWT token:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware as Auth Middleware
|
||||
participant JWTService as JWT Service
|
||||
participant Request as Express Request
|
||||
|
||||
Client->>Middleware: HTTP Request with Token
|
||||
Middleware->>Middleware: Extract token from<br/>Authorization header or cookie
|
||||
|
||||
alt Token not found
|
||||
Middleware->>Client: 401 Unauthorized<br/>(AUTH_REQUIRED)
|
||||
else Token found
|
||||
Middleware->>JWTService: verifyAccessToken(token)
|
||||
|
||||
alt Token invalid or expired
|
||||
JWTService->>Middleware: Verification failed
|
||||
Middleware->>Client: 401 Unauthorized<br/>(INVALID_TOKEN)
|
||||
else Token valid
|
||||
JWTService->>Middleware: Payload (sub, email, roles, permissions)
|
||||
Middleware->>Request: Attach user to req.user
|
||||
Middleware->>Client: Continue to next middleware
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/middlewares/auth.middleware.ts
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { jwtService } from '@goodgo/auth-sdk';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export const authenticate = () => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Extract token from Authorization header or cookie
|
||||
let token: string | null = null;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
} else if (req.cookies?.access_token) {
|
||||
token = req.cookies.access_token;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const payload = await jwtService.verifyAccessToken(token);
|
||||
|
||||
// Attach user to request
|
||||
req.user = {
|
||||
id: payload.sub,
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
roles: payload.roles || [],
|
||||
permissions: payload.permissions || []
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.warn('Authentication failed', { error: error.message });
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Role-Based Authorization
|
||||
|
||||
The authorization decision flow determines whether a user has the required permissions to access a resource:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Request Received]) --> CheckAuth{User<br/>Authenticated?}
|
||||
|
||||
CheckAuth -->|No| Return401[Return 401<br/>AUTH_REQUIRED]
|
||||
CheckAuth -->|Yes| CheckType{Authorization<br/>Type?}
|
||||
|
||||
CheckType -->|Role-Based| CheckRole{User has<br/>Required Role?}
|
||||
CheckType -->|Permission-Based| CheckPermission{User has<br/>Resource:Action<br/>Permission?}
|
||||
CheckType -->|Ownership| CheckOwnership{Resource ID<br/>matches User ID?}
|
||||
|
||||
CheckRole -->|No| LogDenial[Log Permission Denied<br/>with user roles]
|
||||
CheckPermission -->|No| LogDenial
|
||||
CheckOwnership -->|No| LogDenial
|
||||
|
||||
LogDenial --> Return403[Return 403<br/>FORBIDDEN]
|
||||
|
||||
CheckRole -->|Yes| Allow[Allow Request<br/>Continue to Handler]
|
||||
CheckPermission -->|Yes| Allow
|
||||
CheckOwnership -->|Yes| Allow
|
||||
|
||||
Return401 --> End([End])
|
||||
Return403 --> End
|
||||
Allow --> End
|
||||
|
||||
style CheckAuth fill:#e1f5ff
|
||||
style CheckType fill:#e1f5ff
|
||||
style Return401 fill:#ffebee
|
||||
style Return403 fill:#ffebee
|
||||
style Allow fill:#e8f5e9
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/middlewares/rbac.middleware.ts
|
||||
export const requireRole = (...allowedRoles: string[]) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
const userRoles = req.user.roles || [];
|
||||
const hasRole = userRoles.some(role => allowedRoles.includes(role));
|
||||
|
||||
if (!hasRole) {
|
||||
logger.warn('Access denied - insufficient role', {
|
||||
userId: req.user.id,
|
||||
userRoles,
|
||||
requiredRoles: allowedRoles
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Permission-based authorization
|
||||
export const requirePermission = (resource: string, action: string) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
const permission = `${resource}:${action}`;
|
||||
const hasPermission = req.user.permissions?.includes(permission);
|
||||
|
||||
if (!hasPermission) {
|
||||
logger.warn('Access denied - insufficient permission', {
|
||||
userId: req.user.id,
|
||||
required: permission
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Usage in routes
|
||||
router.post(
|
||||
'/api/v1/users',
|
||||
authenticate(),
|
||||
requirePermission('users', 'create'),
|
||||
userController.create
|
||||
);
|
||||
```
|
||||
|
||||
### Resource Ownership Validation
|
||||
|
||||
```typescript
|
||||
// Ensure users can only access their own resources
|
||||
export const requireOwnership = (resourceIdParam: string = 'id') => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const resourceId = req.params[resourceIdParam];
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (resourceId !== userId) {
|
||||
logger.warn('Access denied - resource ownership mismatch', {
|
||||
userId,
|
||||
resourceId
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Access denied' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Data Protection
|
||||
|
||||
### Encryption Service
|
||||
|
||||
The encryption and decryption flow for protecting sensitive data at rest:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service
|
||||
participant EncryptionService
|
||||
participant Crypto as Node.js Crypto
|
||||
participant Database
|
||||
|
||||
Note over Service,Database: Encryption Flow
|
||||
Service->>EncryptionService: encrypt(plaintext)
|
||||
EncryptionService->>Crypto: Generate random IV<br/>(16 bytes)
|
||||
EncryptionService->>Crypto: Create cipher<br/>(AES-256-GCM)
|
||||
EncryptionService->>Crypto: Encrypt plaintext
|
||||
Crypto->>EncryptionService: Encrypted data + Auth Tag
|
||||
EncryptionService->>Service: Format: iv:tag:ciphertext
|
||||
Service->>Database: Store encrypted data
|
||||
|
||||
Note over Service,Database: Decryption Flow
|
||||
Service->>Database: Retrieve encrypted data
|
||||
Database->>Service: iv:tag:ciphertext
|
||||
Service->>EncryptionService: decrypt(encryptedText)
|
||||
EncryptionService->>EncryptionService: Split iv, tag, ciphertext
|
||||
EncryptionService->>Crypto: Create decipher<br/>(AES-256-GCM)
|
||||
EncryptionService->>Crypto: Set auth tag
|
||||
EncryptionService->>Crypto: Decrypt ciphertext
|
||||
Crypto->>EncryptionService: Plaintext
|
||||
EncryptionService->>Service: Return plaintext
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/core/security/encryption.service.ts
|
||||
import crypto from 'crypto';
|
||||
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const IV_LENGTH = 16;
|
||||
const TAG_LENGTH = 16;
|
||||
|
||||
export class EncryptionService {
|
||||
private getKey(): Buffer {
|
||||
const secret = process.env.ENCRYPTION_KEY;
|
||||
if (!secret || secret.length < 32) {
|
||||
throw new Error('ENCRYPTION_KEY must be at least 32 characters');
|
||||
}
|
||||
return crypto.scryptSync(secret, 'salt', 32);
|
||||
}
|
||||
|
||||
encrypt(text: string): string {
|
||||
const key = this.getKey();
|
||||
const iv = crypto.randomBytes(IV_LENGTH);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
||||
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
|
||||
}
|
||||
|
||||
decrypt(encryptedText: string): string {
|
||||
const [ivHex, tagHex, encrypted] = encryptedText.split(':');
|
||||
const iv = Buffer.from(ivHex, 'hex');
|
||||
const tag = Buffer.from(tagHex, 'hex');
|
||||
|
||||
const key = this.getKey();
|
||||
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage: Encrypt PII before storing
|
||||
const encryption = new EncryptionService();
|
||||
const encryptedPhone = encryption.encrypt(user.phone);
|
||||
```
|
||||
|
||||
### Password Hashing
|
||||
|
||||
The password hashing and verification flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Service
|
||||
participant PasswordService
|
||||
participant Bcrypt
|
||||
participant Database
|
||||
|
||||
Note over User,Database: Registration/Password Change
|
||||
User->>Service: Submit password
|
||||
Service->>PasswordService: hash(password)
|
||||
PasswordService->>Bcrypt: bcrypt.hash(password, 12)
|
||||
Bcrypt->>Bcrypt: Generate salt
|
||||
Bcrypt->>Bcrypt: Hash with cost factor 12
|
||||
Bcrypt->>PasswordService: Hashed password
|
||||
PasswordService->>Service: Return hash
|
||||
Service->>Database: Store passwordHash
|
||||
Service->>Service: Sanitize password<br/>before logging
|
||||
|
||||
Note over User,Database: Login Verification
|
||||
User->>Service: Submit credentials
|
||||
Service->>Database: Fetch user by email
|
||||
Database->>Service: User with passwordHash
|
||||
Service->>PasswordService: verify(password, hash)
|
||||
PasswordService->>Bcrypt: bcrypt.compare(password, hash)
|
||||
Bcrypt->>PasswordService: Boolean result
|
||||
PasswordService->>Service: Return verification result
|
||||
|
||||
alt Password matches
|
||||
Service->>User: Authentication success
|
||||
else Password mismatch
|
||||
Service->>User: Invalid credentials<br/>(generic error)
|
||||
end
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Always use bcrypt with appropriate cost factor
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const SALT_ROUNDS = 12; // Production: 12, Development: 10
|
||||
|
||||
export class PasswordService {
|
||||
async hash(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
async verify(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
// Never log passwords
|
||||
sanitizeForLogging(data: any): any {
|
||||
const sanitized = { ...data };
|
||||
if (sanitized.password) sanitized.password = '[REDACTED]';
|
||||
if (sanitized.passwordHash) sanitized.passwordHash = '[REDACTED]';
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Token Hashing
|
||||
|
||||
```typescript
|
||||
// Hash tokens before storing in database
|
||||
import crypto from 'crypto';
|
||||
|
||||
export class TokenService {
|
||||
hashToken(token: string): string {
|
||||
const salt = process.env.TOKEN_SALT || 'default-salt-change-in-production';
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(token + salt)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
generateSecureToken(length: number = 32): string {
|
||||
return crypto.randomBytes(length).toString('hex');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Zod Schema Validation
|
||||
|
||||
```typescript
|
||||
// Always validate inputs with Zod
|
||||
import { z } from 'zod';
|
||||
|
||||
// DTO with validation
|
||||
export const CreateUserDto = z.object({
|
||||
email: z.string().email('Invalid email format'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain number')
|
||||
.regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
|
||||
phone: z.string()
|
||||
.regex(/^\+[1-9]\d{1,14}$/, 'Invalid phone format (E.164)')
|
||||
.optional(),
|
||||
name: z.string().min(1).max(255)
|
||||
});
|
||||
|
||||
// In controller
|
||||
export class UserController {
|
||||
async create(req: Request, res: Response) {
|
||||
try {
|
||||
const dto = CreateUserDto.parse(req.body);
|
||||
const user = await this.service.create(dto);
|
||||
res.status(201).json({ success: true, data: user });
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid input data',
|
||||
details: error.errors
|
||||
}
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File Upload Validation
|
||||
|
||||
```typescript
|
||||
// Validate file uploads
|
||||
import fileType from 'file-type';
|
||||
|
||||
export class FileValidationService {
|
||||
private readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
private readonly ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
|
||||
|
||||
async validateFile(file: Express.Multer.File): Promise<void> {
|
||||
// Size check
|
||||
if (file.size > this.MAX_FILE_SIZE) {
|
||||
throw new HttpError(400, 'FILE_TOO_LARGE', 'File exceeds maximum size');
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!this.ALLOWED_TYPES.includes(file.mimetype)) {
|
||||
throw new HttpError(400, 'INVALID_FILE_TYPE', 'File type not allowed');
|
||||
}
|
||||
|
||||
// Content validation (prevent MIME type spoofing)
|
||||
const type = await fileType.fromBuffer(file.buffer);
|
||||
if (!type || !this.ALLOWED_TYPES.includes(type.mime)) {
|
||||
throw new HttpError(400, 'INVALID_FILE_CONTENT', 'File content mismatch');
|
||||
}
|
||||
|
||||
// TODO: Add virus scanning for production
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQL Injection Prevention
|
||||
|
||||
```typescript
|
||||
// Always use Prisma parameterized queries (automatic)
|
||||
// Never use string concatenation for queries
|
||||
|
||||
// ❌ BAD - Never do this
|
||||
const query = `SELECT * FROM users WHERE email = '${email}'`;
|
||||
|
||||
// ✅ GOOD - Use Prisma
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
// ✅ GOOD - For dynamic queries
|
||||
const where: any = {};
|
||||
if (email) where.email = email;
|
||||
if (status) where.status = status;
|
||||
|
||||
const users = await prisma.user.findMany({ where });
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Implement rate limiting for all endpoints
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redis = new Redis(process.env.REDIS_URL);
|
||||
|
||||
// Standard rate limit
|
||||
export const standardLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:standard:'
|
||||
}),
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // 100 requests per window
|
||||
message: 'Too many requests, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
// Strict rate limit for sensitive operations
|
||||
export const strictLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:strict:'
|
||||
}),
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 10,
|
||||
message: 'Rate limit exceeded for this operation'
|
||||
});
|
||||
|
||||
// Login-specific rate limit
|
||||
export const loginLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:login:'
|
||||
}),
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 5, // 5 login attempts per 15 minutes
|
||||
skipSuccessfulRequests: true,
|
||||
message: 'Too many login attempts, please try again later'
|
||||
});
|
||||
|
||||
// Usage
|
||||
router.post('/api/v1/auth/login', loginLimiter, authController.login);
|
||||
router.post('/api/v1/users', authenticate(), strictLimiter, userController.create);
|
||||
```
|
||||
|
||||
## Error Handling Security
|
||||
|
||||
```typescript
|
||||
// Sanitize error messages to prevent information disclosure
|
||||
export class SecureErrorHandler {
|
||||
handleError(error: Error, req: Request, res: Response) {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
// Log full error internally
|
||||
logger.error('Request error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
userId: req.user?.id
|
||||
});
|
||||
|
||||
// Don't expose user existence
|
||||
if (error.message.includes('user not found') ||
|
||||
error.message.includes('invalid credentials')) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
message: 'Invalid email or password'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validation errors - safe to expose
|
||||
if (error instanceof z.ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid input data',
|
||||
details: error.errors
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generic errors for production
|
||||
if (isProd) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'An error occurred. Please try again later.'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Detailed errors only in development
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
stack: isDev ? error.stack : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Secrets Management
|
||||
|
||||
```typescript
|
||||
// Never hardcode secrets
|
||||
// Always use environment variables with validation
|
||||
import { z } from 'zod';
|
||||
|
||||
const secretsSchema = z.object({
|
||||
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
|
||||
JWT_REFRESH_SECRET: z.string().min(32),
|
||||
DATABASE_URL: z.string().url(),
|
||||
REDIS_URL: z.string().url().optional(),
|
||||
ENCRYPTION_KEY: z.string().min(32).optional()
|
||||
});
|
||||
|
||||
export const secrets = secretsSchema.parse(process.env);
|
||||
|
||||
// For production, use secret management:
|
||||
// - AWS Secrets Manager
|
||||
// - HashiCorp Vault
|
||||
// - Kubernetes Secrets
|
||||
// - Azure Key Vault
|
||||
|
||||
// Rotate secrets regularly (quarterly recommended)
|
||||
```
|
||||
|
||||
## Audit Logging
|
||||
|
||||
```typescript
|
||||
// Log all security-relevant events
|
||||
export class AuditService {
|
||||
async logSecurityEvent(
|
||||
event: string,
|
||||
userId: string | null,
|
||||
details: Record<string, any>,
|
||||
req?: Request
|
||||
) {
|
||||
await this.prisma.auditLog.create({
|
||||
data: {
|
||||
event,
|
||||
userId,
|
||||
type: 'SECURITY',
|
||||
details: this.sanitizeDetails(details),
|
||||
ipAddress: req?.ip || details.ipAddress,
|
||||
userAgent: req?.get('user-agent'),
|
||||
timestamp: new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize PII from logs
|
||||
private sanitizeDetails(details: Record<string, any>): Record<string, any> {
|
||||
const sensitive = ['password', 'token', 'secret', 'ssn', 'creditCard'];
|
||||
const sanitized = { ...details };
|
||||
|
||||
for (const key of sensitive) {
|
||||
if (sanitized[key]) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
await auditService.logSecurityEvent('LOGIN_SUCCESS', user.id, {
|
||||
email: user.email,
|
||||
ipAddress: req.ip
|
||||
}, req);
|
||||
|
||||
await auditService.logSecurityEvent('PERMISSION_DENIED', user.id, {
|
||||
resource: 'users',
|
||||
action: 'delete',
|
||||
targetId: targetUserId
|
||||
}, req);
|
||||
```
|
||||
|
||||
## Security Headers
|
||||
|
||||
```typescript
|
||||
// Add security headers middleware
|
||||
import helmet from 'helmet';
|
||||
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"]
|
||||
}
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
}
|
||||
}));
|
||||
|
||||
// Additional headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
```typescript
|
||||
// Configure CORS securely
|
||||
import cors from 'cors';
|
||||
|
||||
const allowedOrigins = process.env.CORS_ORIGIN?.split(',') || [];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
exposedHeaders: ['X-Request-ID'],
|
||||
maxAge: 86400 // 24 hours
|
||||
}));
|
||||
```
|
||||
|
||||
## Security Testing
|
||||
|
||||
```typescript
|
||||
// Security test patterns
|
||||
describe('Security Tests', () => {
|
||||
it('should prevent SQL injection', async () => {
|
||||
const maliciousInput = "'; DROP TABLE users; --";
|
||||
const response = await request(app)
|
||||
.get(`/api/v1/users?search=${encodeURIComponent(maliciousInput)}`)
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
expect(response.status).not.toBe(500);
|
||||
// Should return 400 or empty results, not crash
|
||||
});
|
||||
|
||||
it('should prevent XSS attacks', async () => {
|
||||
const xssPayload = '<script>alert("XSS")</script>';
|
||||
const response = await request(app)
|
||||
.post('/api/v1/users')
|
||||
.send({ email: xssPayload, password: 'test123' });
|
||||
|
||||
// Response should sanitize or reject
|
||||
expect(response.body.data?.email).not.toContain('<script>');
|
||||
});
|
||||
|
||||
it('should enforce authentication', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/v1/users');
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should enforce authorization', async () => {
|
||||
const userToken = await createUserToken({ roles: ['user'] });
|
||||
const response = await request(app)
|
||||
.delete('/api/v1/users/123')
|
||||
.set('Authorization', `Bearer ${userToken}`);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
||||
it('should rate limit excessive requests', async () => {
|
||||
const requests = Array(20).fill(null).map(() =>
|
||||
request(app).get('/api/v1/users')
|
||||
);
|
||||
|
||||
const responses = await Promise.all(requests);
|
||||
const rateLimited = responses.filter(r => r.status === 429);
|
||||
|
||||
expect(rateLimited.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## Common Security Anti-Patterns
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Hardcoded secrets
|
||||
const SECRET = 'my-secret-key';
|
||||
|
||||
// ✅ GOOD: Environment variables
|
||||
const SECRET = process.env.JWT_SECRET;
|
||||
|
||||
// ❌ BAD: Plain text passwords
|
||||
await prisma.user.create({ data: { password: password } });
|
||||
|
||||
// ✅ GOOD: Hashed passwords
|
||||
await prisma.user.create({
|
||||
data: { passwordHash: await bcrypt.hash(password, 12) }
|
||||
});
|
||||
|
||||
// ❌ BAD: Exposing user existence
|
||||
if (!user) {
|
||||
throw new Error('User not found'); // Reveals user doesn't exist
|
||||
}
|
||||
|
||||
// ✅ GOOD: Generic error
|
||||
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
// ❌ BAD: No input validation
|
||||
const email = req.body.email;
|
||||
|
||||
// ✅ GOOD: Validate with Zod
|
||||
const { email } = CreateUserDto.parse(req.body);
|
||||
|
||||
// ❌ BAD: Stack traces in production
|
||||
res.status(500).json({ error: error.stack });
|
||||
|
||||
// ✅ GOOD: Sanitized errors
|
||||
res.status(500).json({
|
||||
error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
|
||||
});
|
||||
```
|
||||
|
||||
## Incident Response
|
||||
|
||||
```typescript
|
||||
// Security incident detection and response
|
||||
export class SecurityIncidentService {
|
||||
async detectAnomaly(userId: string, event: string, context: any) {
|
||||
// Check for suspicious patterns
|
||||
const recentEvents = await this.getRecentEvents(userId, '1h');
|
||||
|
||||
if (recentEvents.length > 10) {
|
||||
await this.triggerAlert('SUSPICIOUS_ACTIVITY', {
|
||||
userId,
|
||||
eventCount: recentEvents.length,
|
||||
timeWindow: '1h'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for privilege escalation attempts
|
||||
if (event === 'PERMISSION_DENIED' && context.requiredPermission) {
|
||||
await this.logSecurityEvent('PRIVILEGE_ESCALATION_ATTEMPT', userId, context);
|
||||
}
|
||||
}
|
||||
|
||||
async triggerAlert(type: string, details: any) {
|
||||
// Send to monitoring system
|
||||
logger.error('Security alert', { type, details });
|
||||
|
||||
// TODO: Integrate with PagerDuty, Slack, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [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)
|
||||
@@ -1,323 +0,0 @@
|
||||
---
|
||||
name: service-discovery-registry
|
||||
description: Service discovery and registry patterns for GoodGo microservices including service registry, health check orchestration, load balancing strategies, and service mesh integration.
|
||||
---
|
||||
|
||||
# Service Discovery & Registry Patterns
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Implementing service discovery mechanisms
|
||||
- Managing service registry
|
||||
- Orchestrating health checks
|
||||
- Implementing load balancing strategies
|
||||
- Integrating with service mesh
|
||||
- Managing dynamic service registration
|
||||
- Implementing DNS-based discovery
|
||||
- Building service catalog
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Service Discovery Types
|
||||
|
||||
1. **Client-Side Discovery**: Client queries service registry
|
||||
2. **Server-Side Discovery**: Load balancer queries registry (Kubernetes DNS)
|
||||
3. **Service Mesh**: Automatic service discovery (Istio, Linkerd)
|
||||
|
||||
### Service Registry
|
||||
|
||||
- **Static Configuration**: Hardcoded service addresses
|
||||
- **Dynamic Registry**: Services register/unregister dynamically
|
||||
- **DNS-Based**: Use DNS for service discovery (Kubernetes)
|
||||
|
||||
## Service Registration Flow
|
||||
|
||||
The service registration lifecycle involves startup registration, periodic heartbeats, and graceful shutdown unregistration.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Instance
|
||||
participant Registry as Service Registry
|
||||
participant Health as Health Check
|
||||
|
||||
Note over Service,Registry: Service Startup
|
||||
Service->>Registry: Register service info<br/>(name, url, version)
|
||||
Registry->>Registry: Store service metadata
|
||||
Registry-->>Service: Registration confirmed
|
||||
|
||||
Note over Service,Registry: Heartbeat Loop (every 30s)
|
||||
loop Every 30 seconds
|
||||
Service->>Health: Check own health status
|
||||
Health-->>Service: Health status
|
||||
Service->>Registry: Update registration<br/>(status, lastHeartbeat)
|
||||
Registry->>Registry: Update service record
|
||||
end
|
||||
|
||||
Note over Service,Registry: Service Shutdown
|
||||
Service->>Registry: Unregister service
|
||||
Registry->>Registry: Remove service record
|
||||
Registry-->>Service: Unregistration confirmed
|
||||
```
|
||||
|
||||
## Health Check Orchestration
|
||||
|
||||
Health checks ensure services are available and functioning correctly. The system aggregates health status from multiple services to determine overall system health.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Health Check Request]) --> GetServices[Get All Services from Registry]
|
||||
GetServices --> CheckEach{For Each Service}
|
||||
|
||||
CheckEach --> CheckHealth[Check Service Health Endpoint]
|
||||
CheckHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| UpdateHealthy[Update Status: Healthy]
|
||||
HealthOK -->|No| UpdateUnhealthy[Update Status: Unhealthy]
|
||||
|
||||
UpdateHealthy --> CheckTimeout{Last Heartbeat<br/>< 60s?}
|
||||
UpdateUnhealthy --> CheckTimeout
|
||||
|
||||
CheckTimeout -->|Yes| MarkActive[Mark as Active]
|
||||
CheckTimeout -->|No| MarkStale[Mark as Stale]
|
||||
|
||||
MarkActive --> NextService{More Services?}
|
||||
MarkStale --> NextService
|
||||
|
||||
NextService -->|Yes| CheckEach
|
||||
NextService -->|No| AggregateStatus[Aggregate Overall Status]
|
||||
|
||||
AggregateStatus --> CountUnhealthy[Count Unhealthy Services]
|
||||
CountUnhealthy --> DetermineStatus{Unhealthy Count}
|
||||
|
||||
DetermineStatus -->|0| StatusHealthy[Status: Healthy]
|
||||
DetermineStatus -->|< 50%| StatusDegraded[Status: Degraded]
|
||||
DetermineStatus -->|>= 50%| StatusUnhealthy[Status: Unhealthy]
|
||||
|
||||
StatusHealthy --> ReturnResult[Return Health Status]
|
||||
StatusDegraded --> ReturnResult
|
||||
StatusUnhealthy --> ReturnResult
|
||||
|
||||
ReturnResult --> End([End])
|
||||
```
|
||||
|
||||
## Load Balancing Strategies
|
||||
|
||||
Load balancing distributes requests across multiple service instances. Different strategies are used based on service characteristics and requirements.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Incoming Request]) --> GetInstances[Get Available Service Instances]
|
||||
GetInstances --> SelectStrategy{Load Balancing Strategy}
|
||||
|
||||
SelectStrategy -->|Round Robin| RoundRobin[Round Robin Algorithm]
|
||||
SelectStrategy -->|Least Connections| LeastConn[Least Connections Algorithm]
|
||||
SelectStrategy -->|Weighted| Weighted[Weighted Round Robin]
|
||||
|
||||
RoundRobin --> SelectNext[Select Next Instance in Order]
|
||||
SelectNext --> UseInstance[Use Selected Instance]
|
||||
|
||||
LeastConn --> CompareConn[Compare Connection Counts]
|
||||
CompareConn --> SelectMin[Select Instance with<br/>Minimum Connections]
|
||||
SelectMin --> UseInstance
|
||||
|
||||
Weighted --> CalculateWeight[Calculate Total Weight]
|
||||
CalculateWeight --> RandomSelect[Random Selection Based on Weights]
|
||||
RandomSelect --> UseInstance
|
||||
|
||||
UseInstance --> ForwardRequest[Forward Request to Instance]
|
||||
ForwardRequest --> UpdateStats[Update Statistics]
|
||||
UpdateStats --> End([Request Completed])
|
||||
|
||||
style RoundRobin fill:#e1f5ff
|
||||
style LeastConn fill:#e1f5ff
|
||||
style Weighted fill:#e1f5ff
|
||||
```
|
||||
|
||||
## Kubernetes DNS Discovery
|
||||
|
||||
Kubernetes provides built-in DNS-based service discovery. Services are automatically discoverable via DNS names.
|
||||
|
||||
```typescript
|
||||
// Use Kubernetes DNS for service discovery
|
||||
const serviceUrl = `http://user-service.production.svc.cluster.local`;
|
||||
```
|
||||
|
||||
### DNS Patterns
|
||||
|
||||
- **Short form** (same namespace): `http://user-service`
|
||||
- **Full form** (cross-namespace): `http://user-service.production.svc.cluster.local`
|
||||
- **With port**: `http://user-service.production.svc.cluster.local:5000`
|
||||
|
||||
## Service Registry Implementation
|
||||
|
||||
### Register Service
|
||||
|
||||
```typescript
|
||||
// Register service
|
||||
await serviceRegistry.register({
|
||||
name: 'user-service',
|
||||
version: '1.0.0',
|
||||
url: 'http://user-service:5000',
|
||||
healthCheckUrl: 'http://user-service:5000/health',
|
||||
status: 'healthy',
|
||||
lastHeartbeat: new Date(),
|
||||
});
|
||||
```
|
||||
|
||||
### Discover Service
|
||||
|
||||
```typescript
|
||||
// Discover service
|
||||
const service = await serviceRegistry.discover('user-service');
|
||||
if (service?.status === 'healthy') {
|
||||
// Use service
|
||||
}
|
||||
```
|
||||
|
||||
### List Healthy Services
|
||||
|
||||
```typescript
|
||||
// List all healthy services
|
||||
const healthyServices = await serviceRegistry.listHealthyServices();
|
||||
```
|
||||
|
||||
## Health Check Aggregation
|
||||
|
||||
Aggregate health status from multiple services to determine overall system health.
|
||||
|
||||
```typescript
|
||||
// Aggregate health from multiple services
|
||||
const health = await healthAggregator.getAggregatedHealth();
|
||||
// Returns: { status: 'healthy' | 'degraded' | 'unhealthy', services: [...] }
|
||||
```
|
||||
|
||||
## Load Balancing Implementation
|
||||
|
||||
### Round Robin
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.roundRobin(instances);
|
||||
```
|
||||
|
||||
### Least Connections
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.leastConnections(instances);
|
||||
```
|
||||
|
||||
### Weighted Round Robin
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.weightedRoundRobin(instances);
|
||||
```
|
||||
|
||||
## Service Mesh Integration
|
||||
|
||||
Service mesh solutions like Istio provide automatic service discovery and advanced routing capabilities.
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: ServiceEntry
|
||||
metadata:
|
||||
name: external-service
|
||||
spec:
|
||||
hosts:
|
||||
- external-api.example.com
|
||||
ports:
|
||||
- number: 443
|
||||
name: https
|
||||
protocol: HTTPS
|
||||
location: MESH_EXTERNAL
|
||||
resolution: DNS
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Kubernetes DNS**: Use Kubernetes DNS for service discovery in K8s environments
|
||||
2. **Health Checks**: Implement comprehensive health checks (`/health`, `/health/live`, `/health/ready`)
|
||||
3. **Service Registry**: Use registry for dynamic services that need runtime discovery
|
||||
4. **Load Balancing**: Choose appropriate load balancing strategy based on service characteristics
|
||||
5. **Monitoring**: Monitor service discovery and health check metrics
|
||||
6. **Heartbeat**: Implement periodic heartbeat (every 30 seconds) to keep registry updated
|
||||
7. **Graceful Shutdown**: Always unregister services on shutdown to prevent stale entries
|
||||
8. **Fallback**: Provide fallback mechanisms when registry is unavailable
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
1. **Hardcoded Service URLs**: Breaks in different environments
|
||||
```typescript
|
||||
// ❌ BAD: Hardcoded
|
||||
const url = 'http://user-service:5000';
|
||||
|
||||
// ✅ GOOD: Use discovery or env vars
|
||||
const url = discovery.getServiceUrl('user-service');
|
||||
```
|
||||
|
||||
2. **No Heartbeat**: Stale registry entries
|
||||
```typescript
|
||||
// ❌ BAD: Register once
|
||||
await registry.register(service);
|
||||
|
||||
// ✅ GOOD: Periodic heartbeat
|
||||
setInterval(() => registry.register(service), 30000);
|
||||
```
|
||||
|
||||
3. **Missing Graceful Shutdown**: Orphaned registrations
|
||||
```typescript
|
||||
// ✅ Always unregister on shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
await registry.unregister(serviceName);
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
4. **No Fallback**: Fails when registry unavailable
|
||||
```typescript
|
||||
// ❌ BAD: No fallback
|
||||
const url = await registry.discover('service');
|
||||
|
||||
// ✅ GOOD: Fallback to default
|
||||
const url = await registry.discover('service')
|
||||
?? process.env.SERVICE_FALLBACK_URL;
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Discovery Types
|
||||
|
||||
| Discovery Type | Implementation | Use Case |
|
||||
|----------------|----------------|----------|
|
||||
| **K8s DNS** | `service.namespace.svc.cluster.local` | Internal services |
|
||||
| **Service Registry** | Database-backed | Dynamic services |
|
||||
| **Service Mesh** | Istio/Linkerd | Complex routing |
|
||||
| **Environment Vars** | `process.env.SERVICE_URL` | Simple/external |
|
||||
|
||||
### Health Check Endpoints
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/health` | Basic health |
|
||||
| `/health/live` | K8s liveness probe |
|
||||
| `/health/ready` | K8s readiness probe |
|
||||
|
||||
### Load Balancing Strategies
|
||||
|
||||
| Strategy | When to Use |
|
||||
|----------|-------------|
|
||||
| **Round Robin** | Equal capacity servers |
|
||||
| **Least Connections** | Varying request durations |
|
||||
| **Weighted** | Different server capacities |
|
||||
|
||||
### Service Registration Lifecycle
|
||||
|
||||
```
|
||||
Startup → Register → Heartbeat (30s) → ... → Shutdown → Unregister
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Deployment Kubernetes](./deployment-kubernetes.md) - Kubernetes deployment patterns
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Health checks and monitoring
|
||||
- [Project Rules](./project-rules.md) - GoodGo standards
|
||||
- Skill Source: `.cursor/skills/service-discovery-registry/SKILL.md`
|
||||
@@ -1,338 +0,0 @@
|
||||
---
|
||||
name: service-layer-patterns
|
||||
description: Service layer organization and patterns for GoodGo microservices. Use when implementing business logic, organizing service classes, using dependency injection, composing services, or separating concerns between controllers and repositories.
|
||||
---
|
||||
|
||||
# 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 Architecture
|
||||
|
||||
The service layer follows a three-tier architecture pattern, separating concerns between HTTP handling, business logic, and data access:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "HTTP Layer"
|
||||
Controller["Controller<br/>- HTTP request/response<br/>- Input validation<br/>- Status codes"]
|
||||
end
|
||||
|
||||
subgraph "Business Layer"
|
||||
Service["Service<br/>- Business logic<br/>- Business rules<br/>- Orchestration<br/>- Caching<br/>- Logging"]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
Repository["Repository<br/>- Data access<br/>- CRUD operations<br/>- Database queries"]
|
||||
Database[("Database<br/>Prisma")]
|
||||
end
|
||||
|
||||
Controller -->|"Calls"| Service
|
||||
Service -->|"Uses"| Repository
|
||||
Repository -->|"Queries"| Database
|
||||
|
||||
style Controller fill:#e1f5ff
|
||||
style Service fill:#fff4e1
|
||||
style Repository fill:#e8f5e9
|
||||
style Database fill:#f3e5f5
|
||||
```
|
||||
|
||||
### 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. The dependency injection flow shows how components are wired together:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Module Initialization"
|
||||
Prisma[("Prisma Client")]
|
||||
Cache[("Cache Service")]
|
||||
end
|
||||
|
||||
subgraph "Dependency Creation"
|
||||
Repo["UserRepository<br/>(prisma)"]
|
||||
Service["UserService<br/>(repository, cache)"]
|
||||
Controller["UserController<br/>(service)"]
|
||||
end
|
||||
|
||||
subgraph "Router Setup"
|
||||
Router["Router<br/>(controller methods)"]
|
||||
end
|
||||
|
||||
Prisma -->|"Injected"| Repo
|
||||
Cache -->|"Injected"| Service
|
||||
Repo -->|"Injected"| Service
|
||||
Service -->|"Injected"| Controller
|
||||
Controller -->|"Bound to"| Router
|
||||
|
||||
style Prisma fill:#f3e5f5
|
||||
style Cache fill:#f3e5f5
|
||||
style Repo fill:#e8f5e9
|
||||
style Service fill:#fff4e1
|
||||
style Controller fill:#e1f5ff
|
||||
style Router fill:#e1f5ff
|
||||
```
|
||||
|
||||
**Example Implementation:**
|
||||
|
||||
```typescript
|
||||
export class UserService {
|
||||
constructor(
|
||||
private userRepository: UserRepository,
|
||||
private cacheService: CacheService
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
### Request Flow Through Layers
|
||||
|
||||
The following sequence diagram illustrates how a request flows through the Controller → Service → Repository layers:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Controller
|
||||
participant Service
|
||||
participant Cache
|
||||
participant Repository
|
||||
participant Database
|
||||
|
||||
Client->>Controller: HTTP Request (GET /users/:id)
|
||||
Controller->>Controller: Validate input
|
||||
Controller->>Service: getUserById(id)
|
||||
|
||||
Service->>Cache: Check cache
|
||||
alt Cache Hit
|
||||
Cache-->>Service: Return cached user
|
||||
Service-->>Controller: Return user
|
||||
else Cache Miss
|
||||
Service->>Repository: findById(id)
|
||||
Repository->>Database: Query user
|
||||
Database-->>Repository: User data
|
||||
Repository-->>Service: User entity
|
||||
|
||||
alt User Not Found
|
||||
Service-->>Controller: Throw NotFoundError
|
||||
Controller-->>Client: 404 Not Found
|
||||
else User Found
|
||||
Service->>Cache: Set cache (TTL: 5min)
|
||||
Service-->>Controller: Return user
|
||||
Controller-->>Client: 200 OK + User data
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 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 to compose complex operations. The following diagram shows service composition:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "AccessRequestService"
|
||||
ARService["AccessRequestService"]
|
||||
ARRepo["AccessRequestRepository"]
|
||||
end
|
||||
|
||||
subgraph "Dependencies"
|
||||
UserService["UserService"]
|
||||
RBACService["RBACService"]
|
||||
end
|
||||
|
||||
subgraph "Supporting Services"
|
||||
UserRepo["UserRepository"]
|
||||
RBACRepo["RBACRepository"]
|
||||
end
|
||||
|
||||
ARService -->|"Uses"| UserService
|
||||
ARService -->|"Uses"| RBACService
|
||||
ARService -->|"Uses"| ARRepo
|
||||
|
||||
UserService -->|"Uses"| UserRepo
|
||||
RBACService -->|"Uses"| RBACRepo
|
||||
|
||||
style ARService fill:#fff4e1
|
||||
style UserService fill:#fff4e1
|
||||
style RBACService fill:#fff4e1
|
||||
style ARRepo fill:#e8f5e9
|
||||
style UserRepo fill:#e8f5e9
|
||||
style RBACRepo fill:#e8f5e9
|
||||
```
|
||||
|
||||
**Implementation Example:**
|
||||
|
||||
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
|
||||
|
||||
## 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
|
||||
@@ -1,589 +0,0 @@
|
||||
---
|
||||
name: testing-patterns
|
||||
description: Testing best practices for GoodGo microservices. Use when writing unit tests, integration tests, E2E tests, setting up Jest, mocking dependencies, or debugging test failures.
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
1. **Unit Tests**: Test individual functions/classes in isolation
|
||||
- Location: Next to source files (`*.test.ts`)
|
||||
- Scope: Single function or class
|
||||
- Dependencies: Mocked
|
||||
- Speed: Fast (<1s per test)
|
||||
|
||||
2. **Integration Tests**: Test component interactions
|
||||
- Location: `__tests__/` directory
|
||||
- Scope: Multiple components working together
|
||||
- Dependencies: Some real, some mocked
|
||||
- Speed: Medium (1-5s per test)
|
||||
|
||||
3. **E2E Tests**: Test complete request/response cycles
|
||||
- Location: `__tests__/*.e2e.ts`
|
||||
- Scope: Full API workflow
|
||||
- Dependencies: Test database, mocked external services
|
||||
- Speed: Slow (5-10s per test)
|
||||
|
||||
### Testing Pyramid
|
||||
|
||||
The testing pyramid illustrates the recommended distribution of test types. Most tests should be fast unit tests, with fewer integration tests, and the fewest E2E tests.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph pyramid["Testing Pyramid"]
|
||||
E2E["E2E Tests<br/>Fewest<br/>5-10s each<br/>Full stack"]
|
||||
Integration["Integration Tests<br/>Medium<br/>1-5s each<br/>Components"]
|
||||
Unit["Unit Tests<br/>Most<br/><1s each<br/>Isolated"]
|
||||
end
|
||||
|
||||
Unit --> Integration
|
||||
Integration --> E2E
|
||||
|
||||
style E2E fill:#e1f5ff
|
||||
style Integration fill:#b3e5fc
|
||||
style Unit fill:#81d4fa
|
||||
```
|
||||
|
||||
**Key Principles:**
|
||||
- **Unit Tests (Base)**: Many fast tests covering individual functions/classes
|
||||
- **Integration Tests (Middle)**: Fewer tests verifying component interactions
|
||||
- **E2E Tests (Top)**: Fewest tests validating complete workflows
|
||||
|
||||
## Jest Configuration
|
||||
|
||||
```typescript
|
||||
// jest.config.ts
|
||||
import type { Config } from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: [
|
||||
'**/__tests__/**/*.test.ts',
|
||||
'**/__tests__/**/*.e2e.ts',
|
||||
'**/?(*.)+(spec|test).ts'
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/main.ts',
|
||||
'!src/config/**/*.ts'
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70
|
||||
}
|
||||
},
|
||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setupTests.ts'],
|
||||
testTimeout: 10000,
|
||||
clearMocks: true
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## Setup Files
|
||||
|
||||
```typescript
|
||||
// src/__tests__/setupTests.ts
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// Mock Prisma
|
||||
jest.mock('../prisma', () => ({
|
||||
__esModule: true,
|
||||
default: mockDeep<PrismaClient>()
|
||||
}));
|
||||
|
||||
// Mock Redis
|
||||
jest.mock('ioredis', () => {
|
||||
const Redis = jest.requireActual('ioredis-mock');
|
||||
return Redis;
|
||||
});
|
||||
|
||||
// Global test utilities
|
||||
global.testUtils = {
|
||||
generateId: () => `test-${Date.now()}`,
|
||||
createMockRequest: () => ({
|
||||
headers: {},
|
||||
body: {},
|
||||
query: {},
|
||||
params: {}
|
||||
}),
|
||||
createMockResponse: () => {
|
||||
const res: any = {};
|
||||
res.status = jest.fn().mockReturnValue(res);
|
||||
res.json = jest.fn().mockReturnValue(res);
|
||||
res.send = jest.fn().mockReturnValue(res);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
```
|
||||
|
||||
### Test Execution Flow
|
||||
|
||||
The test execution flow shows the lifecycle hooks and execution order in Jest test suites.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Test Suite Starts]) --> BeforeAll[beforeAll<br/>Run once before all tests]
|
||||
BeforeAll --> BeforeEach[beforeEach<br/>Run before each test]
|
||||
BeforeEach --> Test[Execute Test<br/>Arrange-Act-Assert]
|
||||
Test --> AfterEach[afterEach<br/>Run after each test]
|
||||
AfterEach --> MoreTests{More Tests?}
|
||||
MoreTests -->|Yes| BeforeEach
|
||||
MoreTests -->|No| AfterAll[afterAll<br/>Run once after all tests]
|
||||
AfterAll --> End([Test Suite Ends])
|
||||
|
||||
style Start fill:#c8e6c9
|
||||
style End fill:#c8e6c9
|
||||
style Test fill:#fff9c4
|
||||
style BeforeAll fill:#e1bee7
|
||||
style AfterAll fill:#e1bee7
|
||||
style BeforeEach fill:#bbdefb
|
||||
style AfterEach fill:#bbdefb
|
||||
```
|
||||
|
||||
**Execution Order:**
|
||||
1. `beforeAll`: Setup that runs once for the entire suite (e.g., database connection)
|
||||
2. `beforeEach`: Setup that runs before each test (e.g., reset mocks, create test data)
|
||||
3. **Test Execution**: The actual test code following AAA pattern
|
||||
4. `afterEach`: Cleanup after each test (e.g., clear mocks, reset state)
|
||||
5. `afterAll`: Final cleanup after all tests (e.g., close database connection)
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Unit Test Pattern
|
||||
|
||||
```typescript
|
||||
// feature.service.test.ts
|
||||
import { FeatureService } from './feature.service';
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
|
||||
describe('FeatureService', () => {
|
||||
let service: FeatureService;
|
||||
let mockRepository: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRepository = {
|
||||
findById: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn()
|
||||
};
|
||||
service = new FeatureService(mockRepository);
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return feature when found', async () => {
|
||||
// Arrange
|
||||
const mockFeature = { id: '1', name: 'Test Feature' };
|
||||
mockRepository.findById.mockResolvedValue(mockFeature);
|
||||
|
||||
// Act
|
||||
const result = await service.findById('1');
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockFeature);
|
||||
expect(mockRepository.findById).toHaveBeenCalledWith('1');
|
||||
});
|
||||
|
||||
it('should throw error when feature not found', async () => {
|
||||
// Arrange
|
||||
mockRepository.findById.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.findById('999')).rejects.toThrow('Feature not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Test Pattern
|
||||
|
||||
```typescript
|
||||
// auth.middleware.test.ts
|
||||
import { authMiddleware } from '../auth.middleware';
|
||||
import { createMockRequest, createMockResponse } from '../../test-utils';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
describe('Auth Middleware', () => {
|
||||
const mockNext = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call next when valid token provided', async () => {
|
||||
// Arrange
|
||||
const req = createMockRequest();
|
||||
const res = createMockResponse();
|
||||
const token = jwt.sign({ userId: '123' }, 'secret');
|
||||
req.headers.authorization = `Bearer ${token}`;
|
||||
|
||||
// Act
|
||||
await authMiddleware(req, res, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
expect(req.user).toEqual({ userId: '123' });
|
||||
});
|
||||
|
||||
it('should return 401 when no token provided', async () => {
|
||||
// Arrange
|
||||
const req = createMockRequest();
|
||||
const res = createMockResponse();
|
||||
|
||||
// Act
|
||||
await authMiddleware(req, res, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(res.status).toHaveBeenCalledWith(401);
|
||||
expect(mockNext).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### E2E Test Pattern
|
||||
|
||||
```typescript
|
||||
// feature.e2e.ts
|
||||
import supertest from 'supertest';
|
||||
import { createApp } from '../app';
|
||||
import { prisma } from '../prisma';
|
||||
|
||||
describe('Feature API E2E', () => {
|
||||
let app: any;
|
||||
let request: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createApp();
|
||||
request = supertest(app);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await prisma.feature.deleteMany();
|
||||
});
|
||||
|
||||
describe('POST /api/features', () => {
|
||||
it('should create a new feature', async () => {
|
||||
// Arrange
|
||||
const featureData = {
|
||||
name: 'New Feature',
|
||||
description: 'Feature description'
|
||||
};
|
||||
|
||||
// Act
|
||||
const response = await request
|
||||
.post('/api/features')
|
||||
.set('Authorization', 'Bearer valid-token')
|
||||
.send(featureData)
|
||||
.expect(201);
|
||||
|
||||
// Assert
|
||||
expect(response.body).toMatchObject({
|
||||
success: true,
|
||||
data: {
|
||||
name: 'New Feature',
|
||||
description: 'Feature description'
|
||||
}
|
||||
});
|
||||
|
||||
const created = await prisma.feature.findFirst({
|
||||
where: { name: 'New Feature' }
|
||||
});
|
||||
expect(created).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Mocking Strategies
|
||||
|
||||
### Mocking Strategy by Test Type
|
||||
|
||||
Different test types require different levels of mocking. This diagram shows what should be mocked at each test level.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph UnitTests["Unit Tests"]
|
||||
UnitCode[Application Code]
|
||||
UnitMock[All Dependencies Mocked<br/>- Database<br/>- External APIs<br/>- Services<br/>- Repositories]
|
||||
UnitCode --> UnitMock
|
||||
end
|
||||
|
||||
subgraph IntegrationTests["Integration Tests"]
|
||||
IntCode[Application Code]
|
||||
IntReal[Real Internal Components]
|
||||
IntMock[External Dependencies Mocked<br/>- External APIs<br/>- Third-party Services]
|
||||
IntCode --> IntReal
|
||||
IntReal --> IntMock
|
||||
end
|
||||
|
||||
subgraph E2ETests["E2E Tests"]
|
||||
E2ECode[Application Code]
|
||||
E2EReal[Real Internal Stack<br/>- Database<br/>- Services<br/>- Middleware]
|
||||
E2EMock[Only External Services Mocked<br/>- Payment APIs<br/>- Email Services<br/>- Third-party APIs]
|
||||
E2ECode --> E2EReal
|
||||
E2EReal --> E2EMock
|
||||
end
|
||||
|
||||
style UnitTests fill:#ffcdd2
|
||||
style IntegrationTests fill:#fff9c4
|
||||
style E2ETests fill:#c8e6c9
|
||||
style UnitMock fill:#f8bbd0
|
||||
style IntMock fill:#ffe082
|
||||
style E2EMock fill:#a5d6a7
|
||||
```
|
||||
|
||||
**Mocking Guidelines:**
|
||||
- **Unit Tests**: Mock all external dependencies to test logic in isolation
|
||||
- **Integration Tests**: Use real internal components, mock only external services
|
||||
- **E2E Tests**: Use real database and internal stack, mock only third-party external services
|
||||
|
||||
### Mock Prisma
|
||||
|
||||
```typescript
|
||||
// __mocks__/prisma.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { mockDeep, DeepMockProxy } from 'jest-mock-extended';
|
||||
|
||||
export const prismaMock = mockDeep<PrismaClient>();
|
||||
|
||||
jest.mock('../src/prisma', () => ({
|
||||
__esModule: true,
|
||||
default: prismaMock,
|
||||
}));
|
||||
|
||||
// Usage in tests
|
||||
import { prismaMock } from '../__mocks__/prisma';
|
||||
|
||||
test('should create user', async () => {
|
||||
const user = { id: '1', email: 'test@example.com' };
|
||||
prismaMock.user.create.mockResolvedValue(user);
|
||||
|
||||
const result = await createUser({ email: 'test@example.com' });
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
```
|
||||
|
||||
### Mock Redis
|
||||
|
||||
```typescript
|
||||
// __mocks__/redis.ts
|
||||
import Redis from 'ioredis-mock';
|
||||
|
||||
export const redisMock = new Redis();
|
||||
|
||||
// Usage in tests
|
||||
test('should cache value', async () => {
|
||||
const cache = new CacheService(redisMock);
|
||||
await cache.set('key', 'value');
|
||||
|
||||
const result = await cache.get('key');
|
||||
expect(result).toBe('value');
|
||||
});
|
||||
```
|
||||
|
||||
### Mock External APIs
|
||||
|
||||
```typescript
|
||||
// Mock axios
|
||||
jest.mock('axios');
|
||||
import axios from 'axios';
|
||||
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
||||
|
||||
test('should fetch external data', async () => {
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: { result: 'success' }
|
||||
});
|
||||
|
||||
const result = await fetchExternalData();
|
||||
expect(result).toEqual({ result: 'success' });
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Utilities
|
||||
|
||||
```typescript
|
||||
// test-utils.ts
|
||||
export class TestFactory {
|
||||
static createUser(overrides = {}) {
|
||||
return {
|
||||
id: 'test-user-1',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
createdAt: new Date(),
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
static createAuthToken(userId: string) {
|
||||
return jwt.sign({ userId }, 'test-secret');
|
||||
}
|
||||
|
||||
static async cleanDatabase() {
|
||||
await prisma.user.deleteMany();
|
||||
await prisma.feature.deleteMany();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const user = TestFactory.createUser({ name: 'Custom Name' });
|
||||
const token = TestFactory.createAuthToken(user.id);
|
||||
```
|
||||
|
||||
## Common Test Scenarios
|
||||
|
||||
### Testing Error Handling
|
||||
|
||||
```typescript
|
||||
test('should handle database errors gracefully', async () => {
|
||||
prismaMock.user.findUnique.mockRejectedValue(
|
||||
new Error('Database connection failed')
|
||||
);
|
||||
|
||||
const response = await request
|
||||
.get('/api/users/123')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'Internal server error'
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Validation
|
||||
|
||||
```typescript
|
||||
describe('Validation', () => {
|
||||
it('should reject invalid email', async () => {
|
||||
const response = await request
|
||||
.post('/api/auth/register')
|
||||
.send({ email: 'invalid-email', password: '123456' })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.error.code).toBe('VALIDATION_ERROR');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Pagination
|
||||
|
||||
```typescript
|
||||
test('should paginate results', async () => {
|
||||
// Create test data
|
||||
const items = Array(25).fill(null).map((_, i) => ({
|
||||
id: `item-${i}`,
|
||||
name: `Item ${i}`
|
||||
}));
|
||||
|
||||
prismaMock.item.findMany.mockResolvedValue(items.slice(0, 10));
|
||||
prismaMock.item.count.mockResolvedValue(25);
|
||||
|
||||
const response = await request
|
||||
.get('/api/items?page=1&limit=10')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
success: true,
|
||||
data: items.slice(0, 10),
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 25,
|
||||
totalPages: 3
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Test Commands
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:unit": "jest --testPathPattern=\\.test\\.ts$",
|
||||
"test:e2e": "jest --testPathPattern=\\.e2e\\.ts$",
|
||||
"test:ci": "jest --coverage --silent --maxWorkers=2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Debug with VS Code
|
||||
|
||||
```json
|
||||
// .vscode/launch.json
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Jest Tests",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["test", "--", "--runInBand"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
5. Use debugger breakpoints in VS Code
|
||||
|
||||
## Best Practices Checklist
|
||||
|
||||
- [ ] 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
|
||||
- [ ] Run tests before committing
|
||||
- [ ] Keep test data realistic
|
||||
- [ ] Clean up after tests
|
||||
@@ -1,578 +0,0 @@
|
||||
# Thiết Kế API RESTful
|
||||
|
||||
RESTful API design standards for GoodGo microservices. Use when creating new API endpoints, designing DTOs, implementing controllers, writing OpenAPI documentation, or standardizing API responses.
|
||||
> Tiêu chuẩn thiết kế API RESTful cho các microservices của GoodGo. Sử dụng khi tạo endpoint API mới, thiết kế DTOs, triển khai controllers, viết tài liệu OpenAPI, hoặc chuẩn hóa response API.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
This skill covers the RESTful API design patterns and standards used across all GoodGo microservices. It ensures consistency, predictability, and maintainability of APIs through standardized URL structures, HTTP methods, response formats, error handling, and DTO validation.
|
||||
|
||||
Skill này bao gồm các pattern và tiêu chuẩn thiết kế API RESTful được sử dụng trong tất cả các microservices của GoodGo. Nó đảm bảo tính nhất quán, dự đoán được và dễ bảo trì của APIs thông qua cấu trúc URL chuẩn hóa, HTTP methods, định dạng response, xử lý lỗi và validation DTO.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
- Designing resource relationships
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Tạo endpoint API mới
|
||||
- Thiết kế DTOs cho request/response
|
||||
- Triển khai controllers và routes
|
||||
- Viết tài liệu OpenAPI/Swagger
|
||||
- Chuẩn hóa error responses
|
||||
- Triển khai pagination, filtering và sorting
|
||||
- Thiết lập API versioning
|
||||
- Thiết kế quan hệ giữa các resources
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Luồng Request/Response
|
||||
|
||||
Sơ đồ sau minh họa cách một request đi qua các lớp của API:
|
||||
|
||||
The following diagram illustrates how a request flows through the API layers:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware
|
||||
participant Controller
|
||||
participant Service
|
||||
participant Repository
|
||||
participant Database
|
||||
|
||||
Client->>Middleware: HTTP Request
|
||||
Middleware->>Middleware: Authentication<br/>Rate Limiting<br/>Validation
|
||||
Middleware->>Controller: Validated Request
|
||||
Controller->>Controller: Parse DTO<br/>Extract Params
|
||||
Controller->>Service: Business Logic Call
|
||||
Service->>Repository: Data Access Call
|
||||
Repository->>Database: Query Execution
|
||||
Database-->>Repository: Data Result
|
||||
Repository-->>Service: Entity/Entities
|
||||
Service-->>Controller: Business Result
|
||||
Controller->>Controller: Transform to DTO<br/>Format Response
|
||||
Controller-->>Middleware: Response Object
|
||||
Middleware-->>Client: HTTP Response
|
||||
```
|
||||
|
||||
### Cấu Trúc Phân Cấp API
|
||||
|
||||
Sơ đồ sau cho thấy cấu trúc phân cấp của các RESTful API endpoints:
|
||||
|
||||
The following diagram shows the hierarchical structure of RESTful API endpoints:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[API Base URL<br/>https://api.goodgo.com] --> B[Version<br/>/v1]
|
||||
B --> C[Resource Collection<br/>/users]
|
||||
B --> D[Resource Collection<br/>/orders]
|
||||
B --> E[Resource Collection<br/>/products]
|
||||
|
||||
C --> F[Resource Instance<br/>/users/:id]
|
||||
C --> G[Sub-Resource<br/>/users/:id/orders]
|
||||
|
||||
F --> H[GET /users/:id<br/>Retrieve user]
|
||||
F --> I[PUT /users/:id<br/>Update user]
|
||||
F --> J[DELETE /users/:id<br/>Delete user]
|
||||
|
||||
C --> K[GET /users<br/>List users]
|
||||
C --> L[POST /users<br/>Create user]
|
||||
|
||||
G --> M[GET /users/:id/orders<br/>List user orders]
|
||||
G --> N[POST /users/:id/orders<br/>Create order]
|
||||
|
||||
style A fill:#e1f5ff
|
||||
style B fill:#b3e5fc
|
||||
style C fill:#81d4fa
|
||||
style D fill:#81d4fa
|
||||
style E fill:#81d4fa
|
||||
style F fill:#4fc3f7
|
||||
style G fill:#4fc3f7
|
||||
```
|
||||
|
||||
### Cấu Trúc URL
|
||||
|
||||
URLs follow a hierarchical resource-based structure with versioning.
|
||||
|
||||
URLs tuân theo cấu trúc phân cấp dựa trên resource với versioning.
|
||||
|
||||
```
|
||||
https://api.goodgo.com/v1/{resource}/{id}/{sub-resource}
|
||||
|
||||
Examples:
|
||||
GET /v1/users # List users / Liệt kê users
|
||||
POST /v1/users # Create user / Tạo user
|
||||
GET /v1/users/123 # Get user by ID / Lấy user theo ID
|
||||
PUT /v1/users/123 # Update user / Cập nhật user
|
||||
DELETE /v1/users/123 # Delete user / Xóa user
|
||||
GET /v1/users/123/orders # Get user's orders / Lấy orders của user
|
||||
POST /v1/users/123/orders # Create order for user / Tạo order cho user
|
||||
```
|
||||
|
||||
### Phương Thức HTTP
|
||||
|
||||
- **GET**: Retrieve resource(s) - Safe, Idempotent / Lấy resource(s) - An toàn, Idempotent
|
||||
- **POST**: Create new resource - Not idempotent / Tạo resource mới - Không idempotent
|
||||
- **PUT**: Full update - Idempotent / Cập nhật toàn bộ - Idempotent
|
||||
- **PATCH**: Partial update - Idempotent / Cập nhật một phần - Idempotent
|
||||
- **DELETE**: Remove resource - Idempotent / Xóa resource - Idempotent
|
||||
|
||||
### Định Dạng Response Chuẩn
|
||||
|
||||
All API responses follow a consistent structure with `success`, `data`, and optional `metadata` fields.
|
||||
|
||||
Tất cả API responses tuân theo cấu trúc nhất quán với các trường `success`, `data`, và `metadata` tùy chọn.
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Pattern Response Thành Công
|
||||
|
||||
```typescript
|
||||
interface SuccessResponse<T> {
|
||||
success: true;
|
||||
data: T;
|
||||
message?: string;
|
||||
timestamp?: string;
|
||||
pagination?: {
|
||||
total: number;
|
||||
skip: number;
|
||||
take: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/identity/user/user.controller.ts
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users: result.users,
|
||||
pagination: {
|
||||
total: result.total,
|
||||
skip: filters.skip,
|
||||
take: filters.take,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern Response Lỗi
|
||||
|
||||
```typescript
|
||||
interface ErrorResponse {
|
||||
success: false;
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: any;
|
||||
field?: string;
|
||||
};
|
||||
timestamp?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/identity/user/user.controller.ts
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid filters',
|
||||
details: error.errors,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Mã Trạng Thái HTTP
|
||||
|
||||
```typescript
|
||||
// Mã thành công
|
||||
200 OK // GET, PUT, PATCH success
|
||||
201 Created // POST success with resource creation
|
||||
204 No Content // DELETE success
|
||||
|
||||
// Lỗi client
|
||||
// Dữ liệu request không hợp lệ
|
||||
// Thiếu/sai xác thực
|
||||
// Xác thực hợp lệ nhưng không có quyền
|
||||
// Resource không tồn tại
|
||||
// Xung đột resource (trùng lặp)
|
||||
// Lỗi validation
|
||||
|
||||
// Lỗi server
|
||||
// Lỗi server không mong đợi
|
||||
// Lỗi dịch vụ bên ngoài
|
||||
// Dịch vụ tạm thời không khả dụng
|
||||
```
|
||||
|
||||
### DTOs với Zod Validation
|
||||
|
||||
DTOs use Zod for runtime validation with bilingual error messages.
|
||||
|
||||
DTOs sử dụng Zod cho validation runtime với thông báo lỗi song ngữ.
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/identity/identity.dto.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreateOrganizationDto = z.object({
|
||||
name: z.string().min(1, 'Organization name is required / Tên tổ chức là bắt buộc').max(255),
|
||||
domain: z.string().email().optional().or(z.string().min(1).max(255).optional()),
|
||||
parentId: z.string().optional(),
|
||||
settings: z.record(z.any()).optional(),
|
||||
});
|
||||
|
||||
export type CreateOrganizationDto = z.infer<typeof CreateOrganizationDto>;
|
||||
|
||||
export const UserFiltersDto = z.object({
|
||||
organizationId: z.string().optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
emailVerified: z.boolean().optional(),
|
||||
search: z.string().optional(),
|
||||
skip: z.number().int().min(0).default(0),
|
||||
take: z.number().int().min(1).max(100).default(20),
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern Triển Khai Controller
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/identity/user/user.controller.ts
|
||||
export class UserManagementController {
|
||||
/**
|
||||
List users
|
||||
* VI: Liệt kê users
|
||||
*/
|
||||
async list(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const filters = UserFiltersDto.parse({
|
||||
organizationId: req.query.organizationId as string,
|
||||
isActive: req.query.isActive === 'true' ? true : req.query.isActive === 'false' ? false : undefined,
|
||||
emailVerified: req.query.emailVerified === 'true' ? true : req.query.emailVerified === 'false' ? false : undefined,
|
||||
search: req.query.search as string,
|
||||
skip: parseInt(req.query.skip as string) || 0,
|
||||
take: parseInt(req.query.take as string) || 20,
|
||||
});
|
||||
|
||||
const result = await userManagementService.searchUsers(filters);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users: result.users,
|
||||
pagination: {
|
||||
total: result.total,
|
||||
skip: filters.skip,
|
||||
take: filters.take,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid filters',
|
||||
details: error.errors,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'LIST_USERS_FAILED',
|
||||
message: error.message || 'Failed to list users',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get user by ID
|
||||
* VI: Lấy user theo ID
|
||||
*/
|
||||
async get(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const user = await userManagementService.getUser(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user,
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error instanceof NotFoundError) {
|
||||
res.status(404).json(error.toApiResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'GET_USER_FAILED',
|
||||
message: error.message || 'Failed to get user',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Luồng Xử Lý Lỗi
|
||||
|
||||
Sơ đồ sau minh họa luồng xử lý lỗi trong API:
|
||||
|
||||
The following diagram illustrates the error handling flow in the API:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Request Received] --> B{Validation<br/>Middleware}
|
||||
B -->|Invalid| C[ValidationError]
|
||||
B -->|Valid| D[Controller]
|
||||
|
||||
D --> E{Business Logic}
|
||||
E -->|Not Found| F[NotFoundError]
|
||||
E -->|Unauthorized| G[UnauthorizedError]
|
||||
E -->|Forbidden| H[ForbiddenError]
|
||||
E -->|Conflict| I[ConflictError]
|
||||
E -->|Success| J[Return Success Response]
|
||||
|
||||
E -->|Unexpected Error| K[Generic Error]
|
||||
|
||||
C --> L[Error Handler<br/>Middleware]
|
||||
F --> L
|
||||
G --> L
|
||||
H --> L
|
||||
I --> L
|
||||
K --> L
|
||||
|
||||
L --> M{Error Type?}
|
||||
M -->|ValidationError| N[400 Bad Request<br/>VALIDATION_ERROR]
|
||||
M -->|UnauthorizedError| O[401 Unauthorized<br/>UNAUTHORIZED]
|
||||
M -->|ForbiddenError| P[403 Forbidden<br/>FORBIDDEN]
|
||||
M -->|NotFoundError| Q[404 Not Found<br/>NOT_FOUND]
|
||||
M -->|ConflictError| R[409 Conflict<br/>CONFLICT]
|
||||
M -->|Unknown Error| S{Development<br/>Mode?}
|
||||
|
||||
S -->|Yes| T[500 Internal Error<br/>INTERNAL_ERROR<br/>+ Stack Trace]
|
||||
S -->|No| U[500 Internal Error<br/>INTERNAL_ERROR<br/>Generic Message]
|
||||
|
||||
N --> V[Format Error Response<br/>success: false<br/>error: code, message, details]
|
||||
O --> V
|
||||
P --> V
|
||||
Q --> V
|
||||
R --> V
|
||||
T --> V
|
||||
U --> V
|
||||
|
||||
V --> W[Log Error<br/>Server-Side]
|
||||
W --> X[Send HTTP Response]
|
||||
J --> X
|
||||
|
||||
style A fill:#e1f5ff
|
||||
style J fill:#c8e6c9
|
||||
style L fill:#fff9c4
|
||||
style V fill:#ffccbc
|
||||
style X fill:#e1f5ff
|
||||
```
|
||||
|
||||
### Xử Lý Lỗi với Custom Error Classes
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/errors/http-error.ts
|
||||
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.name = this.constructor.name;
|
||||
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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends HttpError {
|
||||
constructor(resource: string = 'Resource / Tài nguyên', details?: any) {
|
||||
super(`${resource} not found / ${resource} không tìm thấy`, 404, 'NOT_FOUND', true, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationError extends HttpError {
|
||||
constructor(message: string = 'Validation failed / Validation thất bại', details?: any) {
|
||||
super(message, 422, 'VALIDATION_ERROR', true, details);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Đặt Tên Resource
|
||||
|
||||
- ✅ Use plural nouns (`/users` not `/user`) / Sử dụng danh từ số nhiều
|
||||
- ✅ Use kebab-case for multi-word resources (`/user-profiles`) / Sử dụng kebab-case cho resource nhiều từ
|
||||
- ✅ Keep URLs as short as possible / Giữ URLs ngắn gọn nhất có thể
|
||||
- ❌ Avoid verbs in URLs (use HTTP methods instead) / Tránh động từ trong URLs (dùng HTTP methods thay thế)
|
||||
|
||||
### Phiên Bản Hóa
|
||||
|
||||
- ✅ Include version in URL (`/v1/users`) / Bao gồm version trong URL
|
||||
- ✅ Maintain backward compatibility / Duy trì tương thích ngược
|
||||
- ✅ Deprecate old versions gracefully with warnings / Ngừng hỗ trợ các version cũ một cách lịch sự với cảnh báo
|
||||
|
||||
### Bảo Mật
|
||||
|
||||
- ✅ Always use HTTPS / Luôn sử dụng HTTPS
|
||||
- ✅ Implement rate limiting / Triển khai rate limiting
|
||||
- ✅ Validate all inputs with DTOs / Validate tất cả inputs với DTOs
|
||||
- ✅ Use proper authentication/authorization middleware / Sử dụng middleware xác thực/ủy quyền phù hợp
|
||||
- ✅ Never expose sensitive data in responses / Không bao giờ expose dữ liệu nhạy cảm trong responses
|
||||
|
||||
### Hiệu Năng
|
||||
|
||||
- ✅ Implement pagination for lists (use `skip` and `take`) / Triển khai pagination cho danh sách
|
||||
- ✅ Use field filtering when possible (`select` in Prisma) / Sử dụng field filtering khi có thể
|
||||
- ✅ Cache responses appropriately / Cache responses phù hợp
|
||||
- ✅ Compress responses (gzip) / Nén responses (gzip)
|
||||
|
||||
### Tài Liệu
|
||||
|
||||
- ✅ Keep OpenAPI spec up to date / Giữ OpenAPI spec cập nhật
|
||||
- ✅ Include examples in documentation / Bao gồm ví dụ trong tài liệu
|
||||
- ✅ Document error responses / Tài liệu hóa error responses
|
||||
- ✅ Version your documentation / Phiên bản hóa tài liệu
|
||||
|
||||
### Xử Lý Lỗi
|
||||
|
||||
- ✅ Use consistent error response format / Sử dụng định dạng error response nhất quán
|
||||
- ✅ Provide actionable error messages / Cung cấp thông báo lỗi có thể hành động
|
||||
- ✅ Include error codes for programmatic handling / Bao gồm error codes cho xử lý lập trình
|
||||
- ✅ Log errors server-side, don't expose stack traces in production / Log lỗi phía server, không expose stack traces trong production
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Ví Dụ Controller
|
||||
|
||||
- **User Management**: [`services/iam-service/src/modules/identity/user/user.controller.ts`](../../../services/iam-service/src/modules/identity/user/user.controller.ts)
|
||||
- **Feature Controller**: [`services/iam-service/src/modules/feature/feature.controller.ts`](../../../services/iam-service/src/modules/feature/feature.controller.ts)
|
||||
- **RBAC Controller**: [`services/iam-service/src/modules/rbac/rbac.controller.ts`](../../../services/iam-service/src/modules/rbac/rbac.controller.ts)
|
||||
|
||||
### Ví Dụ DTO
|
||||
|
||||
- **Identity DTOs**: [`services/iam-service/src/modules/identity/identity.dto.ts`](../../../services/iam-service/src/modules/identity/identity.dto.ts)
|
||||
- **Auth DTOs**: [`services/iam-service/src/modules/auth/auth.dto.ts`](../../../services/iam-service/src/modules/auth/auth.dto.ts)
|
||||
- **RBAC DTOs**: [`services/iam-service/src/modules/rbac/rbac.dto.ts`](../../../services/iam-service/src/modules/rbac/rbac.dto.ts)
|
||||
|
||||
### Ví Dụ Xử Lý Lỗi
|
||||
|
||||
- **HTTP Error Classes**: [`services/iam-service/src/errors/http-error.ts`](../../../services/iam-service/src/errors/http-error.ts)
|
||||
- **Error Middleware**: [`services/iam-service/src/middlewares/error.middleware.ts`](../../../services/iam-service/src/middlewares/error.middleware.ts)
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Bảng Tra Cứu Định Dạng Response
|
||||
|
||||
| Scenario / Tình Huống | Status Code | Response Structure / Cấu Trúc Response |
|
||||
|----------------------|-------------|----------------------------------------|
|
||||
| Success (GET) | 200 | `{ success: true, data: {...} }` |
|
||||
| Success (POST) | 201 | `{ success: true, data: {...} }` |
|
||||
| Success (DELETE) | 200 | `{ success: true, message: "..." }` |
|
||||
| Validation Error / Lỗi Validation | 400/422 | `{ success: false, error: { code, message, details } }` |
|
||||
| Not Found | 404 | `{ success: false, error: { code: "NOT_FOUND", message } }` |
|
||||
| Unauthorized | 401 | `{ success: false, error: { code: "UNAUTHORIZED", message } }` |
|
||||
| Forbidden | 403 | `{ success: false, error: { code: "FORBIDDEN", message } }` |
|
||||
| Server Error / Lỗi Server | 500 | `{ success: false, error: { code: "INTERNAL_ERROR", message } }` |
|
||||
|
||||
### Pattern DTO Thường Dùng
|
||||
|
||||
```typescript
|
||||
// DTO Tạo
|
||||
const CreateDto = z.object({
|
||||
requiredField: z.string().min(1),
|
||||
optionalField: z.string().optional(),
|
||||
});
|
||||
|
||||
// DTO Cập Nhật (tất cả fields tùy chọn)
|
||||
const UpdateDto = z.object({
|
||||
field1: z.string().optional(),
|
||||
field2: z.number().optional(),
|
||||
});
|
||||
|
||||
// DTO Query/Filter
|
||||
const QueryDto = z.object({
|
||||
page: z.number().int().min(1).default(1),
|
||||
limit: z.number().int().min(1).max(100).default(20),
|
||||
search: z.string().optional(),
|
||||
sortBy: z.string().optional(),
|
||||
order: z.enum(['asc', 'desc']).default('desc'),
|
||||
});
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[Database Prisma](./database-prisma.md)**: For database operations and repository patterns / Cho các thao tác database và repository patterns
|
||||
- **[Security](./security.md)**: For authentication, authorization, and security best practices / Cho xác thực, ủy quyền và thực hành bảo mật tốt nhất
|
||||
- **[Testing Patterns](./testing-patterns.md)**: For testing API endpoints / Cho test API endpoints
|
||||
- **[Documentation](./documentation.md)**: For writing API documentation / Cho viết tài liệu API
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tài Liệu Bên Ngoài
|
||||
|
||||
- [REST API Design Best Practices](https://restfulapi.net/)
|
||||
- [HTTP Status Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
|
||||
- [Zod Documentation](https://zod.dev/)
|
||||
- [OpenAPI Specification](https://swagger.io/specification/)
|
||||
|
||||
### Tài Liệu Nội Bộ
|
||||
|
||||
- [API Reference](../api/openapi/iam-service.yaml)
|
||||
- [Service Communication](../architecture/service-communication.md)
|
||||
- [Project Rules](./project-rules.md)
|
||||
@@ -1,197 +0,0 @@
|
||||
# API Gateway Nâng Cao (API Gateway Advanced)
|
||||
|
||||
Advanced API Gateway patterns for GoodGo microservices including API composition, request/response transformation, service mesh integration, advanced routing, and gateway-level resilience.
|
||||
> Các patterns API Gateway nâng cao cho GoodGo microservices bao gồm API composition, request/response transformation, tích hợp service mesh, routing nâng cao, và resilience ở gateway level.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Advanced API Gateway patterns extend basic gateway functionality with composition, transformation, service mesh integration, and gateway-level resilience patterns.
|
||||
|
||||
Các patterns API Gateway nâng cao mở rộng chức năng gateway cơ bản với composition, transformation, tích hợp service mesh, và các resilience patterns ở gateway level.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when implementing API composition, request/response transformation, or service mesh integration.
|
||||
|
||||
Sử dụng skill này khi implement API composition, request/response transformation, hoặc tích hợp service mesh.
|
||||
|
||||
## API Gateway Architecture / Kiến Trúc API Gateway
|
||||
|
||||
The API Gateway serves as the single entry point for all client requests, handling routing, composition, transformation, and resilience patterns.
|
||||
|
||||
API Gateway hoạt động như điểm vào duy nhất cho tất cả client requests, xử lý routing, composition, transformation, và các resilience patterns.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Client[Client Application] --> Gateway[API Gateway]
|
||||
|
||||
subgraph Gateway["API Gateway Components"]
|
||||
Router[Request Router]
|
||||
Auth[Authentication/Authorization]
|
||||
RateLimit[Rate Limiting]
|
||||
CircuitBreaker[Circuit Breaker]
|
||||
Cache[Gateway Cache]
|
||||
Transformer[Request/Response Transformer]
|
||||
Composition[API Composition Engine]
|
||||
end
|
||||
|
||||
Gateway --> Router
|
||||
Router --> Auth
|
||||
Auth --> RateLimit
|
||||
RateLimit --> CircuitBreaker
|
||||
CircuitBreaker --> Cache
|
||||
Cache --> Transformer
|
||||
Transformer --> Composition
|
||||
|
||||
Composition --> Service1[User Service]
|
||||
Composition --> Service2[Order Service]
|
||||
Composition --> Service3[Payment Service]
|
||||
Composition --> Service4[Other Services]
|
||||
|
||||
CircuitBreaker -.-> Service1
|
||||
CircuitBreaker -.-> Service2
|
||||
CircuitBreaker -.-> Service3
|
||||
CircuitBreaker -.-> Service4
|
||||
|
||||
Cache --> Redis[(Redis Cache)]
|
||||
|
||||
style Gateway fill:#e1f5ff
|
||||
style Composition fill:#fff4e1
|
||||
style CircuitBreaker fill:#ffe1e1
|
||||
```
|
||||
|
||||
## Request Routing Flow / Luồng Request Routing
|
||||
|
||||
Requests flow through the gateway middleware chain in a specific order, ensuring proper handling at each stage.
|
||||
|
||||
Requests đi qua chuỗi middleware của gateway theo thứ tự cụ thể, đảm bảo xử lý đúng ở mỗi giai đoạn.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant RateLimit
|
||||
participant Auth
|
||||
participant CircuitBreaker
|
||||
participant Cache
|
||||
participant Transformer
|
||||
participant Service
|
||||
|
||||
Client->>Gateway: HTTP Request
|
||||
Gateway->>RateLimit: Check Rate Limit
|
||||
RateLimit-->>Gateway: Allowed
|
||||
|
||||
Gateway->>Auth: Validate Token
|
||||
Auth-->>Gateway: Authenticated
|
||||
|
||||
Gateway->>CircuitBreaker: Check Circuit State
|
||||
alt Circuit Open
|
||||
CircuitBreaker-->>Gateway: Service Unavailable
|
||||
Gateway-->>Client: 503 Error
|
||||
else Circuit Closed/Half-Open
|
||||
Gateway->>Cache: Check Cache
|
||||
alt Cache Hit
|
||||
Cache-->>Gateway: Cached Response
|
||||
Gateway->>Transformer: Transform Response
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway-->>Client: Response
|
||||
else Cache Miss
|
||||
Gateway->>Transformer: Transform Request
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway->>Service: Forward Request
|
||||
Service-->>Gateway: Response
|
||||
Gateway->>Cache: Store in Cache
|
||||
Gateway->>Transformer: Transform Response
|
||||
Transformer-->>Gateway: Transformed
|
||||
Gateway-->>Client: Response
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## API Composition Patterns / Các Patterns API Composition
|
||||
|
||||
API composition enables the gateway to aggregate data from multiple services, reducing client round trips and improving performance.
|
||||
|
||||
API composition cho phép gateway tổng hợp dữ liệu từ nhiều services, giảm số lần round trip của client và cải thiện hiệu suất.
|
||||
|
||||
### Fan-Out / Fan-In Pattern (Parallel Aggregation) / Pattern Fan-Out / Fan-In (Tổng Hợp Song Song)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Client[Client Request] --> Gateway[API Gateway]
|
||||
|
||||
subgraph Gateway["API Composition"]
|
||||
Comp[Composition Handler]
|
||||
end
|
||||
|
||||
Gateway --> Comp
|
||||
|
||||
Comp -->|Parallel Calls| S1[User Service]
|
||||
Comp -->|Parallel Calls| S2[Order Service]
|
||||
Comp -->|Parallel Calls| S3[Payment Service]
|
||||
|
||||
S1 -->|Response| Comp
|
||||
S2 -->|Response| Comp
|
||||
S3 -->|Response| Comp
|
||||
|
||||
Comp -->|Aggregated Response| Client
|
||||
|
||||
style Comp fill:#fff4e1
|
||||
style S1 fill:#e1ffe1
|
||||
style S2 fill:#e1ffe1
|
||||
style S3 fill:#e1ffe1
|
||||
```
|
||||
|
||||
### Chaining Pattern (Sequential Calls with Compensation) / Pattern Chaining (Gọi Tuần Tự với Compensation)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant OrderService
|
||||
participant PaymentService
|
||||
|
||||
Client->>Gateway: Create Order Request
|
||||
Gateway->>OrderService: POST /orders
|
||||
OrderService-->>Gateway: Order Created
|
||||
|
||||
Gateway->>PaymentService: POST /payments
|
||||
alt Payment Success
|
||||
PaymentService-->>Gateway: Payment Processed
|
||||
Gateway-->>Client: Success Response
|
||||
else Payment Failed
|
||||
PaymentService-->>Gateway: Payment Error
|
||||
Gateway->>OrderService: DELETE /orders/:id (Compensate)
|
||||
OrderService-->>Gateway: Order Deleted
|
||||
Gateway-->>Client: Error Response
|
||||
end
|
||||
```
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### API Composition / API Composition
|
||||
|
||||
```typescript
|
||||
// EN: Aggregate multiple service responses
|
||||
// VI: Tổng hợp responses từ nhiều services
|
||||
const [user, orders, payments] = await Promise.all([
|
||||
userClient.get(`/users/${userId}`),
|
||||
orderClient.get(`/orders?userId=${userId}`),
|
||||
paymentClient.get(`/payments?userId=${userId}`),
|
||||
]);
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. Use API composition for aggregating related data / Sử dụng API composition để tổng hợp dữ liệu liên quan
|
||||
2. Cache at gateway / Cache ở gateway
|
||||
3. Implement circuit breaker at gateway / Implement circuit breaker ở gateway
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Middleware Patterns](./middleware-patterns.md) - Middleware patterns / Các patterns middleware
|
||||
- [Resilience Patterns](./resilience-patterns.md) - Circuit breaker / Circuit breaker
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- Skill Source: `.cursor/skills/api-gateway-advanced/SKILL.md`
|
||||
@@ -1,456 +0,0 @@
|
||||
# Chiến Lược Versioning API (API Versioning Strategy)
|
||||
|
||||
API versioning strategies for GoodGo microservices including semantic versioning, backward compatibility patterns, API deprecation, version negotiation, and breaking changes handling.
|
||||
> Các chiến lược versioning API cho GoodGo microservices bao gồm semantic versioning, backward compatibility patterns, API deprecation, version negotiation, và xử lý breaking changes.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
API versioning strategies enable managing API evolution while maintaining compatibility and clearly communicating changes to consumers.
|
||||
|
||||
Các chiến lược versioning API cho phép quản lý sự phát triển API đồng thời duy trì tương thích và giao tiếp rõ ràng các thay đổi tới consumers.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when versioning APIs, handling breaking changes, or implementing deprecation.
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Versioning APIs
|
||||
- Xử lý breaking changes
|
||||
- Implement API deprecation
|
||||
- Duy trì backward compatibility
|
||||
- Implement version negotiation
|
||||
- Quản lý nhiều API versions
|
||||
- Lập kế hoạch phát triển API
|
||||
- Giao tiếp các thay đổi API với consumers
|
||||
|
||||
## Các Khái Niệm Cốt Lõi
|
||||
|
||||
### Các Chiến Lược Versioning
|
||||
|
||||
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 (ví dụ: 1.2.3)
|
||||
|
||||
### Các Loại Tương Thích
|
||||
|
||||
- **Backward Compatible**: Version mới hoạt động với clients cũ
|
||||
- **Forward Compatible**: Version cũ hoạt động với clients mới
|
||||
- **Breaking Changes**: Thay đổi không tương thích yêu cầu version mới
|
||||
|
||||
## Version Negotiation
|
||||
|
||||
Version negotiation allows clients to request a specific API version through headers while maintaining clean URLs. The middleware extracts the version from the `Accept` header and routes to the appropriate handler.
|
||||
|
||||
Version negotiation cho phép clients yêu cầu một API version cụ thể thông qua headers trong khi duy trì URLs sạch. Middleware trích xuất version từ header `Accept` và định tuyến tới handler phù hợp.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware as Version Negotiation<br/>Middleware
|
||||
participant Controller as Version-Aware<br/>Controller
|
||||
participant Service
|
||||
|
||||
Client->>Middleware: Request with Accept header<br/>Accept: application/vnd.goodgo.v1+json
|
||||
Middleware->>Middleware: Extract version from header
|
||||
alt Version specified
|
||||
Middleware->>Middleware: Parse version number
|
||||
alt Version supported
|
||||
Middleware->>Controller: Set req.apiVersion = 1
|
||||
Controller->>Controller: Check version
|
||||
Controller->>Service: Call service method
|
||||
Service-->>Controller: Return data
|
||||
Controller->>Controller: Format response for v1
|
||||
Controller-->>Client: v1 response format
|
||||
else Version not supported
|
||||
Middleware-->>Client: 400 Unsupported Version
|
||||
end
|
||||
else No version specified
|
||||
Middleware->>Controller: Set req.apiVersion = latest (2)
|
||||
Controller->>Service: Call service method
|
||||
Service-->>Controller: Return data
|
||||
Controller->>Controller: Format response for v2
|
||||
Controller-->>Client: v2 response format (default)
|
||||
end
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```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();
|
||||
}
|
||||
```
|
||||
|
||||
## API Deprecation Timeline
|
||||
|
||||
API deprecation follows a structured timeline to give consumers adequate time to migrate. The lifecycle progresses through active, deprecated, sunset, and removed phases.
|
||||
|
||||
API deprecation tuân theo một timeline có cấu trúc để cung cấp cho consumers đủ thời gian để migrate. Vòng đời tiến triển qua các giai đoạn active, deprecated, sunset, và removed.
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title API Version Lifecycle Timeline
|
||||
dateFormat YYYY-MM-DD
|
||||
section Version 1
|
||||
Active (v1 only) :active, v1-active, 2024-01-01, 2024-06-01
|
||||
Deprecated (v1 + v2) :crit, v1-deprecated, 2024-06-01, 2024-12-31
|
||||
Sunset Period :v1-sunset, 2024-12-31, 2025-01-31
|
||||
Removed :v1-removed, 2025-01-31, 1d
|
||||
section Version 2
|
||||
Development :v2-dev, 2024-03-01, 2024-06-01
|
||||
Active (v1 + v2) :active, v2-active, 2024-06-01, 2025-12-31
|
||||
```
|
||||
|
||||
### Deprecation Phases
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active: Version Released
|
||||
Active --> Deprecated: New Version Released<br/>Add Deprecation Headers
|
||||
Deprecated --> Sunset: Sunset Date Reached<br/>Stop Accepting New Requests
|
||||
Sunset --> Removed: Grace Period Ended<br/>Remove Routes
|
||||
Removed --> [*]
|
||||
|
||||
note right of Active
|
||||
- Version fully supported
|
||||
- No warnings
|
||||
- All features available
|
||||
end note
|
||||
|
||||
note right of Deprecated
|
||||
- Deprecation header set
|
||||
- Warning headers added
|
||||
- Migration guide provided
|
||||
- Still functional
|
||||
end note
|
||||
|
||||
note right of Sunset
|
||||
- Read-only mode
|
||||
- No new requests accepted
|
||||
- Existing requests honored
|
||||
end note
|
||||
```
|
||||
|
||||
### 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);
|
||||
```
|
||||
|
||||
## Migration Flow
|
||||
|
||||
Breaking changes require a careful 3-phase migration strategy to ensure zero downtime and smooth client transitions.
|
||||
|
||||
Breaking changes yêu cầu một chiến lược migration 3 giai đoạn cẩn thận để đảm bảo zero downtime và chuyển đổi client mượt mà.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Breaking Change Identified]) --> Phase1[Phase 1: Support Both Versions]
|
||||
|
||||
Phase1 --> DeployV2[Deploy v2 alongside v1]
|
||||
DeployV2 --> Monitor1[Monitor v1 and v2 usage]
|
||||
Monitor1 --> Wait1[Wait for client adoption]
|
||||
Wait1 --> Phase2{Sufficient<br/>v2 adoption?}
|
||||
|
||||
Phase2 -->|No| Wait1
|
||||
Phase2 -->|Yes| Phase2Start[Phase 2: Deprecate v1]
|
||||
|
||||
Phase2Start --> AddHeaders[Add deprecation headers to v1]
|
||||
AddHeaders --> NotifyClients[Notify clients via<br/>deprecation warnings]
|
||||
NotifyClients --> ProvideGuide[Provide migration guide]
|
||||
ProvideGuide --> Monitor2[Monitor migration progress]
|
||||
Monitor2 --> Wait2[Wait until sunset date]
|
||||
Wait2 --> Phase3{Sunset date<br/>reached?}
|
||||
|
||||
Phase3 -->|No| Monitor2
|
||||
Phase3 -->|Yes| Phase3Start[Phase 3: Remove v1]
|
||||
|
||||
Phase3Start --> StopAccepting[Stop accepting new v1 requests]
|
||||
StopAccepting --> GracePeriod[Grace period for<br/>existing requests]
|
||||
GracePeriod --> RemoveRoutes[Remove v1 routes]
|
||||
RemoveRoutes --> End([Migration Complete])
|
||||
|
||||
style Phase1 fill:#e1f5ff
|
||||
style Phase2Start fill:#fff4e1
|
||||
style Phase3Start fill:#ffe1e1
|
||||
style End fill:#e1ffe1
|
||||
```
|
||||
|
||||
### Implementation 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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;
|
||||
```
|
||||
|
||||
## 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();
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Versioning Strategy**: Choose URL path or header, be consistent / Chọn chiến lược và nhất quán
|
||||
2. **Semantic Versioning**: Use MAJOR.MINOR.PATCH / Sử dụng semantic versioning
|
||||
3. **Deprecation**: Always deprecate before removing / Luôn deprecate trước khi xóa
|
||||
4. **Migration Guide**: Provide clear migration documentation / Cung cấp tài liệu migration rõ ràng
|
||||
5. **Backward Compatibility**: Maintain compatibility when possible / Duy trì tương thích khi có thể
|
||||
6. **Communication**: Clearly communicate version changes / Giao tiếp rõ ràng các thay đổi version
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
1. **No Deprecation Period**: Breaking clients suddenly / Phá vỡ clients đột ngột
|
||||
```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 / Gây nhầm lẫn cho client
|
||||
```
|
||||
# ❌ 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 / Chiến lược không nhất quán
|
||||
```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
|
||||
```
|
||||
|
||||
## 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"
|
||||
```
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [API Design](./api-design.md) - API design patterns
|
||||
- [Middleware Patterns](./middleware-patterns.md) - Middleware patterns
|
||||
- [Project Rules](./project-rules.md) - GoodGo standards
|
||||
- Skill Source: `.cursor/skills/api-versioning-strategy/SKILL.md`
|
||||
@@ -1,420 +0,0 @@
|
||||
# Các Pattern Caching
|
||||
|
||||
Caching strategies and patterns for GoodGo microservices including multi-layer cache, Redis caching, cache key naming, TTL strategies, cache invalidation, and cache-aside patterns.
|
||||
> Các strategies và patterns về caching cho GoodGo microservices bao gồm multi-layer cache, Redis caching, cache key naming, TTL strategies, cache invalidation, và cache-aside patterns.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Caching is a critical performance optimization technique that stores frequently accessed data in fast-access storage. This guide covers multi-layer caching strategies, cache key naming conventions, TTL (Time To Live) strategies, cache invalidation patterns, and best practices for implementing effective caching in GoodGo microservices.
|
||||
|
||||
Caching là kỹ thuật tối ưu hiệu suất quan trọng lưu trữ dữ liệu thường xuyên truy cập trong storage có tốc độ truy cập nhanh. Hướng dẫn này bao gồm các strategies multi-layer caching, conventions đặt tên cache keys, TTL strategies, patterns cache invalidation, và best practices để implement caching hiệu quả trong GoodGo microservices.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use caching patterns 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
|
||||
- Improving application performance
|
||||
|
||||
Sử dụng caching patterns khi:
|
||||
- Implement caching cho dữ liệu thường xuyên truy cập
|
||||
- Optimize database queries với caching
|
||||
- Thiết kế cache key naming conventions
|
||||
- Thiết lập TTL strategies
|
||||
- Implement cache invalidation patterns
|
||||
- Sử dụng multi-layer cache (L1: Memory, L2: Redis)
|
||||
- Xử lý cache failures một cách graceful
|
||||
- Cải thiện hiệu suất ứng dụng
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Chiến Lược Cache Đa Tầng
|
||||
|
||||
The platform uses a two-layer cache architecture to balance speed and capacity.
|
||||
|
||||
Nền tảng sử dụng kiến trúc cache hai tầng để cân bằng tốc độ và dung lượng.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Application["Application Layer"]
|
||||
App[Application Code]
|
||||
end
|
||||
|
||||
subgraph L1Layer["L1 Cache - Memory (NodeCache)"]
|
||||
L1[In-Memory Cache]
|
||||
L1Props["• Speed: < 1ms<br/>• Capacity: 10k keys<br/>• TTL: 60s-5min<br/>• Scope: Per-instance"]
|
||||
end
|
||||
|
||||
subgraph L2Layer["L2 Cache - Redis (Distributed)"]
|
||||
L2[Redis Cache]
|
||||
L2Props["• Speed: < 5ms<br/>• Capacity: Large<br/>• TTL: Configurable<br/>• Scope: Shared"]
|
||||
end
|
||||
|
||||
subgraph DataLayer["Data Source"]
|
||||
DB[(Database)]
|
||||
API[External API]
|
||||
end
|
||||
|
||||
App -->|Check First| L1
|
||||
L1 -->|Miss| L2
|
||||
L2 -->|Miss| DB
|
||||
L2 -->|Miss| API
|
||||
DB -->|Store| L2
|
||||
API -->|Store| L2
|
||||
L2 -->|Warm| L1
|
||||
|
||||
L1 -.-> L1Props
|
||||
L2 -.-> L2Props
|
||||
|
||||
style L1 fill:#e1f5ff
|
||||
style L2 fill:#fff4e1
|
||||
style DB fill:#ffe1e1
|
||||
style API fill:#ffe1e1
|
||||
```
|
||||
|
||||
**Đặc Điểm Các Tầng / Layer Characteristics:**
|
||||
|
||||
**Cache Bộ Nhớ / L1 Cache (Memory)**:
|
||||
- **Storage**: NodeCache in-memory cache
|
||||
- **Tốc Độ / Speed**: Very fast (< 1ms access time)
|
||||
- **Dung Lượng / Capacity**: Limited (10k keys default)
|
||||
- **TTL**: Short (60 seconds default, max 5 minutes)
|
||||
- **Phạm Vi / Scope**: Per-instance (not shared across instances)
|
||||
- **Trường Hợp Sử Dụng / Use Case**: Ultra-fast access for hot data
|
||||
|
||||
**Cache Redis / L2 Cache (Redis)**:
|
||||
- **Storage**: Distributed Redis cache
|
||||
- **Tốc Độ / Speed**: Fast (< 5ms access time)
|
||||
- **Dung Lượng / Capacity**: Large
|
||||
- **TTL**: Longer (configurable)
|
||||
- **Phạm Vi / Scope**: Shared across all service instances
|
||||
- **Trường Hợp Sử Dụng / Use Case**: Distributed caching, larger datasets
|
||||
|
||||
### Luồng Cache
|
||||
|
||||
The cache lookup follows this flow: Check L1 first, if miss check L2, if miss fetch from source and populate both layers.
|
||||
|
||||
Luồng lookup cache theo trình tự: Kiểm tra L1 trước, nếu miss thì kiểm tra L2, nếu miss thì fetch từ nguồn và populate cả hai tầng.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Request Data]) --> CheckL1{Check L1 Cache<br/>Memory}
|
||||
CheckL1 -->|Hit| ReturnL1[Return Data<br/>from L1]
|
||||
CheckL1 -->|Miss| CheckL2{Check L2 Cache<br/>Redis}
|
||||
CheckL2 -->|Hit| StoreL1[Store in L1<br/>Warm Cache]
|
||||
StoreL1 --> ReturnL2[Return Data<br/>from L2]
|
||||
CheckL2 -->|Miss| FetchSource[Fetch from<br/>Data Source]
|
||||
FetchSource --> StoreBoth[Store in L1 & L2]
|
||||
StoreBoth --> ReturnSource[Return Data<br/>from Source]
|
||||
|
||||
ReturnL1 --> End([End])
|
||||
ReturnL2 --> End
|
||||
ReturnSource --> End
|
||||
|
||||
style CheckL1 fill:#e1f5ff
|
||||
style CheckL2 fill:#fff4e1
|
||||
style FetchSource fill:#ffe1e1
|
||||
style ReturnL1 fill:#e1ffe1
|
||||
style ReturnL2 fill:#e1ffe1
|
||||
style ReturnSource fill:#e1ffe1
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/core/cache/multi-layer-cache.ts`](../../../services/iam-service/src/core/cache/multi-layer-cache.ts)
|
||||
|
||||
## Patterns
|
||||
|
||||
### Sử Dụng Cache Service
|
||||
|
||||
Use the cache service wrapper for all cache operations.
|
||||
|
||||
Sử dụng cache service wrapper cho tất cả các thao tác cache.
|
||||
|
||||
```typescript
|
||||
import { cacheService } from '../core/cache';
|
||||
|
||||
// Get/set đơn giản
|
||||
const cached = await cacheService.get<User>('user:123');
|
||||
await cacheService.set('user:123', userData, 300); // 5 minutes TTL
|
||||
|
||||
// Pattern get or set (cache-aside)
|
||||
const user = await cacheService.getOrSet(
|
||||
'user:123',
|
||||
async () => {
|
||||
return await userRepository.findById('123');
|
||||
},
|
||||
300 // TTL in seconds
|
||||
);
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/core/cache/cache.service.ts`](../../../services/iam-service/src/core/cache/cache.service.ts)
|
||||
|
||||
### Quy Ước Đặt Tên Cache Key
|
||||
|
||||
Use consistent naming patterns to avoid collisions and enable pattern-based invalidation.
|
||||
|
||||
Sử dụng patterns đặt tên nhất quán để tránh collisions và cho phép invalidation dựa trên pattern.
|
||||
|
||||
**Các 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'
|
||||
```
|
||||
|
||||
**Bộ Tạo Key**:
|
||||
```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}`,
|
||||
};
|
||||
```
|
||||
|
||||
### Pattern Cache-Aside
|
||||
|
||||
The most common caching pattern - check cache first, fetch if miss, store in cache.
|
||||
|
||||
Pattern caching phổ biến nhất - kiểm tra cache trước, fetch nếu miss, lưu vào cache.
|
||||
|
||||
```typescript
|
||||
async getUserPermissions(userId: string): Promise<string[]> {
|
||||
const cacheKey = cacheService.keys.userPermissions(userId);
|
||||
|
||||
// Thử cache trước
|
||||
const cached = await cacheService.get<string[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Cache miss - fetch từ nguồn
|
||||
const permissions = await calculatePermissions(userId);
|
||||
|
||||
// Lưu vào cache
|
||||
await cacheService.set(cacheKey, permissions, 300); // 5 min TTL
|
||||
|
||||
return permissions;
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/rbac/rbac.service.ts`](../../../services/iam-service/src/modules/rbac/rbac.service.ts)
|
||||
|
||||
### Pattern Get hoặc Set
|
||||
|
||||
Simplified cache-aside pattern that automatically handles cache miss scenarios.
|
||||
|
||||
Pattern cache-aside đơn giản hóa tự động xử lý các trường hợp cache miss.
|
||||
|
||||
```typescript
|
||||
const permissions = await cacheService.getOrSet(
|
||||
cacheService.keys.userPermissions(userId),
|
||||
async () => {
|
||||
// Function này chỉ chạy khi cache miss
|
||||
return await calculatePermissions(userId);
|
||||
},
|
||||
300 // TTL
|
||||
);
|
||||
```
|
||||
|
||||
### Chiến Lược TTL
|
||||
|
||||
Choose TTL (Time To Live) based on data characteristics and change frequency.
|
||||
|
||||
Chọn TTL (Time To Live) dựa trên đặc điểm dữ liệu và tần suất thay đổi.
|
||||
|
||||
**Short TTL (60-300s) / TTL Ngắn**: Frequently changing data / Dữ liệu thay đổi thường xuyên
|
||||
- User permissions (300s)
|
||||
- Session data (varies)
|
||||
- Real-time statistics / Thống kê thời gian thực
|
||||
|
||||
**Medium TTL (300-1800s) / TTL Trung Bình**: Moderately changing data / Dữ liệu thay đổi vừa phải
|
||||
- User profiles (600s)
|
||||
- Organization data (900s)
|
||||
- Configuration (1800s)
|
||||
|
||||
**Long TTL (1800-3600s) / TTL Dài**: Rarely changing data / Dữ liệu hiếm khi thay đổi
|
||||
- Static configurations (3600s)
|
||||
- Reference data (7200s)
|
||||
|
||||
**No TTL / Không TTL**: Very stable data (use with caution) / Dữ liệu rất ổn định (sử dụng cẩn thận)
|
||||
- Rarely use - prefer long TTL instead / Hiếm khi sử dụng - nên dùng TTL dài
|
||||
|
||||
### Làm Mất Hiệu Lực Cache
|
||||
|
||||
Invalidate cache when data changes to prevent serving stale data. The platform supports multiple invalidation strategies.
|
||||
|
||||
Làm mất hiệu lực cache khi dữ liệu thay đổi để tránh phục vụ dữ liệu cũ. Nền tảng hỗ trợ nhiều chiến lược invalidation.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Data Changed]) --> ChooseStrategy{Choose<br/>Invalidation<br/>Strategy}
|
||||
|
||||
ChooseStrategy -->|Single Key| SingleKey[Single Key<br/>Invalidation]
|
||||
SingleKey --> DelL1[Delete from L1]
|
||||
DelL1 --> DelL2[Delete from L2]
|
||||
DelL2 --> Done1([Complete])
|
||||
|
||||
ChooseStrategy -->|Pattern Match| PatternMatch[Pattern-Based<br/>Invalidation]
|
||||
PatternMatch --> FindKeys[Find Matching Keys<br/>user:123:*]
|
||||
FindKeys --> DelManyL1[Delete from L1<br/>All Matching]
|
||||
DelManyL1 --> DelManyL2[Delete from L2<br/>All Matching]
|
||||
DelManyL2 --> Done2([Complete])
|
||||
|
||||
ChooseStrategy -->|Multiple Keys| MultipleKeys[Multiple Keys<br/>Invalidation]
|
||||
MultipleKeys --> ListKeys[List Keys to Delete<br/>user:123<br/>user:123:permissions<br/>user:123:roles]
|
||||
ListKeys --> BatchDelL1[Batch Delete from L1]
|
||||
BatchDelL1 --> BatchDelL2[Batch Delete from L2]
|
||||
BatchDelL2 --> Done3([Complete])
|
||||
|
||||
style SingleKey fill:#e1f5ff
|
||||
style PatternMatch fill:#fff4e1
|
||||
style MultipleKeys fill:#ffe1e1
|
||||
style Done1 fill:#e1ffe1
|
||||
style Done2 fill:#e1ffe1
|
||||
style Done3 fill:#e1ffe1
|
||||
```
|
||||
|
||||
**Ví Dụ Triển Khai / Implementation Examples:**
|
||||
|
||||
```typescript
|
||||
// Làm mất hiệu lực key đơn / Single key invalidation
|
||||
await cacheService.del(cacheService.keys.user(userId));
|
||||
await cacheService.del(cacheService.keys.userPermissions(userId));
|
||||
|
||||
// Làm mất hiệu lực dựa trên pattern / Pattern-based invalidation
|
||||
await cacheService.invalidatePattern('user:123:*');
|
||||
|
||||
// Nhiều keys / Multiple keys
|
||||
await cacheService.delMany([
|
||||
cacheService.keys.user(userId),
|
||||
cacheService.keys.userPermissions(userId),
|
||||
cacheService.keys.userRoles(userId),
|
||||
]);
|
||||
```
|
||||
|
||||
### Làm Nóng Cache
|
||||
|
||||
Pre-populate cache with frequently accessed data to improve initial performance.
|
||||
|
||||
Pre-populate cache với dữ liệu thường xuyên truy cập để cải thiện hiệu suất ban đầu.
|
||||
|
||||
```typescript
|
||||
async warmCache() {
|
||||
const activeUsers = await userRepository.findActiveUsers();
|
||||
|
||||
for (const user of activeUsers) {
|
||||
// Pre-cache dữ liệu user
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Xử Lý Lỗi
|
||||
|
||||
Cache failures should not break the application - always fallback to data source.
|
||||
|
||||
Cache failures không nên làm hỏng ứng dụng - luôn fallback về data source.
|
||||
|
||||
```typescript
|
||||
async getWithCache(key: string): Promise<Data | null> {
|
||||
try {
|
||||
// Thử cache trước
|
||||
const cached = await cacheService.get<Data>(key);
|
||||
if (cached) return cached;
|
||||
} catch (error) {
|
||||
// Log nhưng tiếp tục - fallback về nguồn
|
||||
logger.warn('Cache get failed, falling back to source', { key, error });
|
||||
}
|
||||
|
||||
// Fallback về data source
|
||||
return await fetchFromSource();
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Cache Keys / Cache Keys**: Sử dụng naming conventions nhất quán
|
||||
2. **TTL Selection / Chọn TTL**: Chọn TTL dựa trên tần suất thay đổi dữ liệu
|
||||
3. **Invalidation / Làm Mất Hiệu Lực**: Làm mất hiệu lực cache khi dữ liệu thay đổi
|
||||
4. **Error Handling / Xử Lý Lỗi**: Không để cache failures làm hỏng ứng dụng
|
||||
5. **Cache-Aside / Cache-Aside**: Sử dụng cache-aside pattern cho hầu hết các trường hợp
|
||||
6. **Avoid Over-Caching / Tránh Cache Quá Nhiều**: Không cache dữ liệu thay đổi quá thường xuyên
|
||||
7. **Monitor Hit Rates / Giám Sát Tỷ Lệ Hit**: Theo dõi cache hit rates để optimize TTL
|
||||
8. **Warm Cache / Làm Nóng Cache**: Pre-populate cache cho critical data
|
||||
9. **Use Multi-Layer / Sử Dụng Đa Tầng**: Tận dụng cả L1 và L2 cache
|
||||
10. **Serialize Properly / Serialize Đúng Cách**: Đảm bảo dữ liệu có thể JSON serialize
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **Cache Key Collisions / Va Chạm Cache Key**: Sử dụng keys chung chung dẫn đến va chạm
|
||||
2. **Stale Data / Dữ Liệu Cũ**: Không invalidate cache khi dữ liệu thay đổi
|
||||
3. **Too Short TTL / TTL Quá Ngắn**: Đặt TTL quá ngắn, làm mất lợi ích của cache
|
||||
4. **Too Long TTL / TTL Quá Dài**: Đặt TTL quá dài, phục vụ dữ liệu cũ
|
||||
5. **No Error Handling / Không Xử Lý Lỗi**: Để cache errors làm hỏng ứng dụng
|
||||
6. **Caching Everything / Cache Tất Cả**: Cache dữ liệu không có lợi từ caching
|
||||
7. **Not Warming Cache / Không Làm Nóng Cache**: Không pre-populate critical cache data
|
||||
8. **Ignoring Hit Rates / Bỏ Qua Tỷ Lệ Hit**: Không monitor cache performance
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Tỷ Lệ Hit Thấp
|
||||
|
||||
**Problem / Vấn Đề**: Cache hit rate thấp, cache không hiệu quả
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Review TTL values - có thể quá ngắn
|
||||
- Check cache key patterns - đảm bảo sử dụng nhất quán
|
||||
- Verify cache invalidation không quá aggressive
|
||||
- Monitor dữ liệu nào đang được cache
|
||||
|
||||
### Vấn Đề Dữ Liệu Cũ
|
||||
|
||||
**Problem / Vấn Đề**: Phục vụ dữ liệu cũ từ cache
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Review TTL values - có thể quá dài
|
||||
- Đảm bảo cache invalidation khi dữ liệu cập nhật
|
||||
- Sử dụng TTL ngắn hơn cho dữ liệu thay đổi thường xuyên
|
||||
- Implement cache versioning nếu cần
|
||||
|
||||
### Vấn Đề Hiệu Suất Cache
|
||||
|
||||
**Problem / Vấn Đề**: Cache operations chậm
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Kiểm tra Redis connection và network latency
|
||||
- Monitor Redis memory usage
|
||||
- Review cache key patterns để hiệu quả
|
||||
- Xem xét L1 cache hit rate (nên cao)
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [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 example
|
||||
- [Redis Configuration](../../../services/iam-service/src/config/redis.config.ts) - Redis configuration
|
||||
@@ -1,578 +0,0 @@
|
||||
# CI/CD Patterns Nâng Cao (CI/CD Advanced Patterns)
|
||||
|
||||
Advanced CI/CD patterns for GoodGo microservices including blue-green deployments, canary releases, automated rollback, deployment verification, and progressive delivery.
|
||||
> Các patterns CI/CD nâng cao cho GoodGo microservices bao gồm blue-green deployments, canary releases, automated rollback, deployment verification, và progressive delivery.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Advanced CI/CD patterns enable safe, zero-downtime deployments with blue-green, canary releases, automated rollbacks, and deployment verification.
|
||||
|
||||
Các patterns CI/CD nâng cao cho phép deployments an toàn, zero-downtime với blue-green, canary releases, automated rollbacks, và deployment verification.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when implementing advanced deployment strategies, automated rollbacks, or progressive delivery.
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Implement blue-green deployments / Triển khai blue-green deployments
|
||||
- Setting up canary releases / Thiết lập canary releases
|
||||
- Implementing automated rollback mechanisms / Triển khai cơ chế rollback tự động
|
||||
- Creating deployment verification pipelines / Tạo pipelines xác minh deployment
|
||||
- Implementing progressive delivery / Triển khai progressive delivery
|
||||
- Setting up deployment gates / Thiết lập deployment gates
|
||||
- Implementing smoke tests / Triển khai smoke tests
|
||||
- Managing deployment strategies in Kubernetes / Quản lý chiến lược deployment trong Kubernetes
|
||||
|
||||
## Khái Niệm Cốt Lõi
|
||||
|
||||
### Deployment Strategies / Chiến Lược Deployment
|
||||
|
||||
1. **Rolling Update**: Gradual replacement (default K8s) / Thay thế dần dần (mặc định K8s)
|
||||
2. **Blue-Green**: Two identical environments, switch traffic / Hai môi trường giống hệt, chuyển traffic
|
||||
3. **Canary**: Gradual rollout to subset of users / Rollout dần dần tới subset users
|
||||
4. **Recreate**: Stop old, start new (downtime) / Dừng cũ, khởi động mới (có downtime)
|
||||
|
||||
### Deployment Verification / Xác Minh Deployment
|
||||
|
||||
- Smoke tests / Kiểm tra smoke
|
||||
- Health checks / Kiểm tra sức khỏe
|
||||
- Performance tests / Kiểm tra hiệu suất
|
||||
- Rollback triggers / Kích hoạt rollback
|
||||
|
||||
## Blue-Green Deployment
|
||||
|
||||
Blue-green deployment maintains two identical production environments (blue and green). At any time, only one environment serves live traffic. The new version is deployed to the idle environment, verified, and then traffic is switched.
|
||||
|
||||
Blue-green deployment duy trì hai môi trường production giống hệt nhau (blue và green). Tại bất kỳ thời điểm nào, chỉ một môi trường phục vụ traffic trực tiếp. Phiên bản mới được triển khai vào môi trường không hoạt động, được xác minh, sau đó traffic được chuyển đổi.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Deployment Triggered]) --> DeployGreen[Deploy to Green Environment]
|
||||
DeployGreen --> WaitRollout[Wait for Rollout Complete]
|
||||
WaitRollout --> RunSmokeTests[Run Smoke Tests]
|
||||
RunSmokeTests --> TestsPassed{Tests Passed?}
|
||||
|
||||
TestsPassed -->|Yes| SwitchTraffic[Switch Service Selector to Green]
|
||||
TestsPassed -->|No| RollbackToBlue[Rollback: Keep Blue Active]
|
||||
|
||||
SwitchTraffic --> MonitorHealth[Monitor Health Metrics]
|
||||
MonitorHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| Complete([Deployment Complete])
|
||||
HealthOK -->|No| AutoRollback[Auto Rollback to Blue]
|
||||
|
||||
AutoRollback --> Complete
|
||||
RollbackToBlue --> Fail([Deployment Failed])
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail fill:#f8d7da
|
||||
style TestsPassed fill:#fff3cd
|
||||
style HealthOK fill:#fff3cd
|
||||
```
|
||||
|
||||
### Kubernetes Implementation / Triển Khai Kubernetes
|
||||
|
||||
```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
|
||||
# EN: Switch service selector between blue/green
|
||||
# VI: Service selector chuyển đổi giữa 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
|
||||
```
|
||||
|
||||
## Canary Deployment
|
||||
|
||||
Canary deployment gradually rolls out changes to a small subset of users before making them available to everyone. This allows for real-world testing with minimal risk.
|
||||
|
||||
Canary deployment triển khai thay đổi dần dần tới một subset nhỏ users trước khi làm cho chúng có sẵn cho tất cả mọi người. Điều này cho phép kiểm tra trong môi trường thực tế với rủi ro tối thiểu.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Canary Deployment Started]) --> DeployCanary[Deploy Canary Version<br/>1 Replica]
|
||||
DeployCanary --> Route10[Route 10% Traffic to Canary]
|
||||
Route10 --> Wait10[Wait 5-10 minutes]
|
||||
Wait10 --> Check10{Health & Metrics OK?}
|
||||
|
||||
Check10 -->|No| RollbackCanary[Rollback: Route 0% to Canary]
|
||||
Check10 -->|Yes| Route25[Route 25% Traffic to Canary]
|
||||
|
||||
Route25 --> Wait25[Wait 5-10 minutes]
|
||||
Wait25 --> Check25{Health & Metrics OK?}
|
||||
|
||||
Check25 -->|No| RollbackCanary
|
||||
Check25 -->|Yes| Route50[Route 50% Traffic to Canary]
|
||||
|
||||
Route50 --> Wait50[Wait 5-10 minutes]
|
||||
Wait50 --> Check50{Health & Metrics OK?}
|
||||
|
||||
Check50 -->|No| RollbackCanary
|
||||
Check50 -->|Yes| Route75[Route 75% Traffic to Canary]
|
||||
|
||||
Route75 --> Wait75[Wait 5-10 minutes]
|
||||
Wait75 --> Check75{Health & Metrics OK?}
|
||||
|
||||
Check75 -->|No| RollbackCanary
|
||||
Check75 -->|Yes| Route100[Route 100% Traffic to Canary]
|
||||
|
||||
Route100 --> PromoteCanary[Promote Canary to Stable]
|
||||
PromoteCanary --> Complete([Canary Complete])
|
||||
|
||||
RollbackCanary --> Fail([Canary Failed])
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail fill:#f8d7da
|
||||
style Check10 fill:#fff3cd
|
||||
style Check25 fill:#fff3cd
|
||||
style Check50 fill:#fff3cd
|
||||
style Check75 fill:#fff3cd
|
||||
```
|
||||
|
||||
### Kubernetes Canary with Service Mesh / Canary Kubernetes với 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
|
||||
# EN: VirtualService splits traffic
|
||||
# VI: VirtualService chia 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
|
||||
```
|
||||
|
||||
## Automated Rollback / Rollback Tự Động
|
||||
|
||||
Automated rollback mechanisms detect deployment failures and automatically revert to the previous stable version, minimizing downtime and impact.
|
||||
|
||||
Cơ chế rollback tự động phát hiện lỗi deployment và tự động quay lại phiên bản ổn định trước đó, giảm thiểu downtime và tác động.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Deployment Completed]) --> RunSmokeTests[Run Smoke Tests]
|
||||
RunSmokeTests --> SmokePassed{Smoke Tests Pass?}
|
||||
|
||||
SmokePassed -->|No| GetPreviousRev[Get Previous Revision]
|
||||
GetPreviousRev --> RollbackDeploy[Rollback Deployment]
|
||||
RollbackDeploy --> VerifyRollback[Verify Rollback Success]
|
||||
VerifyRollback --> RollbackComplete([Rollback Complete])
|
||||
|
||||
SmokePassed -->|Yes| MonitorHealth[Monitor Health Metrics]
|
||||
MonitorHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| MonitorErrors[Monitor Error Rates]
|
||||
HealthOK -->|No| GetPreviousRev
|
||||
|
||||
MonitorErrors --> ErrorRateOK{Error Rate < Threshold?}
|
||||
|
||||
ErrorRateOK -->|Yes| MonitorPerformance[Monitor Performance]
|
||||
ErrorRateOK -->|No| GetPreviousRev
|
||||
|
||||
MonitorPerformance --> PerfOK{Performance OK?}
|
||||
|
||||
PerfOK -->|Yes| DeploymentSuccess([Deployment Successful])
|
||||
PerfOK -->|No| GetPreviousRev
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style DeploymentSuccess fill:#d4edda
|
||||
style RollbackComplete fill:#f8d7da
|
||||
style SmokePassed fill:#fff3cd
|
||||
style HealthOK fill:#fff3cd
|
||||
style ErrorRateOK fill:#fff3cd
|
||||
style PerfOK fill:#fff3cd
|
||||
```
|
||||
|
||||
### Rollback Script / Script Rollback
|
||||
|
||||
```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 / Rollback Tự Động Khi Lỗi
|
||||
|
||||
```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 / Xác Minh Deployment
|
||||
|
||||
### Smoke Tests / Kiểm Tra Smoke
|
||||
|
||||
```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 / Script Kiểm Tra Sức Khỏe
|
||||
|
||||
```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 / Cổng Deployment
|
||||
|
||||
Deployment gates add checkpoints in the CI/CD pipeline that must pass before proceeding to the next stage.
|
||||
|
||||
Deployment gates thêm các điểm kiểm tra trong CI/CD pipeline phải vượt qua trước khi tiến tới giai đoạn tiếp theo.
|
||||
|
||||
```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 / Thực Hành Tốt
|
||||
|
||||
1. **Blue-Green**: Use for zero-downtime deployments / Sử dụng cho zero-downtime deployments
|
||||
2. **Canary**: Use for gradual rollouts with monitoring / Sử dụng cho rollouts dần dần với monitoring
|
||||
3. **Automated Rollback**: Always have rollback plan / Luôn có kế hoạch rollback
|
||||
4. **Smoke Tests**: Run immediately after deployment / Chạy ngay sau deployment
|
||||
5. **Health Checks**: Monitor health continuously / Giám sát sức khỏe liên tục
|
||||
6. **Gates**: Use deployment gates for critical deployments / Sử dụng deployment gates cho deployments quan trọng
|
||||
|
||||
## Common Mistakes / Lỗi Thường Gặp
|
||||
|
||||
1. **No Rollback Plan**: Can't recover from failed deployment / Không thể phục hồi từ deployment thất bại
|
||||
```yaml
|
||||
# ✅ Always have rollback command ready
|
||||
# ✅ Luôn có lệnh rollback sẵn sàng
|
||||
kubectl rollout undo deployment/service
|
||||
```
|
||||
|
||||
2. **Skipping Smoke Tests**: Catching issues too late / Phát hiện vấn đề quá muộn
|
||||
```yaml
|
||||
# ✅ Run smoke tests immediately after deploy
|
||||
# ✅ Chạy smoke tests ngay sau khi deploy
|
||||
- name: Smoke Tests
|
||||
run: ./scripts/smoke-tests.sh
|
||||
```
|
||||
|
||||
3. **100% Traffic Switch**: All-or-nothing failures / Lỗi tất cả hoặc không gì cả
|
||||
```yaml
|
||||
# ❌ BAD: Immediate full switch
|
||||
# ❌ XẤU: Chuyển đổi toàn bộ ngay lập tức
|
||||
# ✅ GOOD: Gradual rollout (10% → 50% → 100%)
|
||||
# ✅ TỐT: Rollout dần dần (10% → 50% → 100%)
|
||||
```
|
||||
|
||||
4. **No Health Monitoring**: Missing deployment issues / Bỏ lỡ vấn đề deployment
|
||||
```yaml
|
||||
# ✅ Monitor health after deployment
|
||||
# ✅ Giám sát sức khỏe sau deployment
|
||||
- name: Monitor Health
|
||||
run: kubectl rollout status deployment/service --timeout=5m
|
||||
```
|
||||
|
||||
## Quick Reference / Tham Khảo Nhanh
|
||||
|
||||
| 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 / Lệnh Deployment:**
|
||||
```bash
|
||||
# Apply deployment
|
||||
kubectl apply -f kubernetes/
|
||||
|
||||
# Check rollout status
|
||||
kubectl rollout status deployment/service
|
||||
|
||||
# Rollback
|
||||
kubectl rollout undo deployment/service
|
||||
|
||||
# Canary traffic split (Istio)
|
||||
kubectl apply -f virtualservice-canary.yaml
|
||||
```
|
||||
|
||||
**GitHub Actions Triggers / Kích Hoạt GitHub Actions:**
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [main] # Deploy to prod
|
||||
tags: ['v*'] # Release
|
||||
pull_request:
|
||||
branches: [main] # PR checks
|
||||
```
|
||||
|
||||
**Deployment Gates / Cổng Deployment:**
|
||||
```
|
||||
Build → Test → Security Scan → Deploy Staging
|
||||
→ Smoke Tests → Manual Approval → Deploy Prod
|
||||
```
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
||||
- [Istio Traffic Management](https://istio.io/latest/docs/tasks/traffic-management/)
|
||||
- [Deployment Kubernetes](./deployment-kubernetes.md) - K8s deployment patterns / Patterns deployment K8s
|
||||
- [Testing Patterns](./testing-patterns.md) - Testing strategies / Chiến lược testing
|
||||
- [Project Rules](./project-rules.md) - GoodGo coding standards / Tiêu chuẩn coding GoodGo
|
||||
- Skill Source: `.cursor/skills/cicd-advanced-patterns/SKILL.md`
|
||||
@@ -1,682 +0,0 @@
|
||||
# Viết Comment Code
|
||||
|
||||
Guidelines for adding comprehensive bilingual code comments (English and Vietnamese) to improve code readability for international and Vietnamese teams.
|
||||
> Hướng dẫn thêm comments song ngữ (tiếng Anh và tiếng Việt) để cải thiện khả năng đọc code cho các đội quốc tế và Việt Nam.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Code comments are essential for maintaining and understanding codebase, especially in a bilingual development environment. This guide provides comprehensive patterns for writing clear, consistent, and helpful comments in both English and Vietnamese across the GoodGo microservices platform. It covers JSDoc documentation, inline comments, special comment types, and best practices for effective code documentation.
|
||||
|
||||
Comments code là điều cần thiết để bảo trì và hiểu codebase, đặc biệt trong môi trường phát triển song ngữ. Hướng dẫn này cung cấp các patterns toàn diện để viết comments rõ ràng, nhất quán và hữu ích bằng cả tiếng Anh và tiếng Việt trên nền tảng microservices GoodGo. Nó bao gồm tài liệu JSDoc, inline comments, các loại comment đặc biệt, và best practices cho tài liệu code hiệu quả.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use bilingual commenting patterns when:
|
||||
- Adding comments to new code
|
||||
- Documenting existing code
|
||||
- Creating JSDoc/TSDoc documentation
|
||||
- Writing function/class descriptions
|
||||
- Explaining complex logic or algorithms
|
||||
- Adding inline comments for clarification
|
||||
- Documenting API endpoints and interfaces
|
||||
- Explaining configuration and setup code
|
||||
- Writing security-critical code documentation
|
||||
- Creating error handling documentation
|
||||
|
||||
Sử dụng các patterns comment song ngữ khi:
|
||||
- Thêm comments vào code mới
|
||||
- Tài liệu hóa code hiện có
|
||||
- Tạo tài liệu JSDoc/TSDoc
|
||||
- Viết mô tả functions/classes
|
||||
- Giải thích logic hoặc thuật toán phức tạp
|
||||
- Thêm inline comments để làm rõ
|
||||
- Tài liệu hóa API endpoints và interfaces
|
||||
- Giải thích code cấu hình và setup
|
||||
- Viết tài liệu code bảo mật quan trọng
|
||||
- Tạo tài liệu xử lý lỗi
|
||||
|
||||
## Cấu Trúc Comment
|
||||
|
||||
Biểu đồ sau minh họa cấu trúc và phân cấp các loại comment được sử dụng trong codebase GoodGo:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph CommentTypes["Comment Types"]
|
||||
SingleLine["Single-line Comments<br/>// EN: ...<br/>// VI: ..."]
|
||||
MultiLine["Multi-line Comments<br/>/* EN: ...<br/>VI: ... */"]
|
||||
JSDoc["JSDoc Comments<br/>/** EN: ...<br/>VI: ... */"]
|
||||
Prisma["Prisma Comments<br/>/// EN: ...<br/>VI: ..."]
|
||||
end
|
||||
|
||||
subgraph Contexts["Code Contexts"]
|
||||
Functions["Functions"]
|
||||
Classes["Classes"]
|
||||
Interfaces["Interfaces/Types"]
|
||||
Components["React Components"]
|
||||
Controllers["API Controllers"]
|
||||
Middleware["Middleware"]
|
||||
Schema["Prisma Schema"]
|
||||
Config["Configuration"]
|
||||
end
|
||||
|
||||
subgraph SpecialTypes["Special Comment Types"]
|
||||
TODO["TODO Comments"]
|
||||
FIXME["FIXME Comments"]
|
||||
WARNING["WARNING Comments"]
|
||||
NOTE["NOTE Comments"]
|
||||
end
|
||||
|
||||
subgraph Format["Bilingual Format"]
|
||||
EN["English (EN)<br/>First"]
|
||||
VI["Vietnamese (VI)<br/>Second"]
|
||||
end
|
||||
|
||||
JSDoc --> Functions
|
||||
JSDoc --> Classes
|
||||
JSDoc --> Interfaces
|
||||
JSDoc --> Components
|
||||
JSDoc --> Controllers
|
||||
JSDoc --> Middleware
|
||||
|
||||
SingleLine --> Config
|
||||
SingleLine --> SpecialTypes
|
||||
|
||||
MultiLine --> Functions
|
||||
MultiLine --> Classes
|
||||
|
||||
Prisma --> Schema
|
||||
|
||||
Format --> CommentTypes
|
||||
Format --> Contexts
|
||||
Format --> SpecialTypes
|
||||
|
||||
style CommentTypes fill:#e1f5ff
|
||||
style Contexts fill:#fff4e1
|
||||
style SpecialTypes fill:#ffe1f5
|
||||
style Format fill:#e1ffe1
|
||||
```
|
||||
|
||||
## Quy Trình Tài Liệu Hóa
|
||||
|
||||
Biểu đồ sau cho thấy quy trình quyết định khi thêm comments vào code:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Bắt đầu: Viết Code]) --> CheckType{Loại phần tử<br/>code nào?}
|
||||
|
||||
CheckType -->|Public API| HighPriority[Ưu tiên cao:<br/>Luôn tài liệu hóa]
|
||||
CheckType -->|Logic phức tạp| HighPriority
|
||||
CheckType -->|Code bảo mật| HighPriority
|
||||
CheckType -->|Config/Setup| HighPriority
|
||||
CheckType -->|Xử lý lỗi| HighPriority
|
||||
|
||||
CheckType -->|Helper Function| MediumPriority[Ưu tiên trung bình:<br/>Tài liệu khi hữu ích]
|
||||
CheckType -->|Chuyển đổi dữ liệu| MediumPriority
|
||||
CheckType -->|Tích hợp bên ngoài| MediumPriority
|
||||
|
||||
CheckType -->|Getter/Setter đơn giản| LowPriority[Ưu tiên thấp:<br/>Tùy chọn]
|
||||
CheckType -->|Tự giải thích| LowPriority
|
||||
CheckType -->|CRUD tiêu chuẩn| LowPriority
|
||||
|
||||
HighPriority --> ChooseFormat{Chọn định dạng<br/>Comment}
|
||||
MediumPriority --> ChooseFormat
|
||||
LowPriority --> ChooseFormat
|
||||
|
||||
ChooseFormat -->|Function/Class| UseJSDoc[Sử dụng JSDoc<br/>/** EN: ...<br/>VI: ... */]
|
||||
ChooseFormat -->|Giải thích ngắn| UseSingleLine[Sử dụng Single-line<br/>// EN: ...<br/>// VI: ...]
|
||||
ChooseFormat -->|Quy trình nhiều bước| UseMultiLine[Sử dụng Multi-line<br/>/* EN: ...<br/>VI: ... */]
|
||||
ChooseFormat -->|Prisma Schema| UsePrisma[Sử dụng Prisma<br/>/// EN: ...<br/>VI: ...]
|
||||
|
||||
UseJSDoc --> AddParams[Thêm @param tags<br/>Thêm @returns tag<br/>Thêm @throws nếu cần]
|
||||
UseSingleLine --> WriteBilingual[Viết song ngữ:<br/>EN trước, VI sau]
|
||||
UseMultiLine --> WriteBilingual
|
||||
UsePrisma --> WriteBilingual
|
||||
|
||||
AddParams --> WriteBilingual
|
||||
WriteBilingual --> CheckSpecial{Loại comment<br/>đặc biệt?}
|
||||
|
||||
CheckSpecial -->|Công việc tương lai| AddTODO[Thêm prefix TODO]
|
||||
CheckSpecial -->|Cần sửa| AddFIXME[Thêm prefix FIXME]
|
||||
CheckSpecial -->|Cảnh báo quan trọng| AddWARNING[Thêm prefix WARNING]
|
||||
CheckSpecial -->|Ghi chú quan trọng| AddNOTE[Thêm prefix NOTE]
|
||||
CheckSpecial -->|Không| End([Hoàn thành])
|
||||
|
||||
AddTODO --> End
|
||||
AddFIXME --> End
|
||||
AddWARNING --> End
|
||||
AddNOTE --> End
|
||||
|
||||
style HighPriority fill:#ffcccc
|
||||
style MediumPriority fill:#ffffcc
|
||||
style LowPriority fill:#ccffcc
|
||||
style UseJSDoc fill:#cce5ff
|
||||
style UseSingleLine fill:#cce5ff
|
||||
style UseMultiLine fill:#cce5ff
|
||||
style UsePrisma fill:#cce5ff
|
||||
```
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Định Dạng Comment
|
||||
|
||||
All comments in the GoodGo project should be bilingual, with English (EN) followed by Vietnamese (VI) translations.
|
||||
|
||||
**Pattern / Mẫu**:
|
||||
```
|
||||
[English explanation]
|
||||
// VI: [Vietnamese explanation]
|
||||
```
|
||||
|
||||
### Các Loại Comment
|
||||
|
||||
1. **Single-line Comments** (`//`) / **Comments Một Dòng**: For brief explanations / Cho giải thích ngắn gọn
|
||||
2. **Multi-line Comments** (`/* */`) / **Comments Nhiều Dòng**: For longer explanations / Cho giải thích dài hơn
|
||||
3. **JSDoc Comments** (`/** */`) / **Comments JSDoc**: For function, class, and API documentation / Cho tài liệu function, class và API
|
||||
4. **Prisma Comments** (`///`) / **Comments Prisma**: For database schema documentation / Cho tài liệu schema database
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Comments Một Dòng
|
||||
|
||||
Use single-line comments for brief explanations of code behavior / Sử dụng single-line comments cho giải thích ngắn gọn về hành vi code.
|
||||
|
||||
```typescript
|
||||
Initialize database connection
|
||||
// VI: Khởi tạo kết nối database
|
||||
const db = await createConnection();
|
||||
|
||||
Enable detailed logging in development, minimal in production
|
||||
// VI: Bật ghi log chi tiết trong development, tối thiểu trong production
|
||||
'error';
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/config/database.config.ts`](../../../services/iam-service/src/config/database.config.ts)
|
||||
|
||||
### Comments Nhiều Dòng
|
||||
|
||||
Use multi-line comments for detailed explanations or step-by-step processes / Sử dụng multi-line comments cho giải thích chi tiết hoặc quy trình từng bước.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
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 here
|
||||
// VI: Implementation ở đây
|
||||
}
|
||||
```
|
||||
|
||||
### Tài Liệu Function
|
||||
|
||||
Use JSDoc format for all public functions with bilingual descriptions / Sử dụng format JSDoc cho tất cả public functions với mô tả song ngữ.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
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 {
|
||||
Apply discount first
|
||||
// VI: Áp dụng giảm giá trước
|
||||
const discountedPrice = basePrice - discount;
|
||||
|
||||
Then calculate tax
|
||||
// VI: Sau đó tính thuế
|
||||
const tax = discountedPrice * taxRate;
|
||||
|
||||
return discountedPrice + tax;
|
||||
}
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/modules/feature/feature.service.ts`](../../../services/iam-service/src/modules/feature/feature.service.ts)
|
||||
|
||||
### Tài Liệu Class
|
||||
|
||||
Document classes with bilingual descriptions and explain their purpose / Tài liệu hóa classes với mô tả song ngữ và giải thích mục đích của chúng.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
Service for managing features in the system
|
||||
* VI: Service để quản lý các features trong hệ thống
|
||||
*/
|
||||
export class FeatureService {
|
||||
/**
|
||||
Create a new feature
|
||||
* VI: Tạo một feature mới
|
||||
*
|
||||
* @param data - Feature data / Dữ liệu feature
|
||||
* @returns Created feature / Feature đã tạo
|
||||
*/
|
||||
async create(data: { name: string; title?: string }) {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/modules/feature/feature.service.ts`](../../../services/iam-service/src/modules/feature/feature.service.ts)
|
||||
|
||||
### Tài Liệu Interface/Type
|
||||
|
||||
Document interfaces and types to explain their structure and purpose / Tài liệu hóa interfaces và types để giải thích cấu trúc và mục đích của chúng.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
User data transfer object
|
||||
* VI: Đối tượng truyền dữ liệu người dùng
|
||||
*/
|
||||
interface UserDto {
|
||||
Unique user identifier / VI: Mã định danh duy nhất */
|
||||
id: string;
|
||||
|
||||
User email address / VI: Địa chỉ email người dùng */
|
||||
email: string;
|
||||
|
||||
User display name / VI: Tên hiển thị người dùng */
|
||||
name: string;
|
||||
|
||||
User role for authorization / VI: Vai trò người dùng để phân quyền */
|
||||
role: 'admin' | 'user' | 'guest';
|
||||
}
|
||||
```
|
||||
|
||||
### Comments Cấu Hình
|
||||
|
||||
Document configuration files and environment variables with clear explanations / Tài liệu hóa file cấu hình và biến môi trường với giải thích rõ ràng.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
Prisma client instance configured for the application
|
||||
* VI: Instance Prisma client được cấu hình cho ứng dụng
|
||||
*/
|
||||
export const prisma = new PrismaClient({
|
||||
Enable detailed logging in development, minimal in production
|
||||
// VI: Bật ghi log chi tiết trong development, tối thiểu trong production
|
||||
['error'],
|
||||
});
|
||||
|
||||
/**
|
||||
Establish database connection on application startup
|
||||
* VI: Thiết lập kết nối database khi khởi động ứng dụng
|
||||
*/
|
||||
export const connectDatabase = async (): Promise<void> => {
|
||||
try {
|
||||
Connect to database using Prisma
|
||||
// VI: Kết nối tới database sử dụng Prisma
|
||||
await prisma.$connect();
|
||||
logger.info('Database connected successfully / Kết nối database thành công');
|
||||
} catch (error) {
|
||||
Log error and exit if database connection fails
|
||||
// VI: Ghi log lỗi và thoát nếu kết nối database thất bại
|
||||
logger.error('Database connection failed / Kết nối database thất bại', { error });
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/config/database.config.ts`](../../../services/iam-service/src/config/database.config.ts)
|
||||
|
||||
### Tài Liệu Middleware
|
||||
|
||||
Document middleware functions with their purpose and behavior / Tài liệu hóa middleware functions với mục đích và hành vi của chúng.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
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
|
||||
) {
|
||||
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 {
|
||||
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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Comments Schema Prisma
|
||||
|
||||
Use triple-slash comments (`///`) for Prisma schema documentation / Sử dụng triple-slash comments (`///`) cho tài liệu Prisma schema.
|
||||
|
||||
```prisma
|
||||
User model for authentication and profile
|
||||
/// VI: Model người dùng cho xác thực và hồ sơ
|
||||
model User {
|
||||
Unique identifier / VI: Mã định danh duy nhất
|
||||
id String @id @default(cuid())
|
||||
|
||||
User email (unique) / VI: Email người dùng (duy nhất)
|
||||
email String @unique
|
||||
|
||||
Hashed password / VI: Mật khẩu đã mã hóa
|
||||
password String
|
||||
|
||||
Display name / VI: Tên hiển thị
|
||||
name String
|
||||
|
||||
Account creation timestamp / VI: Thời gian tạo tài khoản
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
Last update timestamp / VI: Thời gian cập nhật cuối
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
```
|
||||
|
||||
### Comments Test
|
||||
|
||||
Document test setup and explain test scenarios in both languages / Tài liệu hóa test setup và giải thích test scenarios bằng cả hai ngôn ngữ.
|
||||
|
||||
```typescript
|
||||
Mock environment variables for tests
|
||||
// VI: Mock biến môi trường cho tests
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test_db';
|
||||
|
||||
Mock external services to avoid real network calls
|
||||
// VI: Mock các service bên ngoài để tránh gọi mạng thật
|
||||
jest.mock('@goodgo/logger', () => ({
|
||||
logger: {
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('FeatureService', () => {
|
||||
it('should create a feature successfully', async () => {
|
||||
Arrange
|
||||
// VI: Chuẩn bị
|
||||
const testData = { name: 'test-feature' };
|
||||
|
||||
Act
|
||||
// VI: Thực hiện
|
||||
const result = await service.create(testData);
|
||||
|
||||
Assert
|
||||
// VI: Kiểm tra
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
||||
|
||||
### Comments Logic Phức Tạp
|
||||
|
||||
When explaining complex algorithms or business logic, break it down into steps / Khi giải thích thuật toán phức tạp hoặc logic nghiệp vụ, chia nhỏ thành các bước.
|
||||
|
||||
```typescript
|
||||
Step 1: Validate input parameters
|
||||
// VI: Bước 1: Xác thực tham số đầu vào
|
||||
if (!email || !password) {
|
||||
throw new ValidationError('Email and password required');
|
||||
}
|
||||
|
||||
Step 2: Check if user exists in database
|
||||
// VI: Bước 2: Kiểm tra xem người dùng có tồn tại trong database
|
||||
const user = await prisma.user.findUnique({ where: { email } });
|
||||
if (!user) {
|
||||
throw new NotFoundError('User not found');
|
||||
}
|
||||
|
||||
Step 3: Verify password hash
|
||||
// VI: Bước 3: Xác minh hash mật khẩu
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
if (!isValidPassword) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
}
|
||||
|
||||
Step 4: Generate and return JWT token
|
||||
// VI: Bước 4: Tạo và trả về JWT token
|
||||
const token = jwt.sign({ userId: user.id }, JWT_SECRET);
|
||||
return { token, user };
|
||||
```
|
||||
|
||||
## Các Loại Comment Đặc Biệt
|
||||
|
||||
### Comments TODO
|
||||
|
||||
Use TODO comments for future improvements with bilingual descriptions / Sử dụng TODO comments cho cải tiến trong tương lai với mô tả song ngữ.
|
||||
|
||||
```typescript
|
||||
Implement caching for better performance
|
||||
// TODO VI: Triển khai caching để cải thiện hiệu suất
|
||||
|
||||
Add rate limiting to prevent abuse
|
||||
// TODO VI: Thêm rate limiting để ngăn chặn lạm dụng
|
||||
```
|
||||
|
||||
### Comments FIXME
|
||||
|
||||
Use FIXME comments for code that needs fixing / Sử dụng FIXME comments cho code cần sửa.
|
||||
|
||||
```typescript
|
||||
This causes memory leak, needs refactoring
|
||||
// FIXME VI: Đoạn này gây rò rỉ bộ nhớ, cần refactor
|
||||
|
||||
Temporary workaround, should implement proper solution
|
||||
// FIXME VI: Giải pháp tạm thời, nên triển khai giải pháp đúng đắn
|
||||
```
|
||||
|
||||
### Comments CẢNH BÁO
|
||||
|
||||
Use WARNING comments for code that requires special attention / Sử dụng WARNING comments cho code cần chú ý đặc biệt.
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
This function modifies global state, use with caution
|
||||
// WARNING VI: Function này thay đổi global state, sử dụng cẩn thận
|
||||
```
|
||||
|
||||
### Comments GHI CHÚ
|
||||
|
||||
Use NOTE comments for important information or explanations / Sử dụng NOTE comments cho thông tin hoặc giải thích quan trọng.
|
||||
|
||||
```typescript
|
||||
This is intentionally async to avoid blocking the event loop
|
||||
// NOTE VI: Đây là async có chủ ý để tránh block event loop
|
||||
|
||||
Priority: Docker Compose > .env.local > .env > System environment
|
||||
// NOTE VI: Ưu tiên: Docker Compose > .env.local > .env > Môi trường hệ thống
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Vị Trí Comment
|
||||
|
||||
- ✅ Place bilingual comments together (EN first, then VI) / Đặt comments song ngữ cùng nhau (EN trước, sau đó VI)
|
||||
- ✅ Keep comments close to the code they describe / Giữ comments gần với code mà chúng mô tả
|
||||
- ✅ Use JSDoc format for functions and classes / Sử dụng format JSDoc cho functions và classes
|
||||
- ✅ Update comments when code changes / Cập nhật comments khi code thay đổi
|
||||
|
||||
### Nội Dung Comment
|
||||
|
||||
- ✅ **DO**: Explain WHY, not WHAT (code shows what) / Giải thích TẠI SAO, không phải CÁI GÌ
|
||||
- ✅ **DO**: Document complex logic and business rules / Tài liệu hóa logic phức tạp và quy tắc nghiệp vụ
|
||||
- ✅ **DO**: Include parameter descriptions and return types / Bao gồm mô tả tham số và kiểu trả về
|
||||
- ✅ **DO**: Document error conditions and exceptions / Tài liệu hóa điều kiện lỗi và ngoại lệ
|
||||
- ✅ **DON'T**: State the obvious / Không nói điều hiển nhiên
|
||||
- ✅ **DON'T**: Write redundant comments / Không viết comments thừa
|
||||
- ✅ **DON'T**: Comment out code (use version control instead) / Không comment code (sử dụng version control)
|
||||
|
||||
### Hướng Dẫn Ngôn Ngữ
|
||||
|
||||
**English / Tiếng Anh**:
|
||||
- Use clear, concise technical English / Sử dụng tiếng Anh kỹ thuật rõ ràng, ngắn gọn
|
||||
- Use proper technical terminology / Sử dụng thuật ngữ kỹ thuật đúng
|
||||
- Be specific and actionable / Cụ thể và có thể thực hiện
|
||||
|
||||
**Vietnamese / Tiếng Việt**:
|
||||
- Use proper Vietnamese technical terms / Sử dụng thuật ngữ kỹ thuật tiếng Việt đúng
|
||||
- Keep translations accurate and natural / Giữ bản dịch chính xác và tự nhiên
|
||||
- Use consistent terminology across codebase / Sử dụng thuật ngữ nhất quán trên codebase
|
||||
- Prefer technical Vietnamese terms over literal translations / Ưu tiên thuật ngữ kỹ thuật Việt hơn dịch theo nghĩa đen
|
||||
|
||||
### Ưu Tiên Tài Liệu
|
||||
|
||||
**High Priority** (Always document / Luôn tài liệu hóa):
|
||||
- Public APIs and exported functions / API công khai và functions được export
|
||||
- Complex algorithms and business logic / Thuật toán phức tạp và logic nghiệp vụ
|
||||
- Security-critical code / Code bảo mật quan trọng
|
||||
- Configuration and environment setup / Cấu hình và thiết lập môi trường
|
||||
- Error handling strategies / Chiến lược xử lý lỗi
|
||||
|
||||
**Medium Priority** (Document when helpful / Tài liệu khi hữu ích):
|
||||
- Helper functions with non-obvious behavior / Helper functions có hành vi không rõ ràng
|
||||
- Data transformations / Chuyển đổi dữ liệu
|
||||
- Integration points with external services / Điểm tích hợp với services bên ngoài
|
||||
|
||||
**Low Priority** (Optional / Tùy chọn):
|
||||
- Simple getters/setters / Getters/setters đơn giản
|
||||
- Self-explanatory code / Code tự giải thích
|
||||
- Standard CRUD operations / Các thao tác CRUD tiêu chuẩn
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Ví Dụ Comment Thực Tế
|
||||
|
||||
1. **Service Comments**: [`services/iam-service/src/modules/feature/feature.service.ts`](../../../services/iam-service/src/modules/feature/feature.service.ts)
|
||||
2. **Config Comments**: [`services/iam-service/src/config/database.config.ts`](../../../services/iam-service/src/config/database.config.ts)
|
||||
3. **Test Comments**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
||||
4. **Jest Config Comments**: [`services/iam-service/jest.config.ts`](../../../services/iam-service/jest.config.ts)
|
||||
|
||||
### Patterns Comment Trong Các Ngữ Cảnh Khác Nhau
|
||||
|
||||
- **Controllers**: Document API endpoints and request/response handling / Tài liệu hóa API endpoints và xử lý request/response
|
||||
- **Services**: Document business logic and data processing / Tài liệu hóa logic nghiệp vụ và xử lý dữ liệu
|
||||
- **Middleware**: Document authentication, authorization, and request processing / Tài liệu hóa xác thực, phân quyền và xử lý request
|
||||
- **Repositories**: Document database operations and query logic / Tài liệu hóa các thao tác database và logic query
|
||||
- **Config Files**: Document configuration options and environment variables / Tài liệu hóa tùy chọn cấu hình và biến môi trường
|
||||
- **Tests**: Document test scenarios and setup procedures / Tài liệu hóa test scenarios và quy trình setup
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Template Comment Function
|
||||
|
||||
```typescript
|
||||
/**
|
||||
[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
|
||||
*/
|
||||
```
|
||||
|
||||
### Template Inline Comment
|
||||
|
||||
```typescript
|
||||
[English explanation]
|
||||
// VI: [Giải thích tiếng Việt]
|
||||
```
|
||||
|
||||
### Template Block Phức Tạp
|
||||
|
||||
```typescript
|
||||
Step N: [What this block does]
|
||||
// VI: Bước N: [Block này làm gì]
|
||||
```
|
||||
|
||||
### Template Comment Class
|
||||
|
||||
```typescript
|
||||
/**
|
||||
[Class purpose in English]
|
||||
* VI: [Mục đích class bằng tiếng Việt]
|
||||
*/
|
||||
export class ClassName {
|
||||
/**
|
||||
[Property description]
|
||||
* VI: [Mô tả property]
|
||||
*/
|
||||
private property: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Template Comment Interface
|
||||
|
||||
```typescript
|
||||
/**
|
||||
[Interface purpose in English]
|
||||
* VI: [Mục đích interface bằng tiếng Việt]
|
||||
*/
|
||||
interface InterfaceName {
|
||||
Property description / VI: Mô tả property */
|
||||
property: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[Testing Patterns](./testing-patterns.md)**: Writing comments in test files / Viết comments trong file test
|
||||
- **[API Design](./api-design.md)**: Documenting API endpoints / Tài liệu hóa API endpoints
|
||||
- **[Documentation](./documentation.md)**: Writing technical documentation / Viết tài liệu kỹ thuật
|
||||
- **[Project Rules](./project-rules.md)**: Code organization and standards / Tổ chức code và tiêu chuẩn
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tiêu Chuẩn Tài Liệu
|
||||
|
||||
- [JSDoc Documentation](https://jsdoc.app/)
|
||||
- [TypeScript JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html)
|
||||
- [TSDoc Specification](https://tsdoc.org/)
|
||||
|
||||
### Tài Liệu Nội Bộ
|
||||
|
||||
- [Documentation Writing Guidelines](./documentation.md)
|
||||
- [API Design Standards](./api-design.md)
|
||||
- [Project Coding Standards](./project-rules.md)
|
||||
|
||||
### Công Cụ
|
||||
|
||||
- **JSDoc**: Generate API documentation from comments / Tạo tài liệu API từ comments
|
||||
- **ESLint**: Enforce comment style and completeness / Thực thi style và tính đầy đủ của comments
|
||||
- **Prettier**: Format comments consistently / Định dạng comments nhất quán
|
||||
- **VS Code**: IntelliSense uses JSDoc comments for better autocomplete / IntelliSense sử dụng JSDoc comments cho autocomplete tốt hơn
|
||||
@@ -1,119 +0,0 @@
|
||||
# Quản Lý Cấu Hình (Configuration Management)
|
||||
|
||||
Configuration management patterns for GoodGo microservices including feature flags, dynamic configuration reloading, environment-specific configurations, and secrets management.
|
||||
> Các patterns quản lý cấu hình cho GoodGo microservices bao gồm feature flags, dynamic configuration reloading, environment-specific configurations, và secrets management.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Configuration management patterns enable flexible, secure, and environment-aware configuration handling with feature flags, dynamic reloading, and secrets management.
|
||||
|
||||
Các patterns quản lý cấu hình cho phép xử lý cấu hình linh hoạt, an toàn và nhận biết environment với feature flags, dynamic reloading, và quản lý secrets.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when implementing feature flags, managing configurations, or handling secrets.
|
||||
|
||||
Sử dụng skill này khi implement feature flags, quản lý configurations, hoặc xử lý secrets.
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Configuration Loading Flow / Luồng Tải Cấu Hình
|
||||
|
||||
Quá trình tải cấu hình lấy configuration từ nhiều nguồn, validate và hỗ trợ dynamic reloading:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Application Startup]) --> LoadConfig[Load Configuration]
|
||||
LoadConfig --> FetchSource{Fetch from Source}
|
||||
|
||||
FetchSource --> |Environment Variables| EnvVars[Read Env Vars]
|
||||
FetchSource --> |Config Files| ConfigFiles[Read JSON/YAML]
|
||||
FetchSource --> |Database| Database[Query Config Table]
|
||||
FetchSource --> |External Service| ExternalService[Call Config API]
|
||||
|
||||
EnvVars --> Validate[Validate with Zod Schema]
|
||||
ConfigFiles --> Validate
|
||||
Database --> Validate
|
||||
ExternalService --> Validate
|
||||
|
||||
Validate --> |Valid| StoreConfig[Store in Memory Map]
|
||||
Validate --> |Invalid| LogError[Log Validation Error]
|
||||
LogError --> ThrowError[Throw Error]
|
||||
ThrowError --> End([Application Fails to Start])
|
||||
|
||||
StoreConfig --> CheckChange{Value Changed?}
|
||||
CheckChange --> |Yes| EmitEvent[Emit 'config-changed' Event]
|
||||
CheckChange --> |No| SkipEvent[Skip Event]
|
||||
|
||||
EmitEvent --> Ready[Configuration Ready]
|
||||
SkipEvent --> Ready
|
||||
Ready --> End
|
||||
|
||||
Ready --> AutoReload{Auto-Reload Enabled?}
|
||||
AutoReload --> |Yes| SetInterval[Set Interval Timer]
|
||||
AutoReload --> |No| End
|
||||
SetInterval --> Wait[Wait Interval]
|
||||
Wait --> LoadConfig
|
||||
```
|
||||
|
||||
### Feature Flag Evaluation Flow / Luồng Đánh Giá Feature Flag
|
||||
|
||||
Feature flags hỗ trợ nhiều chiến lược đánh giá bao gồm global flags, user-specific flags, và percentage-based rollouts:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Check Feature Flag]) --> GetFlag[Get Flag by Key]
|
||||
GetFlag --> FlagExists{Flag Exists?}
|
||||
|
||||
FlagExists --> |No| ReturnFalse[Return false]
|
||||
ReturnFalse --> End([End])
|
||||
|
||||
FlagExists --> |Yes| CheckEnabled{Flag Enabled?}
|
||||
CheckEnabled --> |No| ReturnFalse
|
||||
|
||||
CheckEnabled --> |Yes| HasUserId{User ID Provided?}
|
||||
HasUserId --> |No| ReturnTrue[Return true]
|
||||
ReturnTrue --> End
|
||||
|
||||
HasUserId --> |Yes| CheckUserSpecific{User-Specific Flag?}
|
||||
CheckUserSpecific --> |Yes| MatchUser{User ID Matches?}
|
||||
MatchUser --> |Yes| ReturnTrue
|
||||
MatchUser --> |No| CheckPercentage
|
||||
|
||||
CheckUserSpecific --> |No| CheckPercentage{Percentage Rollout?}
|
||||
CheckPercentage --> |No| ReturnTrue
|
||||
|
||||
CheckPercentage --> |Yes| HashUser[Hash User ID]
|
||||
HashUser --> CalcHash[Calculate Hash % 100]
|
||||
CalcHash --> CompareHash{Hash < Percentage?}
|
||||
|
||||
CompareHash --> |Yes| ReturnTrue
|
||||
CompareHash --> |No| ReturnFalse
|
||||
```
|
||||
|
||||
### Feature Flags / Feature Flags
|
||||
|
||||
```typescript
|
||||
// EN: Check if feature is enabled
|
||||
// VI: Kiểm tra xem feature có được bật không
|
||||
const enabled = await featureFlagService.isEnabled('new-feature', userId);
|
||||
```
|
||||
|
||||
### Dynamic Configuration / Configuration Động
|
||||
|
||||
```typescript
|
||||
// EN: Auto-reload configuration
|
||||
// VI: Tự động tải lại configuration
|
||||
await configService.load();
|
||||
configService.startAutoReload(60000);
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. Always validate configuration / Luôn validate configuration
|
||||
2. Never commit secrets / Không bao giờ commit secrets
|
||||
3. Use feature flags for rollouts / Sử dụng feature flags cho rollouts
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- Skill Source: `.cursor/skills/configuration-management/SKILL.md`
|
||||
@@ -1,378 +0,0 @@
|
||||
# Patterns Nhất Quán Dữ Liệu (Data Consistency Patterns)
|
||||
|
||||
Data consistency patterns for distributed microservices including Saga patterns, distributed transactions, eventual consistency, compensation, and idempotency. Use when handling distributed transactions, implementing eventual consistency, or managing data synchronization across services.
|
||||
> Các patterns nhất quán dữ liệu cho distributed microservices bao gồm Saga patterns, distributed transactions, eventual consistency, compensation, và idempotency. Sử dụng khi xử lý distributed transactions, implement eventual consistency, hoặc quản lý đồng bộ dữ liệu giữa các services.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Data consistency in distributed systems requires different approaches than traditional ACID transactions. This skill covers Saga patterns for distributed transactions, idempotency for retries, optimistic locking for conflicts, and eventual consistency strategies.
|
||||
|
||||
Nhất quán dữ liệu trong hệ thống phân tán yêu cầu các cách tiếp cận khác với ACID transactions truyền thống. Skill này bao gồm Saga patterns cho distributed transactions, idempotency cho retries, optimistic locking cho conflicts, và các chiến lược eventual consistency.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Implementing distributed transactions across multiple services
|
||||
- Handling eventual consistency in microservices
|
||||
- Implementing Saga patterns for distributed workflows
|
||||
- Designing compensation strategies
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Implement distributed transactions qua nhiều services
|
||||
- Xử lý eventual consistency trong microservices
|
||||
- Implement Saga patterns cho distributed workflows
|
||||
- Thiết kế chiến lược compensation
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### ACID vs BASE
|
||||
|
||||
**ACID (Truyền Thống)**: Atomicity, Consistency, Isolation, Durability
|
||||
|
||||
**BASE (Phân Tán)**: Basic Availability, Soft state, Eventual consistency
|
||||
|
||||
### Các Mô Hình Nhất Quán
|
||||
|
||||
- **Strong Consistency**: Tất cả nodes thấy cùng dữ liệu cùng lúc
|
||||
- **Eventual Consistency**: Hệ thống trở nên nhất quán theo thời gian
|
||||
- **Weak Consistency**: Không đảm bảo về thời điểm nhất quán
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Saga Orchestrator Pattern
|
||||
|
||||
```typescript
|
||||
// EN: Centralized orchestrator coordinates steps
|
||||
// VI: Orchestrator tập trung điều phối các bước
|
||||
const saga = new SagaOrchestrator();
|
||||
await saga.execute({
|
||||
sagaId: 'saga_123',
|
||||
steps: [
|
||||
{ name: 'create-order', execute: createOrder, compensate: cancelOrder },
|
||||
{ name: 'reserve-inventory', execute: reserveInventory, compensate: releaseInventory },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
#### Luồng Điều Phối Saga (Saga Orchestration Flow)
|
||||
|
||||
Biểu đồ sau minh họa cách Saga orchestrator thực thi các bước tuần tự và xử lý compensation khi thất bại:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Orchestrator
|
||||
participant Step1 as Step 1: Create Order
|
||||
participant Step2 as Step 2: Reserve Inventory
|
||||
participant Step3 as Step 3: Process Payment
|
||||
|
||||
Client->>Orchestrator: Execute Saga
|
||||
Orchestrator->>Step1: Execute Step 1
|
||||
Step1-->>Orchestrator: Success (Order Created)
|
||||
Orchestrator->>Step2: Execute Step 2
|
||||
Step2-->>Orchestrator: Success (Inventory Reserved)
|
||||
Orchestrator->>Step3: Execute Step 3
|
||||
Step3-->>Orchestrator: Failure (Payment Failed)
|
||||
Orchestrator->>Step2: Compensate Step 2
|
||||
Step2-->>Orchestrator: Compensation Complete
|
||||
Orchestrator->>Step1: Compensate Step 1
|
||||
Step1-->>Orchestrator: Compensation Complete
|
||||
Orchestrator-->>Client: Saga Failed (Compensated)
|
||||
```
|
||||
|
||||
#### Luồng Compensation (Compensation Flow)
|
||||
|
||||
Khi một bước thất bại, orchestrator sẽ compensate tất cả các bước đã hoàn thành trước đó theo thứ tự ngược lại:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Saga Execution Starts]) --> Step1[Execute Step 1]
|
||||
Step1 -->|Success| Step2[Execute Step 2]
|
||||
Step1 -->|Failure| Fail1[Saga Failed<br/>No Compensation Needed]
|
||||
|
||||
Step2 -->|Success| Step3[Execute Step 3]
|
||||
Step2 -->|Failure| Comp1[Compensate Step 1]
|
||||
|
||||
Step3 -->|Success| Complete([Saga Completed])
|
||||
Step3 -->|Failure| Comp2[Compensate Step 2]
|
||||
|
||||
Comp2 --> Comp1
|
||||
Comp1 --> Fail2[Saga Failed<br/>All Steps Compensated]
|
||||
|
||||
style Start fill:#e1f5ff
|
||||
style Complete fill:#d4edda
|
||||
style Fail1 fill:#f8d7da
|
||||
style Fail2 fill:#f8d7da
|
||||
style Comp1 fill:#fff3cd
|
||||
style Comp2 fill:#fff3cd
|
||||
```
|
||||
|
||||
#### Luồng Eventual Consistency (Eventual Consistency Flow)
|
||||
|
||||
Biểu đồ này cho thấy cách dữ liệu trở nên nhất quán giữa các services theo thời gian thông qua việc truyền events:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph ServiceA[Service A: Write Model]
|
||||
Write[Write Operation] --> EventStore[Event Store]
|
||||
EventStore --> Publish[Publish Event]
|
||||
end
|
||||
|
||||
subgraph EventBus[Event Bus]
|
||||
Publish --> Queue[Event Queue]
|
||||
end
|
||||
|
||||
subgraph ServiceB[Service B: Read Model]
|
||||
Queue --> Consume[Consume Event]
|
||||
Consume --> Update[Update Read Model]
|
||||
Update --> Consistent[Eventually Consistent]
|
||||
end
|
||||
|
||||
subgraph ServiceC[Service C: Read Model]
|
||||
Queue --> Consume2[Consume Event]
|
||||
Consume2 --> Update2[Update Read Model]
|
||||
Update2 --> Consistent2[Eventually Consistent]
|
||||
end
|
||||
|
||||
style Write fill:#e1f5ff
|
||||
style Consistent fill:#d4edda
|
||||
style Consistent2 fill:#d4edda
|
||||
style Queue fill:#fff3cd
|
||||
```
|
||||
|
||||
### Saga Choreography Pattern / Pattern Saga Choreography
|
||||
|
||||
Trong choreography, các services phản ứng với events mà không cần coordinator tập trung:
|
||||
|
||||
```typescript
|
||||
// EN: Services react to events
|
||||
// VI: Các services phản ứng với events
|
||||
eventConsumer.on('order.created', async (event) => {
|
||||
await inventoryService.reserve(event.data.items);
|
||||
await eventPublisher.publish('inventory.reserved', {...});
|
||||
});
|
||||
```
|
||||
|
||||
#### Luồng Saga Choreography (Saga Choreography Flow)
|
||||
|
||||
Biểu đồ sau cho thấy cách các services phối hợp thông qua events trong pattern choreography:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant OrderService
|
||||
participant EventBus
|
||||
participant InventoryService
|
||||
participant PaymentService
|
||||
participant NotificationService
|
||||
|
||||
OrderService->>EventBus: Publish order.created
|
||||
EventBus->>InventoryService: order.created event
|
||||
InventoryService->>InventoryService: Reserve Inventory
|
||||
InventoryService->>EventBus: Publish inventory.reserved
|
||||
EventBus->>PaymentService: inventory.reserved event
|
||||
PaymentService->>PaymentService: Process Payment
|
||||
PaymentService->>EventBus: Publish payment.processed
|
||||
EventBus->>NotificationService: payment.processed event
|
||||
NotificationService->>NotificationService: Send Confirmation
|
||||
|
||||
Note over InventoryService,PaymentService: If payment fails,<br/>compensation events are published
|
||||
PaymentService->>EventBus: Publish payment.failed
|
||||
EventBus->>InventoryService: payment.failed event
|
||||
InventoryService->>InventoryService: Release Inventory
|
||||
EventBus->>OrderService: payment.failed event
|
||||
OrderService->>OrderService: Cancel Order
|
||||
```
|
||||
|
||||
### Idempotency / Idempotency
|
||||
|
||||
Idempotency đảm bảo các operations có thể được retry an toàn mà không có side effects:
|
||||
|
||||
```typescript
|
||||
// EN: Execute with idempotency check
|
||||
// VI: Thực thi với idempotency check
|
||||
await idempotencyHandler.execute(
|
||||
idempotencyKey,
|
||||
async () => await userService.create(data)
|
||||
);
|
||||
```
|
||||
|
||||
#### Luồng Idempotency (Idempotency Flow)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Request[Client Request] --> Check{Idempotency Key<br/>Exists?}
|
||||
Check -->|Yes| Return[Return Cached Result]
|
||||
Check -->|No| Execute[Execute Operation]
|
||||
Execute --> Store[Store Result with Key]
|
||||
Store --> Return2[Return Result]
|
||||
|
||||
Return --> Client[Client Response]
|
||||
Return2 --> Client
|
||||
|
||||
style Check fill:#fff3cd
|
||||
style Return fill:#d4edda
|
||||
style Return2 fill:#d4edda
|
||||
```
|
||||
|
||||
### Optimistic Locking / Khóa Lạc Quan
|
||||
|
||||
Optimistic locking ngăn chặn lost updates bằng cách sử dụng version fields:
|
||||
|
||||
```typescript
|
||||
// EN: Update with version check
|
||||
// VI: Cập nhật với kiểm tra version
|
||||
await optimisticLockService.updateWithLock(
|
||||
repository,
|
||||
id,
|
||||
(current) => ({ ...current, name: newName })
|
||||
);
|
||||
```
|
||||
|
||||
#### Luồng Optimistic Locking (Optimistic Locking Flow)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client1
|
||||
participant Client2
|
||||
participant Service
|
||||
participant DB[(Database)]
|
||||
|
||||
Client1->>Service: Read Entity (version=1)
|
||||
Service->>DB: SELECT * WHERE id=123
|
||||
DB-->>Service: Entity (version=1)
|
||||
Service-->>Client1: Entity Data
|
||||
|
||||
Client2->>Service: Read Entity (version=1)
|
||||
Service->>DB: SELECT * WHERE id=123
|
||||
DB-->>Service: Entity (version=1)
|
||||
Service-->>Client2: Entity Data
|
||||
|
||||
Client1->>Service: Update (version=1)
|
||||
Service->>DB: UPDATE WHERE id=123 AND version=1
|
||||
DB-->>Service: Success (version=2)
|
||||
|
||||
Client2->>Service: Update (version=1)
|
||||
Service->>DB: UPDATE WHERE id=123 AND version=1
|
||||
DB-->>Service: Conflict (version=2 exists)
|
||||
Service-->>Client2: OptimisticLockError
|
||||
Note over Client2: Retry with new version
|
||||
```
|
||||
|
||||
### CQRS Pattern / Pattern CQRS
|
||||
|
||||
Command Query Responsibility Segregation tách biệt read và write operations để tối ưu hiệu suất và khả năng mở rộng.
|
||||
|
||||
#### Luồng Kiến Trúc CQRS (CQRS Architecture Flow)
|
||||
|
||||
Biểu đồ sau minh họa cách CQRS tách biệt write path và read path:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph WritePath[Write Path]
|
||||
Command[Command] --> WriteModel[Write Model<br/>Normalized]
|
||||
WriteModel --> Event[Domain Event]
|
||||
Event --> EventStore[(Event Store)]
|
||||
end
|
||||
|
||||
subgraph ReadPath[Read Path]
|
||||
Query[Query] --> ReadModel[Read Model<br/>Denormalized]
|
||||
ReadModel --> Response[Query Response]
|
||||
end
|
||||
|
||||
subgraph Sync[Eventual Consistency]
|
||||
EventStore --> EventHandler[Event Handler]
|
||||
EventHandler --> Projection[Projection]
|
||||
Projection --> ReadModel
|
||||
end
|
||||
|
||||
style WritePath fill:#e1f5ff
|
||||
style ReadPath fill:#d4edda
|
||||
style Sync fill:#fff3cd
|
||||
style EventStore fill:#f8d7da
|
||||
```
|
||||
|
||||
```typescript
|
||||
// EN: Write path: Command handler
|
||||
// VI: Write path: Command handler
|
||||
await commandHandler.handle({
|
||||
type: 'CREATE_ORDER',
|
||||
data: { userId, items }
|
||||
});
|
||||
|
||||
// EN: Read path: Optimized query
|
||||
// VI: Read path: Query tối ưu
|
||||
const orders = await readModel.findOrdersByUser(userId);
|
||||
```
|
||||
|
||||
### Outbox Pattern / Pattern Outbox
|
||||
|
||||
Outbox pattern đảm bảo event publishing đáng tin cậy bằng cách lưu events trong cùng database transaction với business data.
|
||||
|
||||
#### Luồng Outbox Pattern (Outbox Pattern Flow)
|
||||
|
||||
Biểu đồ này cho thấy cách Outbox pattern đảm bảo việc gửi events:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Service
|
||||
participant DB[(Database)]
|
||||
participant OutboxTable[(Outbox Table)]
|
||||
participant Processor[Outbox Processor]
|
||||
participant EventBus[Event Bus]
|
||||
|
||||
Client->>Service: Business Operation
|
||||
Service->>DB: Begin Transaction
|
||||
Service->>DB: Update Business Data
|
||||
Service->>OutboxTable: Insert Event (same transaction)
|
||||
Service->>DB: Commit Transaction
|
||||
|
||||
Note over Service,OutboxTable: Event stored atomically<br/>with business data
|
||||
|
||||
Processor->>OutboxTable: Poll for Unpublished Events
|
||||
OutboxTable-->>Processor: Return Events
|
||||
Processor->>EventBus: Publish Event
|
||||
EventBus-->>Processor: Publish Confirmed
|
||||
Processor->>OutboxTable: Mark as Published
|
||||
|
||||
Note over Processor,EventBus: Background processor<br/>ensures delivery
|
||||
```
|
||||
|
||||
```typescript
|
||||
// EN: Execute business operation and store event in same transaction
|
||||
// VI: Thực thi business operation và lưu event trong cùng transaction
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Business operation
|
||||
await tx.order.create({ data: orderData });
|
||||
|
||||
// Store event in outbox (same transaction)
|
||||
await tx.outboxEvent.create({
|
||||
data: {
|
||||
eventType: 'order.created',
|
||||
payload: orderData,
|
||||
status: 'pending'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// EN: Background processor publishes events from outbox
|
||||
// VI: Background processor publish events từ outbox
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. **Design Compensations**: Mỗi step cần compensation / Every step needs compensation
|
||||
2. **Idempotent Steps**: Làm cho steps idempotent cho retries / Make steps idempotent for retries
|
||||
3. **Conflict Resolution**: Định nghĩa chiến lược giải quyết / Define resolution strategies
|
||||
4. **Monitoring**: Theo dõi saga execution / Track saga execution
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Event-Driven Architecture](./event-driven-architecture.md) - Event patterns / Các patterns event
|
||||
- [Error Handling Patterns](./error-handling-patterns.md) - Error handling / Xử lý lỗi
|
||||
- [Database & Prisma](./database-prisma.md) - Database patterns / Các patterns database
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Saga Pattern](https://microservices.io/patterns/data/saga.html) - Saga pattern overview
|
||||
- Skill Source: `.cursor/skills/data-consistency-patterns/SKILL.md` - Source file đầy đủ
|
||||
@@ -1,683 +0,0 @@
|
||||
# Cơ Sở Dữ Liệu & Prisma
|
||||
|
||||
Prisma ORM and database patterns for GoodGo microservices. Use when working with databases, creating Prisma schemas, writing migrations, implementing repositories, or optimizing queries.
|
||||
> Pattern Prisma ORM và database cho các microservices của GoodGo. Sử dụng khi làm việc với databases, tạo Prisma schemas, viết migrations, triển khai repositories, hoặc tối ưu queries.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
This skill covers Prisma ORM patterns, database schema design, repository patterns, query optimization, and transaction handling used across GoodGo microservices. It ensures type-safe database operations, consistent data access patterns, and optimal performance.
|
||||
|
||||
Skill này bao gồm các pattern Prisma ORM, thiết kế database schema, repository patterns, tối ưu queries, và xử lý transactions được sử dụng trong các microservices của GoodGo. Nó đảm bảo các thao tác database type-safe, pattern truy cập dữ liệu nhất quán, và hiệu năng tối ưu.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Thiết lập Prisma cho service mới
|
||||
- Tạo hoặc sửa đổi database schemas
|
||||
- Viết database migrations
|
||||
- Triển khai repository patterns
|
||||
- Tối ưu database queries
|
||||
- Thiết lập kết nối database
|
||||
- Triển khai transactions
|
||||
- Làm việc với Neon PostgreSQL
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Pattern Repository
|
||||
|
||||
All database operations go through repository classes that extend `BaseRepository`, providing consistent CRUD operations and error handling.
|
||||
|
||||
Tất cả các thao tác database đi qua các repository classes kế thừa `BaseRepository`, cung cấp các CRUD operations nhất quán và xử lý lỗi.
|
||||
|
||||
### Schema Prisma
|
||||
|
||||
Database schema is defined in `prisma/schema.prisma` with models, relations, indexes, and constraints.
|
||||
|
||||
Database schema được định nghĩa trong `prisma/schema.prisma` với models, relations, indexes, và constraints.
|
||||
|
||||
### An Toàn Kiểu
|
||||
|
||||
Prisma generates TypeScript types from schema, ensuring type-safe database operations.
|
||||
|
||||
Prisma generate TypeScript types từ schema, đảm bảo các thao tác database type-safe.
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Pattern Schema Prisma
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```prisma
|
||||
// services/iam-service/prisma/schema.prisma
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
User model - Core user entity
|
||||
// VI: Model User - Entity người dùng cốt lõi
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
username String? @unique
|
||||
passwordHash String? // Nullable for social-only users
|
||||
isActive Boolean @default(true)
|
||||
emailVerified Boolean @default(false)
|
||||
mfaEnabled Boolean @default(false)
|
||||
mfaSecret String?
|
||||
lastLoginAt DateTime?
|
||||
loginCount Int @default(0)
|
||||
organizationId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
userRoles UserRole[]
|
||||
userPermissions UserPermission[]
|
||||
sessions Session[]
|
||||
organization Organization? @relation(fields: [organizationId], references: [id])
|
||||
profile UserProfile?
|
||||
|
||||
@@index([email])
|
||||
@@index([username])
|
||||
@@index([createdAt])
|
||||
@@index([organizationId])
|
||||
@@map("users")
|
||||
}
|
||||
```
|
||||
|
||||
### Sơ Đồ Quan Hệ Schema / Schema Relationships Diagram
|
||||
|
||||
Sơ đồ sau minh họa các quan hệ giữa các models User, Post, và Profile:
|
||||
|
||||
The following diagram illustrates the relationships between User, Post, and Profile models:
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
User ||--o{ Post : "has many"
|
||||
User ||--o| Profile : "has one"
|
||||
|
||||
User {
|
||||
string id PK
|
||||
string email UK
|
||||
string name
|
||||
string password
|
||||
enum role
|
||||
datetime createdAt
|
||||
datetime updatedAt
|
||||
}
|
||||
|
||||
Post {
|
||||
string id PK
|
||||
string title
|
||||
string content
|
||||
boolean published
|
||||
string authorId FK
|
||||
datetime createdAt
|
||||
datetime updatedAt
|
||||
}
|
||||
|
||||
Profile {
|
||||
string id PK
|
||||
string bio
|
||||
string avatar
|
||||
string userId FK,UK
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Kết Nối Database
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/config/database.config.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
/**
|
||||
Prisma client instance configured for the application
|
||||
* VI: Instance Prisma client được cấu hình cho ứng dụng
|
||||
*/
|
||||
export const prisma = new PrismaClient({
|
||||
Enable detailed logging in development, minimal in production
|
||||
// VI: Bật ghi log chi tiết trong development, tối thiểu trong production
|
||||
['error'],
|
||||
});
|
||||
|
||||
/**
|
||||
Establish database connection on application startup
|
||||
* VI: Thiết lập kết nối database khi khởi động ứng dụng
|
||||
*/
|
||||
export const connectDatabase = async (): Promise<void> => {
|
||||
try {
|
||||
await prisma.$connect();
|
||||
logger.info('Database connected successfully / Kết nối database thành công');
|
||||
} catch (error) {
|
||||
logger.error('Database connection failed / Kết nối database thất bại', { error });
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Close database connection on application shutdown
|
||||
* VI: Đóng kết nối database khi tắt ứng dụng
|
||||
*/
|
||||
export const disconnectDatabase = async (): Promise<void> => {
|
||||
await prisma.$disconnect();
|
||||
logger.info('Database disconnected / Đã ngắt kết nối database');
|
||||
};
|
||||
```
|
||||
|
||||
### Pattern Base Repository
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/common/repository.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { logger } from '@goodgo/logger';
|
||||
import { DatabaseError } from '../../errors/http-error';
|
||||
|
||||
/**
|
||||
Base repository class providing common database operations
|
||||
* VI: Base repository class cung cấp các thao tác database chung
|
||||
*/
|
||||
export abstract class BaseRepository<T, CreateInput, UpdateInput> {
|
||||
protected prisma: PrismaClient;
|
||||
protected modelName: string;
|
||||
|
||||
constructor(prisma: PrismaClient, modelName: string) {
|
||||
this.prisma = prisma;
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
/**
|
||||
Find entity by ID
|
||||
* VI: Tìm entity theo ID
|
||||
*/
|
||||
async findById(id: string): Promise<T | null> {
|
||||
try {
|
||||
logger.debug(`Finding ${this.modelName} by ID / Tìm ${this.modelName} theo ID`, { id });
|
||||
|
||||
const entity = await (this.prisma as any)[this.modelName].findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
logger.debug(`${this.modelName} ${entity ? 'found' : 'not found'} / ${this.modelName} ${entity ? 'đã tìm thấy' : 'không tìm thấy'}`, { id });
|
||||
return entity;
|
||||
} catch (error: any) {
|
||||
logger.error(`Failed to find ${this.modelName} by ID / Không thể tìm ${this.modelName} theo ID`, { error, id });
|
||||
throw new DatabaseError(`Failed to find ${this.modelName}`, { id, originalError: error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Find all entities with optional filtering
|
||||
* VI: Tìm tất cả entities với filtering tùy chọn
|
||||
*/
|
||||
async findAll(options?: {
|
||||
where?: any;
|
||||
orderBy?: any;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
include?: any;
|
||||
}): Promise<T[]> {
|
||||
try {
|
||||
logger.debug(`Finding all ${this.modelName} / Tìm tất cả ${this.modelName}`, options);
|
||||
|
||||
const entities = await (this.prisma as any)[this.modelName].findMany(options || {});
|
||||
|
||||
logger.debug(`Found ${entities.length} ${this.modelName} entities / Đã tìm thấy ${entities.length} ${this.modelName} entities`);
|
||||
return entities;
|
||||
} catch (error: any) {
|
||||
logger.error(`Failed to find all ${this.modelName} / Không thể tìm tất cả ${this.modelName}`, { error, options });
|
||||
throw new DatabaseError(`Failed to find ${this.modelName} entities`, { options, originalError: error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Create new entity
|
||||
* VI: Tạo entity mới
|
||||
*/
|
||||
async create(data: CreateInput): Promise<T> {
|
||||
try {
|
||||
logger.debug(`Creating new ${this.modelName} / Tạo ${this.modelName} mới`, { data });
|
||||
|
||||
const entity = await (this.prisma as any)[this.modelName].create({
|
||||
data,
|
||||
});
|
||||
|
||||
logger.debug(`${this.modelName} created successfully / ${this.modelName} đã được tạo thành công`, { id: (entity as any).id });
|
||||
return entity;
|
||||
} catch (error: any) {
|
||||
logger.error(`Failed to create ${this.modelName} / Không thể tạo ${this.modelName}`, { error, data });
|
||||
throw new DatabaseError(`Failed to create ${this.modelName}`, { data, originalError: error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Update entity by ID
|
||||
* VI: Cập nhật entity theo ID
|
||||
*/
|
||||
async update(id: string, data: UpdateInput): Promise<T> {
|
||||
try {
|
||||
logger.debug(`Updating ${this.modelName} / Cập nhật ${this.modelName}`, { id, data });
|
||||
|
||||
const entity = await (this.prisma as any)[this.modelName].update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
|
||||
logger.debug(`${this.modelName} updated successfully / ${this.modelName} đã được cập nhật thành công`, { id });
|
||||
return entity;
|
||||
} catch (error: any) {
|
||||
if (error.code === 'P2025') {
|
||||
logger.warn(`${this.modelName} not found for update / ${this.modelName} không tìm thấy để cập nhật`, { id });
|
||||
throw new DatabaseError(`${this.modelName} not found`, { id });
|
||||
}
|
||||
logger.error(`Failed to update ${this.modelName} / Không thể cập nhật ${this.modelName}`, { error, id, data });
|
||||
throw new DatabaseError(`Failed to update ${this.modelName}`, { id, data, originalError: error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Execute transaction with multiple operations
|
||||
* VI: Thực thi transaction với nhiều operations
|
||||
*/
|
||||
async transaction<R>(callback: (tx: any) => Promise<R>): Promise<R> {
|
||||
try {
|
||||
logger.debug(`Starting ${this.modelName} transaction / Bắt đầu transaction ${this.modelName}`);
|
||||
|
||||
const result = await this.prisma.$transaction(async (tx) => {
|
||||
return await callback(tx);
|
||||
});
|
||||
|
||||
logger.debug(`${this.modelName} transaction completed successfully / Transaction ${this.modelName} đã hoàn thành thành công`);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
logger.error(`${this.modelName} transaction failed / Transaction ${this.modelName} thất bại`, { error });
|
||||
throw new DatabaseError(`${this.modelName} transaction failed`, { originalError: error });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Triển Khai Repository Cụ Thể
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/repositories/user.repository.ts
|
||||
import { PrismaClient, User } from '@prisma/client';
|
||||
import { BaseRepository } from '../modules/common/repository';
|
||||
|
||||
/**
|
||||
User repository for database operations
|
||||
* VI: Repository người dùng cho các thao tác database
|
||||
*/
|
||||
export class UserRepository extends BaseRepository<User, any, any> {
|
||||
constructor(prisma: PrismaClient) {
|
||||
super(prisma, 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
Find user by email
|
||||
* VI: Tìm người dùng theo email
|
||||
*/
|
||||
async findByEmail(email: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: {
|
||||
userRoles: {
|
||||
include: { role: true },
|
||||
},
|
||||
userPermissions: {
|
||||
include: { permission: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Find user by username
|
||||
* VI: Tìm người dùng theo username
|
||||
*/
|
||||
async findByUsername(username: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Find user with roles and permissions
|
||||
* VI: Tìm người dùng với roles và permissions
|
||||
*/
|
||||
async findWithPermissions(userId: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
userRoles: {
|
||||
where: {
|
||||
OR: [
|
||||
{ expiresAt: null },
|
||||
{ expiresAt: { gte: new Date() } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
role: {
|
||||
include: {
|
||||
permissions: {
|
||||
include: { permission: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
userPermissions: {
|
||||
where: {
|
||||
OR: [
|
||||
{ expiresAt: null },
|
||||
{ expiresAt: { gte: new Date() } },
|
||||
],
|
||||
},
|
||||
include: { permission: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Upsert
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/repositories/user-profile.repository.ts
|
||||
export class UserProfileRepository extends BaseRepository<UserProfile, any, any> {
|
||||
/**
|
||||
Create or update profile for user
|
||||
* VI: Tạo hoặc cập nhật profile cho user
|
||||
*/
|
||||
async upsert(userId: string, data: any): Promise<UserProfile> {
|
||||
return this.prisma.userProfile.upsert({
|
||||
where: { userId },
|
||||
update: {
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
create: {
|
||||
userId,
|
||||
...data,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Tối Ưu Query
|
||||
|
||||
**Example from codebase / Ví dụ từ codebase**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/modules/feature/feature.repository.ts
|
||||
export class FeatureRepository extends BaseRepository<Feature, CreateFeatureInput, UpdateFeatureInput> {
|
||||
/**
|
||||
Find features by tags
|
||||
* VI: Tìm features theo tags
|
||||
*/
|
||||
async findByTags(tags: string[]): Promise<Feature[]> {
|
||||
try {
|
||||
logger.debug('Finding features by tags / Tìm features theo tags', { tags });
|
||||
|
||||
const features = await this.prisma.feature.findMany({
|
||||
where: {
|
||||
tags: {
|
||||
hasSome: tags,
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
logger.debug(`Found ${features.length} features by tags / Đã tìm thấy ${features.length} features theo tags`, { tags });
|
||||
return features;
|
||||
} catch (error) {
|
||||
logger.error('Failed to find features by tags / Không thể tìm features theo tags', { error, tags });
|
||||
throw this.handleDatabaseError(error, { tags });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Thiết Kế Schema
|
||||
|
||||
- ✅ Use appropriate field types (String, Int, Boolean, DateTime, Json) / Sử dụng field types phù hợp
|
||||
- ✅ Add indexes for frequently queried fields (`@@index([email])`) / Thêm indexes cho các fields thường query
|
||||
- ✅ Use relations instead of storing JSON when possible / Sử dụng relations thay vì lưu JSON khi có thể
|
||||
- ✅ Implement soft deletes with `deletedAt` field when needed / Triển khai soft deletes với field `deletedAt` khi cần
|
||||
- ✅ Use `@default()` for default values / Sử dụng `@default()` cho giá trị mặc định
|
||||
- ✅ Use `@updatedAt` for automatic timestamp updates / Sử dụng `@updatedAt` cho cập nhật timestamp tự động
|
||||
|
||||
### Pattern Repository
|
||||
|
||||
- ✅ Extend `BaseRepository` for common CRUD operations / Kế thừa `BaseRepository` cho CRUD operations chung
|
||||
- ✅ Add custom methods for domain-specific queries / Thêm methods tùy chỉnh cho queries đặc thù domain
|
||||
- ✅ Use `include` and `select` to control data fetching / Sử dụng `include` và `select` để kiểm soát việc lấy dữ liệu
|
||||
- ✅ Handle Prisma errors and convert to domain errors / Xử lý lỗi Prisma và chuyển đổi sang domain errors
|
||||
- ✅ Log database operations for debugging / Log các thao tác database để debug
|
||||
|
||||
### Luồng Thực Thi Query / Query Execution Flow
|
||||
|
||||
Sơ đồ tuần tự sau minh họa cách các Prisma queries chảy từ lớp ứng dụng đến database:
|
||||
|
||||
The following sequence diagram illustrates how Prisma queries flow from the application layer to the database:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant Repo as Repository
|
||||
participant Client as Prisma Client
|
||||
participant Pool as Connection Pool
|
||||
participant DB as PostgreSQL
|
||||
|
||||
App->>Repo: findById(id)
|
||||
Repo->>Client: prisma.user.findUnique()
|
||||
Client->>Client: Validate query
|
||||
Client->>Client: Generate SQL
|
||||
Client->>Pool: Request connection
|
||||
Pool->>DB: Execute SQL query
|
||||
DB-->>Pool: Return results
|
||||
Pool-->>Client: Return data
|
||||
Client->>Client: Transform to TypeScript types
|
||||
Client-->>Repo: Return typed result
|
||||
Repo-->>App: Return User | null
|
||||
|
||||
Note over App,DB: Prisma ensures type safety<br/>throughout the flow
|
||||
```
|
||||
|
||||
### Tối Ưu Query
|
||||
|
||||
- ✅ Use `select` to fetch only needed fields / Sử dụng `select` để chỉ lấy các fields cần thiết
|
||||
- ✅ Implement pagination with `skip` and `take` / Triển khai pagination với `skip` và `take`
|
||||
- ✅ Use indexes for frequently queried fields / Sử dụng indexes cho các fields thường query
|
||||
- ✅ Avoid N+1 queries by using `include` strategically / Tránh N+1 queries bằng cách sử dụng `include` chiến lược
|
||||
- ✅ Use `findUnique` instead of `findFirst` when possible / Sử dụng `findUnique` thay vì `findFirst` khi có thể
|
||||
- ✅ Use transactions for multiple related operations / Sử dụng transactions cho nhiều operations liên quan
|
||||
|
||||
### Xử Lý Lỗi
|
||||
|
||||
- ✅ Catch Prisma errors and convert to domain errors / Bắt lỗi Prisma và chuyển đổi sang domain errors
|
||||
- ✅ Handle unique constraint violations (P2002) / Xử lý vi phạm ràng buộc duy nhất
|
||||
- ✅ Handle record not found (P2025) / Xử lý không tìm thấy bản ghi
|
||||
- ✅ Log errors with context / Log lỗi kèm context
|
||||
- ✅ Provide meaningful error messages / Cung cấp thông báo lỗi có ý nghĩa
|
||||
|
||||
### Quy Trình Migration / Migration Workflow
|
||||
|
||||
Sơ đồ sau mô tả quy trình migration điển hình từ development đến production:
|
||||
|
||||
The following diagram shows the typical migration workflow from development to production:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Migration]) --> EditSchema[Edit schema.prisma]
|
||||
EditSchema --> CreateMigration[Run: prisma migrate dev]
|
||||
CreateMigration --> GenerateSQL[Prisma generates SQL]
|
||||
GenerateSQL --> ReviewSQL{Review SQL?}
|
||||
ReviewSQL -->|Yes| CheckSQL[Check migration SQL]
|
||||
ReviewSQL -->|No| ApplyDev[Apply to dev database]
|
||||
CheckSQL --> ApplyDev
|
||||
ApplyDev --> GenerateClient[Generate Prisma Client]
|
||||
GenerateClient --> TestDev[Test in development]
|
||||
TestDev --> TestPass{Tests pass?}
|
||||
TestPass -->|No| FixIssues[Fix issues]
|
||||
FixIssues --> EditSchema
|
||||
TestPass -->|Yes| CommitMigration[Commit migration files]
|
||||
CommitMigration --> DeployProd[Deploy to production]
|
||||
DeployProd --> RunDeploy[Run: prisma migrate deploy]
|
||||
RunDeploy --> End([Migration Complete])
|
||||
|
||||
style Start fill:#e1f5e1
|
||||
style End fill:#e1f5e1
|
||||
style TestPass fill:#fff4e1
|
||||
style ReviewSQL fill:#fff4e1
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
- ✅ Keep migrations small and focused / Giữ migrations nhỏ và tập trung
|
||||
- ✅ Test migrations before production / Test migrations trước khi production
|
||||
- ✅ Backup before major changes / Backup trước khi thay đổi lớn
|
||||
- ✅ Use descriptive migration names / Sử dụng tên migration mô tả
|
||||
- ✅ Review generated SQL before applying / Xem xét SQL được generate trước khi áp dụng
|
||||
|
||||
### Quản Lý Kết Nối
|
||||
|
||||
- ✅ Use connection pooling for production / Sử dụng connection pooling cho production
|
||||
- ✅ Close connections on application shutdown / Đóng kết nối khi tắt ứng dụng
|
||||
- ✅ Handle connection errors gracefully / Xử lý lỗi kết nối một cách lịch sự
|
||||
- ✅ Monitor connection pool usage / Giám sát việc sử dụng connection pool
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Ví Dụ Schema
|
||||
|
||||
- **IAM Service Schema**: [`services/iam-service/prisma/schema.prisma`](../../../services/iam-service/prisma/schema.prisma)
|
||||
- **Auth Service Schema**: [`services/iam-service/prisma/schema.prisma`](../../../services/iam-service/prisma/schema.prisma)
|
||||
|
||||
### Ví Dụ Repository
|
||||
|
||||
- **Base Repository**: [`services/iam-service/src/modules/common/repository.ts`](../../../services/iam-service/src/modules/common/repository.ts)
|
||||
- **User Repository**: [`services/iam-service/src/repositories/user.repository.ts`](../../../services/iam-service/src/repositories/user.repository.ts)
|
||||
- **User Profile Repository**: [`services/iam-service/src/repositories/user-profile.repository.ts`](../../../services/iam-service/src/repositories/user-profile.repository.ts)
|
||||
- **Feature Repository**: [`services/iam-service/src/modules/feature/feature.repository.ts`](../../../services/iam-service/src/modules/feature/feature.repository.ts)
|
||||
|
||||
### Ví Dụ Cấu Hình Database
|
||||
|
||||
- **Database Config**: [`services/iam-service/src/config/database.config.ts`](../../../services/iam-service/src/config/database.config.ts)
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Các Thao Tác Prisma Thường Dùng
|
||||
|
||||
```typescript
|
||||
// Tìm theo ID
|
||||
const user = await prisma.user.findUnique({ where: { id } });
|
||||
|
||||
// Tìm với relations
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: { profile: true, roles: true },
|
||||
});
|
||||
|
||||
// Tìm nhiều với filters
|
||||
const users = await prisma.user.findMany({
|
||||
where: { isActive: true },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
// Tạo
|
||||
const user = await prisma.user.create({
|
||||
data: { email, passwordHash },
|
||||
});
|
||||
|
||||
// Cập nhật
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
// Xóa
|
||||
await prisma.user.delete({ where: { id } });
|
||||
|
||||
// Tạo hoặc cập nhật
|
||||
const profile = await prisma.userProfile.upsert({
|
||||
where: { userId },
|
||||
update: { ...data },
|
||||
create: { userId, ...data },
|
||||
});
|
||||
|
||||
// Transaction
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.create({ data: userData });
|
||||
await tx.userProfile.create({ data: { userId: user.id, ...profileData } });
|
||||
});
|
||||
```
|
||||
|
||||
### Mã Lỗi Prisma
|
||||
|
||||
| Code | Description / Mô Tả |
|
||||
|------|-------------------|
|
||||
| P2002 | Unique constraint violation / Vi phạm ràng buộc duy nhất |
|
||||
| P2025 | Record not found / Không tìm thấy bản ghi |
|
||||
| P2003 | Foreign key constraint violation / Vi phạm ràng buộc khóa ngoại |
|
||||
| P2014 | Required relation violation / Vi phạm quan hệ bắt buộc |
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[API Design](./api-design.md)**: For designing API endpoints that use repositories / Cho thiết kế API endpoints sử dụng repositories
|
||||
- **[Testing Patterns](./testing-patterns.md)**: For testing database operations / Cho test các thao tác database
|
||||
- **[Security](./security.md)**: For securing database operations and preventing SQL injection / Cho bảo mật các thao tác database và ngăn chặn SQL injection
|
||||
- **[Observability](./observability-monitoring.md)**: For monitoring database performance / Cho giám sát hiệu năng database
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tài Liệu Bên Ngoài
|
||||
|
||||
- [Prisma Documentation](https://www.prisma.io/docs)
|
||||
- [Prisma Schema Reference](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference)
|
||||
- [Prisma Client API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference)
|
||||
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
||||
- [Neon Documentation](https://neon.tech/docs)
|
||||
|
||||
### Tài Liệu Nội Bộ
|
||||
|
||||
- [Neon Database Guide](../guides/neon-database.md)
|
||||
- [Development Guide](../guides/development.md)
|
||||
@@ -1,585 +0,0 @@
|
||||
# Triển Khai Kubernetes
|
||||
|
||||
Kubernetes deployment patterns for GoodGo microservices. Use when deploying to staging/production, creating K8s manifests, configuring HPA, setting up ingress, or troubleshooting K8s deployments.
|
||||
> Các pattern triển khai Kubernetes cho microservices GoodGo. Sử dụng khi triển khai lên staging/production, tạo K8s manifests, cấu hình HPA, thiết lập ingress, hoặc xử lý sự cố triển khai K8s.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
This skill covers Kubernetes deployment patterns and best practices for GoodGo microservices. It includes creating deployment manifests, configuring autoscaling, managing secrets and configmaps, setting up ingress, and implementing health checks.
|
||||
|
||||
Skill này bao gồm các pattern triển khai Kubernetes và best practices cho microservices GoodGo. Nó bao gồm tạo deployment manifests, cấu hình autoscaling, quản lý secrets và configmaps, thiết lập ingress, và triển khai health checks.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Triển khai services lên môi trường staging/production
|
||||
- Tạo hoặc cập nhật Kubernetes manifests
|
||||
- Cấu hình autoscaling (HPA/VPA)
|
||||
- Thiết lập ingress và load balancing
|
||||
- Quản lý secrets và configmaps
|
||||
- Xử lý sự cố triển khai
|
||||
- Triển khai health checks và probes
|
||||
- Thiết lập monitoring và logging
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Kiến Trúc Kubernetes
|
||||
|
||||
Sơ đồ sau minh họa các thành phần Kubernetes chính và mối quan hệ của chúng trong một triển khai service GoodGo điển hình:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph External["External Traffic"]
|
||||
Client[Client Request]
|
||||
end
|
||||
|
||||
subgraph IngressLayer["Ingress Layer"]
|
||||
Ingress[Ingress<br/>api.goodgo.com]
|
||||
end
|
||||
|
||||
subgraph ServiceLayer["Service Layer"]
|
||||
Service[Service<br/>ClusterIP]
|
||||
end
|
||||
|
||||
subgraph DeploymentLayer["Deployment Layer"]
|
||||
Deployment[Deployment<br/>auth-service]
|
||||
HPA[HorizontalPodAutoscaler<br/>HPA]
|
||||
end
|
||||
|
||||
subgraph PodLayer["Pod Layer"]
|
||||
Pod1[Pod 1<br/>Container]
|
||||
Pod2[Pod 2<br/>Container]
|
||||
Pod3[Pod 3<br/>Container]
|
||||
end
|
||||
|
||||
subgraph ConfigLayer["Configuration Layer"]
|
||||
ConfigMap[ConfigMap<br/>app-config]
|
||||
Secret[Secret<br/>database-secrets]
|
||||
end
|
||||
|
||||
Client -->|HTTPS| Ingress
|
||||
Ingress -->|Route /auth| Service
|
||||
Service -->|Load Balance| Pod1
|
||||
Service -->|Load Balance| Pod2
|
||||
Service -->|Load Balance| Pod3
|
||||
|
||||
Deployment -->|Manages| Pod1
|
||||
Deployment -->|Manages| Pod2
|
||||
Deployment -->|Manages| Pod3
|
||||
|
||||
HPA -->|Scales| Deployment
|
||||
|
||||
Pod1 -->|Reads| ConfigMap
|
||||
Pod2 -->|Reads| ConfigMap
|
||||
Pod3 -->|Reads| ConfigMap
|
||||
|
||||
Pod1 -->|Reads| Secret
|
||||
Pod2 -->|Reads| Secret
|
||||
Pod3 -->|Reads| Secret
|
||||
```
|
||||
|
||||
### Chiến Lược Triển Khai
|
||||
|
||||
- Rolling updates để triển khai không downtime
|
||||
- Resource limits và requests để đảm bảo ổn định
|
||||
- Health checks (liveness/readiness probes)
|
||||
- Horizontal Pod Autoscaler (HPA) để tự động scale
|
||||
- ConfigMaps cho cấu hình
|
||||
- Secrets cho dữ liệu nhạy cảm
|
||||
|
||||
### Vòng Đời Pod
|
||||
|
||||
Pods trải qua các trạng thái khác nhau trong vòng đời của chúng. Health checks (liveness và readiness probes) xác định khả năng sẵn sàng của pod:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Pending: Pod Created
|
||||
|
||||
Pending --> ContainerCreating: Scheduler Assigned
|
||||
ContainerCreating --> Running: Containers Started
|
||||
|
||||
Running --> Running: Liveness Check Pass
|
||||
Running --> Restarting: Liveness Check Fail (3x)
|
||||
Restarting --> Running: Container Restarted
|
||||
|
||||
Running --> Ready: Readiness Check Pass
|
||||
Ready --> Running: Readiness Check Fail (3x)
|
||||
|
||||
Ready --> Terminating: Pod Deleted
|
||||
Terminating --> [*]: Cleanup Complete
|
||||
|
||||
note right of Ready
|
||||
Pod receives traffic
|
||||
from Service
|
||||
end note
|
||||
|
||||
note right of Running
|
||||
Liveness probe checks
|
||||
if container is alive
|
||||
end note
|
||||
|
||||
note right of Restarting
|
||||
Container restarted
|
||||
after 3 failures
|
||||
end note
|
||||
```
|
||||
|
||||
### Luồng Service Discovery
|
||||
|
||||
Kubernetes cung cấp service discovery tích hợp thông qua DNS. Sơ đồ sau cho thấy cách requests chảy từ client bên ngoài đến pods:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Ingress
|
||||
participant Service
|
||||
participant Pod1
|
||||
participant Pod2
|
||||
participant Pod3
|
||||
|
||||
Client->>Ingress: HTTPS Request<br/>api.goodgo.com/auth/login
|
||||
Ingress->>Ingress: TLS Termination
|
||||
Ingress->>Ingress: Path Routing<br/>/auth → auth-service
|
||||
|
||||
Ingress->>Service: HTTP Request<br/>auth-service:80
|
||||
Service->>Service: DNS Resolution<br/>auth-service.goodgo.svc.cluster.local
|
||||
|
||||
Service->>Service: Endpoint Selection<br/>Load Balancing
|
||||
|
||||
alt Pod1 Selected
|
||||
Service->>Pod1: Forward Request
|
||||
Pod1->>Pod1: Process Request
|
||||
Pod1->>Service: Response
|
||||
else Pod2 Selected
|
||||
Service->>Pod2: Forward Request
|
||||
Pod2->>Pod2: Process Request
|
||||
Pod2->>Service: Response
|
||||
else Pod3 Selected
|
||||
Service->>Pod3: Forward Request
|
||||
Pod3->>Pod3: Process Request
|
||||
Pod3->>Service: Response
|
||||
end
|
||||
|
||||
Service->>Ingress: Response
|
||||
Ingress->>Client: HTTPS Response
|
||||
```
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Manifest Triển Khai Service
|
||||
|
||||
Standard deployment manifest structure for GoodGo services.
|
||||
|
||||
Cấu trúc deployment manifest chuẩn cho các services GoodGo.
|
||||
|
||||
**Ví dụ từ codebase**: [`deployments/production/kubernetes/iam-service.yaml`](../../../deployments/production/kubernetes/iam-service.yaml)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: iam-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: iam-service
|
||||
spec:
|
||||
containers:
|
||||
- name: iam-service
|
||||
image: goodgo/iam-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: iam-service-config
|
||||
- secretRef:
|
||||
name: iam-service-secrets
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 5001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 5001
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: production
|
||||
spec:
|
||||
selector:
|
||||
app: iam-service
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5001
|
||||
targetPort: 5001
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
### Tự Động Scale Pod
|
||||
|
||||
Configure HPA to automatically scale pods based on CPU and memory utilization.
|
||||
|
||||
Cấu hình HPA để tự động scale pods dựa trên CPU và memory utilization.
|
||||
|
||||
**Ví dụ từ codebase**: [`deployments/production/kubernetes/iam-service.yaml`](../../../deployments/production/kubernetes/iam-service.yaml)
|
||||
|
||||
```yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: iam-service-hpa
|
||||
namespace: production
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: iam-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
```
|
||||
|
||||
### ConfigMap và Secrets
|
||||
|
||||
Use ConfigMaps for non-sensitive configuration and Secrets for sensitive data.
|
||||
|
||||
Sử dụng ConfigMaps cho cấu hình không nhạy cảm và Secrets cho dữ liệu nhạy cảm.
|
||||
|
||||
**Ví dụ từ codebase**:
|
||||
- ConfigMap: [`deployments/production/kubernetes/iam-service-configmap.yaml`](../../../deployments/production/kubernetes/iam-service-configmap.yaml)
|
||||
- Secrets: [`deployments/production/kubernetes/secrets.yaml.example`](../../../deployments/production/kubernetes/secrets.yaml.example)
|
||||
|
||||
```yaml
|
||||
# ConfigMap
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: iam-service-config
|
||||
namespace: production
|
||||
data:
|
||||
"production"
|
||||
PORT: "5001"
|
||||
API_VERSION: "v1"
|
||||
CORS_ORIGIN: "https://goodgo.vn"
|
||||
LOG_LEVEL: "warn"
|
||||
SERVICE_NAME: "iam-service"
|
||||
"true"
|
||||
|
||||
---
|
||||
# Secret (example - use sealed-secrets in production)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: iam-service-secrets
|
||||
namespace: production
|
||||
type: Opaque
|
||||
stringData:
|
||||
database-url: "postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true"
|
||||
jwt-secret: "your-production-jwt-secret-min-32-chars"
|
||||
jwt-refresh-secret: "your-production-refresh-secret-min-32-chars"
|
||||
redis-password: ""
|
||||
```
|
||||
|
||||
### Cấu Hình Ingress
|
||||
|
||||
Configure ingress for external access with TLS and path-based routing.
|
||||
|
||||
Cấu hình ingress để truy cập từ bên ngoài với TLS và path-based routing.
|
||||
|
||||
**Ví dụ từ codebase**: [`deployments/production/kubernetes/ingress.yaml`](../../../deployments/production/kubernetes/ingress.yaml)
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
namespace: production
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/rule-type: PathPrefix
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- api.goodgo.vn
|
||||
secretName: api-tls-cert
|
||||
rules:
|
||||
- host: api.goodgo.vn
|
||||
http:
|
||||
paths:
|
||||
- path: /api/v1/auth
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Quản Lý Tài Nguyên
|
||||
|
||||
- Always set resource requests and limits
|
||||
- Monitor actual usage and adjust accordingly
|
||||
- Use HPA for automatic scaling
|
||||
- Set appropriate CPU and memory based on service requirements
|
||||
|
||||
**VI**:
|
||||
- Luôn đặt resource requests và limits
|
||||
- Theo dõi sử dụng thực tế và điều chỉnh phù hợp
|
||||
- Sử dụng HPA để tự động scale
|
||||
- Đặt CPU và memory phù hợp dựa trên yêu cầu service
|
||||
|
||||
### Cấu Hình
|
||||
|
||||
- Use ConfigMaps for non-sensitive config
|
||||
- Use Secrets for sensitive data
|
||||
- Never hardcode configuration in images
|
||||
- Use `envFrom` to load entire ConfigMap/Secret
|
||||
|
||||
**VI**:
|
||||
- Sử dụng ConfigMaps cho cấu hình không nhạy cảm
|
||||
- Sử dụng Secrets cho dữ liệu nhạy cảm
|
||||
- Không bao giờ hardcode cấu hình trong images
|
||||
- Sử dụng `envFrom` để load toàn bộ ConfigMap/Secret
|
||||
|
||||
### Kiểm Tra Sức Khỏe
|
||||
|
||||
- Implement both liveness and readiness probes
|
||||
- Set appropriate timeouts and thresholds
|
||||
- Include dependency checks in readiness probe
|
||||
- Use HTTP probes for web services
|
||||
|
||||
**VI**:
|
||||
- Triển khai cả liveness và readiness probes
|
||||
- Đặt timeouts và thresholds phù hợp
|
||||
- Bao gồm kiểm tra dependencies trong readiness probe
|
||||
- Sử dụng HTTP probes cho web services
|
||||
|
||||
**Ví dụ từ codebase**: [`services/iam-service/src/modules/health/health.controller.ts`](../../../services/iam-service/src/modules/health/health.controller.ts)
|
||||
|
||||
```typescript
|
||||
// Liveness probe - is the service alive?
|
||||
health = async (_req: Request, res: Response): Promise<void> => {
|
||||
res.json({
|
||||
success: true,
|
||||
data: { status: 'ok', timestamp: new Date().toISOString() },
|
||||
});
|
||||
};
|
||||
|
||||
// Readiness probe - is the service ready to accept traffic?
|
||||
ready = async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// Check database connection
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
res.json({
|
||||
success: true,
|
||||
data: { status: 'ready' },
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(503).json({
|
||||
success: false,
|
||||
error: { code: 'HEALTH_001', message: 'Service not ready' },
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Triển Khai
|
||||
|
||||
- Use rolling updates for zero-downtime
|
||||
- Set maxSurge and maxUnavailable appropriately
|
||||
- Test deployments in staging first
|
||||
- Use image tags instead of `latest` in production
|
||||
|
||||
**VI**:
|
||||
- Sử dụng rolling updates để không downtime
|
||||
- Đặt maxSurge và maxUnavailable phù hợp
|
||||
- Test triển khai trong staging trước
|
||||
- Sử dụng image tags thay vì `latest` trong production
|
||||
|
||||
### Bảo Mật
|
||||
|
||||
- Run containers as non-root user
|
||||
- Use network policies to restrict traffic
|
||||
- Regularly update base images
|
||||
- Use sealed-secrets or external secret manager
|
||||
- Never commit secrets to Git
|
||||
|
||||
**VI**:
|
||||
- Chạy containers với user không phải root
|
||||
- Sử dụng network policies để hạn chế traffic
|
||||
- Cập nhật base images thường xuyên
|
||||
- Sử dụng sealed-secrets hoặc external secret manager
|
||||
- Không bao giờ commit secrets vào Git
|
||||
|
||||
### Giám Sát
|
||||
|
||||
- Expose metrics endpoint (`/metrics`)
|
||||
- Set up alerts for critical issues
|
||||
- Monitor resource usage and performance
|
||||
- Use ServiceMonitor for Prometheus integration
|
||||
|
||||
**VI**:
|
||||
- Expose metrics endpoint (`/metrics`)
|
||||
- Thiết lập alerts cho các vấn đề quan trọng
|
||||
- Theo dõi sử dụng tài nguyên và hiệu suất
|
||||
- Sử dụng ServiceMonitor cho tích hợp Prometheus
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Triển Khai Production
|
||||
|
||||
- **IAM Service**: [`deployments/production/kubernetes/iam-service.yaml`](../../../deployments/production/kubernetes/iam-service.yaml)
|
||||
- **ConfigMap**: [`deployments/production/kubernetes/iam-service-configmap.yaml`](../../../deployments/production/kubernetes/iam-service-configmap.yaml)
|
||||
- **Ingress**: [`deployments/production/kubernetes/ingress.yaml`](../../../deployments/production/kubernetes/ingress.yaml)
|
||||
|
||||
### Triển Khai Staging
|
||||
|
||||
- **IAM Service**: [`deployments/staging/kubernetes/iam-service.yaml`](../../../deployments/staging/kubernetes/iam-service.yaml)
|
||||
- **ConfigMap**: [`deployments/staging/kubernetes/iam-service-configmap.yaml`](../../../deployments/staging/kubernetes/iam-service-configmap.yaml)
|
||||
|
||||
### Triển Khai Health Check
|
||||
|
||||
- **Health Controller**: [`services/iam-service/src/modules/health/health.controller.ts`](../../../services/iam-service/src/modules/health/health.controller.ts)
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Lệnh Thường Dùng
|
||||
|
||||
```bash
|
||||
# Deploy to production
|
||||
kubectl apply -f deployments/production/kubernetes/ -n production
|
||||
|
||||
# Check deployment status
|
||||
kubectl get deployments -n production
|
||||
kubectl get pods -n production
|
||||
kubectl get svc -n production
|
||||
|
||||
# View logs
|
||||
kubectl logs -f deployment/iam-service -n production
|
||||
kubectl logs -f pod-name -n production --tail=100
|
||||
|
||||
# Scale manually
|
||||
kubectl scale deployment iam-service --replicas=5 -n production
|
||||
|
||||
# Update image
|
||||
kubectl set image deployment/iam-service iam-service=goodgo/iam-service:v1.2.3 -n production
|
||||
|
||||
# Rollback
|
||||
kubectl rollout undo deployment/iam-service -n production
|
||||
|
||||
# Port forward for debugging
|
||||
kubectl port-forward service/iam-service 5001:5001 -n production
|
||||
|
||||
# Execute command in pod
|
||||
kubectl exec -it pod-name -n production -- /bin/sh
|
||||
|
||||
# View HPA status
|
||||
kubectl get hpa -n production
|
||||
kubectl describe hpa iam-service-hpa -n production
|
||||
|
||||
# View resource usage
|
||||
kubectl top nodes
|
||||
kubectl top pods -n production
|
||||
```
|
||||
|
||||
### Xử Lý Sự Cố
|
||||
|
||||
**Pod Not Starting / Pod Không Khởi Động**:
|
||||
```bash
|
||||
# Check pod status
|
||||
kubectl describe pod pod-name -n production
|
||||
|
||||
# Check events
|
||||
kubectl get events -n production --sort-by='.lastTimestamp'
|
||||
|
||||
# Check logs
|
||||
kubectl logs pod-name -n production --previous
|
||||
```
|
||||
|
||||
**ImagePullBackOff**:
|
||||
```bash
|
||||
# Check image name and tag
|
||||
kubectl describe pod pod-name -n production | grep -i image
|
||||
|
||||
# Check image pull secrets
|
||||
kubectl get secrets -n production
|
||||
```
|
||||
|
||||
**CrashLoopBackOff**:
|
||||
```bash
|
||||
# Check logs of crashed container
|
||||
kubectl logs pod-name -n production --previous
|
||||
|
||||
# Check resource limits
|
||||
kubectl describe pod pod-name -n production | grep -A 5 Limits
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Để giám sát các services đã triển khai
|
||||
- [Security](./security.md) - Để bảo mật các triển khai Kubernetes
|
||||
- [Project Rules](./project-rules.md) - Cho cấu trúc và tiêu chuẩn service
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tài Liệu Chính Thức
|
||||
|
||||
- [Kubernetes Documentation](https://kubernetes.io/docs/)
|
||||
- [Kubernetes API Reference](https://kubernetes.io/docs/reference/kubernetes-api/)
|
||||
- [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
|
||||
|
||||
### Tài Nguyên GoodGo
|
||||
|
||||
- [Deployment Guide](../guides/deployment.md)
|
||||
- [Local Deployment Guide](../guides/local-deployment.md)
|
||||
- [Troubleshooting Guide](../guides/troubleshooting.md)
|
||||
@@ -1,602 +0,0 @@
|
||||
# Tài Liệu
|
||||
|
||||
Guidelines for writing technical documentation in the GoodGo project. Use when creating or updating README files, guides, architecture docs, or API documentation. Ensures bilingual (EN/VI) consistency and proper structure.
|
||||
> Hướng dẫn viết tài liệu kỹ thuật trong dự án GoodGo. Sử dụng khi tạo hoặc cập nhật file README, hướng dẫn, tài liệu kiến trúc hoặc tài liệu API. Đảm bảo tính nhất quán song ngữ (EN/VI) và cấu trúc phù hợp.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
The Documentation skill provides comprehensive guidelines for writing, structuring, and maintaining technical documentation in the GoodGo Microservices Platform. It covers documentation structure, bilingual formatting, templates, writing style, and best practices for maintaining documentation quality.
|
||||
|
||||
Skill Documentation cung cấp hướng dẫn toàn diện để viết, cấu trúc và duy trì tài liệu kỹ thuật trong GoodGo Microservices Platform. Nó bao gồm cấu trúc tài liệu, định dạng song ngữ, template, phong cách viết và thực hành tốt nhất để duy trì chất lượng tài liệu.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Creating new documentation files
|
||||
- Updating existing documentation
|
||||
- Writing README files for services or packages
|
||||
- Creating guides or tutorials
|
||||
- Documenting API endpoints
|
||||
- Writing architecture documentation
|
||||
- Creating deployment documentation
|
||||
- Writing runbooks or operational guides
|
||||
- Ensuring bilingual consistency
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Tạo file tài liệu mới
|
||||
- Cập nhật tài liệu hiện có
|
||||
- Viết file README cho services hoặc packages
|
||||
- Tạo hướng dẫn hoặc tutorial
|
||||
- Tài liệu hóa API endpoints
|
||||
- Viết tài liệu kiến trúc
|
||||
- Tạo tài liệu triển khai
|
||||
- Viết runbook hoặc hướng dẫn vận hành
|
||||
- Đảm bảo tính nhất quán song ngữ
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Cấu Trúc Tài Liệu
|
||||
|
||||
The project follows a structured documentation hierarchy:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── en/ # English documentation
|
||||
│ ├── guides/ # How-to guides
|
||||
│ ├── architecture/ # System design docs
|
||||
│ ├── api/ # API documentation
|
||||
│ ├── onboarding/ # New developer guides
|
||||
│ ├── runbooks/ # Operational guides
|
||||
│ └── skills/ # Skill documentation
|
||||
├── vi/ # Vietnamese documentation (mirror structure)
|
||||
└── README.md # Documentation index
|
||||
```
|
||||
|
||||
Dự án tuân theo hệ thống phân cấp tài liệu có cấu trúc:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── en/ # Tài liệu tiếng Anh
|
||||
│ ├── guides/ # Hướng dẫn cách làm
|
||||
│ ├── architecture/ # Tài liệu thiết kế hệ thống
|
||||
│ ├── api/ # Tài liệu API
|
||||
│ ├── onboarding/ # Hướng dẫn cho developer mới
|
||||
│ ├── runbooks/ # Hướng dẫn vận hành
|
||||
│ └── skills/ # Tài liệu skills
|
||||
├── vi/ # Tài liệu tiếng Việt (cấu trúc tương tự)
|
||||
└── README.md # Mục lục tài liệu
|
||||
```
|
||||
|
||||
#### Sơ Đồ Cấu Trúc Tài Liệu
|
||||
|
||||
Sơ đồ sau minh họa nơi đặt các loại tài liệu khác nhau:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start[Documentation Need] --> TypeDecision{Documentation Type?}
|
||||
|
||||
TypeDecision -->|Project-Level| ProjectDocs[Project-Level Documentation]
|
||||
TypeDecision -->|Service/Package| ServiceDocs[Service/Package Documentation]
|
||||
TypeDecision -->|Deployment| DeployDocs[Deployment Documentation]
|
||||
TypeDecision -->|Infrastructure| InfraDocs[Infrastructure Documentation]
|
||||
|
||||
ProjectDocs --> ProjectLoc["docs/en/<br/>docs/vi/"]
|
||||
ProjectLoc --> ProjectSub{Content Type?}
|
||||
ProjectSub -->|Guides| GuidesLoc["guides/<br/>(getting-started.md,<br/>deployment.md)"]
|
||||
ProjectSub -->|Architecture| ArchLoc["architecture/<br/>(system-design.md)"]
|
||||
ProjectSub -->|API Specs| APILoc["api/openapi/<br/>(*.yaml)"]
|
||||
ProjectSub -->|Runbooks| RunbookLoc["runbooks/<br/>(incident-response.md)"]
|
||||
|
||||
ServiceDocs --> ServiceLoc["services/[name]/README.md<br/>packages/[name]/README.md"]
|
||||
ServiceLoc --> ServiceFormat[Format: Side-by-side bilingual]
|
||||
|
||||
DeployDocs --> DeployLoc["deployments/[env]/README.md"]
|
||||
DeployLoc --> DeployFormat[Format: Technical, operations-focused]
|
||||
|
||||
InfraDocs --> InfraLoc["infra/[component]/README.md"]
|
||||
InfraLoc --> InfraFormat[Format: Side-by-side bilingual]
|
||||
|
||||
style ProjectDocs fill:#e1f5ff
|
||||
style ServiceDocs fill:#fff4e1
|
||||
style DeployDocs fill:#ffe1f5
|
||||
style InfraDocs fill:#e1ffe1
|
||||
```
|
||||
|
||||
### Quy Tắc Tài Liệu Song Ngữ
|
||||
|
||||
The project maintains bilingual documentation (English and Vietnamese). Three formats are used:
|
||||
|
||||
1. **Side-by-side**: Short content with EN and VI on the same line
|
||||
2. **Separate files**: Long content in separate EN and VI files
|
||||
3. **Sections**: Mixed content with separate EN and VI sections
|
||||
|
||||
Dự án duy trì tài liệu song ngữ (Tiếng Anh và Tiếng Việt). Ba định dạng được sử dụng:
|
||||
|
||||
1. **Side-by-side**: Nội dung ngắn với EN và VI trên cùng một dòng
|
||||
2. **Separate files**: Nội dung dài trong các file EN và VI riêng biệt
|
||||
3. **Sections**: Nội dung hỗn hợp với các phần EN và VI riêng biệt
|
||||
|
||||
#### Quy Trình Quyết Định Định Dạng Song Ngữ
|
||||
|
||||
Sử dụng cây quyết định sau để chọn định dạng song ngữ phù hợp:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Creating Documentation] --> CheckLength{Content Length?}
|
||||
|
||||
CheckLength -->|Short<br/>< 200 lines| CheckLocation{Document Location?}
|
||||
CheckLength -->|Long<br/>> 200 lines| SeparateFiles[Use Separate Files Format]
|
||||
|
||||
CheckLocation -->|README files<br/>Service/Package docs<br/>Infrastructure docs| SideBySide[Use Side-by-side Format]
|
||||
CheckLocation -->|docs/guides/<br/>Short configuration docs| SideBySide
|
||||
|
||||
CheckLength -->|Medium| CheckType{Content Type?}
|
||||
CheckType -->|API Documentation<br/>Technical Specifications| Sections[Use Sections Format]
|
||||
CheckType -->|Mixed Content| Sections
|
||||
|
||||
SeparateFiles --> SeparateAction["Create docs/en/[path]/file.md<br/>Create docs/vi/[path]/file.md<br/>(Mirror structure)"]
|
||||
SideBySide --> SideBySideAction["Single file with<br/>EN / VI inline<br/>Example: 'Title / Tiêu Đề'"]
|
||||
Sections --> SectionsAction["Single file with<br/>--- separator<br/>EN section then VI section"]
|
||||
|
||||
SeparateAction --> Done[Documentation Complete]
|
||||
SideBySideAction --> Done
|
||||
SectionsAction --> Done
|
||||
|
||||
style SideBySide fill:#e1f5ff
|
||||
style SeparateFiles fill:#fff4e1
|
||||
style Sections fill:#ffe1f5
|
||||
style Done fill:#e1ffe1
|
||||
```
|
||||
|
||||
#### Khi Nào Sử Dụng Từng Định Dạng
|
||||
|
||||
- **Side-by-side**: README files, hướng dẫn ngắn, tài liệu cấu hình
|
||||
- **Separate files**: Hướng dẫn dài (>200 dòng), tài liệu kiến trúc, runbooks
|
||||
- **Sections**: Tài liệu API, đặc tả kỹ thuật
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Template README Service
|
||||
|
||||
Example structure from `services/iam-service/README.md`:
|
||||
|
||||
```markdown
|
||||
# Tên Dịch Vụ
|
||||
|
||||
Brief description in English
|
||||
> Mô tả ngắn gọn bằng tiếng Việt
|
||||
|
||||
## Tính Năng
|
||||
|
||||
- Feature 1 / Tính năng 1
|
||||
- Feature 2 / Tính năng 2
|
||||
|
||||
## Yêu Cầu
|
||||
|
||||
- Node.js 20+
|
||||
- PostgreSQL (Neon)
|
||||
- Redis
|
||||
|
||||
## Bắt Đầu Nhanh
|
||||
|
||||
```bash
|
||||
# Cài đặt dependencies
|
||||
pnpm install
|
||||
|
||||
# Thiết lập môi trường
|
||||
cp .env.example .env
|
||||
|
||||
# Khởi động service
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Cấu Hình
|
||||
|
||||
| Variable | Description / Mô Tả | Default | Required |
|
||||
|----------|---------------------|---------|----------|
|
||||
| PORT | Server port / Cổng server | 5000 | No |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
See [API Documentation](../api/openapi/iam-service.yaml)
|
||||
```
|
||||
|
||||
Ví dụ cấu trúc từ `services/iam-service/README.md`:
|
||||
|
||||
```markdown
|
||||
# Tên Dịch Vụ
|
||||
|
||||
Brief description in English
|
||||
> Mô tả ngắn gọn bằng tiếng Việt
|
||||
|
||||
## Tính Năng
|
||||
|
||||
- Feature 1 / Tính năng 1
|
||||
- Feature 2 / Tính năng 2
|
||||
|
||||
## Yêu Cầu
|
||||
|
||||
- Node.js 20+
|
||||
- PostgreSQL (Neon)
|
||||
- Redis
|
||||
|
||||
## Bắt Đầu Nhanh
|
||||
|
||||
```bash
|
||||
# Cài đặt dependencies
|
||||
pnpm install
|
||||
|
||||
# Thiết lập môi trường
|
||||
cp .env.example .env
|
||||
|
||||
# Khởi động service
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Cấu Hình
|
||||
|
||||
| Variable | Description / Mô Tả | Default | Required |
|
||||
|----------|---------------------|---------|----------|
|
||||
| PORT | Server port / Cổng server | 5000 | No |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
See [API Documentation](../api/openapi/iam-service.yaml)
|
||||
```
|
||||
|
||||
### Template Hướng Dẫn
|
||||
|
||||
Example from `docs/en/guides/getting-started.md`:
|
||||
|
||||
```markdown
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- PNPM >= 8.0.0
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
- Neon account (https://neon.tech) - for database
|
||||
|
||||
## Initial Setup
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Setup Neon Database**
|
||||
```bash
|
||||
# Run setup script
|
||||
./scripts/db/setup-neon.sh
|
||||
```
|
||||
|
||||
See [Neon Setup Guide](../../../infra/databases/neon/README.md) for details.
|
||||
|
||||
3. **Initialize the project**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read [Development Guide](../guides/development.md)
|
||||
- Check [API Documentation](../api/openapi/)
|
||||
- Review [Architecture Overview](../architecture/system-design.md)
|
||||
```
|
||||
|
||||
Ví dụ từ `docs/vi/guides/getting-started.md`:
|
||||
|
||||
```markdown
|
||||
# Bắt Đầu
|
||||
|
||||
## Yêu Cầu
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- PNPM >= 8.0.0
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
- Tài khoản Neon (https://neon.tech) - cho database
|
||||
|
||||
## Thiết Lập Ban Đầu
|
||||
|
||||
1. **Clone repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Thiết lập Neon Database**
|
||||
```bash
|
||||
# Chạy script setup
|
||||
./scripts/db/setup-neon.sh
|
||||
```
|
||||
|
||||
Xem [Hướng Dẫn Thiết Lập Neon](../../../infra/databases/neon/README.md) để biết chi tiết.
|
||||
|
||||
3. **Khởi tạo dự án**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
## Bước Tiếp Theo
|
||||
|
||||
- Đọc [Hướng Dẫn Phát Triển](../guides/development.md)
|
||||
- Xem [Tài Liệu API](../api/openapi/)
|
||||
- Xem lại [Tổng Quan Kiến Trúc](../architecture/system-design.md)
|
||||
```
|
||||
|
||||
### Template Tài Liệu Kiến Trúc
|
||||
|
||||
Example from `docs/en/architecture/system-design.md`:
|
||||
|
||||
```markdown
|
||||
# System Design
|
||||
|
||||
## Overview
|
||||
|
||||
GoodGo Microservices Platform is built using a microservices architecture pattern with the following principles:
|
||||
|
||||
- **Service Independence**: Each service has its own database and can be deployed independently
|
||||
- **API Gateway**: Traefik handles routing, load balancing, and cross-cutting concerns
|
||||
- **Shared Libraries**: Common functionality is extracted into shared packages
|
||||
- **Infrastructure as Code**: All infrastructure configurations are versioned
|
||||
- **Observability**: Full monitoring, logging, and tracing capabilities
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Web App │ │ Mobile App │
|
||||
│ (Next.js) │ │ (React Native)
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
└──────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Traefik │
|
||||
│ (API Gateway) │
|
||||
└────────┬─────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Frontend Layer
|
||||
- **Web App**: Next.js application with App Router
|
||||
- **Mobile App**: React Native application
|
||||
|
||||
### API Gateway
|
||||
- **Traefik**: Reverse proxy, load balancer, SSL termination
|
||||
```
|
||||
|
||||
Ví dụ từ `docs/vi/architecture/system-design.md`:
|
||||
|
||||
```markdown
|
||||
# Thiết Kế Hệ Thống
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
GoodGo Microservices Platform được xây dựng bằng mẫu kiến trúc microservices với các nguyên tắc sau:
|
||||
|
||||
- **Độc Lập Dịch Vụ**: Mỗi service có database riêng và có thể triển khai độc lập
|
||||
- **API Gateway**: Traefik xử lý routing, load balancing và các mối quan tâm chéo
|
||||
- **Thư Viện Dùng Chung**: Chức năng chung được trích xuất thành packages dùng chung
|
||||
- **Infrastructure as Code**: Tất cả cấu hình infrastructure được version
|
||||
- **Observability**: Khả năng giám sát, logging và tracing đầy đủ
|
||||
|
||||
## Sơ Đồ Kiến Trúc
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Web App │ │ Mobile App │
|
||||
│ (Next.js) │ │ (React Native)
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
└──────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Traefik │
|
||||
│ (API Gateway) │
|
||||
└────────┬─────────┘
|
||||
```
|
||||
|
||||
## Thành Phần
|
||||
|
||||
### Lớp Frontend
|
||||
- **Web App**: Ứng dụng Next.js với App Router
|
||||
- **Mobile App**: Ứng dụng React Native
|
||||
|
||||
### API Gateway
|
||||
- **Traefik**: Reverse proxy, load balancer, SSL termination
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Phong Cách Viết
|
||||
|
||||
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
|
||||
|
||||
**VI**:
|
||||
1. **Rõ Ràng và Súc Tích**: Sử dụng ngôn ngữ đơn giản, tránh thuật ngữ
|
||||
2. **Hướng Hành Động**: Bắt đầu bằng động từ (Cài đặt, Cấu hình, Triển khai)
|
||||
3. **Có Cấu Trúc**: Sử dụng tiêu đề, danh sách và bảng
|
||||
4. **Ví Dụ**: Cung cấp ví dụ code và lệnh
|
||||
5. **Trực Quan**: Sử dụng sơ đồ khi hữu ích
|
||||
|
||||
### Ví Dụ Code
|
||||
|
||||
Always provide context and explanation:
|
||||
|
||||
```markdown
|
||||
# Good: With context and explanation
|
||||
Install dependencies using pnpm:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
# Bad: No context
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
```
|
||||
|
||||
Luôn cung cấp ngữ cảnh và giải thích:
|
||||
|
||||
```markdown
|
||||
# Tốt: Có ngữ cảnh và giải thích
|
||||
Cài đặt dependencies sử dụng pnpm:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
# Không tốt: Không có ngữ cảnh
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
```
|
||||
|
||||
### Liên Kết
|
||||
|
||||
- Use relative links for internal docs
|
||||
- Use descriptive link text (not "click here")
|
||||
- Example: `See the [Deployment Guide](../guides/deployment.md) for details.`
|
||||
|
||||
**VI**:
|
||||
- Sử dụng liên kết tương đối cho tài liệu nội bộ
|
||||
- Sử dụng văn bản liên kết mô tả (không phải "click here")
|
||||
- Ví dụ: `Xem [Hướng Dẫn Triển Khai](../guides/deployment.md) để biết chi tiết.`
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Ví Dụ Tài Liệu Tốt
|
||||
|
||||
- `docs/en/guides/getting-started.md` - Clear step-by-step guide
|
||||
- `services/iam-service/README.md` - Comprehensive service README
|
||||
- `docs/en/architecture/system-design.md` - Architecture documentation
|
||||
- `docs/README.md` - Documentation index
|
||||
|
||||
**VI**:
|
||||
- `docs/vi/guides/getting-started.md` - Hướng dẫn từng bước rõ ràng
|
||||
- `services/iam-service/README.md` - README service toàn diện
|
||||
- `docs/vi/architecture/system-design.md` - Tài liệu kiến trúc
|
||||
- `docs/README.md` - Mục lục tài liệu
|
||||
|
||||
### Tham Chiếu Vị Trí Tài Liệu
|
||||
|
||||
|
||||
| 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 |
|
||||
|
||||
**VI**:
|
||||
|
||||
| Loại Nội Dung | Vị Trí | Định Dạng |
|
||||
|--------------|--------|----------|
|
||||
| Bắt Đầu | `docs/vi/guides/getting-started.md` | File riêng biệt |
|
||||
| Thiết Lập Service | `services/[name]/README.md` | Side-by-side |
|
||||
| Triển Khai | `docs/vi/guides/deployment.md` | File riêng biệt |
|
||||
| Kiến Trúc | `docs/vi/architecture/` | File riêng biệt |
|
||||
| Spec API | `docs/vi/api/openapi/` | OpenAPI YAML |
|
||||
| Runbooks | `docs/vi/runbooks/` | File riêng biệt |
|
||||
| Infrastructure | `infra/[component]/README.md` | Side-by-side |
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Đặt Tên File
|
||||
|
||||
- Use kebab-case: `getting-started.md`
|
||||
- Be descriptive: `local-development.md` not `dev.md`
|
||||
- Match EN and VI filenames
|
||||
|
||||
**VI**:
|
||||
- Sử dụng kebab-case: `getting-started.md`
|
||||
- Mô tả rõ ràng: `local-development.md` không phải `dev.md`
|
||||
- Khớp tên file EN và VI
|
||||
|
||||
### Cấp Độ Tiêu Đề
|
||||
|
||||
```markdown
|
||||
# H1: Document Title (only one per file)
|
||||
## H2: Major Sections
|
||||
### H3: Subsections
|
||||
#### H4: Details (use sparingly)
|
||||
```
|
||||
|
||||
### Mẫu Song Ngữ
|
||||
|
||||
```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
|
||||
Install dependencies
|
||||
# VI: Cài đặt dependencies
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Danh Sách Kiểm Tra Tài Liệu
|
||||
|
||||
Before publishing documentation:
|
||||
- [ ] Determine correct location (docs/ vs service README)
|
||||
- [ ] Choose bilingual format (side-by-side vs separate)
|
||||
- [ ] Review existing docs for consistency
|
||||
- [ ] Use clear, concise language
|
||||
- [ ] Include code examples
|
||||
- [ ] Add diagrams where helpful
|
||||
- [ ] Provide troubleshooting section
|
||||
- [ ] Link to related documentation
|
||||
- [ ] Test all commands and code examples
|
||||
- [ ] Check all links work
|
||||
- [ ] Ensure bilingual consistency
|
||||
- [ ] Update documentation index
|
||||
|
||||
Trước khi xuất bản tài liệu:
|
||||
- [ ] Xác định vị trí đúng (docs/ vs service README)
|
||||
- [ ] Chọn định dạng song ngữ (side-by-side vs separate)
|
||||
- [ ] Xem lại tài liệu hiện có để đảm bảo tính nhất quán
|
||||
- [ ] Sử dụng ngôn ngữ rõ ràng, súc tích
|
||||
- [ ] Bao gồm ví dụ code
|
||||
- [ ] Thêm sơ đồ khi hữu ích
|
||||
- [ ] Cung cấp phần xử lý sự cố
|
||||
- [ ] Liên kết đến tài liệu liên quan
|
||||
- [ ] Kiểm tra tất cả lệnh và ví dụ code
|
||||
- [ ] Kiểm tra tất cả liên kết hoạt động
|
||||
- [ ] Đảm bảo tính nhất quán song ngữ
|
||||
- [ ] Cập nhật mục lục tài liệu
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[Project Rules](./project-rules.md)** - Project structure and standards
|
||||
- **[API Design](./api-design.md)** - API documentation patterns
|
||||
- **[Comment Code](./comment-code.md)** - Code commenting guidelines
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Markdown Guide](https://www.markdownguide.org/)
|
||||
- [Mermaid Diagrams](https://mermaid.js.org/)
|
||||
- [Documentation Best Practices](https://www.writethedocs.org/guide/)
|
||||
|
||||
**VI**:
|
||||
- [Hướng Dẫn Markdown](https://www.markdownguide.org/)
|
||||
- [Sơ Đồ Mermaid](https://mermaid.js.org/)
|
||||
- [Thực Hành Tốt Nhất Tài Liệu](https://www.writethedocs.org/guide/)
|
||||
@@ -1,488 +0,0 @@
|
||||
# Các Pattern Xử Lý Lỗi
|
||||
|
||||
Comprehensive error handling patterns and conventions for GoodGo microservices including error classes, error codes, error middleware, and standardized error responses.
|
||||
> Các patterns và conventions toàn diện về error handling cho GoodGo microservices bao gồm error classes, error codes, error middleware, và standardized error responses.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Error handling is a critical aspect of building robust microservices. This guide provides comprehensive patterns and best practices for implementing consistent, maintainable error handling across all services. It covers error types, error code systems, custom error classes, error middleware, and standardized error response formats.
|
||||
|
||||
Error handling là khía cạnh quan trọng của việc xây dựng microservices vững chắc. Hướng dẫn này cung cấp các patterns và best practices toàn diện để implement error handling nhất quán và dễ bảo trì trên tất cả các services. Nó bao gồm các loại errors, hệ thống error codes, custom error classes, error middleware, và các định dạng error response đã được chuẩn hóa.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use these error handling patterns 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
|
||||
|
||||
Sử dụng các error handling patterns này khi:
|
||||
- Implement error handling trong services, controllers, hoặc repositories
|
||||
- Tạo custom error classes cho các error scenarios cụ thể
|
||||
- Chuẩn hóa error responses trên các APIs
|
||||
- Xử lý exceptions từ external services hoặc database operations
|
||||
- Implement error middleware và global error handlers
|
||||
- Debug error scenarios và cải thiện error messages
|
||||
- Phân biệt giữa operational errors và programming errors
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Các Loại Lỗi
|
||||
|
||||
#### Lỗi Vận Hành
|
||||
|
||||
Expected errors that occur during normal operation. These are errors that the application is designed to handle.
|
||||
|
||||
Các lỗi dự kiến xảy ra trong quá trình vận hành bình thường. Đây là các lỗi mà ứng dụng được thiết kế để xử lý.
|
||||
|
||||
**Characteristics / Đặc Điểm**:
|
||||
- **Examples / Ví dụ**: Validation errors, authentication failures, resource not found, rate limiting
|
||||
- **Handling / Xử Lý**: Should be handled gracefully with appropriate HTTP status codes
|
||||
- **Logging / Ghi Log**: Logged as warnings (logger.warn)
|
||||
- **Exposure / Tiết Lộ**: Can expose error details to clients (with caution)
|
||||
- **Recovery / Phục Hồi**: Application can recover and continue operation
|
||||
|
||||
**Example / Ví dụ**: [`services/iam-service/src/errors/http-error.ts`](../../../services/iam-service/src/errors/http-error.ts)
|
||||
|
||||
#### Lỗi Lập Trình
|
||||
|
||||
Unexpected errors due to bugs in code. These indicate problems that need to be fixed.
|
||||
|
||||
Các lỗi không mong đợi do bugs trong code. Đây là các vấn đề cần được sửa.
|
||||
|
||||
**Characteristics / Đặc Điểm**:
|
||||
- **Examples / Ví dụ**: Null pointer exceptions, type errors, logic bugs, undefined behavior
|
||||
- **Handling / Xử Lý**: Should be logged with full details for debugging
|
||||
- **Logging / Ghi Log**: Logged as errors (logger.error)
|
||||
- **Exposure / Tiết Lộ**: Should return generic error messages to clients (hide implementation details)
|
||||
- **Recovery / Phục Hồi**: May require application restart or code fixes
|
||||
|
||||
### Luồng Lan Truyền Lỗi / Error Propagation Flow
|
||||
|
||||
Sơ đồ sau minh họa cách lỗi lan truyền qua các lớp của ứng dụng:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Request] --> B[Controller]
|
||||
B --> C[Service Layer]
|
||||
C --> D[Repository Layer]
|
||||
D --> E{Error Occurs?}
|
||||
E -->|Yes| F[Throw HttpError]
|
||||
E -->|No| G[Return Data]
|
||||
F --> H[Error Middleware]
|
||||
H --> I{Error Type?}
|
||||
I -->|HttpError| J[Extract Status/Code]
|
||||
I -->|Prisma Error| K[Map to HttpError]
|
||||
I -->|Zod Error| L[Map to ValidationError]
|
||||
I -->|Unknown| M[Map to InternalServerError]
|
||||
J --> N[Log Error]
|
||||
K --> N
|
||||
L --> N
|
||||
M --> N
|
||||
N --> O{Is Operational?}
|
||||
O -->|Yes| P[Log as Warning]
|
||||
O -->|No| Q[Log as Error]
|
||||
P --> R[Format Response]
|
||||
Q --> R
|
||||
R --> S{Is Production?}
|
||||
S -->|Yes & 5xx| T[Generic Message]
|
||||
S -->|No or < 5xx| U[Detailed Message]
|
||||
T --> V[Send Response]
|
||||
U --> V
|
||||
G --> V
|
||||
```
|
||||
|
||||
### Cấu Trúc Phân Cấp Lỗi / Error Hierarchy Structure
|
||||
|
||||
Phân cấp lớp lỗi cho thấy mối quan hệ giữa các loại lỗi khác nhau:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Error {
|
||||
<<built-in>>
|
||||
+message: string
|
||||
+stack: string
|
||||
}
|
||||
class HttpError {
|
||||
+statusCode: number
|
||||
+errorCode: string
|
||||
+isOperational: boolean
|
||||
+details?: any
|
||||
+toApiResponse()
|
||||
}
|
||||
class NotFoundError {
|
||||
+statusCode: 404
|
||||
}
|
||||
class BadRequestError {
|
||||
+statusCode: 400
|
||||
}
|
||||
class ValidationError {
|
||||
+statusCode: 422
|
||||
}
|
||||
class UnauthorizedError {
|
||||
+statusCode: 401
|
||||
}
|
||||
class ForbiddenError {
|
||||
+statusCode: 403
|
||||
}
|
||||
class ConflictError {
|
||||
+statusCode: 409
|
||||
}
|
||||
class RateLimitError {
|
||||
+statusCode: 429
|
||||
}
|
||||
class InternalServerError {
|
||||
+statusCode: 500
|
||||
}
|
||||
class ServiceUnavailableError {
|
||||
+statusCode: 503
|
||||
}
|
||||
class DatabaseError {
|
||||
+statusCode: 500
|
||||
}
|
||||
class ExternalServiceError {
|
||||
+statusCode: 502
|
||||
}
|
||||
|
||||
Error <|-- HttpError
|
||||
HttpError <|-- NotFoundError
|
||||
HttpError <|-- BadRequestError
|
||||
HttpError <|-- ValidationError
|
||||
HttpError <|-- UnauthorizedError
|
||||
HttpError <|-- ForbiddenError
|
||||
HttpError <|-- ConflictError
|
||||
HttpError <|-- RateLimitError
|
||||
HttpError <|-- InternalServerError
|
||||
HttpError <|-- ServiceUnavailableError
|
||||
HttpError <|-- DatabaseError
|
||||
HttpError <|-- ExternalServiceError
|
||||
```
|
||||
|
||||
### Cây Quyết Định Xử Lý Lỗi / Error Handling Decision Tree
|
||||
|
||||
Sử dụng cây quyết định này để xác định lớp lỗi nào cần sử dụng:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Error Occurs] --> B{Error Type?}
|
||||
B -->|Resource Not Found| C[NotFoundError<br/>404]
|
||||
B -->|Invalid Input| D{Validation?}
|
||||
B -->|Authentication| E{Type?}
|
||||
B -->|Resource Conflict| F[ConflictError<br/>409]
|
||||
B -->|Rate Limit| G[RateLimitError<br/>429]
|
||||
B -->|Database| H[DatabaseError<br/>500]
|
||||
B -->|External Service| I[ExternalServiceError<br/>502]
|
||||
B -->|Service Unavailable| J[ServiceUnavailableError<br/>503]
|
||||
B -->|Unknown/Programming| K[InternalServerError<br/>500]
|
||||
D -->|Schema Validation| L[ValidationError<br/>422]
|
||||
D -->|Bad Request Format| M[BadRequestError<br/>400]
|
||||
E -->|No Token/Invalid| N[UnauthorizedError<br/>401]
|
||||
E -->|No Permission| O[ForbiddenError<br/>403]
|
||||
C --> P[Set isOperational: true]
|
||||
L --> P
|
||||
M --> P
|
||||
N --> P
|
||||
O --> P
|
||||
F --> P
|
||||
G --> P
|
||||
H --> Q{Is Operational?}
|
||||
I --> Q
|
||||
J --> Q
|
||||
K --> R[Set isOperational: false]
|
||||
Q -->|Yes| P
|
||||
Q -->|No| R
|
||||
P --> S[Include Error Code]
|
||||
R --> S
|
||||
S --> T[Add Context Details]
|
||||
T --> U[Throw Error]
|
||||
```
|
||||
|
||||
### Hệ Thống Error Codes
|
||||
|
||||
The platform uses a centralized error code system that provides unique identifiers for each error type, enables error tracking and analytics, and supports consistent error handling.
|
||||
|
||||
Nền tảng sử dụng hệ thống error codes tập trung cung cấp unique identifiers cho mỗi loại lỗi, cho phép error tracking và analytics, và hỗ trợ error handling nhất quán.
|
||||
|
||||
**Error Code Format / Định Dạng Error Code**: `{CATEGORY}_{NUMBER}`
|
||||
- `AUTH_001` - Authentication errors / Lỗi xác thực
|
||||
- `VALIDATION_001` - Validation errors / Lỗi validation
|
||||
- `RESOURCE_001` - Resource errors / Lỗi tài nguyên
|
||||
- `DB_001` - Database errors / Lỗi database
|
||||
- `SYS_001` - System errors / Lỗi hệ thống
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/errors/error-codes.ts`](../../../services/iam-service/src/errors/error-codes.ts)
|
||||
|
||||
## Patterns
|
||||
|
||||
### Class Lỗi Cơ Sở
|
||||
|
||||
**HttpError** là base class cho tất cả custom errors:
|
||||
|
||||
```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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Các Class Lỗi Chuẩn
|
||||
|
||||
**Resource Errors / Lỗi Tài Nguyên**:
|
||||
- `NotFoundError` - 404: Resource not found / Tài nguyên không tìm thấy
|
||||
- `ConflictError` - 409: Resource conflict / Xung đột tài nguyên (ví dụ: trùng lặp)
|
||||
- `BadRequestError` - 400: Invalid request / Yêu cầu không hợp lệ
|
||||
|
||||
**Validation Errors / Lỗi Validation**:
|
||||
- `ValidationError` - 422: Input validation failed / Validation đầu vào thất bại
|
||||
|
||||
**Authentication/Authorization / Xác Thực/Phân Quyền**:
|
||||
- `UnauthorizedError` - 401: Authentication required / Yêu cầu xác thực
|
||||
- `ForbiddenError` - 403: Access denied / Truy cập bị từ chối
|
||||
|
||||
**System Errors / Lỗi Hệ Thống**:
|
||||
- `InternalServerError` - 500: Internal server error / Lỗi máy chủ nội bộ (programming error)
|
||||
- `ServiceUnavailableError` - 503: Service temporarily unavailable / Dịch vụ tạm thời không khả dụng
|
||||
- `DatabaseError` - 500: Database operation failed / Thao tác database thất bại
|
||||
- `ExternalServiceError` - 502: External service error / Lỗi dịch vụ bên ngoài
|
||||
|
||||
**Rate Limiting / Giới Hạn Tốc Độ**:
|
||||
- `RateLimitError` - 429: Too many requests / Quá nhiều yêu cầu
|
||||
|
||||
### Sử Dụng Errors Trong Services
|
||||
|
||||
```typescript
|
||||
import { NotFoundError, ConflictError } from '../errors/http-error';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/feature/feature.service.ts`](../../../services/iam-service/src/modules/feature/feature.service.ts)
|
||||
|
||||
### Pattern Error Middleware
|
||||
|
||||
Global error handler middleware xử lý tất cả 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';
|
||||
}
|
||||
|
||||
// Log and respond...
|
||||
};
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/middlewares/error.middleware.ts`](../../../services/iam-service/src/middlewares/error.middleware.ts)
|
||||
|
||||
### Wrapper Lỗi Async
|
||||
|
||||
Wrap async route handlers để 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 });
|
||||
}));
|
||||
```
|
||||
|
||||
### Định Dạng Error Response
|
||||
|
||||
Định dạng error response đã được chuẩn hóa:
|
||||
|
||||
```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"
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Use Specific Error Classes / Sử Dụng Error Classes Cụ Thể**: Sử dụng error class cụ thể nhất cho scenario
|
||||
2. **Include Context / Bao Gồm Context**: Cung cấp error messages hữu ích với context liên quan (resource IDs, field names)
|
||||
3. **Mark Operational Errors / Đánh Dấu Operational Errors**: Set `isOperational: true` cho các lỗi dự kiến có thể xử lý gracefully
|
||||
4. **Don't Expose Internal Details / Không Tiết Lộ Chi Tiết Nội Bộ**: Ẩn implementation details trong production (stack traces, internal paths)
|
||||
5. **Log Appropriately / Log Phù Hợp**: Sử dụng `logger.error()` cho programming errors, `logger.warn()` cho operational errors
|
||||
6. **Handle Database Errors / Xử Lý Database Errors**: Map Prisma errors (P2002, P2025, etc.) sang HTTP errors phù hợp
|
||||
7. **Use Error Codes / Sử Dụng Error Codes**: Luôn sử dụng `ErrorCode` enum để nhất quán trên các services
|
||||
8. **Validate Early / Validate Sớm**: Validate input sớm trong request pipeline để catch errors trước khi xử lý
|
||||
9. **Error Middleware Order / Thứ Tự Error Middleware**: Đặt error middleware cuối cùng, sau tất cả routes
|
||||
10. **Async Error Handling / Xử Lý Lỗi Async**: Luôn sử dụng `asyncHandler` cho async route handlers
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **Not Using Error Classes / Không Sử Dụng Error Classes**: Sử dụng generic `Error` thay vì specific error classes như `NotFoundError`
|
||||
2. **Exposing Stack Traces / Tiết Lộ Stack Traces**: Bao gồm stack traces trong production API responses
|
||||
3. **Ignoring Errors / Bỏ Qua Lỗi**: Không xử lý errors trong async operations, để chúng propagate unhandled
|
||||
4. **Generic Error Messages / Error Messages Chung Chung**: Sử dụng error messages mơ hồ không có context (ví dụ: "Error occurred")
|
||||
5. **Not Logging / Không Log**: Quên log errors để debug và monitor
|
||||
6. **Wrong HTTP Status Codes / HTTP Status Codes Sai**: Sử dụng status codes sai cho error types (ví dụ: 500 cho validation errors)
|
||||
7. **Not Using Error Middleware / Không Sử Dụng Error Middleware**: Xử lý errors manually trong mỗi controller thay vì sử dụng middleware
|
||||
8. **Not Marking Operational Errors / Không Đánh Dấu Operational Errors**: Không set `isOperational` flag, khiến tất cả errors bị coi như programming errors
|
||||
9. **Inconsistent Error Codes / Error Codes Không Nhất Quán**: Không sử dụng centralized `ErrorCode` enum, dẫn đến error codes không nhất quán
|
||||
10. **Error Details in Production / Chi Tiết Lỗi Trong Production**: Tiết lộ error details (như database queries) trong production responses
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Lỗi Không Được Catch Bởi Middleware
|
||||
|
||||
**Problem / Vấn Đề**: Error không được catch bởi error middleware và gây unhandled promise rejection
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Đảm bảo error middleware được thêm cuối cùng trong middleware chain (sau tất cả routes)
|
||||
- Sử dụng `asyncHandler` wrapper cho async route handlers
|
||||
- Verify error đang được throw (không chỉ log)
|
||||
|
||||
**Example / Ví Dụ**:
|
||||
```typescript
|
||||
// ❌ Bad: Error not caught
|
||||
app.get('/users/:id', async (req, res) => {
|
||||
const user = await userService.getUserById(req.params.id);
|
||||
res.json(user);
|
||||
});
|
||||
|
||||
// ✅ Good: Error caught by middleware
|
||||
app.get('/users/:id', asyncHandler(async (req, res) => {
|
||||
const user = await userService.getUserById(req.params.id);
|
||||
res.json(user);
|
||||
}));
|
||||
```
|
||||
|
||||
### Error Messages Chung Chung Trong Production
|
||||
|
||||
**Problem / Vấn Đề**: Generic "Internal server error" được hiển thị ngay cả cho operational errors nên hiển thị messages cụ thể
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Kiểm tra `isOperational` flag được set đúng trên error instances
|
||||
- Verify error middleware xử lý tất cả error types đúng cách
|
||||
- Đảm bảo production environment check hoạt động đúng
|
||||
|
||||
### Error Code Không Tìm Thấy
|
||||
|
||||
**Problem / Vấn Đề**: Error code không tìm thấy trong `ErrorCode` enum, gây vấn đề mapping
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Thêm error code vào `ErrorCode` enum theo naming convention (`{CATEGORY}_{NUMBER}`)
|
||||
- Update `ERROR_CODE_TO_STATUS` mapping để bao gồm error code mới
|
||||
- Đảm bảo error code được sử dụng nhất quán trên các services
|
||||
|
||||
### Stack Traces Bị Tiết Lộ
|
||||
|
||||
**Problem / Vấn Đề**: Stack traces hiển thị trong API responses, tiết lộ internal implementation details
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Đảm bảo production environment checks được đặt trong error middleware
|
||||
- Verify `isProduction` flag được set đúng
|
||||
- Filter stack traces và internal error details trong production responses
|
||||
|
||||
### Prisma Errors Không Được Xử Lý
|
||||
|
||||
**Problem / Vấn Đề**: Prisma errors (như unique constraint violations) không được map sang HTTP errors phù hợp
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Thêm Prisma error code handling trong error middleware
|
||||
- Map common Prisma error codes (P2002, P2025, etc.) sang HTTP errors phù hợp
|
||||
- Tham khảo Prisma error code documentation để có mapping đầy đủ
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Error Classes](../../../services/iam-service/src/errors/http-error.ts) - Base error classes và implementations
|
||||
- [Error Codes](../../../services/iam-service/src/errors/error-codes.ts) - Centralized error code definitions
|
||||
- [Error Middleware](../../../services/iam-service/src/middlewares/error.middleware.ts) - Global error handler implementation
|
||||
- [API Design](./api-design.md) - API response formats và standards
|
||||
- [Security](./security.md) - Security-related error handling
|
||||
- [Testing Patterns](./testing-patterns.md) - Testing error scenarios
|
||||
@@ -1,505 +0,0 @@
|
||||
# Kiến Trúc Hướng Sự Kiện (Event-Driven Architecture)
|
||||
|
||||
Event-driven architecture patterns with Apache Kafka for GoodGo microservices. Use when implementing async communication, event publishing/consuming, event sourcing, CQRS, or integrating event streams with HTTP endpoints.
|
||||
> Các patterns kiến trúc hướng sự kiện với Apache Kafka cho GoodGo microservices. Sử dụng khi implement giao tiếp bất đồng bộ, publish/consume events, event sourcing, CQRS, hoặc tích hợp event streams với HTTP endpoints.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Event-Driven Architecture (EDA) enables asynchronous communication between services using Apache Kafka as the message broker. This pattern decouples services, improves scalability, and enables event sourcing, CQRS, and reactive systems. GoodGo platform uses Kafka for high-throughput event streaming and integrates with Traefik to expose events via SSE/WebSocket endpoints.
|
||||
|
||||
Kiến trúc hướng sự kiện (EDA) cho phép giao tiếp bất đồng bộ giữa các services sử dụng Apache Kafka làm message broker. Pattern này tách biệt các services, cải thiện khả năng mở rộng, và cho phép event sourcing, CQRS, và các hệ thống reactive. Nền tảng GoodGo sử dụng Kafka cho event streaming hiệu suất cao và tích hợp với Traefik để expose events qua SSE/WebSocket endpoints.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Implement giao tiếp bất đồng bộ giữa các services
|
||||
- Tách biệt services để cải thiện khả năng mở rộng
|
||||
- Publish domain events cho downstream consumers
|
||||
- Consume events từ các services khác
|
||||
- Implement event sourcing patterns
|
||||
- Implement CQRS (Command Query Responsibility Segregation)
|
||||
- Expose event streams qua HTTP (SSE/WebSocket)
|
||||
- Xử lý eventual consistency giữa các services
|
||||
- Xây dựng reactive systems phản hồi với các thay đổi
|
||||
- Tích hợp với Apache Kafka message broker
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Event-Driven vs Request-Response
|
||||
|
||||
**Request-Response (Synchronous / Đồng Bộ):**
|
||||
- Client waits for response / Client đợi response
|
||||
- Tight coupling between services / Liên kết chặt chẽ giữa các services
|
||||
- Blocking operations / Các thao tác blocking
|
||||
- Immediate consistency / Nhất quán ngay lập tức
|
||||
- Use Traefik API Gateway for HTTP/REST / Sử dụng Traefik API Gateway cho HTTP/REST
|
||||
|
||||
**Event-Driven (Asynchronous / Bất Đồng Bộ):**
|
||||
- Fire-and-forget publishing / Publish fire-and-forget
|
||||
- Loose coupling between services / Liên kết lỏng lẻo giữa các services
|
||||
- Non-blocking operations / Các thao tác non-blocking
|
||||
- Eventual consistency / Nhất quán cuối cùng
|
||||
- Use Kafka for message broker / Sử dụng Kafka cho message broker
|
||||
|
||||
### Kafka Fundamentals / Các Khái Niệm Cơ Bản về Kafka
|
||||
|
||||
- **Topics**: Named streams of events (e.g., `user.created`, `order.placed`) / Các luồng sự kiện được đặt tên
|
||||
- **Partitions**: Physical division of topics for parallelism and scaling / Chia nhỏ vật lý của topics để song song hóa và mở rộng
|
||||
- **Consumer Groups**: Groups of consumers that work together to process events / Các nhóm consumers làm việc cùng nhau để xử lý events
|
||||
- **Producers**: Services that publish events to topics / Services phát hành events tới topics
|
||||
- **Consumers**: Services that subscribe to topics and process events / Services đăng ký topics và xử lý events
|
||||
|
||||
#### Consumer Groups Architecture / Kiến Trúc Consumer Groups
|
||||
|
||||
The following diagram illustrates how consumer groups distribute work across partitions:
|
||||
|
||||
Biểu đồ sau minh họa cách consumer groups phân phối công việc qua các partitions:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Topic["Topic: user.created"]
|
||||
P0["Partition 0"]
|
||||
P1["Partition 1"]
|
||||
P2["Partition 2"]
|
||||
end
|
||||
|
||||
subgraph ConsumerGroup["Consumer Group: notification-service"]
|
||||
C1["Consumer 1"]
|
||||
C2["Consumer 2"]
|
||||
end
|
||||
|
||||
subgraph ConsumerGroup2["Consumer Group: analytics-service"]
|
||||
C3["Consumer 3"]
|
||||
C4["Consumer 4"]
|
||||
C5["Consumer 5"]
|
||||
end
|
||||
|
||||
P0 --> C1
|
||||
P1 --> C2
|
||||
P2 --> C1
|
||||
|
||||
P0 --> C3
|
||||
P1 --> C4
|
||||
P2 --> C5
|
||||
|
||||
style Topic fill:#e1f5ff
|
||||
style ConsumerGroup fill:#fff4e1
|
||||
style ConsumerGroup2 fill:#e8f5e9
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Each partition is consumed by only one consumer per consumer group / Mỗi partition chỉ được consume bởi một consumer trong mỗi consumer group
|
||||
- Multiple consumer groups can independently consume from the same topic / Nhiều consumer groups có thể độc lập consume từ cùng một topic
|
||||
- Consumers in a group automatically rebalance when members join or leave / Consumers trong một group tự động rebalance khi có thành viên tham gia hoặc rời khỏi
|
||||
- More partitions enable better parallelism within a consumer group / Nhiều partitions hơn cho phép song song hóa tốt hơn trong consumer group
|
||||
|
||||
### Traefik Integration / Tích Hợp Traefik
|
||||
|
||||
Traefik serves dual purpose:
|
||||
- **API Gateway**: Routes synchronous HTTP/REST requests / Định tuyến các request HTTP/REST đồng bộ
|
||||
- **Event Streaming Gateway**: Routes SSE/WebSocket connections to event streaming endpoints / Định tuyến các kết nối SSE/WebSocket tới event streaming endpoints
|
||||
|
||||
Services publish events to Kafka, then expose SSE/WebSocket endpoints that consume from Kafka for HTTP clients.
|
||||
|
||||
Services publish events vào Kafka, sau đó expose SSE/WebSocket endpoints consume từ Kafka cho HTTP clients.
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Event Publishing / Phát Hành Events
|
||||
|
||||
```typescript
|
||||
// src/core/events/event-publisher.ts
|
||||
import { producer } from '../config/kafka.config';
|
||||
import { logger } from '@goodgo/logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class EventPublisher {
|
||||
/**
|
||||
* EN: Publish event to Kafka topic
|
||||
* VI: Phát hành event tới Kafka topic
|
||||
*/
|
||||
async publish<T extends BaseEvent>(
|
||||
topic: string,
|
||||
event: Omit<T, 'eventId' | 'timestamp' | 'source'>,
|
||||
options?: { partitionKey?: string }
|
||||
): Promise<void> {
|
||||
const fullEvent: T = {
|
||||
...event,
|
||||
eventId: uuidv4(),
|
||||
timestamp: new Date().toISOString(),
|
||||
source: this.serviceName,
|
||||
} as T;
|
||||
|
||||
await producer.send({
|
||||
topic,
|
||||
messages: [{
|
||||
key: options?.partitionKey || fullEvent.eventId,
|
||||
value: JSON.stringify(fullEvent),
|
||||
headers: {
|
||||
'event-type': event.eventType,
|
||||
'event-version': event.eventVersion,
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: `.cursor/skills/event-driven-architecture/SKILL.md`
|
||||
|
||||
#### Event Publishing Flow / Luồng Phát Hành Events
|
||||
|
||||
The following sequence diagram shows how events are published from a service to Kafka:
|
||||
|
||||
Biểu đồ sequence sau cho thấy cách events được publish từ service tới Kafka:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Layer
|
||||
participant Publisher as EventPublisher
|
||||
participant Kafka as Kafka Broker
|
||||
participant Topic as Topic Partition
|
||||
|
||||
Service->>Publisher: publish(topic, event, options)
|
||||
activate Publisher
|
||||
Publisher->>Publisher: Generate eventId
|
||||
Publisher->>Publisher: Add timestamp & source
|
||||
Publisher->>Publisher: Determine partition key
|
||||
Publisher->>Kafka: send({ topic, messages })
|
||||
activate Kafka
|
||||
Kafka->>Topic: Route to partition
|
||||
activate Topic
|
||||
Topic-->>Kafka: Acknowledge
|
||||
deactivate Topic
|
||||
Kafka-->>Publisher: Success
|
||||
deactivate Kafka
|
||||
Publisher-->>Service: Complete (fire-and-forget)
|
||||
deactivate Publisher
|
||||
Note over Service,Publisher: Non-blocking operation
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Publishing is asynchronous and non-blocking / Publishing là bất đồng bộ và không blocking
|
||||
- Partition key determines which partition receives the event / Partition key xác định partition nào nhận event
|
||||
- Events are acknowledged by Kafka before completion / Events được Kafka xác nhận trước khi hoàn thành
|
||||
- Fire-and-forget pattern prevents blocking request handlers / Pattern fire-and-forget ngăn chặn blocking request handlers
|
||||
|
||||
### Event Consuming / Tiêu Thụ Events
|
||||
|
||||
```typescript
|
||||
// src/core/events/event-consumer.ts
|
||||
import { kafka } from '../config/kafka.config';
|
||||
|
||||
export class EventConsumer {
|
||||
private handlers: Map<string, EventHandler[]> = new Map();
|
||||
|
||||
/**
|
||||
* EN: Register event handler
|
||||
* VI: Đăng ký event handler
|
||||
*/
|
||||
on<T extends BaseEvent>(eventType: string, handler: EventHandler<T>): void {
|
||||
if (!this.handlers.has(eventType)) {
|
||||
this.handlers.set(eventType, []);
|
||||
}
|
||||
this.handlers.get(eventType)!.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Start consuming from topics
|
||||
* VI: Bắt đầu consume từ topics
|
||||
*/
|
||||
async start(topics: string[]): Promise<void> {
|
||||
await this.consumer.connect();
|
||||
await this.consumer.subscribe({ topics, fromBeginning: false });
|
||||
|
||||
await this.consumer.run({
|
||||
eachMessage: async ({ topic, partition, message }) => {
|
||||
const event: BaseEvent = JSON.parse(message.value?.toString() || '{}');
|
||||
const handlers = this.handlers.get(event.eventType) || [];
|
||||
await Promise.all(handlers.map(h => h.handle(event)));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Outbox Pattern / Pattern Outbox
|
||||
|
||||
The Outbox pattern ensures transactional event publishing by storing events in the database within the same transaction as the business data.
|
||||
|
||||
Pattern Outbox đảm bảo publish events trong transaction bằng cách lưu events trong database trong cùng transaction với business data.
|
||||
|
||||
```typescript
|
||||
// EN: Store event in database within transaction
|
||||
// VI: Lưu event vào database trong transaction
|
||||
await prisma.outboxEvent.create({
|
||||
data: {
|
||||
eventType: 'user.created',
|
||||
eventData: userData,
|
||||
topic: 'user.created',
|
||||
status: 'PENDING',
|
||||
},
|
||||
});
|
||||
|
||||
// EN: Separate process publishes from outbox to Kafka
|
||||
// VI: Process riêng publish từ outbox tới Kafka
|
||||
async function processOutbox() {
|
||||
const events = await prisma.outboxEvent.findMany({
|
||||
where: { status: 'PENDING' },
|
||||
});
|
||||
|
||||
for (const event of events) {
|
||||
await eventPublisher.publish(event.topic, event.eventData);
|
||||
await prisma.outboxEvent.update({
|
||||
where: { id: event.id },
|
||||
data: { status: 'PUBLISHED' },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Outbox Pattern Flow / Luồng Outbox Pattern
|
||||
|
||||
The following sequence diagram illustrates the outbox pattern workflow:
|
||||
|
||||
Biểu đồ sequence sau minh họa luồng làm việc của outbox pattern:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Layer
|
||||
participant DB as Database
|
||||
participant Outbox as Outbox Table
|
||||
participant Processor as Outbox Processor
|
||||
participant Publisher as EventPublisher
|
||||
participant Kafka as Kafka Broker
|
||||
|
||||
Service->>DB: Begin Transaction
|
||||
activate DB
|
||||
Service->>DB: Create business entity
|
||||
Service->>Outbox: Insert event (status: PENDING)
|
||||
Outbox-->>DB: Stored
|
||||
Service->>DB: Commit Transaction
|
||||
deactivate DB
|
||||
Note over Service,DB: Event stored atomically with business data
|
||||
|
||||
loop Polling Interval
|
||||
Processor->>Outbox: Find PENDING events
|
||||
Outbox-->>Processor: Return events
|
||||
Processor->>Publisher: publish(event)
|
||||
activate Publisher
|
||||
Publisher->>Kafka: Send to topic
|
||||
Kafka-->>Publisher: Acknowledge
|
||||
Publisher-->>Processor: Success
|
||||
deactivate Publisher
|
||||
Processor->>Outbox: Update status to PUBLISHED
|
||||
end
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Events are stored in the database within the same transaction as business data / Events được lưu trong database trong cùng transaction với business data
|
||||
- A separate background process (Outbox Processor) publishes events to Kafka / Một process nền riêng (Outbox Processor) publish events tới Kafka
|
||||
- Ensures at-least-once delivery guarantee / Đảm bảo giao hàng ít nhất một lần
|
||||
- Prevents lost events if Kafka is temporarily unavailable / Ngăn chặn mất events nếu Kafka tạm thời không khả dụng
|
||||
|
||||
### SSE Endpoint / Endpoint SSE
|
||||
|
||||
Server-Sent Events (SSE) allows clients to receive event streams via HTTP.
|
||||
|
||||
Server-Sent Events (SSE) cho phép clients nhận event streams qua HTTP.
|
||||
|
||||
```typescript
|
||||
// src/modules/events/events.controller.ts
|
||||
async streamEvents(req: Request, res: Response): Promise<void> {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const topic = req.query.topic as string;
|
||||
const consumer = kafka.consumer({ groupId: `sse-${Date.now()}` });
|
||||
|
||||
await consumer.connect();
|
||||
await consumer.subscribe({ topic, fromBeginning: false });
|
||||
|
||||
await consumer.run({
|
||||
eachMessage: async ({ message }) => {
|
||||
const event = JSON.parse(message.value?.toString() || '{}');
|
||||
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
||||
},
|
||||
});
|
||||
|
||||
req.on('close', async () => {
|
||||
await consumer.disconnect();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Cấu Trúc Event / Event Structure
|
||||
|
||||
```typescript
|
||||
interface BaseEvent {
|
||||
eventId: string; // EN: Unique event identifier / VI: Định danh event duy nhất
|
||||
eventType: string; // EN: Event type (e.g., "user.created") / VI: Loại event
|
||||
eventVersion: string; // EN: Schema version / VI: Phiên bản schema
|
||||
timestamp: string; // EN: ISO 8601 timestamp / VI: Timestamp ISO 8601
|
||||
source: string; // EN: Service that published the event / VI: Service phát hành event
|
||||
correlationId?: string; // EN: Request correlation ID / VI: Correlation ID của request
|
||||
traceId?: string; // EN: Distributed tracing ID / VI: ID phân tán tracing
|
||||
data: unknown; // EN: Event payload / VI: Payload của event
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
### Event Naming Conventions / Quy Ước Đặt Tên Events
|
||||
|
||||
- **Event Type**: `{domain}.{action}.v{version}` (e.g., `user.created.v1`) / Loại Event: `{domain}.{action}.v{version}`
|
||||
- **Topic**: `{domain}.{entity}.{action}` (e.g., `user.created`) / Topic: `{domain}.{entity}.{action}`
|
||||
- Use lowercase with dots as separators / Sử dụng chữ thường với dấu chấm làm phân cách
|
||||
- Keep names descriptive and consistent / Giữ tên mô tả và nhất quán
|
||||
|
||||
### Partition Key Selection / Lựa Chọn Partition Key
|
||||
|
||||
- Use entity ID for ordering guarantees (same entity → same partition) / Sử dụng entity ID để đảm bảo thứ tự
|
||||
- Use correlation ID for request tracing / Sử dụng correlation ID để trace request
|
||||
- Use user ID for user-scoped events / Sử dụng user ID cho events phạm vi user
|
||||
- Avoid high-cardinality keys (distributes evenly) / Tránh keys có độ phân tán cao
|
||||
|
||||
### Event Ordering Guarantees / Đảm Bảo Thứ Tự Events
|
||||
|
||||
- Kafka guarantees ordering **per partition** / Kafka đảm bảo thứ tự **theo partition**
|
||||
- Use partition key to ensure related events go to same partition / Sử dụng partition key để đảm bảo events liên quan cùng partition
|
||||
- Events in different partitions have no ordering guarantee / Events ở các partitions khác nhau không có đảm bảo thứ tự
|
||||
- Don't rely on global ordering across all events / Không phụ thuộc vào thứ tự toàn cục
|
||||
|
||||
### Error Handling / Xử Lý Lỗi
|
||||
|
||||
- Implement Dead Letter Queue (DLQ) for failed events / Implement DLQ cho events failed
|
||||
- Use retry with exponential backoff / Sử dụng retry với exponential backoff
|
||||
- Log all event processing failures / Ghi log tất cả lỗi xử lý events
|
||||
- Monitor consumer lag and DLQ size / Giám sát consumer lag và kích thước DLQ
|
||||
|
||||
### Observability / Khả Năng Quan Sát
|
||||
|
||||
- Log all published and consumed events / Ghi log tất cả events đã publish và consume
|
||||
- Track metrics: events published/consumed, processing duration, consumer lag / Theo dõi metrics: events published/consumed, thời gian xử lý, consumer lag
|
||||
- Add distributed tracing to event flows / Thêm distributed tracing vào event flows
|
||||
- Include correlation IDs for request tracking / Bao gồm correlation IDs để theo dõi request
|
||||
|
||||
## Infrastructure Setup / Thiết Lập Hạ Tầng
|
||||
|
||||
### Docker Compose (Local / Cục Bộ)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:7.4.0
|
||||
ports:
|
||||
- "9092:9092"
|
||||
environment:
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
||||
|
||||
schema-registry:
|
||||
image: confluentinc/cp-schema-registry:7.4.0
|
||||
ports:
|
||||
- "8081:8081"
|
||||
environment:
|
||||
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: kafka:9092
|
||||
```
|
||||
|
||||
**Tham Khảo**: `deployments/local/docker-compose.yml`
|
||||
|
||||
## Testing / Kiểm Thử
|
||||
|
||||
### Unit Testing / Kiểm Thử Đơn Vị
|
||||
|
||||
```typescript
|
||||
import { EventPublisher } from '../event-publisher';
|
||||
import { producer } from '../../config/kafka.config';
|
||||
|
||||
jest.mock('../../config/kafka.config');
|
||||
|
||||
describe('EventPublisher', () => {
|
||||
it('should publish event successfully', async () => {
|
||||
const publisher = new EventPublisher();
|
||||
const mockSend = jest.fn().mockResolvedValue({});
|
||||
(producer.send as jest.Mock) = mockSend;
|
||||
|
||||
await publisher.publish('user.created', {
|
||||
eventType: 'user.created',
|
||||
eventVersion: '1.0.0',
|
||||
data: { userId: '123' },
|
||||
});
|
||||
|
||||
expect(mockSend).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Testing / Kiểm Thử Tích Hợp
|
||||
|
||||
Use Kafka test containers for integration testing:
|
||||
|
||||
Sử dụng Kafka test containers cho integration testing:
|
||||
|
||||
```typescript
|
||||
import { KafkaContainer } from '@testcontainers/kafka';
|
||||
|
||||
describe('Event Flow E2E', () => {
|
||||
let kafkaContainer: StartedKafkaContainer;
|
||||
|
||||
beforeAll(async () => {
|
||||
kafkaContainer = await new KafkaContainer().start();
|
||||
process.env.KAFKA_BROKERS = kafkaContainer.getBootstrapServer();
|
||||
});
|
||||
|
||||
it('should publish and consume event', async () => {
|
||||
// EN: Test implementation
|
||||
// VI: Implementation test
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Use Cases / Các Trường Hợp Sử Dụng
|
||||
|
||||
### User Created Event Flow / Luồng User Created Event
|
||||
|
||||
1. Auth Service creates user in database / Auth Service tạo user trong database
|
||||
2. Publishes `user.created` event to Kafka / Publish event `user.created` tới Kafka
|
||||
3. Notification Service consumes event and sends welcome email / Notification Service consume event và gửi email chào mừng
|
||||
4. Analytics Service consumes event and updates metrics / Analytics Service consume event và cập nhật metrics
|
||||
|
||||
### Order Processing with Multiple Consumers / Xử Lý Order với Nhiều Consumers
|
||||
|
||||
1. Order Service publishes `order.placed` event / Order Service publish event `order.placed`
|
||||
2. Payment Service processes payment / Payment Service xử lý thanh toán
|
||||
3. Inventory Service reserves items / Inventory Service dự trữ items
|
||||
4. Notification Service sends confirmation / Notification Service gửi xác nhận
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Resilience Patterns](./resilience-patterns.md) - Circuit breaker, retry patterns / Circuit breaker, các patterns retry
|
||||
- [Error Handling Patterns](./error-handling-patterns.md) - Error handling best practices / Best practices về error handling
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Logging, metrics, tracing / Logging, metrics, tracing
|
||||
- [Middleware Patterns](./middleware-patterns.md) - SSE endpoint middleware / Middleware SSE endpoint
|
||||
- [Project Rules](./project-rules.md) - GoodGo coding standards / Tiêu chuẩn coding GoodGo
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [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 / Tài liệu chính thức Kafka
|
||||
- Skill Source: `.cursor/skills/event-driven-architecture/SKILL.md` - Source file đầy đủ / Full source file
|
||||
@@ -1,244 +0,0 @@
|
||||
# Infrastructure as Code
|
||||
|
||||
Infrastructure as Code patterns for GoodGo platform including Terraform modules, Kubernetes operators, infrastructure testing, GitOps workflows, and multi-environment management.
|
||||
> Các patterns Infrastructure as Code cho nền tảng GoodGo bao gồm Terraform modules, Kubernetes operators, infrastructure testing, GitOps workflows, và multi-environment management.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Infrastructure as Code enables managing infrastructure through code, providing version control, reproducibility, and automation.
|
||||
|
||||
Infrastructure as Code cho phép quản lý infrastructure qua code, cung cấp version control, reproducibility, và automation.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when managing infrastructure, implementing GitOps, or creating reusable modules.
|
||||
|
||||
Sử dụng skill này khi quản lý infrastructure, implement GitOps, hoặc tạo các modules tái sử dụng.
|
||||
|
||||
## Infrastructure as Code Workflow / Quy Trình Infrastructure as Code
|
||||
|
||||
The following diagram illustrates the complete IaC workflow from code changes to infrastructure deployment.
|
||||
|
||||
Sơ đồ sau minh họa quy trình IaC đầy đủ từ thay đổi code đến triển khai infrastructure.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Developer writes IaC code] --> B[Commit to Git repository]
|
||||
B --> C{Code Review}
|
||||
C -->|Rejected| D[Fix issues]
|
||||
D --> B
|
||||
C -->|Approved| E[Merge to branch]
|
||||
E --> F[CI/CD Pipeline triggers]
|
||||
F --> G{Terraform or<br/>Kubernetes?}
|
||||
G -->|Terraform| H[Terraform Workflow]
|
||||
G -->|Kubernetes| I[GitOps Workflow]
|
||||
H --> J[terraform init]
|
||||
J --> K[terraform validate]
|
||||
K --> L[terraform plan]
|
||||
L --> M{Plan review}
|
||||
M -->|Issues found| D
|
||||
M -->|Approved| N[terraform apply]
|
||||
N --> O[Infrastructure updated]
|
||||
I --> P[GitOps tool detects changes]
|
||||
P --> Q[Sync to Kubernetes cluster]
|
||||
Q --> O
|
||||
O --> R[Health checks]
|
||||
R --> S{Deployment successful?}
|
||||
S -->|No| T[Rollback]
|
||||
S -->|Yes| U[Monitor infrastructure]
|
||||
```
|
||||
|
||||
## GitOps Flow / Quy Trình GitOps
|
||||
|
||||
GitOps enables automated synchronization of Kubernetes manifests from Git to clusters.
|
||||
|
||||
GitOps cho phép đồng bộ tự động Kubernetes manifests từ Git đến clusters.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Dev as Developer
|
||||
participant Git as Git Repository
|
||||
participant ArgoCD as ArgoCD/Flux
|
||||
participant K8s as Kubernetes Cluster
|
||||
|
||||
Dev->>Git: Push manifest changes
|
||||
Git->>ArgoCD: Detect changes (poll/webhook)
|
||||
ArgoCD->>Git: Fetch latest manifests
|
||||
ArgoCD->>ArgoCD: Compare desired vs actual state
|
||||
alt Drift detected
|
||||
ArgoCD->>K8s: Apply changes (sync)
|
||||
K8s->>K8s: Update resources
|
||||
K8s->>ArgoCD: Status update
|
||||
else Auto-heal enabled
|
||||
ArgoCD->>K8s: Self-heal (correct drift)
|
||||
end
|
||||
ArgoCD->>Git: Update sync status
|
||||
```
|
||||
|
||||
## Terraform Execution Flow / Quy Trình Thực Thi Terraform
|
||||
|
||||
The Terraform workflow ensures safe and predictable infrastructure changes.
|
||||
|
||||
Quy trình Terraform đảm bảo các thay đổi infrastructure an toàn và có thể dự đoán được.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[terraform init] --> B[Load providers & modules]
|
||||
B --> C[terraform validate]
|
||||
C --> D{Syntax valid?}
|
||||
D -->|No| E[Fix errors]
|
||||
E --> C
|
||||
D -->|Yes| F[terraform plan]
|
||||
F --> G[Read state]
|
||||
G --> H[Build dependency graph]
|
||||
H --> I[Calculate changes]
|
||||
I --> J[Generate plan]
|
||||
J --> K{Review plan}
|
||||
K -->|Issues| L[Adjust code]
|
||||
L --> F
|
||||
K -->|Approved| M[terraform apply]
|
||||
M --> N[Lock state]
|
||||
N --> O[Execute changes]
|
||||
O --> P{Success?}
|
||||
P -->|No| Q[Rollback]
|
||||
P -->|Yes| R[Update state]
|
||||
R --> S[Unlock state]
|
||||
S --> T[Save state to backend]
|
||||
```
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Terraform Modules / Terraform Modules
|
||||
|
||||
Terraform modules enable reusable infrastructure components across environments.
|
||||
|
||||
Terraform modules cho phép tái sử dụng các component infrastructure giữa các môi trường.
|
||||
|
||||
```hcl
|
||||
# EN: Reusable module
|
||||
# VI: Module tái sử dụng
|
||||
module "postgresql" {
|
||||
source = "../../modules/postgresql"
|
||||
database_name = "goodgo"
|
||||
environment = "staging"
|
||||
}
|
||||
```
|
||||
|
||||
### Module Structure / Cấu Trúc Module
|
||||
|
||||
The following diagram shows the typical Terraform module structure and how modules are composed.
|
||||
|
||||
Sơ đồ sau cho thấy cấu trúc Terraform module điển hình và cách các modules được kết hợp.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Module: postgresql] --> B[variables.tf<br/>Input parameters]
|
||||
A --> C[main.tf<br/>Resource definitions]
|
||||
A --> D[outputs.tf<br/>Exported values]
|
||||
E[Environment: staging] --> F[main.tf]
|
||||
F --> G[Module: postgresql]
|
||||
F --> H[Module: redis]
|
||||
F --> I[Module: kubernetes-cluster]
|
||||
G --> J[Output: database_url]
|
||||
H --> K[Output: redis_url]
|
||||
I --> L[Output: cluster_endpoint]
|
||||
F --> M[terraform.tfvars<br/>Environment config]
|
||||
```
|
||||
|
||||
### GitOps with ArgoCD / GitOps với ArgoCD
|
||||
|
||||
GitOps tools like ArgoCD and Flux automatically sync Kubernetes manifests from Git repositories.
|
||||
|
||||
Các công cụ GitOps như ArgoCD và Flux tự động đồng bộ Kubernetes manifests từ Git repositories.
|
||||
|
||||
```yaml
|
||||
# EN: Automated sync from Git
|
||||
# VI: Đồng bộ tự động từ Git
|
||||
spec:
|
||||
source:
|
||||
repoURL: https://github.com/goodgo/platform
|
||||
path: deployments/production/kubernetes
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
```
|
||||
|
||||
### Multi-Environment Management / Quản Lý Đa Môi Trường
|
||||
|
||||
Managing infrastructure across multiple environments requires clear separation and consistent patterns.
|
||||
|
||||
Quản lý infrastructure trên nhiều môi trường yêu cầu tách biệt rõ ràng và các patterns nhất quán.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Git Repository"
|
||||
A[infra/terraform]
|
||||
end
|
||||
subgraph "Modules (Reusable)"
|
||||
B[modules/postgresql]
|
||||
C[modules/redis]
|
||||
D[modules/kubernetes-cluster]
|
||||
end
|
||||
subgraph "Environments"
|
||||
E[environments/staging]
|
||||
F[environments/production]
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
A --> D
|
||||
A --> E
|
||||
A --> F
|
||||
E --> B
|
||||
E --> C
|
||||
E --> D
|
||||
F --> B
|
||||
F --> C
|
||||
F --> D
|
||||
E --> G[terraform.tfvars<br/>staging config]
|
||||
F --> H[terraform.tfvars<br/>production config]
|
||||
G --> I[Remote State Backend<br/>staging/terraform.tfstate]
|
||||
H --> J[Remote State Backend<br/>production/terraform.tfstate]
|
||||
```
|
||||
|
||||
## Infrastructure Testing / Kiểm Thử Infrastructure
|
||||
|
||||
Always validate infrastructure changes before applying them.
|
||||
|
||||
Luôn validate các thay đổi infrastructure trước khi áp dụng.
|
||||
|
||||
```bash
|
||||
# EN: Validate Terraform syntax
|
||||
# VI: Validate cú pháp Terraform
|
||||
terraform init
|
||||
terraform validate
|
||||
|
||||
# EN: Preview changes
|
||||
# VI: Xem trước các thay đổi
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# EN: Review plan before applying
|
||||
# VI: Xem xét plan trước khi apply
|
||||
terraform show tfplan
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. **Version Control / Kiểm Soát Phiên Bản**: Keep all infrastructure in version control / Giữ tất cả infrastructure trong version control
|
||||
2. **Modules / Modules**: Create reusable Terraform modules for common components / Tạo các Terraform modules tái sử dụng cho các component phổ biến
|
||||
3. **Testing / Kiểm Thử**: Test infrastructure changes before applying to production / Kiểm thử các thay đổi infrastructure trước khi áp dụng lên production
|
||||
4. **GitOps / GitOps**: Use GitOps (ArgoCD/Flux) for Kubernetes deployments / Sử dụng GitOps (ArgoCD/Flux) cho Kubernetes deployments
|
||||
5. **Environment Isolation / Cô Lập Môi Trường**: Separate environments completely with different state backends / Tách biệt hoàn toàn các môi trường với các state backends khác nhau
|
||||
6. **State Management / Quản Lý State**: Use remote state backends (S3, GCS) with state locking / Sử dụng remote state backends (S3, GCS) với state locking
|
||||
7. **Secrets / Bí Mật**: Never commit secrets - use environment variables or secrets managers / Không bao giờ commit secrets - sử dụng environment variables hoặc secrets managers
|
||||
|
||||
### Common Mistakes to Avoid / Các Lỗi Thường Gặp Cần Tránh
|
||||
|
||||
1. **Committing Secrets / Commit Secrets**: Never hardcode passwords or API keys / Không bao giờ hardcode passwords hoặc API keys
|
||||
2. **Local State Only / Chỉ Dùng Local State**: Always use remote state backends for team collaboration / Luôn sử dụng remote state backends cho collaboration trong team
|
||||
3. **No State Locking / Không Lock State**: Enable state locking to prevent concurrent modifications / Bật state locking để ngăn chặn các thay đổi đồng thời
|
||||
4. **Direct Apply / Apply Trực Tiếp**: Always review `terraform plan` output before applying / Luôn xem xét output của `terraform plan` trước khi apply
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- Skill Source: `.cursor/skills/infrastructure-as-code/SKILL.md`
|
||||
@@ -1,303 +0,0 @@
|
||||
# Giao Tiếp Giữa Các Services (Inter-Service Communication)
|
||||
|
||||
Inter-service communication patterns for GoodGo microservices including gRPC, GraphQL, service-to-service authentication, protocol selection, and client patterns. Use when implementing service-to-service calls, choosing communication protocols, or building service clients.
|
||||
> Các patterns giao tiếp giữa services cho GoodGo microservices bao gồm gRPC, GraphQL, service-to-service authentication, lựa chọn protocol, và client patterns. Sử dụng khi implement service-to-service calls, chọn communication protocols, hoặc xây dựng service clients.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Inter-service communication enables services to work together in a microservices architecture. GoodGo supports multiple protocols: HTTP/REST (current standard), gRPC (for high performance), and GraphQL (for flexible queries). This skill covers patterns for implementing resilient, secure, and performant inter-service communication.
|
||||
|
||||
Giao tiếp giữa các services cho phép các services làm việc cùng nhau trong kiến trúc microservices. GoodGo hỗ trợ nhiều protocols: HTTP/REST (chuẩn hiện tại), gRPC (cho hiệu suất cao), và GraphQL (cho queries linh hoạt). Skill này bao gồm các patterns để implement giao tiếp giữa services có khả năng phục hồi, bảo mật và hiệu suất cao.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Implement giao tiếp giữa các services
|
||||
- Chọn giữa REST, gRPC, hoặc GraphQL protocols
|
||||
- Setup gRPC services và clients
|
||||
- Implement GraphQL services và resolvers
|
||||
- Implement service-to-service authentication
|
||||
- Xây dựng service clients có khả năng phục hồi với circuit breakers
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Các Lựa Chọn Protocol
|
||||
|
||||
**HTTP/REST:**
|
||||
- Dễ đọc, dễ debug / Human-readable, easy to debug
|
||||
- Tương thích browser / Browser-compatible
|
||||
- Semantics HTTP chuẩn / Standard HTTP semantics
|
||||
- JSON payloads
|
||||
- Tốt cho external APIs / Good for external APIs
|
||||
|
||||
**gRPC:**
|
||||
- Binary protocol (Protocol Buffers)
|
||||
- Hiệu suất cao, độ trễ thấp / High performance, low latency
|
||||
- Hỗ trợ streaming / Streaming support
|
||||
- Strong typing với .proto files
|
||||
- Dựa trên HTTP/2 / HTTP/2 based
|
||||
|
||||
**GraphQL:**
|
||||
- Ngôn ngữ query linh hoạt / Flexible query language
|
||||
- Single endpoint
|
||||
- Client kiểm soát data fetching / Client-controlled data fetching
|
||||
- Strong typing với schema
|
||||
|
||||
### Hướng Dẫn Lựa Chọn Protocol
|
||||
|
||||
Chọn protocol dựa trên use case, yêu cầu hiệu suất, chuyên môn team, và nhu cầu ecosystem.
|
||||
|
||||
Choose protocol based on use case, performance requirements, team expertise, and ecosystem needs.
|
||||
|
||||
#### Sơ Đồ Quyết Định Lựa Chọn Protocol / Protocol Selection Decision Tree
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Need Inter-Service Communication]) --> CheckExternal{External/Public API?}
|
||||
|
||||
CheckExternal -->|Yes| UseREST[Use REST]
|
||||
CheckExternal -->|No| CheckPerformance{High Performance<br/>Required?}
|
||||
|
||||
CheckPerformance -->|Yes| CheckStreaming{Need Streaming?}
|
||||
CheckPerformance -->|No| CheckFlexible{Need Flexible<br/>Queries?}
|
||||
|
||||
CheckStreaming -->|Yes| UseGRPC[Use gRPC]
|
||||
CheckStreaming -->|No| CheckLowLatency{Ultra Low<br/>Latency?}
|
||||
|
||||
CheckLowLatency -->|Yes| UseGRPC
|
||||
CheckLowLatency -->|No| UseREST
|
||||
|
||||
CheckFlexible -->|Yes| UseGraphQL[Use GraphQL]
|
||||
CheckFlexible -->|No| UseREST
|
||||
|
||||
UseREST --> RESTDesc["REST: External APIs<br/>Browser clients<br/>Simple CRUD"]
|
||||
UseGRPC --> GRPCDesc["gRPC: Internal services<br/>High performance<br/>Streaming support"]
|
||||
UseGraphQL --> GraphQLDesc["GraphQL: Complex queries<br/>Mobile apps<br/>Flexible data"]
|
||||
|
||||
style UseREST fill:#e1f5ff
|
||||
style UseGRPC fill:#fff4e1
|
||||
style UseGraphQL fill:#e8f5e9
|
||||
```
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Luồng Gọi Service-to-Service / Service-to-Service Call Flow
|
||||
|
||||
Sơ đồ sau minh họa luồng hoàn chỉnh của service-to-service call, bao gồm authentication, interceptors, và error handling:
|
||||
|
||||
The following diagram illustrates the complete flow of a service-to-service call, including authentication, interceptors, and error handling:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ClientService as Client Service
|
||||
participant ClientLib as Service Client<br/>(HTTP/gRPC/GraphQL)
|
||||
participant Interceptor as Request<br/>Interceptor
|
||||
participant Auth as Auth<br/>Middleware
|
||||
participant TargetService as Target Service
|
||||
participant Logger as Logger
|
||||
participant Metrics as Metrics
|
||||
|
||||
ClientService->>ClientLib: Make request
|
||||
ClientLib->>Interceptor: Add correlation ID<br/>Add service auth header<br/>Add request ID
|
||||
Interceptor->>Logger: Log request start
|
||||
Interceptor->>Metrics: Track request start
|
||||
Interceptor->>TargetService: HTTP/gRPC/GraphQL Request<br/>(with headers)
|
||||
|
||||
TargetService->>Auth: Validate x-service-auth
|
||||
alt Invalid Auth
|
||||
Auth-->>TargetService: 403 Forbidden
|
||||
TargetService-->>ClientLib: Error Response
|
||||
ClientLib->>Logger: Log error
|
||||
ClientLib->>Metrics: Track failure
|
||||
ClientLib-->>ClientService: ServiceError
|
||||
else Valid Auth
|
||||
Auth->>TargetService: Authenticated
|
||||
TargetService->>TargetService: Process request
|
||||
TargetService-->>ClientLib: Success Response
|
||||
ClientLib->>Logger: Log success<br/>(with correlation ID)
|
||||
ClientLib->>Metrics: Track success<br/>(duration, status)
|
||||
ClientLib-->>ClientService: Response Data
|
||||
end
|
||||
```
|
||||
|
||||
### HTTP/REST Service Client
|
||||
|
||||
```typescript
|
||||
// EN: Service client với circuit breaker
|
||||
// VI: Service client với circuit breaker
|
||||
import { ServiceClient } from '../../core/clients/service-client';
|
||||
|
||||
const notificationClient = new ServiceClient({
|
||||
baseURL: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:5003',
|
||||
serviceName: 'notification-service',
|
||||
timeout: 5000,
|
||||
enableCircuitBreaker: true,
|
||||
});
|
||||
|
||||
// EN: Usage
|
||||
// VI: Sử dụng
|
||||
await notificationClient.post('/api/v1/notifications', {
|
||||
userId,
|
||||
message,
|
||||
});
|
||||
```
|
||||
|
||||
### gRPC Service
|
||||
|
||||
```typescript
|
||||
// EN: gRPC server
|
||||
// VI: gRPC server
|
||||
import { UserGrpcServer } from './user.grpc.service';
|
||||
|
||||
const grpcServer = new UserGrpcServer(userService);
|
||||
grpcServer.start(50051);
|
||||
|
||||
// EN: gRPC client
|
||||
// VI: gRPC client
|
||||
const userGrpcClient = new GrpcClient({
|
||||
protoPath: './proto/user_service.proto',
|
||||
packageName: 'goodgo.user.v1',
|
||||
serviceName: 'UserService',
|
||||
serverUrl: 'localhost:50051',
|
||||
});
|
||||
|
||||
const user = await userGrpcClient.call('getUser', { user_id: '123' });
|
||||
```
|
||||
|
||||
### GraphQL Service
|
||||
|
||||
```typescript
|
||||
// EN: GraphQL client
|
||||
// VI: GraphQL client
|
||||
const userGraphQLClient = new GraphQLServiceClient({
|
||||
endpoint: 'http://user-service:5002/graphql',
|
||||
});
|
||||
|
||||
const GET_USER_QUERY = `
|
||||
query GetUser($id: ID!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const user = await userGraphQLClient.query(GET_USER_QUERY, { id: '123' });
|
||||
```
|
||||
|
||||
### Service-to-Service Authentication
|
||||
|
||||
Luồng authentication đảm bảo giao tiếp an toàn giữa các services:
|
||||
|
||||
The authentication flow ensures secure communication between services:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ClientService as Client Service
|
||||
participant ServiceClient as Service Client
|
||||
participant Env as Environment<br/>Variables
|
||||
participant TargetService as Target Service
|
||||
participant AuthMiddleware as Auth<br/>Middleware
|
||||
|
||||
ClientService->>ServiceClient: Create client instance
|
||||
ServiceClient->>Env: Read INTERNAL_API_KEY
|
||||
Env-->>ServiceClient: API Key
|
||||
|
||||
ClientService->>ServiceClient: Make request
|
||||
ServiceClient->>ServiceClient: Auto-add x-service-auth<br/>header with API key
|
||||
|
||||
ServiceClient->>TargetService: HTTP Request<br/>(x-service-auth: token)
|
||||
|
||||
TargetService->>AuthMiddleware: Extract x-service-auth
|
||||
AuthMiddleware->>AuthMiddleware: Compare with<br/>INTERNAL_API_KEY
|
||||
|
||||
alt Token Matches
|
||||
AuthMiddleware->>TargetService: Auth Success
|
||||
TargetService->>TargetService: Process request
|
||||
TargetService-->>ServiceClient: 200 OK + Data
|
||||
else Token Mismatch
|
||||
AuthMiddleware->>TargetService: Auth Failed
|
||||
TargetService-->>ServiceClient: 403 Forbidden<br/>(INVALID_SERVICE_AUTH)
|
||||
ServiceClient->>ServiceClient: Throw ServiceError
|
||||
ServiceClient-->>ClientService: Error Response
|
||||
end
|
||||
```
|
||||
|
||||
#### Implementation / Triển Khai
|
||||
|
||||
```typescript
|
||||
// EN: Internal auth middleware
|
||||
// VI: Internal auth middleware
|
||||
import { internalAuthMiddleware } from '../../middlewares/internal-auth.middleware';
|
||||
|
||||
router.use('/internal', internalAuthMiddleware);
|
||||
|
||||
// EN: Client tự động thêm auth header
|
||||
// VI: Client automatically adds auth header
|
||||
const client = new ServiceClient({
|
||||
baseURL: 'http://service:5000',
|
||||
serviceName: 'service',
|
||||
});
|
||||
// X-Service-Auth header được thêm tự động
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
### Protocol Selection / Lựa Chọn Protocol
|
||||
|
||||
- **REST**: External APIs, browser clients, simple CRUD / APIs ngoài, browser clients, CRUD đơn giản
|
||||
- **gRPC**: Internal services, high performance, streaming / Services nội bộ, hiệu suất cao, streaming
|
||||
- **GraphQL**: Complex queries, mobile apps, flexible data / Queries phức tạp, mobile apps, data linh hoạt
|
||||
|
||||
### Performance / Hiệu Suất
|
||||
|
||||
- Use connection pooling / Sử dụng connection pooling
|
||||
- Enable HTTP keep-alive / Bật HTTP keep-alive
|
||||
- Set appropriate timeouts / Thiết lập timeout phù hợp
|
||||
- Implement circuit breakers / Implement circuit breakers
|
||||
|
||||
### Security / Bảo Mật
|
||||
|
||||
- Always authenticate internal calls / Luôn xác thực internal calls
|
||||
- Use TLS/mTLS / Sử dụng TLS/mTLS
|
||||
- Store secrets securely / Lưu secrets an toàn
|
||||
- Implement rate limiting / Implement rate limiting
|
||||
|
||||
### Observability / Khả Năng Quan Sát
|
||||
|
||||
- Log with correlation IDs / Ghi log với correlation IDs
|
||||
- Track metrics (duration, success rate) / Theo dõi metrics
|
||||
- Add distributed tracing / Thêm distributed tracing
|
||||
- Monitor service health / Giám sát sức khỏe service
|
||||
|
||||
## Testing / Kiểm Thử
|
||||
|
||||
```typescript
|
||||
// EN: Mock service client
|
||||
// VI: Mock service client
|
||||
const mockClient = createMockServiceClient();
|
||||
mockClient.get.mockResolvedValue({ id: '123' });
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Resilience Patterns](./resilience-patterns.md) - Circuit breaker, retry patterns / Circuit breaker, các patterns retry
|
||||
- [Security](./security.md) - Authentication, authorization / Authentication, authorization
|
||||
- [API Design](./api-design.md) - RESTful API patterns / RESTful API patterns
|
||||
- [Project Rules](./project-rules.md) - GoodGo coding standards / Tiêu chuẩn coding GoodGo
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [gRPC Documentation](https://grpc.io/docs/) - Tài liệu gRPC chính thức
|
||||
- [GraphQL Documentation](https://graphql.org/learn/) - Tài liệu GraphQL
|
||||
- [Protocol Buffers](https://developers.google.com/protocol-buffers) - Hướng dẫn Protocol Buffers
|
||||
- Skill Source: `.cursor/skills/inter-service-communication/SKILL.md` - Source file đầy đủ
|
||||
@@ -1,344 +0,0 @@
|
||||
# Quy Trình Phát Triển Microservices (Microservices Development Process)
|
||||
|
||||
Standard development process for creating and maintaining microservices in GoodGo platform. Use when creating new services, migrating services, refactoring services, or planning service implementations.
|
||||
> Quy trình phát triển chuẩn để tạo và duy trì microservices trong nền tảng GoodGo. Sử dụng khi tạo services mới, migrate services, refactor services, hoặc lập kế hoạch implement services.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
The microservices development process provides a structured 8-phase approach to building services, ensuring comprehensive coverage from planning to deployment.
|
||||
|
||||
Quy trình phát triển microservices cung cấp cách tiếp cận có cấu trúc 8 giai đoạn để xây dựng services, đảm bảo bao phủ toàn diện từ lập kế hoạch đến triển khai.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Tạo microservice mới từ đầu
|
||||
- Migrate hoặc refactor service hiện có
|
||||
- Lập kế hoạch implement service với nhiều giai đoạn
|
||||
- Đảm bảo bao phủ toàn diện tất cả các khía cạnh phát triển
|
||||
|
||||
## Quy Trình 8 Giai Đoạn
|
||||
|
||||
The microservices development process follows these phases:
|
||||
Quy trình phát triển microservices theo các giai đoạn sau:
|
||||
|
||||
1. **Planning & Impact Analysis** - Define scope, impact, dependencies / Định nghĩa scope, impact, dependencies
|
||||
2. **Foundation Setup** - Service structure, configs, infrastructure / Cấu trúc service, configs, infrastructure
|
||||
3. **Core Implementation** - Business logic, APIs, data layer / Business logic, APIs, data layer
|
||||
4. **Integration** - Routes, middleware, external services / Routes, middleware, external services
|
||||
5. **Testing** - Unit, integration, E2E tests / Unit, integration, E2E tests
|
||||
6. **Documentation** - API docs, README, guides / API docs, README, guides
|
||||
7. **Cleanup & Verification** - Remove temporary files, verify completeness / Xóa file tạm, xác minh hoàn chỉnh
|
||||
8. **Deployment** - Staging deployment, production deployment / Staging deployment, production deployment
|
||||
|
||||
### Biểu Đồ Luồng Quy Trình / Process Flow Diagram
|
||||
|
||||
This diagram shows the complete 8-phase development process with decision points and feedback loops.
|
||||
Biểu đồ này hiển thị quy trình phát triển 8 giai đoạn đầy đủ với các điểm quyết định và vòng lặp phản hồi.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Luồng Chi Tiết Các Giai Đoạn / Detailed Phase Flow
|
||||
|
||||
This diagram breaks down the tasks within each phase and shows the sequential flow between phases.
|
||||
Biểu đồ này phân tích các nhiệm vụ trong từng giai đoạn và hiển thị luồng tuần tự giữa các giai đoạn.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Các Giai Đoạn Chính
|
||||
|
||||
### Phase 1: Planning & Impact Analysis / Giai Đoạn 1: Lập Kế Hoạch & Phân Tích Tác Động
|
||||
|
||||
**Scope Definition / Định Nghĩa Scope:**
|
||||
- Service Purpose: What business capability does it provide? / Mục đích service: Cung cấp khả năng kinh doanh gì?
|
||||
- API Surface: What endpoints are needed? / API Surface: Cần endpoints nào?
|
||||
- Data Models: What data structures are required? / Data Models: Cần cấu trúc dữ liệu nào?
|
||||
- Dependencies: What services/packages does it depend on? / Dependencies: Phụ thuộc vào services/packages nào?
|
||||
|
||||
**Impact Analysis Checklist / Checklist Phân Tích Tác Động:**
|
||||
- [ ] Service directory: `services/service-name/`
|
||||
- [ ] Prisma schema: `services/service-name/prisma/schema.prisma`
|
||||
- [ ] Dockerfile: `services/service-name/Dockerfile`
|
||||
- [ ] Update `deployments/local/docker-compose.yml`
|
||||
- [ ] Update `infra/traefik/dynamic/routes.yml`
|
||||
|
||||
### Phase 2: Foundation Setup / Giai Đoạn 2: Thiết Lập Nền Tảng
|
||||
|
||||
**Service Structure / Cấu Trúc Service:**
|
||||
```bash
|
||||
# EN: Copy template
|
||||
# VI: Copy template
|
||||
cp -r services/_template services/new-service-name
|
||||
cd services/new-service-name
|
||||
```
|
||||
|
||||
**Acceptance Criteria / Tiêu Chí Chấp Nhận:**
|
||||
- [ ] Service starts: `pnpm dev` (health check passes) / Service khởi động: `pnpm dev` (health check pass)
|
||||
- [ ] Docker build succeeds / Docker build thành công
|
||||
- [ ] Service accessible via Traefik / Service có thể truy cập qua Traefik
|
||||
|
||||
### Phase 3: Core Implementation / Giai Đoạn 3: Implementation Cốt Lõi
|
||||
|
||||
**Implementation Order / Thứ Tự Implementation:**
|
||||
1. **DTOs** - Zod schemas for validation / Zod schemas cho validation
|
||||
2. **Repository** - Prisma-based data access / Data access dựa trên Prisma
|
||||
3. **Service** - Business logic / Business logic
|
||||
4. **Controller** - HTTP request handling / Xử lý HTTP requests
|
||||
5. **Module** - Wire up components / Kết nối các components
|
||||
|
||||
**Luồng Implementation / Implementation Flow:**
|
||||
|
||||
This diagram shows the step-by-step implementation order for each feature module within Phase 3.
|
||||
Biểu đồ này hiển thị thứ tự implementation từng bước cho mỗi feature module trong Giai đoạn 3.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Phase 4: Integration / Giai Đoạn 4: Tích Hợp
|
||||
|
||||
**Required Middlewares / Middlewares Bắt Buộc:**
|
||||
1. Correlation middleware
|
||||
2. Logging middleware
|
||||
3. Metrics middleware
|
||||
4. CORS middleware
|
||||
5. Rate limiting middleware
|
||||
6. Authentication middleware (if needed) / (nếu cần)
|
||||
7. Error middleware (always last) / (luôn cuối cùng)
|
||||
|
||||
### Phase 5: Testing / Giai Đoạn 5: Kiểm Thử
|
||||
|
||||
**Test Coverage Targets / Mục Tiêu Test Coverage:**
|
||||
- Minimum: 70% coverage / Tối thiểu: 70% coverage
|
||||
- Critical paths: 90%+ coverage / Đường dẫn quan trọng: 90%+ coverage
|
||||
- Repositories: 80%+ coverage
|
||||
- Services: 80%+ coverage
|
||||
|
||||
**Testing Checklist / Checklist Testing:**
|
||||
- [ ] Unit tests pass / Unit tests pass
|
||||
- [ ] Integration tests pass / Integration tests pass
|
||||
- [ ] E2E tests pass / E2E tests pass
|
||||
- [ ] Coverage meets thresholds / Coverage đạt ngưỡng
|
||||
|
||||
### Phase 6: Documentation / Giai Đoạn 6: Tài Liệu
|
||||
|
||||
**Required Documentation / Tài Liệu Bắt Buộc:**
|
||||
- Service README (bilingual EN/VI) / README service (song ngữ EN/VI)
|
||||
- Swagger/OpenAPI spec / Swagger/OpenAPI spec
|
||||
- Architecture docs (if complex) / Tài liệu kiến trúc (nếu phức tạp)
|
||||
|
||||
### Phase 7: Cleanup & Verification / Giai Đoạn 7: Dọn Dẹp & Xác Minh
|
||||
|
||||
**Luồng Quy Trình Xác Minh / Verification Process Flow:**
|
||||
|
||||
This diagram illustrates the cleanup and verification workflow for Phase 7, including the decision point for migrations and the comprehensive verification steps.
|
||||
Biểu đồ này minh họa quy trình dọn dẹp và xác minh cho Giai đoạn 7, bao gồm điểm quyết định cho migrations và các bước xác minh toàn diện.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start[Start Cleanup] --> Remove[Remove Temporary Files]
|
||||
Remove --> Update{Is Migration?}
|
||||
Update -->|Yes| RefUpdate[Update References<br/>grep & replace]
|
||||
Update -->|No| Verify[Run Verification]
|
||||
RefUpdate --> Verify
|
||||
|
||||
Verify --> TypeCheck[TypeScript Check]
|
||||
TypeCheck --> LintCheck[Lint Check]
|
||||
LintCheck --> TestCheck[Test Check]
|
||||
TestCheck --> BuildCheck[Build Check]
|
||||
BuildCheck --> DockerCheck[Docker Build]
|
||||
DockerCheck --> HealthCheck[Health Check]
|
||||
HealthCheck --> TraefikCheck[Traefik Check]
|
||||
TraefikCheck --> AllPass{All Pass?}
|
||||
|
||||
AllPass -->|No| Fix[Fix Issues]
|
||||
Fix --> Verify
|
||||
AllPass -->|Yes| Complete[Phase Complete]
|
||||
|
||||
style Remove fill:#ffe1e1
|
||||
style RefUpdate fill:#fff4e1
|
||||
style Verify fill:#e1ffe1
|
||||
style Complete fill:#d4edda
|
||||
```
|
||||
|
||||
**Verification Steps / Các Bước Xác Minh:**
|
||||
- [ ] TypeScript check: `pnpm typecheck` / Kiểm tra TypeScript
|
||||
- [ ] Lint check: `pnpm lint` / Kiểm tra lint
|
||||
- [ ] Test check: `pnpm test` / Kiểm tra test
|
||||
- [ ] Build check: `pnpm build` / Kiểm tra build
|
||||
- [ ] Docker build succeeds / Docker build thành công
|
||||
|
||||
### Phase 8: Deployment / Giai Đoạn 8: Triển Khai
|
||||
|
||||
**Staging Deployment / Triển Khai Staging:**
|
||||
```bash
|
||||
# EN: Build and push Docker image
|
||||
# VI: Build và push Docker image
|
||||
docker build -t goodgo/service-name:latest .
|
||||
docker push goodgo/service-name:latest
|
||||
|
||||
# EN: Apply Kubernetes configs
|
||||
# VI: Áp dụng configs Kubernetes
|
||||
kubectl apply -f deployments/staging/kubernetes/service-name.yaml
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. **Always Plan First**: Complete impact analysis before coding / Luôn lập kế hoạch trước: Hoàn thành phân tích tác động trước khi code
|
||||
2. **Follow Phases**: Don't skip verification steps / Tuân theo các giai đoạn: Không bỏ qua các bước xác minh
|
||||
3. **Test Early**: Write tests alongside implementation / Test sớm: Viết tests cùng với implementation
|
||||
4. **Document as You Go**: Don't leave documentation for the end / Tài liệu khi làm: Không để tài liệu đến cuối
|
||||
5. **Verify Comprehensively**: Use checklists to ensure nothing is missed / Xác minh toàn diện: Sử dụng checklists để đảm bảo không bỏ sót
|
||||
|
||||
## Common Pitfalls to Avoid / Các Lỗi Thường Gặp Cần Tránh
|
||||
|
||||
1. **Skipping Impact Analysis**: Leads to missing updates / Bỏ qua phân tích tác động: Dẫn đến thiếu cập nhật
|
||||
2. **No Verification Steps**: Misses broken references / Không có bước xác minh: Bỏ sót references bị hỏng
|
||||
3. **Incomplete Testing**: Missing edge cases / Testing không đầy đủ: Thiếu edge cases
|
||||
4. **Poor Documentation**: Makes maintenance difficult / Tài liệu kém: Làm khó bảo trì
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Project Rules](./project-rules.md) - Architecture and conventions / Kiến trúc và conventions
|
||||
- [API Design](./api-design.md) - API design patterns / Các patterns thiết kế API
|
||||
- [Testing Patterns](./testing-patterns.md) - Testing best practices / Best practices về testing
|
||||
- [Documentation](./documentation.md) - Documentation guidelines / Hướng dẫn tài liệu
|
||||
- [Database Prisma](./database-prisma.md) - Prisma patterns / Các patterns Prisma
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- Service Template: `services/_template/` - Template service
|
||||
- Skill Source: `.cursor/skills/microservices-development-process/SKILL.md` - Source file đầy đủ
|
||||
@@ -1,462 +0,0 @@
|
||||
# Các Pattern Middleware
|
||||
|
||||
Express middleware patterns and best practices for GoodGo microservices including middleware chain ordering, custom middleware creation, authentication, validation, logging, and error handling middleware.
|
||||
> Các patterns và best practices về Express middleware cho GoodGo microservices bao gồm thứ tự middleware chain, tạo custom middleware, authentication, validation, logging, và error handling middleware.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Middleware functions are the fundamental building blocks of Express.js applications. They provide a way to execute code, make changes to requests and responses, end the request-response cycle, and call the next middleware. This guide covers middleware patterns, ordering, common middleware implementations, and best practices for GoodGo microservices.
|
||||
|
||||
Middleware functions là các building blocks cơ bản của ứng dụng Express.js. Chúng cung cấp cách để thực thi code, thay đổi requests và responses, kết thúc request-response cycle, và gọi middleware tiếp theo. Hướng dẫn này bao gồm các middleware patterns, thứ tự, common middleware implementations, và best practices cho GoodGo microservices.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use middleware patterns 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
|
||||
|
||||
Sử dụng middleware patterns khi:
|
||||
- Tạo custom Express middleware
|
||||
- Tổ chức middleware chains và thứ tự
|
||||
- Implement authentication/authorization middleware
|
||||
- Tạo request/response transformation middleware
|
||||
- Xử lý cross-cutting concerns (logging, metrics, validation)
|
||||
- Implement async middleware patterns
|
||||
- Testing middleware implementations
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Chữ Ký Function Middleware
|
||||
|
||||
Express middleware functions have a specific signature that receives request, response, and next function.
|
||||
|
||||
Express middleware functions có chữ ký cụ thể nhận request, response, và next function.
|
||||
|
||||
```typescript
|
||||
(req: Request, res: Response, next: NextFunction) => void | Promise<void>
|
||||
```
|
||||
|
||||
### Các Loại Middleware
|
||||
|
||||
1. **Application-level / Cấp Ứng Dụng**: Applied to all routes (`app.use()`)
|
||||
2. **Router-level / Cấp Router**: Applied to specific routes (`router.use()`)
|
||||
3. **Route-level / Cấp Route**: Applied to specific route handlers
|
||||
|
||||
### Thứ Tự Thực Thi Middleware
|
||||
|
||||
Middleware order is critical! Execution flows top-to-bottom in the order they are registered.
|
||||
|
||||
Thứ tự middleware rất quan trọng! Thực thi theo trình tự từ trên xuống dưới theo thứ tự đăng ký.
|
||||
|
||||
```
|
||||
Request → Middleware 1 → Middleware 2 → ... → Route Handler → Response
|
||||
```
|
||||
|
||||
Biểu đồ sau minh họa luồng middleware chain hoàn chỉnh trong GoodGo services:
|
||||
|
||||
The following diagram illustrates the complete middleware chain flow in GoodGo services:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([HTTP Request]) --> Security["Security Middleware<br/>Helmet, CORS"]
|
||||
Security --> RateLimit["Rate Limiting<br/>Middleware"]
|
||||
RateLimit --> Correlation["Correlation ID<br/>Middleware"]
|
||||
Correlation --> BodyParsing["Body Parsing<br/>JSON, URLEncoded, Cookies"]
|
||||
BodyParsing --> Logging["Request Logging<br/>Middleware"]
|
||||
Logging --> Metrics["Metrics Collection<br/>Middleware"]
|
||||
Metrics --> Routes["Route Handlers"]
|
||||
Routes -->|Success| Response([HTTP Response])
|
||||
Routes -->|Error| ErrorHandler["Error Handler<br/>Middleware"]
|
||||
Routes -->|Not Found| NotFound["Not Found<br/>Handler"]
|
||||
ErrorHandler --> Response
|
||||
NotFound --> Response
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
### Thứ Tự Middleware Chain
|
||||
|
||||
Standard middleware order in GoodGo services ensures proper request processing flow.
|
||||
|
||||
Thứ tự middleware chuẩn trong GoodGo services đảm bảo luồng xử lý request đúng cách.
|
||||
|
||||
```typescript
|
||||
// Bảo mật
|
||||
app.use(helmet());
|
||||
app.use(cors({ ... }));
|
||||
|
||||
// Giới hạn tốc độ
|
||||
app.use('/api', rateLimitMiddleware);
|
||||
|
||||
// Correlation ID (sớm cho tracing)
|
||||
app.use(correlationMiddleware());
|
||||
|
||||
// Phân tích body
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
|
||||
// Ghi log request
|
||||
app.use(loggerMiddleware);
|
||||
|
||||
// Metrics
|
||||
app.use(metricsMiddleware);
|
||||
|
||||
// Routes
|
||||
app.use(createRouter());
|
||||
|
||||
// Xử lý lỗi (LUÔN CUỐI CÙNG)
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/main.ts`](../../../services/iam-service/src/main.ts)
|
||||
|
||||
### Pattern Correlation Middleware
|
||||
|
||||
Adds correlation ID for request tracing across services.
|
||||
|
||||
Thêm correlation ID để tracing requests qua các services.
|
||||
|
||||
```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();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/middlewares/correlation.middleware.ts`](../../../services/iam-service/src/middlewares/correlation.middleware.ts)
|
||||
|
||||
### Pattern Authentication Middleware
|
||||
|
||||
Verifies JWT tokens and attaches user information to request.
|
||||
|
||||
Xác minh JWT tokens và gắn thông tin người dùng vào 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' });
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Biểu đồ tuần tự sau minh họa luồng authentication middleware:
|
||||
|
||||
The following sequence diagram illustrates the authentication middleware flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant AuthMW as Authentication Middleware
|
||||
participant JWTService as JWT Service
|
||||
participant RouteHandler as Route Handler
|
||||
|
||||
Client->>AuthMW: HTTP Request with Token
|
||||
AuthMW->>AuthMW: Extract token from headers
|
||||
alt Token exists
|
||||
AuthMW->>JWTService: Verify token
|
||||
alt Token valid
|
||||
JWTService-->>AuthMW: Payload (user data)
|
||||
AuthMW->>AuthMW: Attach user to req.user
|
||||
AuthMW->>RouteHandler: next() - Continue
|
||||
RouteHandler->>Client: HTTP Response (200)
|
||||
else Token invalid
|
||||
JWTService-->>AuthMW: Verification error
|
||||
AuthMW->>Client: HTTP Response (401 Unauthorized)
|
||||
end
|
||||
else No token
|
||||
AuthMW->>Client: HTTP Response (401 Unauthorized)
|
||||
end
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/middlewares/auth.middleware.ts`](../../../services/iam-service/src/middlewares/auth.middleware.ts)
|
||||
|
||||
### Pattern Validation Middleware
|
||||
|
||||
Validates request data using Zod schemas.
|
||||
|
||||
Validate dữ liệu request sử dụng Zod schemas.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Biểu đồ tuần tự sau minh họa luồng validation middleware:
|
||||
|
||||
The following sequence diagram shows the validation middleware flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant ValidateMW as Validation Middleware
|
||||
participant ZodSchema as Zod Schema
|
||||
participant RouteHandler as Route Handler
|
||||
|
||||
Client->>ValidateMW: HTTP Request with Data
|
||||
ValidateMW->>ValidateMW: Extract data from req[property]
|
||||
ValidateMW->>ZodSchema: schema.parse(data)
|
||||
alt Validation successful
|
||||
ZodSchema-->>ValidateMW: Validated data
|
||||
ValidateMW->>ValidateMW: Replace req[property] with validated data
|
||||
ValidateMW->>RouteHandler: next() - Continue
|
||||
RouteHandler->>Client: HTTP Response (200)
|
||||
else Validation failed
|
||||
ZodSchema-->>ValidateMW: ZodError with details
|
||||
ValidateMW->>ValidateMW: Format error response
|
||||
ValidateMW->>Client: HTTP Response (400 Validation Error)
|
||||
end
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/middlewares/validation.middleware.ts`](../../../services/iam-service/src/middlewares/validation.middleware.ts)
|
||||
|
||||
### Pattern Async Middleware
|
||||
|
||||
Handle async operations properly to catch promise rejections.
|
||||
|
||||
Xử lý async operations đúng cách để catch promise rejections.
|
||||
|
||||
```typescript
|
||||
export const asyncHandler = (fn: Function) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
};
|
||||
|
||||
// Usage
|
||||
app.get('/users', asyncHandler(async (req, res) => {
|
||||
const users = await userService.findAll();
|
||||
res.json({ success: true, data: users });
|
||||
}));
|
||||
```
|
||||
|
||||
Biểu đồ tuần tự sau minh họa xử lý lỗi async middleware:
|
||||
|
||||
The following sequence diagram illustrates async middleware error handling:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant AsyncMW as Async Middleware Wrapper
|
||||
participant AsyncHandler as Async Route Handler
|
||||
participant ErrorHandler as Error Handler
|
||||
|
||||
Client->>AsyncMW: HTTP Request
|
||||
AsyncMW->>AsyncHandler: Execute async function
|
||||
alt Async operation succeeds
|
||||
AsyncHandler->>AsyncHandler: Process request
|
||||
AsyncHandler->>Client: HTTP Response (200)
|
||||
else Async operation fails
|
||||
AsyncHandler-->>AsyncMW: Promise rejection (Error)
|
||||
AsyncMW->>ErrorHandler: next(error)
|
||||
ErrorHandler->>ErrorHandler: Format error response
|
||||
ErrorHandler->>Client: HTTP Response (500 Error)
|
||||
end
|
||||
```
|
||||
|
||||
### Pattern Logging Middleware
|
||||
|
||||
Log request details with timing and correlation tracking.
|
||||
|
||||
Ghi log chi tiết request với timing và correlation tracking.
|
||||
|
||||
```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();
|
||||
};
|
||||
```
|
||||
|
||||
Biểu đồ tuần tự sau minh họa cách logging middleware theo dõi vòng đời request:
|
||||
|
||||
The following sequence diagram illustrates how logging middleware tracks request lifecycle:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant LogMW as Logging Middleware
|
||||
participant RouteHandler as Route Handler
|
||||
participant Logger as Logger Service
|
||||
participant Response as Response Object
|
||||
|
||||
Client->>LogMW: HTTP Request
|
||||
LogMW->>LogMW: Record startTime = Date.now()
|
||||
LogMW->>RouteHandler: next() - Continue chain
|
||||
|
||||
RouteHandler->>RouteHandler: Process request
|
||||
RouteHandler->>Response: res.json(data) or res.send()
|
||||
|
||||
Response->>Response: Set statusCode, send response
|
||||
Response->>Response: Emit 'finish' event
|
||||
|
||||
Response->>LogMW: 'finish' event triggered
|
||||
LogMW->>LogMW: Calculate duration = Date.now() - startTime
|
||||
LogMW->>Logger: logger.info('Request completed', {<br/>method, url, statusCode,<br/>duration, correlationId})
|
||||
Logger->>Logger: Write structured log entry
|
||||
|
||||
Response->>Client: HTTP Response
|
||||
```
|
||||
|
||||
### Chuyển Đổi Request/Response
|
||||
|
||||
Transform request or response data in middleware.
|
||||
|
||||
Chuyển đổi dữ liệu request hoặc response trong middleware.
|
||||
|
||||
```typescript
|
||||
export const transformResponse = () => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
res.json = function(data: any) {
|
||||
const transformed = {
|
||||
success: true,
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
return originalJson(transformed);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Biểu đồ tuần tự sau minh họa cách middleware chuyển đổi request và response hoạt động:
|
||||
|
||||
The following sequence diagram shows how request and response transformation middleware works:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant TransformMW as Transform Middleware
|
||||
participant RouteHandler as Route Handler
|
||||
participant Response as Response Object
|
||||
|
||||
Client->>TransformMW: HTTP Request
|
||||
Note over TransformMW: Intercept res.json()
|
||||
TransformMW->>TransformMW: Store original res.json
|
||||
TransformMW->>TransformMW: Override res.json with transformation logic
|
||||
TransformMW->>RouteHandler: next() - Continue chain
|
||||
|
||||
RouteHandler->>RouteHandler: Process request, Generate data
|
||||
RouteHandler->>Response: res.json(rawData)
|
||||
|
||||
Note over Response: Transformed res.json executes
|
||||
Response->>Response: Wrap data: success, data, timestamp
|
||||
Response->>Client: HTTP Response (transformed)
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Order Matters / Thứ Tự Quan Trọng**: Đặt middleware đúng thứ tự (security → correlation → parsing → logging → routes → errors)
|
||||
2. **Error Handling / Xử Lý Lỗi**: Luôn xử lý errors và gọi `next(error)` cho error middleware
|
||||
3. **Async Support / Hỗ Trợ Async**: Wrap async middleware đúng cách để catch promise rejections
|
||||
4. **Early Returns / Return Sớm**: Sử dụng early returns cho validation failures (không gọi `next()`)
|
||||
5. **Request Extension / Mở Rộng Request**: Sử dụng TypeScript declaration merging để mở rộng Request type
|
||||
6. **Conditional Logic / Logic Có Điều Kiện**: Sử dụng middleware factories cho conditional middleware
|
||||
7. **Reusability / Tái Sử Dụng**: Tạo reusable middleware functions
|
||||
8. **Performance / Hiệu Suất**: Giữ middleware lightweight, tránh heavy operations
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **Wrong Order / Thứ Tự Sai**: Đặt middleware sai thứ tự (ví dụ: error handler trước routes)
|
||||
2. **Not Calling Next / Không Gọi Next**: Quên gọi `next()` hoặc `next(error)`
|
||||
3. **Async Errors / Lỗi Async**: Không xử lý promise rejections trong async middleware
|
||||
4. **Early Return Issues / Vấn Đề Return Sớm**: Gọi `next()` sau khi đã send response
|
||||
5. **Type Safety / An Toàn Kiểu**: Không mở rộng Express Request type đúng cách
|
||||
6. **Performance / Hiệu Suất**: Thực hiện heavy operations trong middleware
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Middleware Không Thực Thi
|
||||
|
||||
**Problem / Vấn Đề**: Middleware không được gọi
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Kiểm tra thứ tự middleware, đảm bảo nó được thêm trước routes
|
||||
- Verify `next()` được gọi
|
||||
- Check middleware registration order
|
||||
|
||||
### Lỗi Async Không Được Bắt
|
||||
|
||||
**Problem / Vấn Đề**: Unhandled promise rejections trong async middleware
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Sử dụng `asyncHandler` wrapper
|
||||
- Hoặc wrap async code trong try-catch với `next(error)`
|
||||
|
||||
**Example / Ví Dụ**:
|
||||
```typescript
|
||||
// ❌ Bad: Unhandled promise rejection
|
||||
app.get('/users', async (req, res) => {
|
||||
const users = await userService.findAll(); // Error not caught!
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
// ✅ Good: Proper error handling
|
||||
app.get('/users', asyncHandler(async (req, res) => {
|
||||
const users = await userService.findAll();
|
||||
res.json(users);
|
||||
}));
|
||||
```
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Correlation Middleware](../../../services/iam-service/src/middlewares/correlation.middleware.ts) - Correlation ID implementation
|
||||
- [Auth Middleware](../../../services/iam-service/src/middlewares/auth.middleware.ts) - Authentication middleware
|
||||
- [Validation Middleware](../../../services/iam-service/src/middlewares/validation.middleware.ts) - Request validation
|
||||
- [Error Middleware](../../../services/iam-service/src/middlewares/error.middleware.ts) - Error handling
|
||||
- [Metrics Middleware](../../../services/iam-service/src/middlewares/metrics.middleware.ts) - Metrics collection
|
||||
- [Error Handling](./error-handling-patterns.md) - Error middleware patterns
|
||||
@@ -1,723 +0,0 @@
|
||||
# Khả Năng Quan Sát & Giám Sát
|
||||
|
||||
Observability and monitoring patterns for GoodGo microservices. Use when adding metrics, implementing logging, setting up tracing, creating health checks, or debugging production issues.
|
||||
> Các pattern observability và monitoring cho microservices GoodGo. Sử dụng khi thêm metrics, triển khai logging, thiết lập tracing, tạo health checks, hoặc debug các vấn đề production.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
This skill covers the three pillars of observability (logs, metrics, traces) and how to implement them in GoodGo microservices. It includes structured logging, Prometheus metrics, distributed tracing with OpenTelemetry, health checks, and error tracking.
|
||||
|
||||
Skill này bao gồm ba trụ cột của observability (logs, metrics, traces) và cách triển khai chúng trong microservices GoodGo. Nó bao gồm structured logging, Prometheus metrics, distributed tracing với OpenTelemetry, health checks, và error tracking.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
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
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Thiết lập hạ tầng logging
|
||||
- Triển khai thu thập metrics
|
||||
- Thêm distributed tracing
|
||||
- Tạo health check endpoints
|
||||
- Thiết lập monitoring dashboards
|
||||
- Debug các vấn đề production
|
||||
- Triển khai alerting rules
|
||||
- Phân tích performance bottlenecks
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Ba Trụ Cột Của Observability
|
||||
|
||||
1. **Logs**: Event records for debugging and auditing
|
||||
2. **Metrics**: Numerical measurements over time (counters, gauges, histograms)
|
||||
3. **Traces**: Request flow across services (distributed tracing)
|
||||
|
||||
**VI**:
|
||||
1. **Logs**: Bản ghi sự kiện để debug và audit
|
||||
2. **Metrics**: Đo lường số học theo thời gian (counters, gauges, histograms)
|
||||
3. **Traces**: Luồng request qua các services (distributed tracing)
|
||||
|
||||
### Công Nghệ
|
||||
|
||||
- **Logging**: `@goodgo/logger` (Pino-based structured logging)
|
||||
- **Metrics**: Prometheus + Grafana
|
||||
- **Tracing**: OpenTelemetry + Jaeger (`@goodgo/tracing`)
|
||||
- **Correlation IDs**: Request tracking across services
|
||||
|
||||
**VI**:
|
||||
- **Logging**: `@goodgo/logger` (structured logging dựa trên Pino)
|
||||
- **Metrics**: Prometheus + Grafana
|
||||
- **Tracing**: OpenTelemetry + Jaeger (`@goodgo/tracing`)
|
||||
- **Correlation IDs**: Theo dõi request qua các services
|
||||
|
||||
### Kiến Trúc Observability Stack
|
||||
|
||||
Observability stack bao gồm ba trụ cột hoạt động cùng nhau để cung cấp khả năng quan sát toàn diện vào hành vi hệ thống:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Application Layer"
|
||||
App[Microservice]
|
||||
end
|
||||
|
||||
subgraph "Three Pillars of Observability"
|
||||
Logs[Logs<br/>Winston/Pino]
|
||||
Metrics[Metrics<br/>Prometheus]
|
||||
Traces[Traces<br/>OpenTelemetry]
|
||||
end
|
||||
|
||||
subgraph "Aggregation & Storage"
|
||||
Loki[Loki<br/>Log Aggregation]
|
||||
Prom[Prometheus<br/>Metrics Storage]
|
||||
Jaeger[Jaeger<br/>Trace Storage]
|
||||
end
|
||||
|
||||
subgraph "Visualization & Alerting"
|
||||
Grafana[Grafana<br/>Dashboards]
|
||||
AlertManager[AlertManager<br/>Alerts]
|
||||
end
|
||||
|
||||
App -->|Structured Logs| Logs
|
||||
App -->|HTTP Metrics| Metrics
|
||||
App -->|Distributed Spans| Traces
|
||||
|
||||
Logs -->|Collect| Loki
|
||||
Metrics -->|Scrape /metrics| Prom
|
||||
Traces -->|Export| Jaeger
|
||||
|
||||
Loki -->|Query| Grafana
|
||||
Prom -->|Query| Grafana
|
||||
Prom -->|Alerts| AlertManager
|
||||
Jaeger -->|Query| Grafana
|
||||
|
||||
style App fill:#e1f5ff
|
||||
style Logs fill:#fff4e1
|
||||
style Metrics fill:#e1ffe1
|
||||
style Traces fill:#ffe1f5
|
||||
style Grafana fill:#e1e1ff
|
||||
```
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Logging Có Cấu Trúc
|
||||
|
||||
Use structured logging with correlation IDs for request tracking.
|
||||
|
||||
Sử dụng structured logging với correlation IDs để theo dõi request.
|
||||
|
||||
**Ví dụ từ codebase**: [`services/iam-service/src/middlewares/logger.middleware.ts`](../../../services/iam-service/src/middlewares/logger.middleware.ts)
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '@goodgo/logger';
|
||||
import { getCorrelationId, getRequestId } from './correlation.middleware';
|
||||
|
||||
export const requestLogger = (req: Request, res: Response, next: NextFunction): void => {
|
||||
// Bỏ qua logging chi tiết cho health checks và metrics
|
||||
if (req.path.startsWith('/health') || req.path.startsWith('/metrics')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
const correlationId = getCorrelationId(req);
|
||||
const requestId = getRequestId(req);
|
||||
|
||||
logger.info('Request processed / Request đã xử lý', {
|
||||
correlationId,
|
||||
requestId,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
statusCode: res.statusCode,
|
||||
duration: `${duration}ms`,
|
||||
contentLength: res.get('Content-Length') || 0,
|
||||
userAgent: req.get('User-Agent'),
|
||||
ip: req.ip,
|
||||
userId: (req as any).user?.userId,
|
||||
});
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
```
|
||||
|
||||
### Luồng Logging
|
||||
|
||||
Luồng logging cho thấy cách các request được log với correlation IDs và chảy qua hệ thống:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Service as Microservice
|
||||
participant Logger as Winston/Pino Logger
|
||||
participant Aggregator as Log Aggregator<br/>(Loki)
|
||||
participant Dashboard as Grafana Dashboard
|
||||
|
||||
Client->>Service: HTTP Request<br/>(with x-correlation-id)
|
||||
Service->>Service: Generate/Extract<br/>Correlation ID
|
||||
Service->>Logger: Log Request Start<br/>{correlationId, method, url}
|
||||
Service->>Service: Process Request
|
||||
Service->>Logger: Log Business Event<br/>{correlationId, event, data}
|
||||
Service->>Client: HTTP Response<br/>(with x-correlation-id)
|
||||
Service->>Logger: Log Request End<br/>{correlationId, status, duration}
|
||||
|
||||
Logger->>Aggregator: Send Structured Logs<br/>(JSON format)
|
||||
Aggregator->>Dashboard: Index & Store Logs
|
||||
Dashboard->>Dashboard: Query by correlationId<br/>to trace request flow
|
||||
```
|
||||
|
||||
### Correlation IDs
|
||||
|
||||
Use correlation IDs to track requests across services.
|
||||
|
||||
Sử dụng correlation IDs để theo dõi request qua các services.
|
||||
|
||||
**Ví dụ từ codebase**: [`services/iam-service/src/middlewares/correlation.middleware.ts`](../../../services/iam-service/src/middlewares/correlation.middleware.ts)
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export const CORRELATION_ID_HEADER = 'x-correlation-id';
|
||||
export const REQUEST_ID_HEADER = 'x-request-id';
|
||||
|
||||
export const correlationMiddleware = (
|
||||
options: {
|
||||
headerName?: string;
|
||||
generateId?: () => string;
|
||||
skipPaths?: string[];
|
||||
} = {}
|
||||
) => {
|
||||
const {
|
||||
headerName = CORRELATION_ID_HEADER,
|
||||
generateId = randomUUID,
|
||||
skipPaths = ['/health', '/metrics', '/favicon.ico'],
|
||||
} = options;
|
||||
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
// Lấy correlation ID từ header hoặc tạo mới
|
||||
const correlationId = req.headers[headerName.toLowerCase()] as string || generateId();
|
||||
const requestId = generateId();
|
||||
|
||||
// Gắn vào request object
|
||||
req.correlationId = correlationId;
|
||||
req.requestId = requestId;
|
||||
|
||||
// Thêm vào response headers
|
||||
res.setHeader(headerName, correlationId);
|
||||
res.setHeader(REQUEST_ID_HEADER, requestId);
|
||||
|
||||
// Log request start
|
||||
logger.info('Request started / Request bắt đầu', {
|
||||
correlationId,
|
||||
requestId,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
userAgent: req.get('User-Agent'),
|
||||
ip: req.ip,
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Thu Thập Metrics
|
||||
|
||||
Expose Prometheus metrics for monitoring and alerting.
|
||||
|
||||
Expose Prometheus metrics để monitoring và alerting.
|
||||
|
||||
**Ví dụ từ codebase**: [`services/iam-service/src/middlewares/metrics.middleware.ts`](../../../services/iam-service/src/middlewares/metrics.middleware.ts)
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import client from 'prom-client';
|
||||
import { getCorrelationId } from './correlation.middleware';
|
||||
|
||||
// Tạo Registry để đăng ký các metrics
|
||||
const register = client.register;
|
||||
|
||||
// Thu thập các metrics mặc định
|
||||
client.collectDefaultMetrics({ register });
|
||||
|
||||
// Tạo histogram cho thời lượng request HTTP
|
||||
const httpRequestDurationSeconds = new client.Histogram({
|
||||
name: 'http_request_duration_seconds',
|
||||
help: 'Duration of HTTP requests in seconds',
|
||||
labelNames: ['method', 'route', 'status_code', 'correlation_id'],
|
||||
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
|
||||
});
|
||||
|
||||
// Tạo counter cho tổng số request HTTP
|
||||
const httpRequestsTotal = new client.Counter({
|
||||
name: 'http_requests_total',
|
||||
help: 'Total number of HTTP requests',
|
||||
labelNames: ['method', 'route', 'status_code'],
|
||||
});
|
||||
|
||||
// Tạo gauge cho active requests
|
||||
const activeRequests = new client.Gauge({
|
||||
name: 'http_active_requests',
|
||||
help: 'Number of active HTTP requests',
|
||||
});
|
||||
|
||||
// Tạo counter cho lỗi HTTP request
|
||||
const httpRequestErrors = new client.Counter({
|
||||
name: 'http_request_errors_total',
|
||||
help: 'Total number of HTTP request errors',
|
||||
labelNames: ['method', 'route', 'error_type'],
|
||||
});
|
||||
|
||||
export const metricsMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
// Tăng active requests
|
||||
activeRequests.inc();
|
||||
|
||||
// Bắt đầu bấm giờ
|
||||
const start = process.hrtime.bigint();
|
||||
|
||||
res.on('finish', () => {
|
||||
// Giảm active requests
|
||||
activeRequests.dec();
|
||||
|
||||
// Tính toán thời lượng
|
||||
const end = process.hrtime.bigint();
|
||||
const durationInSeconds = Number(end - start) / 1e9;
|
||||
|
||||
// Chuẩn hóa path để tránh high cardinality
|
||||
const route = normalizeRoutePath(req);
|
||||
const correlationId = getCorrelationId(req) || 'unknown';
|
||||
|
||||
// Ghi nhận thời lượng
|
||||
httpRequestDurationSeconds
|
||||
.labels(req.method, route, res.statusCode.toString(), correlationId)
|
||||
.observe(durationInSeconds);
|
||||
|
||||
// Tăng bộ đếm request
|
||||
httpRequestsTotal
|
||||
.labels(req.method, route, res.statusCode.toString())
|
||||
.inc();
|
||||
|
||||
// Theo dõi lỗi
|
||||
if (res.statusCode >= 400) {
|
||||
const errorType = res.statusCode >= 500 ? 'server_error' : 'client_error';
|
||||
httpRequestErrors
|
||||
.labels(req.method, route, errorType)
|
||||
.inc();
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Chuẩn hóa route path để ngăn high cardinality metrics
|
||||
function normalizeRoutePath(req: Request): string {
|
||||
if (req.route && req.route.path) {
|
||||
return req.route.path;
|
||||
}
|
||||
|
||||
let path = req.path;
|
||||
// Thay thế UUIDs và numeric IDs bằng placeholders
|
||||
path = path.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':uuid');
|
||||
path = path.replace(/\d+/g, ':id');
|
||||
|
||||
return path;
|
||||
}
|
||||
```
|
||||
|
||||
### Luồng Thu Thập Metrics
|
||||
|
||||
Metrics được thu thập từ services và expose cho Prometheus để monitoring và alerting:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Service Instance"
|
||||
App[Application]
|
||||
Middleware[Metrics Middleware]
|
||||
Registry[Prometheus Registry]
|
||||
Endpoint[/metrics Endpoint]
|
||||
end
|
||||
|
||||
subgraph "Metrics Types"
|
||||
Counter[Counter<br/>http_requests_total]
|
||||
Gauge[Gauge<br/>active_users]
|
||||
Histogram[Histogram<br/>request_duration]
|
||||
end
|
||||
|
||||
subgraph "Collection"
|
||||
Prometheus[Prometheus<br/>Scraper]
|
||||
end
|
||||
|
||||
subgraph "Storage & Query"
|
||||
PromDB[(Prometheus<br/>Time Series DB)]
|
||||
end
|
||||
|
||||
subgraph "Visualization"
|
||||
Grafana[Grafana<br/>Dashboards]
|
||||
Alerts[AlertManager<br/>Rules]
|
||||
end
|
||||
|
||||
App -->|HTTP Request| Middleware
|
||||
Middleware -->|Record| Counter
|
||||
Middleware -->|Record| Histogram
|
||||
App -->|Update| Gauge
|
||||
|
||||
Counter --> Registry
|
||||
Gauge --> Registry
|
||||
Histogram --> Registry
|
||||
Registry --> Endpoint
|
||||
|
||||
Prometheus -->|Scrape every 15s| Endpoint
|
||||
Prometheus -->|Store| PromDB
|
||||
|
||||
PromDB -->|Query| Grafana
|
||||
PromDB -->|Evaluate| Alerts
|
||||
Alerts -->|Trigger| Grafana
|
||||
|
||||
style App fill:#e1f5ff
|
||||
style Prometheus fill:#ffe1e1
|
||||
style Grafana fill:#e1e1ff
|
||||
```
|
||||
|
||||
### Distributed Tracing
|
||||
|
||||
Use OpenTelemetry for distributed tracing across services.
|
||||
|
||||
Sử dụng OpenTelemetry cho distributed tracing qua các services.
|
||||
|
||||
**Ví dụ từ codebase**: [`packages/tracing/src/index.ts`](../../../packages/tracing/src/index.ts)
|
||||
|
||||
```typescript
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
||||
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||
|
||||
export interface TracingConfig {
|
||||
serviceName: string;
|
||||
jaegerEndpoint?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export const initTracing = (config: TracingConfig): NodeSDK | null => {
|
||||
if (config.enabled === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tạo Jaeger exporter nếu endpoint được cung cấp
|
||||
const jaegerExporter = config.jaegerEndpoint
|
||||
? new JaegerExporter({
|
||||
endpoint: config.jaegerEndpoint,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
// Khởi tạo OpenTelemetry NodeSDK với auto-instrumentations
|
||||
const sdk = new NodeSDK({
|
||||
resource: new Resource({
|
||||
[SemanticResourceAttributes.SERVICE_NAME]: config.serviceName,
|
||||
}),
|
||||
traceExporter: jaegerExporter,
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
|
||||
// Khởi động tracing SDK
|
||||
sdk.start();
|
||||
|
||||
return sdk;
|
||||
};
|
||||
```
|
||||
|
||||
**Cách sử dụng trong service**:
|
||||
|
||||
```typescript
|
||||
// services/iam-service/src/main.ts
|
||||
import { initTracing } from '@goodgo/tracing';
|
||||
|
||||
// Khởi tạo tracing
|
||||
if (process.env.TRACING_ENABLED === 'true') {
|
||||
initTracing({
|
||||
serviceName: process.env.SERVICE_NAME || 'iam-service',
|
||||
jaegerEndpoint: process.env.JAEGER_ENDPOINT,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Luồng Distributed Tracing
|
||||
|
||||
Distributed tracing theo dõi requests qua nhiều services sử dụng OpenTelemetry:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway as API Gateway
|
||||
participant ServiceA as Service A<br/>(User Service)
|
||||
participant ServiceB as Service B<br/>(Order Service)
|
||||
participant DB as Database
|
||||
participant Jaeger as Jaeger<br/>Collector
|
||||
|
||||
Client->>Gateway: Request<br/>(Trace ID: abc123)
|
||||
Gateway->>Gateway: Create Root Span<br/>Span: gateway.request
|
||||
Gateway->>ServiceA: HTTP Call<br/>(Trace ID: abc123,<br/>Span ID: span-1)
|
||||
|
||||
ServiceA->>ServiceA: Create Child Span<br/>Span: user.getById
|
||||
ServiceA->>DB: Query User<br/>(Trace ID: abc123,<br/>Span ID: span-2)
|
||||
DB-->>ServiceA: User Data
|
||||
ServiceA->>ServiceA: End Span span-2
|
||||
ServiceA->>ServiceB: HTTP Call<br/>(Trace ID: abc123,<br/>Span ID: span-3)
|
||||
|
||||
ServiceB->>ServiceB: Create Child Span<br/>Span: order.getByUserId
|
||||
ServiceB->>DB: Query Orders<br/>(Trace ID: abc123,<br/>Span ID: span-4)
|
||||
DB-->>ServiceB: Orders Data
|
||||
ServiceB->>ServiceB: End Span span-4
|
||||
ServiceB->>ServiceB: End Span span-3
|
||||
ServiceB-->>ServiceA: Response
|
||||
ServiceA->>ServiceA: End Span span-1
|
||||
ServiceA-->>Gateway: Response
|
||||
Gateway->>Gateway: End Span gateway.request
|
||||
Gateway-->>Client: Final Response
|
||||
|
||||
ServiceA->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
ServiceB->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
Gateway->>Jaeger: Export Spans<br/>(Trace ID: abc123)
|
||||
|
||||
Note over Jaeger: All spans linked by<br/>Trace ID: abc123
|
||||
```
|
||||
|
||||
### Kiểm Tra Sức Khỏe
|
||||
|
||||
Implement liveness and readiness probes for Kubernetes.
|
||||
|
||||
Triển khai liveness và readiness probes cho Kubernetes.
|
||||
|
||||
**Ví dụ từ codebase**: [`services/iam-service/src/modules/health/health.controller.ts`](../../../services/iam-service/src/modules/health/health.controller.ts)
|
||||
|
||||
```typescript
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma } from '../../config/database.config';
|
||||
import { ApiResponse } from '@goodgo/types';
|
||||
|
||||
export class HealthController {
|
||||
/**
|
||||
Basic liveness probe
|
||||
* VI: Kiểm tra liveness cơ bản
|
||||
*/
|
||||
health = async (_req: Request, res: Response): Promise<void> => {
|
||||
const response: ApiResponse<{ status: string; timestamp: string }> = {
|
||||
success: true,
|
||||
data: {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
/**
|
||||
Readiness probe (checks database connection)
|
||||
* VI: Kiểm tra readiness (kiểm tra kết nối database)
|
||||
*/
|
||||
ready = async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// Kiểm tra kết nối database
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
res.json({
|
||||
success: true,
|
||||
data: { status: 'ready' },
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
// Trả về 503 nếu database chưa sẵn sàng
|
||||
res.status(503).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'HEALTH_001',
|
||||
message: 'Service not ready',
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Alias for health check
|
||||
* VI: Alias cho kiểm tra sức khỏe
|
||||
*/
|
||||
live = async (_req: Request, res: Response): Promise<void> => {
|
||||
res.json({
|
||||
success: true,
|
||||
data: { status: 'live' },
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Logging
|
||||
|
||||
- Use structured logging (JSON format)
|
||||
- Include correlation IDs for request tracing
|
||||
- Log at appropriate levels (ERROR, WARN, INFO, DEBUG)
|
||||
- Avoid logging sensitive data (passwords, tokens, PII)
|
||||
- Use consistent log format across services
|
||||
|
||||
**VI**:
|
||||
- Sử dụng structured logging (định dạng JSON)
|
||||
- Bao gồm correlation IDs để theo dõi request
|
||||
- Log ở mức độ phù hợp (ERROR, WARN, INFO, DEBUG)
|
||||
- Tránh log dữ liệu nhạy cảm (mật khẩu, tokens, PII)
|
||||
- Sử dụng format log nhất quán giữa các services
|
||||
|
||||
### Metrics
|
||||
|
||||
- Use standard metric types (Counter, Gauge, Histogram)
|
||||
- Keep cardinality low (avoid high-cardinality labels)
|
||||
- Define SLIs and SLOs for critical paths
|
||||
- Monitor business metrics, not just technical ones
|
||||
- Normalize route paths to prevent high cardinality
|
||||
|
||||
**VI**:
|
||||
- Sử dụng các loại metric chuẩn (Counter, Gauge, Histogram)
|
||||
- Giữ cardinality thấp (tránh high-cardinality labels)
|
||||
- Định nghĩa SLIs và SLOs cho các đường dẫn quan trọng
|
||||
- Giám sát business metrics, không chỉ technical metrics
|
||||
- Chuẩn hóa route paths để tránh high cardinality
|
||||
|
||||
### Tracing
|
||||
|
||||
- Add traces for critical operations
|
||||
- Include relevant context in spans
|
||||
- Sample appropriately to control costs
|
||||
- Use distributed tracing for microservices
|
||||
- Propagate correlation IDs across service boundaries
|
||||
|
||||
**VI**:
|
||||
- Thêm traces cho các thao tác quan trọng
|
||||
- Bao gồm context liên quan trong spans
|
||||
- Sample phù hợp để kiểm soát chi phí
|
||||
- Sử dụng distributed tracing cho microservices
|
||||
- Truyền correlation IDs qua ranh giới service
|
||||
|
||||
### Cảnh Báo
|
||||
|
||||
- Alert on symptoms, not causes
|
||||
- Include runbook links in alerts
|
||||
- Avoid alert fatigue with proper thresholds
|
||||
- Test alerting rules regularly
|
||||
- Use correlation IDs in alert context
|
||||
|
||||
**VI**:
|
||||
- Cảnh báo về triệu chứng, không phải nguyên nhân
|
||||
- Bao gồm links runbook trong alerts
|
||||
- Tránh alert fatigue với thresholds phù hợp
|
||||
- Test alerting rules thường xuyên
|
||||
- Sử dụng correlation IDs trong alert context
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Triển Khai Logging
|
||||
|
||||
- **Request Logger**: [`services/iam-service/src/middlewares/logger.middleware.ts`](../../../services/iam-service/src/middlewares/logger.middleware.ts)
|
||||
- **Correlation Middleware**: [`services/iam-service/src/middlewares/correlation.middleware.ts`](../../../services/iam-service/src/middlewares/correlation.middleware.ts)
|
||||
|
||||
### Triển Khai Metrics
|
||||
|
||||
- **Metrics Middleware**: [`services/iam-service/src/middlewares/metrics.middleware.ts`](../../../services/iam-service/src/middlewares/metrics.middleware.ts)
|
||||
- **Metrics Endpoint**: Exposed at `/metrics` in all services
|
||||
|
||||
### Triển Khai Tracing
|
||||
|
||||
- **Tracing Package**: [`packages/tracing/src/index.ts`](../../../packages/tracing/src/index.ts)
|
||||
- **Service Integration**: [`services/iam-service/src/main.ts`](../../../services/iam-service/src/main.ts)
|
||||
|
||||
### Health Checks
|
||||
|
||||
- **Health Controller**: [`services/iam-service/src/modules/health/health.controller.ts`](../../../services/iam-service/src/modules/health/health.controller.ts)
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Mức Độ Log
|
||||
|
||||
- `ERROR`: Errors that require immediate attention
|
||||
- `WARN`: Warnings that may indicate issues
|
||||
- `INFO`: Informational messages (default)
|
||||
- `DEBUG`: Detailed debugging information
|
||||
|
||||
**VI**:
|
||||
- `ERROR`: Lỗi cần chú ý ngay lập tức
|
||||
- `WARN`: Cảnh báo có thể chỉ ra vấn đề
|
||||
- `INFO`: Thông điệp thông tin (mặc định)
|
||||
- `DEBUG`: Thông tin debug chi tiết
|
||||
|
||||
### Loại Metrics
|
||||
|
||||
- **Counter**: Monotonically increasing value (e.g., request count)
|
||||
- **Gauge**: Value that can go up or down (e.g., active connections)
|
||||
- **Histogram**: Distribution of values (e.g., request duration)
|
||||
|
||||
**VI**:
|
||||
- **Counter**: Giá trị tăng đơn điệu (ví dụ: số lượng request)
|
||||
- **Gauge**: Giá trị có thể tăng hoặc giảm (ví dụ: kết nối đang hoạt động)
|
||||
- **Histogram**: Phân phối giá trị (ví dụ: thời lượng request)
|
||||
|
||||
### Endpoints Health Check
|
||||
|
||||
- `/health` or `/health/live`: Liveness probe (service is running)
|
||||
- `/health/ready`: Readiness probe (service is ready to accept traffic)
|
||||
|
||||
**VI**:
|
||||
- `/health` hoặc `/health/live`: Liveness probe (service đang chạy)
|
||||
- `/health/ready`: Readiness probe (service sẵn sàng nhận traffic)
|
||||
|
||||
### Truy Vấn Prometheus
|
||||
|
||||
```promql
|
||||
# Request rate
|
||||
rate(http_requests_total[5m])
|
||||
|
||||
# Error rate
|
||||
rate(http_requests_total{status_code=~"5.."}[5m])
|
||||
|
||||
# 95th percentile latency
|
||||
histogram_quantile(0.95, http_request_duration_seconds)
|
||||
|
||||
# Active requests
|
||||
http_active_requests
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Kubernetes Deployment](./deployment-kubernetes.md) - Để cấu hình health checks trong K8s
|
||||
- [Security](./security.md) - Để logging và monitoring an toàn
|
||||
- [Project Rules](./project-rules.md) - Cho cấu trúc và tiêu chuẩn service
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tài Liệu Chính Thức
|
||||
|
||||
- [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
|
||||
- [Prometheus Documentation](https://prometheus.io/docs/)
|
||||
- [Jaeger Documentation](https://www.jaegertracing.io/docs/)
|
||||
|
||||
### Tài Nguyên GoodGo
|
||||
|
||||
- [Observability Guide](../guides/observability.md)
|
||||
- [Troubleshooting Guide](../guides/troubleshooting.md)
|
||||
- [Logger Package](../../../packages/logger/README.md)
|
||||
- [Tracing Package](../../../packages/tracing/README.md)
|
||||
@@ -1,154 +0,0 @@
|
||||
# Tối Ưu Hiệu Suất (Performance Optimization)
|
||||
|
||||
Performance optimization patterns for GoodGo microservices including database query optimization, memory leak detection, profiling, connection pooling, and caching strategies.
|
||||
> Các patterns tối ưu hiệu suất cho GoodGo microservices bao gồm tối ưu database queries, phát hiện memory leaks, profiling, connection pooling, và caching strategies.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Performance optimization patterns help identify and fix performance bottlenecks, optimize database queries, detect memory leaks, and improve overall application performance.
|
||||
|
||||
Các patterns tối ưu hiệu suất giúp xác định và sửa các nút cổ chai hiệu suất, tối ưu database queries, phát hiện memory leaks, và cải thiện hiệu suất ứng dụng tổng thể.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when optimizing performance, profiling applications, or detecting bottlenecks.
|
||||
|
||||
Sử dụng skill này khi tối ưu hiệu suất, profiling ứng dụng, hoặc phát hiện bottlenecks.
|
||||
|
||||
## Quy Trình Tối Ưu Hiệu Suất
|
||||
|
||||
Quy trình tối ưu hiệu suất tuân theo phương pháp có hệ thống để xác định, phân tích và giải quyết các nút cổ chai hiệu suất.
|
||||
|
||||
The performance optimization process follows a systematic approach to identify, analyze, and resolve performance bottlenecks.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Optimization]) --> Identify[Identify Performance Issue]
|
||||
Identify --> Monitor[Monitor Metrics]
|
||||
Monitor --> Profiling[Profile Application]
|
||||
Profiling --> Analyze[Analyze Results]
|
||||
Analyze --> IdentifyBottleneck{Identify Bottleneck}
|
||||
|
||||
IdentifyBottleneck -->|Database| OptimizeDB[Optimize Queries]
|
||||
IdentifyBottleneck -->|Memory| OptimizeMem[Fix Memory Leaks]
|
||||
IdentifyBottleneck -->|Network| OptimizeNet[Optimize Connections]
|
||||
IdentifyBottleneck -->|Cache| OptimizeCache[Improve Caching]
|
||||
|
||||
OptimizeDB --> Implement[Implement Optimization]
|
||||
OptimizeMem --> Implement
|
||||
OptimizeNet --> Implement
|
||||
OptimizeCache --> Implement
|
||||
|
||||
Implement --> Test[Test Changes]
|
||||
Test --> Verify{Performance Improved?}
|
||||
Verify -->|Yes| Monitor
|
||||
Verify -->|No| Analyze
|
||||
Monitor --> Threshold{Meets Targets?}
|
||||
Threshold -->|Yes| Complete([Optimization Complete])
|
||||
Threshold -->|No| Profiling
|
||||
```
|
||||
|
||||
## Quy Trình Tối Ưu Query
|
||||
|
||||
Tối ưu database query là một khía cạnh quan trọng của hiệu suất. Quy trình này cho thấy cách tối ưu query một cách có hệ thống.
|
||||
|
||||
Database query optimization is a critical aspect of performance. This flow shows how to systematically optimize queries.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Query Performance Issue]) --> Analyze[Analyze Query Performance]
|
||||
Analyze --> CheckIndexes[Check Indexes]
|
||||
CheckIndexes --> Explain[Run EXPLAIN ANALYZE]
|
||||
Explain --> IdentifyIssues{Identify Issues}
|
||||
|
||||
IdentifyIssues -->|N+1 Queries| FixN1[Use Include/Join]
|
||||
IdentifyIssues -->|Missing Index| AddIndex[Add Database Index]
|
||||
IdentifyIssues -->|Slow Query| OptimizeQuery[Rewrite Query]
|
||||
IdentifyIssues -->|Unbounded| AddPagination[Add Pagination]
|
||||
|
||||
FixN1 --> VerifyQuery[Verify Query Performance]
|
||||
AddIndex --> VerifyQuery
|
||||
OptimizeQuery --> VerifyQuery
|
||||
AddPagination --> VerifyQuery
|
||||
|
||||
VerifyQuery --> TestQuery{Query Time < 50ms?}
|
||||
TestQuery -->|Yes| Complete([Optimization Complete])
|
||||
TestQuery -->|No| Analyze
|
||||
|
||||
style FixN1 fill:#e1f5e1
|
||||
style AddIndex fill:#e1f5e1
|
||||
style OptimizeQuery fill:#e1f5e1
|
||||
style AddPagination fill:#e1f5e1
|
||||
```
|
||||
|
||||
## Quy Trình Profiling
|
||||
|
||||
Quy trình profiling giúp xác định các nút cổ chai hiệu suất thông qua việc thu thập và phân tích dữ liệu có hệ thống.
|
||||
|
||||
The profiling process helps identify performance bottlenecks through systematic data collection and analysis.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Profiling]) --> Setup[Setup Profiling Tools]
|
||||
Setup --> ChooseTool{Choose Profiling Type}
|
||||
|
||||
ChooseTool -->|CPU| CPUProf[CPU Profiling]
|
||||
ChooseTool -->|Memory| MemProf[Memory Profiling]
|
||||
ChooseTool -->|Database| DBProf[Database Query Profiling]
|
||||
|
||||
CPUProf --> CollectCPU[Collect CPU Metrics]
|
||||
MemProf --> CollectMem[Collect Memory Metrics]
|
||||
DBProf --> CollectDB[Collect Query Metrics]
|
||||
|
||||
CollectCPU --> Analyze[Analyze Profiling Data]
|
||||
CollectMem --> Analyze
|
||||
CollectDB --> Analyze
|
||||
|
||||
Analyze --> IdentifyHotspots[Identify Hotspots]
|
||||
IdentifyHotspots --> Prioritize[Prioritize Issues]
|
||||
Prioritize --> Optimize[Optimize Critical Paths]
|
||||
Optimize --> ReProfile[Re-run Profiling]
|
||||
ReProfile --> Compare{Performance Improved?}
|
||||
Compare -->|Yes| Complete([Profiling Complete])
|
||||
Compare -->|No| IdentifyHotspots
|
||||
|
||||
style CPUProf fill:#e3f2fd
|
||||
style MemProf fill:#e3f2fd
|
||||
style DBProf fill:#e3f2fd
|
||||
```
|
||||
|
||||
## Các Patterns Chính
|
||||
|
||||
### Database Query Optimization / Tối Ưu Database Queries
|
||||
|
||||
```typescript
|
||||
// EN: Avoid N+1 queries
|
||||
// VI: Tránh N+1 queries
|
||||
const users = await userRepository.findAll({
|
||||
include: { orders: true }, // EN: Single query / VI: Single query
|
||||
});
|
||||
```
|
||||
|
||||
### Memory Profiling / Memory Profiling
|
||||
|
||||
```typescript
|
||||
// EN: Monitor memory usage
|
||||
// VI: Giám sát memory usage
|
||||
const profiler = new MemoryProfiler();
|
||||
profiler.start();
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. Use indexes, avoid N+1 queries / Sử dụng indexes, tránh N+1 queries
|
||||
2. Monitor memory usage / Giám sát memory usage
|
||||
3. Cache frequently accessed data / Cache dữ liệu thường truy cập
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- [Caching Patterns](./caching-patterns.md) - Caching strategies / Chiến lược caching
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Monitoring / Giám sát
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- Skill Source: `.cursor/skills/performance-optimization/SKILL.md`
|
||||
@@ -1,564 +0,0 @@
|
||||
# Quy Tắc Dự Án
|
||||
|
||||
GoodGo Microservices Platform coding standards and architecture patterns. Use when working with services, apps, packages, or infrastructure.
|
||||
> Tiêu chuẩn mã hóa và mẫu kiến trúc của GoodGo Microservices Platform. Sử dụng khi làm việc với services, apps, packages, hoặc infrastructure.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
The Project Rules skill provides comprehensive guidelines for the GoodGo Microservices Platform architecture, coding standards, naming conventions, workflows, and best practices. This skill ensures consistency across all services, packages, and applications in the monorepo.
|
||||
|
||||
Skill Project Rules cung cấp hướng dẫn toàn diện về kiến trúc GoodGo Microservices Platform, tiêu chuẩn mã hóa, quy ước đặt tên, quy trình làm việc và thực hành tốt nhất. Skill này đảm bảo tính nhất quán trên tất cả services, packages và applications trong monorepo.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Creating a new service or package
|
||||
- Setting up project structure
|
||||
- Following naming conventions
|
||||
- Understanding deployment patterns
|
||||
- Working with dependencies
|
||||
- Configuring Docker containers
|
||||
- Setting up CI/CD workflows
|
||||
- Understanding the monorepo structure
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Tạo service hoặc package mới
|
||||
- Thiết lập cấu trúc dự án
|
||||
- Tuân theo quy ước đặt tên
|
||||
- Hiểu các mẫu triển khai
|
||||
- Làm việc với dependencies
|
||||
- Cấu hình Docker containers
|
||||
- Thiết lập CI/CD workflows
|
||||
- Hiểu cấu trúc monorepo
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Kiến Trúc
|
||||
|
||||
The platform follows a microservices architecture with:
|
||||
- **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)
|
||||
|
||||
Nền tảng tuân theo kiến trúc microservices với:
|
||||
- **Apps**: Next.js (web) + Flutter (mobile)
|
||||
- **Services**: Node.js/TypeScript microservices (Express)
|
||||
- **Packages**: Thư viện dùng chung (logger, types, http-client, auth-sdk, tracing)
|
||||
- **Infrastructure**: Traefik (API Gateway), Redis, Neon PostgreSQL, Observability
|
||||
- **Deployments**: Local (Docker Compose), Staging/Production (Kubernetes)
|
||||
|
||||
#### Kiến Trúc Monorepo
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph apps[Apps Layer]
|
||||
webAdmin[web-admin<br/>Next.js Admin]
|
||||
webClient[web-client<br/>Next.js Client]
|
||||
appAdmin[app-admin<br/>Flutter Admin]
|
||||
appClient[app-client<br/>Flutter Client]
|
||||
end
|
||||
|
||||
subgraph gateway[API Gateway]
|
||||
traefik[Traefik<br/>Path-based Routing]
|
||||
end
|
||||
|
||||
subgraph services[Services Layer]
|
||||
iamService[iam-service<br/>IAM Service]
|
||||
templateService[_template<br/>Service Template]
|
||||
otherServices[Other Services<br/>Node.js/TypeScript]
|
||||
end
|
||||
|
||||
subgraph packages[Shared Packages]
|
||||
loggerPackage[@goodgo/logger<br/>Centralized Logging]
|
||||
typesPackage[@goodgo/types<br/>TypeScript Types]
|
||||
httpClientPackage[@goodgo/http-client<br/>API Client]
|
||||
authSdkPackage[@goodgo/auth-sdk<br/>Auth Utilities]
|
||||
tracingPackage[@goodgo/tracing<br/>OpenTelemetry]
|
||||
configPackage[@goodgo/config<br/>Shared Configs]
|
||||
end
|
||||
|
||||
subgraph infrastructure[Infrastructure]
|
||||
postgres[Neon PostgreSQL<br/>Database]
|
||||
redis[Redis<br/>Cache]
|
||||
prometheus[Prometheus<br/>Metrics]
|
||||
grafana[Grafana<br/>Dashboards]
|
||||
loki[Loki<br/>Log Aggregation]
|
||||
end
|
||||
|
||||
subgraph deployments[Deployments]
|
||||
dockerCompose[Docker Compose<br/>Local Development]
|
||||
kubernetes[Kubernetes<br/>Staging/Production]
|
||||
end
|
||||
|
||||
webAdmin --> traefik
|
||||
webClient --> traefik
|
||||
appAdmin --> traefik
|
||||
appClient --> traefik
|
||||
traefik --> iamService
|
||||
traefik --> otherServices
|
||||
iamService --> packages
|
||||
otherServices --> packages
|
||||
iamService --> postgres
|
||||
otherServices --> postgres
|
||||
iamService --> redis
|
||||
otherServices --> redis
|
||||
services --> prometheus
|
||||
services --> loki
|
||||
prometheus --> grafana
|
||||
loki --> grafana
|
||||
services --> deployments
|
||||
```
|
||||
|
||||
### Công Nghệ
|
||||
|
||||
**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
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Cấu Trúc Service
|
||||
|
||||
Standard service structure:
|
||||
```
|
||||
services/[service-name]/
|
||||
├── src/
|
||||
│ ├── config/ # Configuration files
|
||||
│ ├── modules/ # Feature modules
|
||||
│ ├── middlewares/ # Express middlewares
|
||||
│ ├── repositories/ # Data access layer
|
||||
│ ├── routes/ # Route definitions
|
||||
│ └── main.ts # Entry point
|
||||
├── prisma/ # Database schema
|
||||
├── Dockerfile # Container definition
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
Cấu trúc service chuẩn:
|
||||
```
|
||||
services/[service-name]/
|
||||
├── src/
|
||||
│ ├── config/ # File cấu hình
|
||||
│ ├── modules/ # Module tính năng
|
||||
│ ├── middlewares/ # Express middlewares
|
||||
│ ├── repositories/ # Lớp truy cập dữ liệu
|
||||
│ ├── routes/ # Định nghĩa routes
|
||||
│ └── main.ts # Điểm vào
|
||||
├── prisma/ # Database schema
|
||||
├── Dockerfile # Định nghĩa container
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
#### Biểu Đồ Cấu Trúc Chi Tiết
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph service[Service Structure]
|
||||
serviceRoot[service-name/]
|
||||
serviceSrc[src/]
|
||||
serviceConfig[config/<br/>Configuration]
|
||||
serviceModules[modules/<br/>Feature Modules]
|
||||
serviceMiddlewares[middlewares/<br/>Express Middlewares]
|
||||
serviceRoutes[routes/<br/>Route Definitions]
|
||||
serviceMain[main.ts<br/>Entry Point]
|
||||
servicePrisma[prisma/<br/>Schema & Migrations]
|
||||
serviceDockerfile[Dockerfile<br/>Container Definition]
|
||||
servicePackageJson[package.json<br/>Dependencies]
|
||||
|
||||
serviceRoot --> serviceSrc
|
||||
serviceRoot --> servicePrisma
|
||||
serviceRoot --> serviceDockerfile
|
||||
serviceRoot --> servicePackageJson
|
||||
serviceSrc --> serviceConfig
|
||||
serviceSrc --> serviceModules
|
||||
serviceSrc --> serviceMiddlewares
|
||||
serviceSrc --> serviceRoutes
|
||||
serviceSrc --> serviceMain
|
||||
end
|
||||
|
||||
subgraph package[Package Structure]
|
||||
packageRoot[package-name/]
|
||||
packageSrc[src/]
|
||||
packageIndex[index.ts<br/>Main Export]
|
||||
packagePackageJson[package.json<br/>Package Metadata]
|
||||
packageTsconfig[tsconfig.json<br/>TypeScript Config]
|
||||
packageReadme[README.md<br/>Documentation]
|
||||
|
||||
packageRoot --> packageSrc
|
||||
packageRoot --> packagePackageJson
|
||||
packageRoot --> packageTsconfig
|
||||
packageRoot --> packageReadme
|
||||
packageSrc --> packageIndex
|
||||
end
|
||||
|
||||
subgraph app[App Structure - Next.js]
|
||||
appRoot[app-name/]
|
||||
appSrc[src/]
|
||||
appApp[app/<br/>Next.js App Router]
|
||||
appServicesApi[services/api/<br/>API Clients]
|
||||
appStores[stores/<br/>State Management]
|
||||
appDockerfile[Dockerfile<br/>Container Definition]
|
||||
appPackageJson[package.json<br/>Dependencies]
|
||||
|
||||
appRoot --> appSrc
|
||||
appRoot --> appDockerfile
|
||||
appRoot --> appPackageJson
|
||||
appSrc --> appApp
|
||||
appSrc --> appServicesApi
|
||||
appSrc --> appStores
|
||||
end
|
||||
|
||||
subgraph module[Module Structure inside modules/]
|
||||
moduleRoot[modules/feature-name/]
|
||||
moduleController[feature.controller.ts<br/>HTTP Handlers]
|
||||
moduleService[feature.service.ts<br/>Business Logic]
|
||||
moduleRepository[feature.repository.ts<br/>Data Access]
|
||||
moduleDto[feature.dto.ts<br/>Zod Schemas]
|
||||
moduleTypes[feature.types.ts<br/>TypeScript Types]
|
||||
moduleTest[feature.controller.test.ts<br/>Unit Tests]
|
||||
|
||||
moduleRoot --> moduleController
|
||||
moduleRoot --> moduleService
|
||||
moduleRoot --> moduleRepository
|
||||
moduleRoot --> moduleDto
|
||||
moduleRoot --> moduleTypes
|
||||
moduleRoot --> moduleTest
|
||||
moduleController --> moduleService
|
||||
moduleService --> moduleRepository
|
||||
end
|
||||
|
||||
serviceModules --> moduleRoot
|
||||
```
|
||||
|
||||
### Pattern Module
|
||||
|
||||
Controller → Service → Repository pattern:
|
||||
|
||||
```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 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pattern Controller → Service → Repository:
|
||||
|
||||
```typescript
|
||||
// DTO với 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 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Phản Hồi API
|
||||
|
||||
Standardized API responses:
|
||||
|
||||
```typescript
|
||||
// Success response
|
||||
{
|
||||
success: true,
|
||||
data: any,
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
// Error response
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: string,
|
||||
message: string,
|
||||
details?: any
|
||||
},
|
||||
timestamp: string
|
||||
}
|
||||
```
|
||||
|
||||
Phản hồi API chuẩn hóa:
|
||||
|
||||
```typescript
|
||||
// Phản hồi thành công
|
||||
{
|
||||
success: true,
|
||||
data: any,
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
// Phản hồi lỗi
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: string,
|
||||
message: string,
|
||||
details?: any
|
||||
},
|
||||
timestamp: string
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Quy Ước Đặt Tên
|
||||
|
||||
- **Services/Packages**: `kebab-case` (e.g., `iam-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`
|
||||
|
||||
**VI**:
|
||||
- **Services/Packages**: `kebab-case` (ví dụ: `iam-service`, `http-client`)
|
||||
- **Files**: `kebab-case.type.ts` (ví dụ: `user.controller.ts`)
|
||||
- **Components**: `PascalCase.tsx` (React), `snake_case.dart` (Flutter)
|
||||
- **Classes**: `PascalCase`
|
||||
- **Functions**: `camelCase`
|
||||
- **Constants**: `UPPER_SNAKE_CASE`
|
||||
- **Package Names**: `@goodgo/package-name`
|
||||
|
||||
### Tiêu Chuẩn TypeScript
|
||||
|
||||
- Strict mode enabled
|
||||
- No `any` type (use `unknown` instead)
|
||||
- Zod for runtime validation
|
||||
- Export shared types from `@goodgo/types`
|
||||
|
||||
**VI**:
|
||||
- Bật strict mode
|
||||
- Không dùng type `any` (dùng `unknown` thay thế)
|
||||
- Zod cho runtime validation
|
||||
- Export shared types từ `@goodgo/types`
|
||||
|
||||
### Ghi Log
|
||||
|
||||
Use `@goodgo/logger`:
|
||||
|
||||
```typescript
|
||||
import { logger } from '@goodgo/logger';
|
||||
logger.info('Message', { context });
|
||||
logger.error('Error', { error, context });
|
||||
```
|
||||
|
||||
Sử dụng `@goodgo/logger`:
|
||||
|
||||
```typescript
|
||||
import { logger } from '@goodgo/logger';
|
||||
logger.info('Message', { context });
|
||||
logger.error('Error', { error, context });
|
||||
```
|
||||
|
||||
### Biến Môi Trường
|
||||
|
||||
- Use `.env.example` template
|
||||
- Never commit `.env` files
|
||||
- Validate with Zod at startup
|
||||
- Document all variables in README
|
||||
|
||||
**VI**:
|
||||
- Sử dụng template `.env.example`
|
||||
- Không bao giờ commit file `.env`
|
||||
- Validate với Zod khi khởi động
|
||||
- Tài liệu hóa tất cả biến trong README
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Template Service
|
||||
|
||||
See `services/_template/` for a complete service template with:
|
||||
- Standard structure
|
||||
- Middleware setup
|
||||
- Error handling
|
||||
- Health checks
|
||||
- Swagger documentation
|
||||
|
||||
Xem `services/_template/` để có template service hoàn chỉnh với:
|
||||
- Cấu trúc chuẩn
|
||||
- Thiết lập middleware
|
||||
- Xử lý lỗi
|
||||
- Health checks
|
||||
- Tài liệu Swagger
|
||||
|
||||
### Cấu Hình Traefik
|
||||
|
||||
Services are registered in `deployments/local/docker-compose.yml`:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
Services được đăng ký trong `deployments/local/docker-compose.yml`:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
### Ví Dụ Service Thực Tế
|
||||
|
||||
|
||||
- `services/iam-service/` - Identity and Access Management service (RBAC, OIDC, MFA, Identity, Access, Governance)
|
||||
- `packages/logger/` - Shared logging package
|
||||
- `packages/types/` - Shared TypeScript types
|
||||
|
||||
**VI**:
|
||||
- `services/iam-service/` - Service quản lý danh tính và quyền truy cập (RBAC, OIDC, MFA, Identity, Access, Governance)
|
||||
- `packages/logger/` - Package logging dùng chung
|
||||
- `packages/types/` - TypeScript types dùng chung
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Tạo Service Mới
|
||||
|
||||
```bash
|
||||
# 1. Copy template
|
||||
cp -r services/_template services/my-service
|
||||
|
||||
# 2. Update package.json name
|
||||
# Change to: "@goodgo/my-service"
|
||||
|
||||
# 3. Add to docker-compose.yml
|
||||
# Add service configuration with Traefik labels
|
||||
|
||||
# 4. Configure Prisma schema
|
||||
cd services/my-service
|
||||
pnpm prisma migrate dev
|
||||
|
||||
# 5. Add health check endpoint
|
||||
# Already included in template
|
||||
```
|
||||
|
||||
### Làm Việc Với Dependencies
|
||||
|
||||
```bash
|
||||
# Add external package
|
||||
pnpm --filter @goodgo/service-name add package-name
|
||||
|
||||
# Add workspace package
|
||||
pnpm --filter @goodgo/service-name add @goodgo/logger
|
||||
|
||||
# Add dev dependency
|
||||
pnpm --filter @goodgo/service-name add -D @types/pkg
|
||||
```
|
||||
|
||||
### Lệnh Database
|
||||
|
||||
```bash
|
||||
# Run migrations
|
||||
pnpm --filter @goodgo/service-name prisma migrate dev
|
||||
|
||||
# Generate Prisma client
|
||||
pnpm --filter @goodgo/service-name prisma generate
|
||||
|
||||
# Seed database
|
||||
pnpm --filter @goodgo/service-name prisma db seed
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[API Design](./api-design.md)** - RESTful API design standards
|
||||
- **[Database Prisma](./database-prisma.md)** - Prisma ORM patterns
|
||||
- **[Security](./security.md)** - Security best practices
|
||||
- **[Testing Patterns](./testing-patterns.md)** - Testing guidelines
|
||||
- **[Documentation](./documentation.md)** - Documentation writing guidelines
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Architecture Docs](../architecture/system-design.md)
|
||||
- [API Specs](../api/openapi/)
|
||||
- [Development Guide](../guides/development.md)
|
||||
- [Deployment Guide](../guides/deployment.md)
|
||||
- [Contributing Guide](../../../CONTRIBUTING.md)
|
||||
|
||||
**VI**:
|
||||
- [Tài Liệu Kiến Trúc](../architecture/system-design.md)
|
||||
- [Spec API](../api/openapi/)
|
||||
- [Hướng Dẫn Phát Triển](../guides/development.md)
|
||||
- [Hướng Dẫn Triển Khai](../guides/deployment.md)
|
||||
- [Hướng Dẫn Đóng Góp](../../../CONTRIBUTING.md)
|
||||
@@ -1,421 +0,0 @@
|
||||
# Pattern Repository
|
||||
|
||||
Repository pattern implementation and best practices for GoodGo microservices including BaseRepository extension, custom queries, transactions, and query optimization.
|
||||
> Implementation và best practices về repository pattern cho GoodGo microservices bao gồm mở rộng BaseRepository, custom queries, transactions, và query optimization.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
The Repository Pattern provides an abstraction layer between business logic and data access, making code more maintainable, testable, and consistent. This guide covers how to implement repositories using the BaseRepository class, write custom queries, handle transactions, and optimize database operations in GoodGo microservices.
|
||||
|
||||
Repository Pattern cung cấp abstraction layer giữa business logic và data access, làm cho code dễ bảo trì, testable, và nhất quán hơn. Hướng dẫn này bao gồm cách implement repositories sử dụng BaseRepository class, viết custom queries, xử lý transactions, và optimize database operations trong GoodGo microservices.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use the repository pattern when:
|
||||
- Implementing data access layers for new modules
|
||||
- Extending BaseRepository for specific entity types
|
||||
- Writing custom database queries beyond standard CRUD
|
||||
- Handling database transactions for multiple operations
|
||||
- Optimizing database queries and operations
|
||||
- Testing repository implementations
|
||||
- Organizing data access code consistently
|
||||
|
||||
Sử dụng repository pattern khi:
|
||||
- Implement data access layers cho modules mới
|
||||
- Mở rộng BaseRepository cho các entity types cụ thể
|
||||
- Viết custom database queries ngoài CRUD chuẩn
|
||||
- Xử lý database transactions cho nhiều operations
|
||||
- Optimize database queries và operations
|
||||
- Testing repository implementations
|
||||
- Tổ chức data access code một cách nhất quán
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Lợi Ích Của Repository Pattern
|
||||
|
||||
1. **Abstraction / Trừu Tượng Hóa**: Tách biệt business logic khỏi data access
|
||||
2. **Testability / Khả Năng Test**: Dễ dàng mock repositories để test
|
||||
3. **Maintainability / Khả Năng Bảo Trì**: Centralized database operations
|
||||
4. **Consistency / Tính Nhất Quán**: Standardized CRUD operations
|
||||
5. **Type Safety / An Toàn Kiểu**: TypeScript generics cung cấp type safety
|
||||
|
||||
### Kiến Trúc Repository
|
||||
|
||||
Repository pattern tạo abstraction layer giữa service layer và data access layer, cung cấp sự tách biệt rõ ràng giữa các concerns.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Controller["Controller<br/>(HTTP Handler)"] --> Service["Service Layer<br/>(Business Logic)"]
|
||||
Service --> Repository["Repository<br/>(Data Access)"]
|
||||
Repository --> Prisma["Prisma Client<br/>(ORM)"]
|
||||
Prisma --> Database[("Database<br/>(PostgreSQL)")]
|
||||
|
||||
style Controller fill:#e1f5ff
|
||||
style Service fill:#fff4e1
|
||||
style Repository fill:#e8f5e9
|
||||
style Prisma fill:#f3e5f5
|
||||
style Database fill:#ffebee
|
||||
```
|
||||
|
||||
### Class BaseRepository
|
||||
|
||||
The `BaseRepository` abstract class provides common database operations that can be extended by specific repositories. It handles error handling, logging, and provides type-safe methods for CRUD operations.
|
||||
|
||||
Class abstract `BaseRepository` cung cấp các database operations chung có thể được mở rộng bởi các repositories cụ thể. Nó xử lý error handling, logging, và cung cấp các methods type-safe cho CRUD operations.
|
||||
|
||||
**Available Methods / Các Methods Có Sẵn**:
|
||||
- `findById(id)` - Find entity by ID / Tìm entity theo ID
|
||||
- `findByUnique(field, value)` - Find by unique field / Tìm theo field duy nhất
|
||||
- `findAll(options)` - Find all with filtering, pagination, sorting / Tìm tất cả với filtering, pagination, sorting
|
||||
- `create(data)` - Create new entity / Tạo entity mới
|
||||
- `update(id, data)` - Update entity / Cập nhật entity
|
||||
- `delete(id)` - Delete entity / Xóa entity
|
||||
- `count(where)` - Count entities / Đếm entities
|
||||
- `exists(id)` - Check if entity exists / Kiểm tra entity có tồn tại
|
||||
- `transaction(callback)` - Execute transaction / Thực thi transaction
|
||||
|
||||
### Cấu Trúc Class Hierarchy
|
||||
|
||||
Repositories extend abstract class `BaseRepository`, kế thừa các CRUD operations chung trong khi cho phép thêm custom query methods.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BaseRepository {
|
||||
<<abstract>>
|
||||
#prisma: PrismaClient
|
||||
#modelName: string
|
||||
+findById(id: string) Promise~T~null~
|
||||
+findByUnique(field: string, value: any) Promise~T~null~
|
||||
+findAll(options?: any) Promise~T[]~
|
||||
+create(data: CreateInput) Promise~T~
|
||||
+update(id: string, data: UpdateInput) Promise~T~
|
||||
+delete(id: string) Promise~boolean~
|
||||
+count(where?: any) Promise~number~
|
||||
+exists(id: string) Promise~boolean~
|
||||
+transaction(callback: Function) Promise~R~
|
||||
}
|
||||
|
||||
class IRepository {
|
||||
<<interface>>
|
||||
+findById(id: string) Promise~T~null~
|
||||
+findByUnique(field: string, value: any) Promise~T~null~
|
||||
+findAll(options?: any) Promise~T[]~
|
||||
+create(data: CreateInput) Promise~T~
|
||||
+update(id: string, data: UpdateInput) Promise~T~
|
||||
+delete(id: string) Promise~boolean~
|
||||
+count(where?: any) Promise~number~
|
||||
+exists(id: string) Promise~boolean~
|
||||
}
|
||||
|
||||
class UserRepository {
|
||||
+findByEmail(email: string) Promise~User~null~
|
||||
+findByUsername(username: string) Promise~User~null~
|
||||
+findWithPermissions(userId: string) Promise~User~null~
|
||||
+findActiveUsers(organizationId?: string) Promise~User[]~
|
||||
}
|
||||
|
||||
class ProductRepository {
|
||||
+findByCategory(categoryId: string) Promise~Product[]~
|
||||
+findActiveProducts() Promise~Product[]~
|
||||
}
|
||||
|
||||
BaseRepository <|-- UserRepository : extends
|
||||
BaseRepository <|-- ProductRepository : extends
|
||||
IRepository <|.. UserRepository : implements
|
||||
```
|
||||
|
||||
**Lưu ý**: Các repositories cụ thể như `UserRepository` và `ProductRepository` extend `BaseRepository` và có thể implement interface `IRepository` để có thêm type safety. Chúng kế thừa tất cả base CRUD methods và thêm các domain-specific query methods.
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/common/repository.ts`](../../../services/iam-service/src/modules/common/repository.ts)
|
||||
|
||||
## Patterns
|
||||
|
||||
### Mở Rộng BaseRepository
|
||||
|
||||
Create a repository by extending BaseRepository and passing the Prisma model name.
|
||||
|
||||
Tạo repository bằng cách mở rộng BaseRepository và truyền tên Prisma model.
|
||||
|
||||
```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');
|
||||
}
|
||||
|
||||
// Thêm các methods tùy chỉnh
|
||||
async findByEmail(email: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/repositories/user.repository.ts`](../../../services/iam-service/src/repositories/user.repository.ts)
|
||||
|
||||
### Các Methods Query Tùy Chỉnh
|
||||
|
||||
Add domain-specific query methods for complex queries that go beyond standard CRUD operations.
|
||||
|
||||
Thêm các methods query domain-specific cho các queries phức tạp vượt quá CRUD operations chuẩn.
|
||||
|
||||
```typescript
|
||||
export class UserRepository extends BaseRepository<User, CreateUserInput, UpdateUserInput> {
|
||||
// Tìm user với dữ liệu liên quan
|
||||
async findWithPermissions(userId: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
userRoles: {
|
||||
include: { role: true },
|
||||
},
|
||||
userPermissions: {
|
||||
include: { permission: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Query phức tạp với filtering
|
||||
async findActiveUsers(organizationId?: string): Promise<User[]> {
|
||||
return this.prisma.user.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
...(organizationId && { organizationId }),
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sử Dụng Repository Interface
|
||||
|
||||
Implement the `IRepository` interface for additional type safety and consistency.
|
||||
|
||||
Implement interface `IRepository` để có thêm type safety và tính nhất quán.
|
||||
|
||||
```typescript
|
||||
import { IRepository } from '../modules/common/repository';
|
||||
|
||||
export class UserRepository
|
||||
extends BaseRepository<User, CreateUserInput, UpdateUserInput>
|
||||
implements IRepository<User, CreateUserInput, UpdateUserInput> {
|
||||
|
||||
constructor(prisma: PrismaClient) {
|
||||
super(prisma, 'user');
|
||||
}
|
||||
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### Xử Lý Lỗi
|
||||
|
||||
BaseRepository handles errors automatically, wrapping database operations in try-catch blocks and converting errors to DatabaseError instances.
|
||||
|
||||
BaseRepository xử lý errors tự động, bọc các database operations trong try-catch blocks và chuyển đổi errors thành DatabaseError instances.
|
||||
|
||||
```typescript
|
||||
// BaseRepository tự động xử lý errors
|
||||
async findById(id: string): Promise<T | null> {
|
||||
try {
|
||||
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 the repository transaction method for operations that need to be atomic (all succeed or all fail).
|
||||
|
||||
Sử dụng method transaction của repository cho các operations cần atomic (tất cả thành công hoặc tất cả thất bại).
|
||||
|
||||
```typescript
|
||||
// Thực thi nhiều operations trong transaction
|
||||
await userRepository.transaction(async (tx) => {
|
||||
const user = await tx.user.create({ data: userData });
|
||||
await tx.userProfile.create({
|
||||
data: { userId: user.id, ...profileData }
|
||||
});
|
||||
return user;
|
||||
});
|
||||
```
|
||||
|
||||
**Transaction Flow / Luồng Transaction**:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service
|
||||
participant Repository
|
||||
participant Prisma as Prisma Client
|
||||
participant DB as Database
|
||||
|
||||
Service->>Repository: transaction(callback)
|
||||
Repository->>Prisma: $transaction(callback)
|
||||
Prisma->>DB: BEGIN TRANSACTION
|
||||
|
||||
Note over Service,DB: All operations use transaction client (tx)<br/>Tất cả operations sử dụng transaction client (tx)
|
||||
|
||||
Service->>Repository: tx.user.create(data)
|
||||
Repository->>Prisma: tx.user.create(data)
|
||||
Prisma->>DB: INSERT INTO users ...
|
||||
DB-->>Prisma: User created
|
||||
Prisma-->>Repository: User entity
|
||||
Repository-->>Service: User entity
|
||||
|
||||
Service->>Repository: tx.userProfile.create(data)
|
||||
Repository->>Prisma: tx.userProfile.create(data)
|
||||
Prisma->>DB: INSERT INTO user_profiles ...
|
||||
DB-->>Prisma: Profile created
|
||||
Prisma-->>Repository: Profile entity
|
||||
Repository-->>Service: Profile entity
|
||||
|
||||
alt All operations succeed / Tất cả operations thành công
|
||||
Prisma->>DB: COMMIT
|
||||
DB-->>Prisma: Transaction committed
|
||||
Prisma-->>Repository: Success result
|
||||
Repository-->>Service: Success result
|
||||
else Error occurs / Lỗi xảy ra
|
||||
Prisma->>DB: ROLLBACK
|
||||
DB-->>Prisma: Transaction rolled back
|
||||
Prisma-->>Repository: Error thrown
|
||||
Repository-->>Service: DatabaseError thrown
|
||||
end
|
||||
```
|
||||
|
||||
**Important / Quan Trọng**: Tất cả operations trong transaction callback phải sử dụng transaction client (`tx`) parameter, không phải main Prisma client, để đảm bảo tính atomic.
|
||||
|
||||
### Các Tùy Chọn Query
|
||||
|
||||
Use Prisma query options in findAll for filtering, pagination, sorting, and including relations.
|
||||
|
||||
Sử dụng các tùy chọn query của Prisma trong findAll để filtering, pagination, sorting, và include relations.
|
||||
|
||||
```typescript
|
||||
// Phân trang
|
||||
const users = await userRepository.findAll({
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
});
|
||||
|
||||
// Lọc
|
||||
const activeUsers = await userRepository.findAll({
|
||||
where: { isActive: true },
|
||||
});
|
||||
|
||||
// Sắp xếp
|
||||
const recentUsers = await userRepository.findAll({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
// Bao gồm relations
|
||||
const usersWithRoles = await userRepository.findAll({
|
||||
include: { userRoles: true },
|
||||
});
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Extend BaseRepository / Mở Rộng BaseRepository**: Luôn extend BaseRepository thay vì implement từ đầu
|
||||
2. **Custom Methods / Methods Tùy Chỉnh**: Thêm domain-specific query methods trong repository subclasses
|
||||
3. **Type Safety / An Toàn Kiểu**: Sử dụng TypeScript generics cho type safety
|
||||
4. **Error Handling / Xử Lý Lỗi**: Để BaseRepository xử lý common errors, xử lý domain-specific errors trong custom methods
|
||||
5. **Logging / Ghi Log**: BaseRepository xử lý logging tự động
|
||||
6. **Transactions / Transactions**: Sử dụng repository transaction method cho multi-step operations
|
||||
7. **Query Optimization / Tối Ưu Query**: Sử dụng Prisma query options (select, include) để optimize queries
|
||||
8. **Single Responsibility / Trách Nhiệm Đơn**: Mỗi repository xử lý một entity type
|
||||
9. **Dependency Injection / Tiêm Phụ Thuộc**: Inject PrismaClient trong constructor để dễ test
|
||||
10. **Avoid Business Logic / Tránh Business Logic**: Không đặt business logic trong repository, đặt trong service layer
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **Not Extending BaseRepository / Không Mở Rộng BaseRepository**: Implement CRUD từ đầu thay vì extend
|
||||
2. **Business Logic in Repository / Business Logic Trong Repository**: Đặt business logic trong repository thay vì service layer
|
||||
3. **Exposing Prisma Client / Tiết Lộ Prisma Client**: Expose raw Prisma client thay vì sử dụng repository methods
|
||||
4. **Missing Error Handling / Thiếu Xử Lý Lỗi**: Không xử lý errors trong custom query methods
|
||||
5. **Over-fetching Data / Lấy Quá Nhiều Dữ Liệu**: Sử dụng `include` không cần thiết, fetch quá nhiều data
|
||||
6. **No Type Safety / Không An Toàn Kiểu**: Không sử dụng TypeScript generics đúng cách
|
||||
7. **Transaction Mistakes / Lỗi Transaction**: Không sử dụng repository transaction method cho related operations
|
||||
8. **N+1 Query Problems / Vấn Đề N+1 Query**: Fetch related data trong loops thay vì sử dụng include
|
||||
9. **Missing Indexes / Thiếu Indexes**: Không thêm database indexes cho các fields thường query
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Lỗi Kiểu Với Prisma
|
||||
|
||||
**Problem / Vấn Đề**: TypeScript errors khi sử dụng Prisma client methods
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Đảm bảo Prisma client đã được generate: `pnpm prisma generate`
|
||||
- Sử dụng type assertions nếu cần: `(this.prisma as any)[this.modelName]`
|
||||
- Verify Prisma schema matches TypeScript types
|
||||
|
||||
### Vấn Đề Rollback Transaction
|
||||
|
||||
**Problem / Vấn Đề**: Transaction không rollback khi có lỗi
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Đảm bảo tất cả operations trong transaction callback sử dụng transaction client (`tx`) parameter
|
||||
- Không sử dụng main Prisma client trong transaction callback
|
||||
- Let errors propagate, transaction will rollback automatically
|
||||
|
||||
**Example / Ví Dụ**:
|
||||
```typescript
|
||||
// ❌ Bad: Using main client in transaction
|
||||
await repository.transaction(async (tx) => {
|
||||
await repository.prisma.user.create(...); // Wrong!
|
||||
});
|
||||
|
||||
// ✅ Good: Using transaction client
|
||||
await repository.transaction(async (tx) => {
|
||||
await tx.user.create(...); // Correct!
|
||||
});
|
||||
```
|
||||
|
||||
### Vấn Đề Hiệu Suất
|
||||
|
||||
**Problem / Vấn Đề**: Queries chậm hoặc N+1 query problems
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Sử dụng `include` để fetch related data trong single query
|
||||
- Sử dụng `select` để limit fields được fetch
|
||||
- Thêm database indexes cho các fields thường query
|
||||
- Avoid fetching unnecessary relations
|
||||
|
||||
**Example / Ví Dụ**:
|
||||
```typescript
|
||||
// ❌ Bad: N+1 query problem
|
||||
const users = await userRepository.findAll();
|
||||
for (const user of users) {
|
||||
const roles = await roleRepository.findByUserId(user.id); // N queries!
|
||||
}
|
||||
|
||||
// ✅ Good: Single query with include
|
||||
const users = await userRepository.findAll({
|
||||
include: { userRoles: { include: { role: true } } },
|
||||
});
|
||||
```
|
||||
|
||||
### Tên Model Không Khớp
|
||||
|
||||
**Problem / Vấn Đề**: Model name không khớp với Prisma schema
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Verify model name matches Prisma schema exactly (case-sensitive)
|
||||
- Use camelCase for model names (e.g., 'user', 'userProfile', 'accessRequest')
|
||||
- Check Prisma schema for correct model names
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [BaseRepository](../../../services/iam-service/src/modules/common/repository.ts) - Base repository implementation
|
||||
- [User Repository](../../../services/iam-service/src/repositories/user.repository.ts) - Example repository implementation
|
||||
- [Database Prisma](./database-prisma.md) - Prisma ORM patterns và best practices
|
||||
- [Error Handling](./error-handling-patterns.md) - Error handling patterns trong repositories
|
||||
@@ -1,217 +0,0 @@
|
||||
# Các Pattern Resilience
|
||||
|
||||
Resilience patterns for GoodGo microservices including circuit breaker, retry strategies, timeout handling, and graceful degradation for improved fault tolerance and system reliability.
|
||||
> Các resilience patterns cho GoodGo microservices bao gồm circuit breaker, retry strategies, timeout handling, và graceful degradation để cải thiện fault tolerance và độ tin cậy hệ thống.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Resilience patterns help microservices handle failures gracefully, prevent cascading failures, and maintain system availability even when dependencies fail. This guide covers circuit breaker patterns, retry strategies, timeout handling, and graceful degradation techniques.
|
||||
|
||||
Resilience patterns giúp microservices xử lý failures một cách graceful, ngăn chặn cascading failures, và duy trì tính khả dụng của hệ thống ngay cả khi dependencies fail. Hướng dẫn này bao gồm circuit breaker patterns, retry strategies, timeout handling, và các kỹ thuật graceful degradation.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use resilience patterns 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
|
||||
|
||||
Sử dụng resilience patterns khi:
|
||||
- Implement circuit breaker patterns cho external services
|
||||
- Thêm retry logic cho transient failures
|
||||
- Thiết lập timeout handling cho long-running operations
|
||||
- Implement graceful degradation strategies
|
||||
- Xử lý external service failures
|
||||
- Cải thiện system fault tolerance
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Các Pattern Resilience
|
||||
|
||||
1. **Circuit Breaker / Ngắt Mạch**: Prevents cascading failures by stopping calls to failing services / Ngăn chặn cascading failures bằng cách dừng calls tới services đang fail
|
||||
2. **Retry / Thử Lại**: Automatically retries failed operations with backoff / Tự động retry các operations thất bại với backoff
|
||||
3. **Timeout / Hết Thời Gian**: Sets maximum time limits for operations / Thiết lập giới hạn thời gian tối đa cho operations
|
||||
4. **Bulkhead / Ngăn Cách**: Isolates failures to prevent spread / Cô lập failures để ngăn chặn lây lan
|
||||
5. **Graceful Degradation / Suy Giảm Nhẹ Nhàng**: Provides fallback behavior when services fail / Cung cấp fallback behavior khi services fail
|
||||
|
||||
## Patterns
|
||||
|
||||
### Pattern Circuit Breaker
|
||||
|
||||
Protects against cascading failures by opening the circuit when error threshold is reached.
|
||||
|
||||
Bảo vệ chống lại cascading failures bằng cách mở circuit khi đạt ngưỡng lỗi.
|
||||
|
||||
Circuit breaker có ba trạng thái chuyển đổi dựa trên tỷ lệ lỗi và timeout:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> CLOSED: Initial State
|
||||
CLOSED --> OPEN: Errors exceed threshold<br/>(errorThresholdPercentage: 50%)
|
||||
OPEN --> HALF_OPEN: Reset timeout expires<br/>(resetTimeout: 30s)
|
||||
HALF_OPEN --> CLOSED: Request succeeds
|
||||
HALF_OPEN --> OPEN: Request fails
|
||||
CLOSED --> [*]: Normal operation
|
||||
OPEN --> [*]: Circuit open (rejecting requests)
|
||||
HALF_OPEN --> [*]: Testing recovery
|
||||
```
|
||||
|
||||
**Circuit Breaker States / Trạng Thái Circuit Breaker:**
|
||||
- **CLOSED**: Normal operation, requests pass through / Hoạt động bình thường, requests được cho phép
|
||||
- **OPEN**: Circuit is open, requests are immediately rejected / Circuit mở, requests bị từ chối ngay lập tức
|
||||
- **HALF-OPEN**: Testing if service has recovered, allows limited requests / Kiểm tra service đã phục hồi, cho phép số lượng requests hạn chế
|
||||
|
||||
```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', () => {
|
||||
${name}`);
|
||||
});
|
||||
|
||||
return breaker;
|
||||
};
|
||||
|
||||
// Usage
|
||||
const externalApiBreaker = createCircuitBreaker(
|
||||
async (data) => await externalApi.call(data),
|
||||
'external-api'
|
||||
);
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/common/circuit-breaker.ts`](../../../services/iam-service/src/modules/common/circuit-breaker.ts)
|
||||
|
||||
### Pattern Retry
|
||||
|
||||
Retry transient failures with exponential backoff.
|
||||
|
||||
Retry transient failures với exponential backoff.
|
||||
|
||||
Pattern retry thử lại operation nhiều lần với delay tăng dần giữa các lần thử:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Start Operation]) --> Attempt[Attempt Operation]
|
||||
Attempt --> Success{Success?}
|
||||
Success -->|Yes| Return([Return Result])
|
||||
Success -->|No| CheckRetries{Attempt < Max Retries?}
|
||||
CheckRetries -->|No| ThrowError([Throw Error])
|
||||
CheckRetries -->|Yes| CalculateDelay[Calculate Delay:<br/>baseDelay × 2^attempt]
|
||||
CalculateDelay --> Wait[Wait for Delay]
|
||||
Wait --> IncrementAttempt[Increment Attempt]
|
||||
IncrementAttempt --> Attempt
|
||||
|
||||
style Start fill:#e1f5e1
|
||||
style Return fill:#e1f5e1
|
||||
style ThrowError fill:#ffe1e1
|
||||
style CalculateDelay fill:#fff4e1
|
||||
```
|
||||
|
||||
**Exponential Backoff Example / Ví Dụ Exponential Backoff:**
|
||||
- Attempt 1: 1s delay
|
||||
- Attempt 2: 2s delay
|
||||
- Attempt 3: 4s delay
|
||||
- Attempt 4: 8s delay
|
||||
|
||||
```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');
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Timeout
|
||||
|
||||
Set maximum time limits for operations.
|
||||
|
||||
Thiết lập giới hạn thời gian tối đa cho operations.
|
||||
|
||||
Pattern timeout sử dụng Promise.race để thực thi giới hạn thời gian tối đa:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant TimeoutWrapper
|
||||
participant Operation
|
||||
participant TimeoutTimer
|
||||
|
||||
Client->>TimeoutWrapper: Execute with timeout
|
||||
TimeoutWrapper->>Operation: Start operation
|
||||
TimeoutWrapper->>TimeoutTimer: Start timeout timer
|
||||
|
||||
alt Operation completes first
|
||||
Operation-->>TimeoutWrapper: Return result
|
||||
TimeoutWrapper-->>Client: Return result
|
||||
TimeoutWrapper->>TimeoutTimer: Cancel timer
|
||||
else Timeout expires first
|
||||
TimeoutTimer-->>TimeoutWrapper: Timeout error
|
||||
TimeoutWrapper->>Operation: (Operation may continue)
|
||||
TimeoutWrapper-->>Client: Reject with timeout error
|
||||
end
|
||||
```
|
||||
|
||||
**Timeout Behavior / Hành Vi Timeout:**
|
||||
- Uses `Promise.race()` to compete operation vs timeout / Sử dụng `Promise.race()` để so sánh operation với timeout
|
||||
- First to resolve/reject wins / Cái nào resolve/reject trước sẽ thắng
|
||||
- Operation may continue after timeout, but result is ignored / Operation có thể tiếp tục sau timeout, nhưng kết quả bị bỏ qua
|
||||
|
||||
```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]);
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Circuit Breaker / Circuit Breaker**: Sử dụng cho external service calls
|
||||
2. **Retry / Retry**: Chỉ retry transient failures (network, timeout)
|
||||
3. **Timeout / Timeout**: Thiết lập timeouts phù hợp cho tất cả external calls
|
||||
4. **Fallback / Fallback**: Luôn cung cấp fallback behavior
|
||||
5. **Monitoring / Giám Sát**: Monitor circuit breaker states và retry rates
|
||||
6. **Logging / Ghi Log**: Log tất cả resilience actions để debug
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **Retrying Non-Retryable Errors / Retry Các Lỗi Không Thể Retry**: Retry 4xx errors (client errors)
|
||||
2. **No Timeout / Không Có Timeout**: Thiếu timeouts trên external calls
|
||||
3. **No Fallback / Không Có Fallback**: Không có graceful degradation strategy
|
||||
4. **Too Many Retries / Retry Quá Nhiều**: Retry quá nhiều gây performance issues
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Circuit Breaker](../../../services/iam-service/src/modules/common/circuit-breaker.ts) - Circuit breaker implementation
|
||||
@@ -1,853 +0,0 @@
|
||||
# Bảo Mật
|
||||
|
||||
> Thực hành và mẫu bảo mật tốt nhất cho nền tảng microservices GoodGo. Sử dụng khi triển khai xác thực, phân quyền, bảo vệ dữ liệu, xác thực đầu vào, giới hạn tốc độ, quản lý bí mật hoặc kiểm tra bảo mật trên tất cả các dịch vụ.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Skill Security cung cấp các mẫu bảo mật toàn diện, thực hành tốt nhất và ví dụ triển khai để bảo vệ các microservices GoodGo. Nó bao gồm xác thực, phân quyền, bảo vệ dữ liệu, xác thực đầu vào, giới hạn tốc độ, quản lý bí mật và kiểm tra bảo mật.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Sử dụng skill này khi:
|
||||
- Triển khai xác thực và phân quyền trong bất kỳ service nào
|
||||
- Bảo vệ dữ liệu nhạy cảm (PII, thông tin đăng nhập, token)
|
||||
- Xác thực đầu vào người dùng và tải lên tệp
|
||||
- Triển khai giới hạn tốc độ và bảo vệ DDoS
|
||||
- Thiết lập ghi nhật ký kiểm toán và giám sát bảo mật
|
||||
- Mã hóa dữ liệu khi nghỉ và khi truyền
|
||||
- Quản lý bí mật và thông tin đăng nhập
|
||||
- Triển khai kiểm tra bảo mật
|
||||
- Xử lý sự cố bảo mật
|
||||
- Thiết kế các endpoint API an toàn
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Nguyên Tắc Bảo Mật Cốt Lõi
|
||||
|
||||
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
|
||||
|
||||
**VI**:
|
||||
1. **Defense in Depth**: Nhiều lớp kiểm soát bảo mật
|
||||
2. **Least Privilege**: Cấp quyền tối thiểu cần thiết
|
||||
3. **Fail Secure**: Mặc định từ chối truy cập
|
||||
4. **Separation of Duties**: Các thao tác quan trọng yêu cầu nhiều phê duyệt
|
||||
5. **Audit Everything**: Ghi log tất cả sự kiện liên quan đến bảo mật
|
||||
6. **Encrypt Sensitive Data**: PII, token, thông tin đăng nhập phải được mã hóa
|
||||
7. **Validate All Inputs**: Không bao giờ tin tưởng đầu vào người dùng
|
||||
8. **Principle of Least Exposure**: Giảm thiểu bề mặt tấn công
|
||||
9. **Secure by Default**: Bảo mật được tích hợp sẵn, không phải thêm vào sau
|
||||
10. **Assume Breach**: Thiết kế để phát hiện và phản ứng
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Xác Thực & Phân Quyền
|
||||
|
||||
#### Xác Thực Token JWT
|
||||
|
||||
Sơ đồ sau minh họa luồng xác thực khi client gửi request với JWT token:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Middleware as Auth Middleware
|
||||
participant JWTService as JWT Service
|
||||
participant Request as Express Request
|
||||
|
||||
Client->>Middleware: HTTP Request with Token
|
||||
Middleware->>Middleware: Extract token from<br/>Authorization header or cookie
|
||||
|
||||
alt Token not found
|
||||
Middleware->>Client: 401 Unauthorized<br/>(AUTH_REQUIRED)
|
||||
else Token found
|
||||
Middleware->>JWTService: verifyAccessToken(token)
|
||||
|
||||
alt Token invalid or expired
|
||||
JWTService->>Middleware: Verification failed
|
||||
Middleware->>Client: 401 Unauthorized<br/>(INVALID_TOKEN)
|
||||
else Token valid
|
||||
JWTService->>Middleware: Payload (sub, email, roles, permissions)
|
||||
Middleware->>Request: Attach user to req.user
|
||||
Middleware->>Client: Continue to next middleware
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Example from `services/iam-service/src/middlewares/auth.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import { jwtService } from '../modules/token/jwt.service';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export const authenticate = () => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Extract token from Authorization header or cookie
|
||||
let token: string | null = null;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
} else if (req.cookies?.access_token) {
|
||||
token = req.cookies.access_token;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_001', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const payload = await jwtService.verifyAccessToken(token);
|
||||
|
||||
// Attach user to request
|
||||
req.user = {
|
||||
id: payload.sub,
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
roles: payload.roles || [],
|
||||
permissions: payload.permissions || []
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.warn('Authentication failed', { error: error.message });
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_002', message: 'Invalid or expired token' }
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/middlewares/auth.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import { jwtService } from '../modules/token/jwt.service';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export const authenticate = () => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Trích xuất token từ header Authorization hoặc cookie
|
||||
let token: string | null = null;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
} else if (req.cookies?.access_token) {
|
||||
token = req.cookies.access_token;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_001', message: 'Yêu cầu xác thực' }
|
||||
});
|
||||
}
|
||||
|
||||
// Xác minh token
|
||||
const payload = await jwtService.verifyAccessToken(token);
|
||||
|
||||
// Gắn user vào request
|
||||
req.user = {
|
||||
id: payload.sub,
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
roles: payload.roles || [],
|
||||
permissions: payload.permissions || []
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.warn('Xác thực thất bại', { error: error.message });
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_002', message: 'Token không hợp lệ hoặc hết hạn' }
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### Phân Quyền Dựa Trên Quyền
|
||||
|
||||
Luồng quyết định phân quyền xác định xem người dùng có quyền truy cập tài nguyên hay không:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Request Received]) --> CheckAuth{User<br/>Authenticated?}
|
||||
|
||||
CheckAuth -->|No| Return401[Return 401<br/>AUTH_REQUIRED]
|
||||
CheckAuth -->|Yes| CheckType{Authorization<br/>Type?}
|
||||
|
||||
CheckType -->|Role-Based| CheckRole{User has<br/>Required Role?}
|
||||
CheckType -->|Permission-Based| CheckPermission{User has<br/>Resource:Action<br/>Permission?}
|
||||
CheckType -->|Ownership| CheckOwnership{Resource ID<br/>matches User ID?}
|
||||
|
||||
CheckRole -->|No| LogDenial[Log Permission Denied<br/>with user roles]
|
||||
CheckPermission -->|No| LogDenial
|
||||
CheckOwnership -->|No| LogDenial
|
||||
|
||||
LogDenial --> Return403[Return 403<br/>FORBIDDEN]
|
||||
|
||||
CheckRole -->|Yes| Allow[Allow Request<br/>Continue to Handler]
|
||||
CheckPermission -->|Yes| Allow
|
||||
CheckOwnership -->|Yes| Allow
|
||||
|
||||
Return401 --> End([End])
|
||||
Return403 --> End
|
||||
Allow --> End
|
||||
|
||||
style CheckAuth fill:#e1f5ff
|
||||
style CheckType fill:#e1f5ff
|
||||
style Return401 fill:#ffebee
|
||||
style Return403 fill:#ffebee
|
||||
style Allow fill:#e8f5e9
|
||||
```
|
||||
|
||||
Example from `services/iam-service/src/middlewares/rbac.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
export const requirePermission = (
|
||||
resource: string,
|
||||
action: string,
|
||||
scope?: string
|
||||
) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const userId = req.user?.id || req.user?.sub;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'UNAUTHORIZED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
const hasPermission = await rbacService.hasPermission(
|
||||
userId,
|
||||
resource,
|
||||
action,
|
||||
scope
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INSUFFICIENT_PERMISSIONS',
|
||||
message: `Requires ${action} permission on ${resource}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/middlewares/rbac.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
export const requirePermission = (
|
||||
resource: string,
|
||||
action: string,
|
||||
scope?: string
|
||||
) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const userId = req.user?.id || req.user?.sub;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'UNAUTHORIZED', message: 'Yêu cầu xác thực' }
|
||||
});
|
||||
}
|
||||
|
||||
const hasPermission = await rbacService.hasPermission(
|
||||
userId,
|
||||
resource,
|
||||
action,
|
||||
scope
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INSUFFICIENT_PERMISSIONS',
|
||||
message: `Yêu cầu quyền ${action} trên ${resource}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Xác Thực Đầu Vào
|
||||
|
||||
Example from `services/iam-service/src/middlewares/validation.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import { AnyZodObject, ZodError } from 'zod';
|
||||
|
||||
export const validateDto = (
|
||||
schema: AnyZodObject,
|
||||
property: 'body' | 'query' | 'params' = 'body'
|
||||
) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Sanitize input by trimming strings
|
||||
const sanitizedData = sanitizeInput(req[property]);
|
||||
|
||||
// Validate the sanitized data
|
||||
const validatedData = schema.parse(sanitizedData);
|
||||
|
||||
// Replace original data with validated data
|
||||
(req as any)[property] = validatedData;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid request data',
|
||||
details: error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
code: err.code
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function sanitizeInput(data: any): any {
|
||||
if (typeof data === 'string') {
|
||||
return data.trim();
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(sanitizeInput);
|
||||
}
|
||||
if (data !== null && typeof data === 'object') {
|
||||
const sanitized: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
sanitized[key] = sanitizeInput(value);
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/middlewares/validation.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import { AnyZodObject, ZodError } from 'zod';
|
||||
|
||||
export const validateDto = (
|
||||
schema: AnyZodObject,
|
||||
property: 'body' | 'query' | 'params' = 'body'
|
||||
) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Làm sạch đầu vào bằng cách cắt chuỗi
|
||||
const sanitizedData = sanitizeInput(req[property]);
|
||||
|
||||
// Xác thực dữ liệu đã được làm sạch
|
||||
const validatedData = schema.parse(sanitizedData);
|
||||
|
||||
// Thay thế dữ liệu gốc bằng dữ liệu đã xác thực
|
||||
(req as any)[property] = validatedData;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Dữ liệu request không hợp lệ',
|
||||
details: error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
code: err.code
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function sanitizeInput(data: any): any {
|
||||
if (typeof data === 'string') {
|
||||
return data.trim();
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(sanitizeInput);
|
||||
}
|
||||
if (data !== null && typeof data === 'object') {
|
||||
const sanitized: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
sanitized[key] = sanitizeInput(value);
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
### Giới Hạn Tốc Độ
|
||||
|
||||
Example from `services/iam-service/src/middlewares/rate-limit.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { RateLimiterRedis } from 'rate-limit-redis';
|
||||
import { getRedisClient } from '../config/redis.config';
|
||||
|
||||
export const dynamicRateLimit = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const userId = req.user?.id || req.user?.sub;
|
||||
|
||||
// Default limits
|
||||
let windowMs = 15 * 60 * 1000; // 15 minutes
|
||||
let max = 100; // 100 requests
|
||||
|
||||
if (userId) {
|
||||
const roles = await rbacService.getUserRoles(userId);
|
||||
|
||||
// Set limits based on role
|
||||
if (roles.includes('SUPER_ADMIN')) {
|
||||
max = 1000;
|
||||
} else if (roles.includes('ADMIN')) {
|
||||
max = 500;
|
||||
} else if (roles.includes('MODERATOR')) {
|
||||
max = 300;
|
||||
}
|
||||
} else {
|
||||
// Unauthenticated users - stricter limits
|
||||
max = 50;
|
||||
}
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs,
|
||||
max,
|
||||
store: new RateLimiterRedis({
|
||||
client: getRedisClient(),
|
||||
prefix: 'rl:'
|
||||
}),
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
message: 'Too many requests, please try again later'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
limiter(req, res, next);
|
||||
};
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/middlewares/rate-limit.middleware.ts`:
|
||||
|
||||
```typescript
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { RateLimiterRedis } from 'rate-limit-redis';
|
||||
import { getRedisClient } from '../config/redis.config';
|
||||
|
||||
export const dynamicRateLimit = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const userId = req.user?.id || req.user?.sub;
|
||||
|
||||
// Giới hạn mặc định
|
||||
let windowMs = 15 * 60 * 1000; // 15 phút
|
||||
let max = 100; // 100 requests
|
||||
|
||||
if (userId) {
|
||||
const roles = await rbacService.getUserRoles(userId);
|
||||
|
||||
// Đặt giới hạn dựa trên vai trò
|
||||
if (roles.includes('SUPER_ADMIN')) {
|
||||
max = 1000;
|
||||
} else if (roles.includes('ADMIN')) {
|
||||
max = 500;
|
||||
} else if (roles.includes('MODERATOR')) {
|
||||
max = 300;
|
||||
}
|
||||
} else {
|
||||
// Người dùng chưa xác thực - giới hạn nghiêm ngặt hơn
|
||||
max = 50;
|
||||
}
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs,
|
||||
max,
|
||||
store: new RateLimiterRedis({
|
||||
client: getRedisClient(),
|
||||
prefix: 'rl:'
|
||||
}),
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
message: 'Quá nhiều yêu cầu, vui lòng thử lại sau'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
limiter(req, res, next);
|
||||
};
|
||||
```
|
||||
|
||||
### Header Bảo Mật
|
||||
|
||||
Example from `services/iam-service/src/main.ts`:
|
||||
|
||||
```typescript
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
|
||||
// Security middleware
|
||||
app.use(helmet());
|
||||
app.use(cors({
|
||||
origin: appConfig.corsOrigin,
|
||||
credentials: true
|
||||
}));
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/main.ts`:
|
||||
|
||||
```typescript
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
|
||||
// Middleware bảo mật
|
||||
app.use(helmet());
|
||||
app.use(cors({
|
||||
origin: appConfig.corsOrigin,
|
||||
credentials: true
|
||||
}));
|
||||
```
|
||||
|
||||
Traefik security headers from `infra/traefik/dynamic/middlewares.yml`:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
secure-headers:
|
||||
headers:
|
||||
sslRedirect: true
|
||||
stsSeconds: 31536000
|
||||
contentTypeNosniff: true
|
||||
browserXssFilter: true
|
||||
frameDeny: true
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Proto: "https"
|
||||
```
|
||||
|
||||
Header bảo mật Traefik từ `infra/traefik/dynamic/middlewares.yml`:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
secure-headers:
|
||||
headers:
|
||||
sslRedirect: true
|
||||
stsSeconds: 31536000
|
||||
contentTypeNosniff: true
|
||||
browserXssFilter: true
|
||||
frameDeny: true
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Proto: "https"
|
||||
```
|
||||
|
||||
### Ghi Log Kiểm Toán
|
||||
|
||||
Example from `services/iam-service/src/core/events/audit.service.ts`:
|
||||
|
||||
```typescript
|
||||
export class AuditService {
|
||||
async logAuthEvent(
|
||||
eventType: string,
|
||||
data: {
|
||||
userId?: string;
|
||||
success: boolean;
|
||||
errorMessage?: string;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
metadata?: any;
|
||||
}
|
||||
): Promise<void> {
|
||||
await this.prisma.authEvent.create({
|
||||
data: {
|
||||
userId: data.userId || null,
|
||||
eventType,
|
||||
eventData: data.metadata || {},
|
||||
ipAddress: data.ipAddress,
|
||||
userAgent: data.userAgent,
|
||||
success: data.success,
|
||||
errorMessage: data.errorMessage
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ví dụ từ `services/iam-service/src/core/events/audit.service.ts`:
|
||||
|
||||
```typescript
|
||||
export class AuditService {
|
||||
async logAuthEvent(
|
||||
eventType: string,
|
||||
data: {
|
||||
userId?: string;
|
||||
success: boolean;
|
||||
errorMessage?: string;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
metadata?: any;
|
||||
}
|
||||
): Promise<void> {
|
||||
await this.prisma.authEvent.create({
|
||||
data: {
|
||||
userId: data.userId || null,
|
||||
eventType,
|
||||
eventData: data.metadata || {},
|
||||
ipAddress: data.ipAddress,
|
||||
userAgent: data.userAgent,
|
||||
success: data.success,
|
||||
errorMessage: data.errorMessage
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Bảo Mật Mật Khẩu
|
||||
|
||||
Luồng hash và xác minh mật khẩu:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Service
|
||||
participant PasswordService
|
||||
participant Bcrypt
|
||||
participant Database
|
||||
|
||||
Note over User,Database: Registration/Password Change
|
||||
User->>Service: Submit password
|
||||
Service->>PasswordService: hash(password)
|
||||
PasswordService->>Bcrypt: bcrypt.hash(password, 12)
|
||||
Bcrypt->>Bcrypt: Generate salt
|
||||
Bcrypt->>Bcrypt: Hash with cost factor 12
|
||||
Bcrypt->>PasswordService: Hashed password
|
||||
PasswordService->>Service: Return hash
|
||||
Service->>Database: Store passwordHash
|
||||
Service->>Service: Sanitize password<br/>before logging
|
||||
|
||||
Note over User,Database: Login Verification
|
||||
User->>Service: Submit credentials
|
||||
Service->>Database: Fetch user by email
|
||||
Database->>Service: User with passwordHash
|
||||
Service->>PasswordService: verify(password, hash)
|
||||
PasswordService->>Bcrypt: bcrypt.compare(password, hash)
|
||||
Bcrypt->>PasswordService: Boolean result
|
||||
PasswordService->>Service: Return verification result
|
||||
|
||||
alt Password matches
|
||||
Service->>User: Authentication success
|
||||
else Password mismatch
|
||||
Service->>User: Invalid credentials<br/>(generic error)
|
||||
end
|
||||
```
|
||||
|
||||
- Always use bcrypt with cost factor 12+ in production
|
||||
- Never log passwords or password hashes
|
||||
- Use strong password requirements (min 8 chars, uppercase, lowercase, number, special char)
|
||||
- Implement password reset with secure tokens
|
||||
|
||||
**VI**:
|
||||
- Luôn sử dụng bcrypt với hệ số chi phí 12+ trong production
|
||||
- Không bao giờ ghi log mật khẩu hoặc hash mật khẩu
|
||||
- Sử dụng yêu cầu mật khẩu mạnh (tối thiểu 8 ký tự, chữ hoa, chữ thường, số, ký tự đặc biệt)
|
||||
- Triển khai đặt lại mật khẩu với token an toàn
|
||||
|
||||
### Bảo Mật Token
|
||||
|
||||
- Hash tokens before storing in database
|
||||
- Use short-lived access tokens (15 minutes)
|
||||
- Use longer-lived refresh tokens (7 days) with rotation
|
||||
- Store refresh tokens securely (httpOnly cookies)
|
||||
- Implement token revocation
|
||||
|
||||
**VI**:
|
||||
- Hash token trước khi lưu vào database
|
||||
- Sử dụng access token có thời gian sống ngắn (15 phút)
|
||||
- Sử dụng refresh token có thời gian sống dài hơn (7 ngày) với xoay vòng
|
||||
- Lưu trữ refresh token an toàn (httpOnly cookies)
|
||||
- Triển khai thu hồi token
|
||||
|
||||
### Mã Hóa Dữ Liệu
|
||||
|
||||
Luồng mã hóa và giải mã để bảo vệ dữ liệu nhạy cảm khi lưu trữ:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service
|
||||
participant EncryptionService
|
||||
participant Crypto as Node.js Crypto
|
||||
participant Database
|
||||
|
||||
Note over Service,Database: Encryption Flow
|
||||
Service->>EncryptionService: encrypt(plaintext)
|
||||
EncryptionService->>Crypto: Generate random IV<br/>(16 bytes)
|
||||
EncryptionService->>Crypto: Create cipher<br/>(AES-256-GCM)
|
||||
EncryptionService->>Crypto: Encrypt plaintext
|
||||
Crypto->>EncryptionService: Encrypted data + Auth Tag
|
||||
EncryptionService->>Service: Format: iv:tag:ciphertext
|
||||
Service->>Database: Store encrypted data
|
||||
|
||||
Note over Service,Database: Decryption Flow
|
||||
Service->>Database: Retrieve encrypted data
|
||||
Database->>Service: iv:tag:ciphertext
|
||||
Service->>EncryptionService: decrypt(encryptedText)
|
||||
EncryptionService->>EncryptionService: Split iv, tag, ciphertext
|
||||
EncryptionService->>Crypto: Create decipher<br/>(AES-256-GCM)
|
||||
EncryptionService->>Crypto: Set auth tag
|
||||
EncryptionService->>Crypto: Decrypt ciphertext
|
||||
Crypto->>EncryptionService: Plaintext
|
||||
EncryptionService->>Service: Return plaintext
|
||||
```
|
||||
|
||||
### Ngăn Chặn SQL Injection
|
||||
|
||||
- Always use Prisma parameterized queries (automatic)
|
||||
- Never use string concatenation for queries
|
||||
- Validate and sanitize all inputs
|
||||
|
||||
**VI**:
|
||||
- Luôn sử dụng truy vấn tham số hóa của Prisma (tự động)
|
||||
- Không bao giờ sử dụng nối chuỗi cho truy vấn
|
||||
- Xác thực và làm sạch tất cả đầu vào
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Middleware Xác Thực
|
||||
|
||||
See `services/iam-service/src/middlewares/auth.middleware.ts` for complete authentication implementation.
|
||||
|
||||
Xem `services/iam-service/src/middlewares/auth.middleware.ts` để có implementation xác thực hoàn chỉnh.
|
||||
|
||||
### Middleware RBAC
|
||||
|
||||
See `services/iam-service/src/middlewares/rbac.middleware.ts` for permission-based authorization.
|
||||
|
||||
Xem `services/iam-service/src/middlewares/rbac.middleware.ts` để có phân quyền dựa trên quyền.
|
||||
|
||||
### Middleware Xác Thực
|
||||
|
||||
See `services/iam-service/src/middlewares/validation.middleware.ts` for input validation patterns.
|
||||
|
||||
Xem `services/iam-service/src/middlewares/validation.middleware.ts` để có các mẫu xác thực đầu vào.
|
||||
|
||||
### Giới Hạn Tốc Độ
|
||||
|
||||
See `services/iam-service/src/middlewares/rate-limit.middleware.ts` for dynamic rate limiting.
|
||||
|
||||
Xem `services/iam-service/src/middlewares/rate-limit.middleware.ts` để có giới hạn tốc độ động.
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Danh Sách Kiểm Tra Bảo Mật
|
||||
|
||||
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
|
||||
|
||||
Trước khi triển khai bất kỳ service nào:
|
||||
- [ ] Tất cả endpoint yêu cầu xác thực (trừ public)
|
||||
- [ ] Kiểm tra phân quyền đã triển khai (RBAC/ABAC)
|
||||
- [ ] Xác thực đầu vào với Zod schemas
|
||||
- [ ] Giới hạn tốc độ đã cấu hình
|
||||
- [ ] Thông báo lỗi đã được làm sạch (không tiết lộ thông tin)
|
||||
- [ ] PII được mã hóa khi nghỉ
|
||||
- [ ] Mật khẩu được hash với bcrypt (chi phí 12+)
|
||||
- [ ] Token được hash trước khi lưu
|
||||
- [ ] Bí mật trong biến môi trường (không bao giờ hardcode)
|
||||
- [ ] HTTPS được bắt buộc (TLS 1.2+)
|
||||
- [ ] CORS được cấu hình đúng
|
||||
- [ ] Header bảo mật được đặt (helmet)
|
||||
- [ ] Ghi log kiểm toán được bật
|
||||
- [ ] SQL injection được ngăn chặn (sử dụng Prisma)
|
||||
- [ ] Ngăn chặn XSS (làm sạch đầu vào)
|
||||
- [ ] Xác thực tải lên tệp
|
||||
- [ ] Kiểm tra bảo mật đã vượt qua
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[Project Rules](./project-rules.md)** - Coding standards and architecture
|
||||
- **[API Design](./api-design.md)** - Secure API design patterns
|
||||
- **[Testing Patterns](./testing-patterns.md)** - Security testing
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [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)
|
||||
|
||||
**VI**:
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
|
||||
- [Thực Hành Bảo Mật Node.js](https://nodejs.org/en/docs/guides/security/)
|
||||
- [Thực Hành Bảo Mật Express](https://expressjs.com/en/advanced/best-practice-security.html)
|
||||
@@ -1,344 +0,0 @@
|
||||
---
|
||||
name: service-discovery-registry
|
||||
description: Service discovery and registry patterns for GoodGo microservices including service registry, health check orchestration, load balancing strategies, and service mesh integration.
|
||||
---
|
||||
|
||||
# Service Discovery & Registry Patterns
|
||||
# Các Patterns Service Discovery & Registry
|
||||
|
||||
## When to Use This Skill / Khi Nào Sử Dụng Skill Này
|
||||
|
||||
Use this skill when:
|
||||
Sử dụng skill này khi:
|
||||
- Implementing service discovery mechanisms / Triển khai cơ chế service discovery
|
||||
- Managing service registry / Quản lý service registry
|
||||
- Orchestrating health checks / Điều phối health checks
|
||||
- Implementing load balancing strategies / Triển khai các chiến lược load balancing
|
||||
- Integrating with service mesh / Tích hợp với service mesh
|
||||
- Managing dynamic service registration / Quản lý đăng ký service động
|
||||
- Implementing DNS-based discovery / Triển khai discovery dựa trên DNS
|
||||
- Building service catalog / Xây dựng service catalog
|
||||
|
||||
## Core Concepts / Khái Niệm Cốt Lõi
|
||||
|
||||
### Service Discovery Types / Các Loại Service Discovery
|
||||
|
||||
1. **Client-Side Discovery / Discovery Phía Client**: Client queries service registry / Client truy vấn service registry
|
||||
2. **Server-Side Discovery / Discovery Phía Server**: Load balancer queries registry (Kubernetes DNS) / Load balancer truy vấn registry (Kubernetes DNS)
|
||||
3. **Service Mesh / Service Mesh**: Automatic service discovery (Istio, Linkerd) / Service discovery tự động (Istio, Linkerd)
|
||||
|
||||
### Service Registry / Service Registry
|
||||
|
||||
- **Static Configuration / Cấu Hình Tĩnh**: Hardcoded service addresses / Địa chỉ service được hardcode
|
||||
- **Dynamic Registry / Registry Động**: Services register/unregister dynamically / Services đăng ký/hủy đăng ký động
|
||||
- **DNS-Based / Dựa Trên DNS**: Use DNS for service discovery (Kubernetes) / Sử dụng DNS cho service discovery (Kubernetes)
|
||||
|
||||
## Service Registration Flow / Luồng Đăng Ký Service
|
||||
|
||||
The service registration lifecycle involves startup registration, periodic heartbeats, and graceful shutdown unregistration.
|
||||
|
||||
Vòng đời đăng ký service bao gồm đăng ký khi khởi động, heartbeat định kỳ, và hủy đăng ký khi shutdown.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Service as Service Instance
|
||||
participant Registry as Service Registry
|
||||
participant Health as Health Check
|
||||
|
||||
Note over Service,Registry: Service Startup
|
||||
Service->>Registry: Register service info<br/>(name, url, version)
|
||||
Registry->>Registry: Store service metadata
|
||||
Registry-->>Service: Registration confirmed
|
||||
|
||||
Note over Service,Registry: Heartbeat Loop (every 30s)
|
||||
loop Every 30 seconds
|
||||
Service->>Health: Check own health status
|
||||
Health-->>Service: Health status
|
||||
Service->>Registry: Update registration<br/>(status, lastHeartbeat)
|
||||
Registry->>Registry: Update service record
|
||||
end
|
||||
|
||||
Note over Service,Registry: Service Shutdown
|
||||
Service->>Registry: Unregister service
|
||||
Registry->>Registry: Remove service record
|
||||
Registry-->>Service: Unregistration confirmed
|
||||
```
|
||||
|
||||
## Health Check Orchestration / Điều Phối Health Check
|
||||
|
||||
Health checks ensure services are available and functioning correctly. The system aggregates health status from multiple services to determine overall system health.
|
||||
|
||||
Health checks đảm bảo services có sẵn và hoạt động đúng. Hệ thống tổng hợp trạng thái health từ nhiều services để xác định health tổng thể của hệ thống.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Health Check Request]) --> GetServices[Get All Services from Registry]
|
||||
GetServices --> CheckEach{For Each Service}
|
||||
|
||||
CheckEach --> CheckHealth[Check Service Health Endpoint]
|
||||
CheckHealth --> HealthOK{Health OK?}
|
||||
|
||||
HealthOK -->|Yes| UpdateHealthy[Update Status: Healthy]
|
||||
HealthOK -->|No| UpdateUnhealthy[Update Status: Unhealthy]
|
||||
|
||||
UpdateHealthy --> CheckTimeout{Last Heartbeat<br/>< 60s?}
|
||||
UpdateUnhealthy --> CheckTimeout
|
||||
|
||||
CheckTimeout -->|Yes| MarkActive[Mark as Active]
|
||||
CheckTimeout -->|No| MarkStale[Mark as Stale]
|
||||
|
||||
MarkActive --> NextService{More Services?}
|
||||
MarkStale --> NextService
|
||||
|
||||
NextService -->|Yes| CheckEach
|
||||
NextService -->|No| AggregateStatus[Aggregate Overall Status]
|
||||
|
||||
AggregateStatus --> CountUnhealthy[Count Unhealthy Services]
|
||||
CountUnhealthy --> DetermineStatus{Unhealthy Count}
|
||||
|
||||
DetermineStatus -->|0| StatusHealthy[Status: Healthy]
|
||||
DetermineStatus -->|< 50%| StatusDegraded[Status: Degraded]
|
||||
DetermineStatus -->|>= 50%| StatusUnhealthy[Status: Unhealthy]
|
||||
|
||||
StatusHealthy --> ReturnResult[Return Health Status]
|
||||
StatusDegraded --> ReturnResult
|
||||
StatusUnhealthy --> ReturnResult
|
||||
|
||||
ReturnResult --> End([End])
|
||||
```
|
||||
|
||||
## Load Balancing Strategies / Các Chiến Lược Load Balancing
|
||||
|
||||
Load balancing distributes requests across multiple service instances. Different strategies are used based on service characteristics and requirements.
|
||||
|
||||
Load balancing phân phối requests qua nhiều service instances. Các chiến lược khác nhau được sử dụng dựa trên đặc điểm và yêu cầu của service.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Incoming Request]) --> GetInstances[Get Available Service Instances]
|
||||
GetInstances --> SelectStrategy{Load Balancing Strategy}
|
||||
|
||||
SelectStrategy -->|Round Robin| RoundRobin[Round Robin Algorithm]
|
||||
SelectStrategy -->|Least Connections| LeastConn[Least Connections Algorithm]
|
||||
SelectStrategy -->|Weighted| Weighted[Weighted Round Robin]
|
||||
|
||||
RoundRobin --> SelectNext[Select Next Instance in Order]
|
||||
SelectNext --> UseInstance[Use Selected Instance]
|
||||
|
||||
LeastConn --> CompareConn[Compare Connection Counts]
|
||||
CompareConn --> SelectMin[Select Instance with<br/>Minimum Connections]
|
||||
SelectMin --> UseInstance
|
||||
|
||||
Weighted --> CalculateWeight[Calculate Total Weight]
|
||||
CalculateWeight --> RandomSelect[Random Selection Based on Weights]
|
||||
RandomSelect --> UseInstance
|
||||
|
||||
UseInstance --> ForwardRequest[Forward Request to Instance]
|
||||
ForwardRequest --> UpdateStats[Update Statistics]
|
||||
UpdateStats --> End([Request Completed])
|
||||
|
||||
style RoundRobin fill:#e1f5ff
|
||||
style LeastConn fill:#e1f5ff
|
||||
style Weighted fill:#e1f5ff
|
||||
```
|
||||
|
||||
## Kubernetes DNS Discovery / Discovery DNS Kubernetes
|
||||
|
||||
Kubernetes provides built-in DNS-based service discovery. Services are automatically discoverable via DNS names.
|
||||
|
||||
Kubernetes cung cấp service discovery dựa trên DNS tích hợp sẵn. Services có thể được discover tự động qua tên DNS.
|
||||
|
||||
```typescript
|
||||
// EN: Use Kubernetes DNS for service discovery
|
||||
// VI: Sử dụng Kubernetes DNS cho service discovery
|
||||
const serviceUrl = `http://user-service.production.svc.cluster.local`;
|
||||
```
|
||||
|
||||
### DNS Patterns / Các Pattern DNS
|
||||
|
||||
- **Short form / Dạng ngắn** (same namespace / cùng namespace): `http://user-service`
|
||||
- **Full form / Dạng đầy đủ** (cross-namespace / khác namespace): `http://user-service.production.svc.cluster.local`
|
||||
- **With port / Với port**: `http://user-service.production.svc.cluster.local:5000`
|
||||
|
||||
## Service Registry Implementation / Triển Khai Service Registry
|
||||
|
||||
### Register Service / Đăng Ký Service
|
||||
|
||||
```typescript
|
||||
// EN: Register service
|
||||
// VI: Đăng ký service
|
||||
await serviceRegistry.register({
|
||||
name: 'user-service',
|
||||
version: '1.0.0',
|
||||
url: 'http://user-service:5000',
|
||||
healthCheckUrl: 'http://user-service:5000/health',
|
||||
status: 'healthy',
|
||||
lastHeartbeat: new Date(),
|
||||
});
|
||||
```
|
||||
|
||||
### Discover Service / Tìm Service
|
||||
|
||||
```typescript
|
||||
// EN: Discover service
|
||||
// VI: Tìm service
|
||||
const service = await serviceRegistry.discover('user-service');
|
||||
if (service?.status === 'healthy') {
|
||||
// EN: Use service
|
||||
// VI: Sử dụng service
|
||||
}
|
||||
```
|
||||
|
||||
### List Healthy Services / Liệt Kê Services Healthy
|
||||
|
||||
```typescript
|
||||
// EN: List all healthy services
|
||||
// VI: Liệt kê tất cả services healthy
|
||||
const healthyServices = await serviceRegistry.listHealthyServices();
|
||||
```
|
||||
|
||||
## Health Check Aggregation / Tổng Hợp Health Check
|
||||
|
||||
Aggregate health status from multiple services to determine overall system health.
|
||||
|
||||
Tổng hợp trạng thái health từ nhiều services để xác định health tổng thể của hệ thống.
|
||||
|
||||
```typescript
|
||||
// EN: Aggregate health from multiple services
|
||||
// VI: Tổng hợp health từ nhiều services
|
||||
const health = await healthAggregator.getAggregatedHealth();
|
||||
// EN: Returns: { status: 'healthy' | 'degraded' | 'unhealthy', services: [...] }
|
||||
// VI: Trả về: { status: 'healthy' | 'degraded' | 'unhealthy', services: [...] }
|
||||
```
|
||||
|
||||
## Load Balancing Implementation / Triển Khai Load Balancing
|
||||
|
||||
### Round Robin / Round Robin
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.roundRobin(instances);
|
||||
```
|
||||
|
||||
### Least Connections / Least Connections
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.leastConnections(instances);
|
||||
```
|
||||
|
||||
### Weighted Round Robin / Weighted Round Robin
|
||||
|
||||
```typescript
|
||||
const instance = loadBalancer.weightedRoundRobin(instances);
|
||||
```
|
||||
|
||||
## Service Mesh Integration / Tích Hợp Service Mesh
|
||||
|
||||
Service mesh solutions like Istio provide automatic service discovery and advanced routing capabilities.
|
||||
|
||||
Các giải pháp service mesh như Istio cung cấp service discovery tự động và khả năng routing nâng cao.
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: ServiceEntry
|
||||
metadata:
|
||||
name: external-service
|
||||
spec:
|
||||
hosts:
|
||||
- external-api.example.com
|
||||
ports:
|
||||
- number: 443
|
||||
name: https
|
||||
protocol: HTTPS
|
||||
location: MESH_EXTERNAL
|
||||
resolution: DNS
|
||||
```
|
||||
|
||||
## Best Practices / Thực Hành Tốt
|
||||
|
||||
1. **Kubernetes DNS / Kubernetes DNS**: Use Kubernetes DNS for service discovery in K8s environments / Sử dụng Kubernetes DNS cho service discovery trong môi trường K8s
|
||||
2. **Health Checks / Health Checks**: Implement comprehensive health checks (`/health`, `/health/live`, `/health/ready`) / Triển khai health checks toàn diện (`/health`, `/health/live`, `/health/ready`)
|
||||
3. **Service Registry / Service Registry**: Use registry for dynamic services that need runtime discovery / Sử dụng registry cho các services động cần discovery tại runtime
|
||||
4. **Load Balancing / Load Balancing**: Choose appropriate load balancing strategy based on service characteristics / Chọn chiến lược load balancing phù hợp dựa trên đặc điểm service
|
||||
5. **Monitoring / Giám Sát**: Monitor service discovery and health check metrics / Giám sát metrics service discovery và health check
|
||||
6. **Heartbeat / Heartbeat**: Implement periodic heartbeat (every 30 seconds) to keep registry updated / Triển khai heartbeat định kỳ (mỗi 30 giây) để giữ registry được cập nhật
|
||||
7. **Graceful Shutdown / Shutdown Nhẹ Nhàng**: Always unregister services on shutdown to prevent stale entries / Luôn hủy đăng ký services khi shutdown để tránh entries cũ
|
||||
8. **Fallback / Dự Phòng**: Provide fallback mechanisms when registry is unavailable / Cung cấp cơ chế dự phòng khi registry không khả dụng
|
||||
|
||||
## Common Mistakes / Các Lỗi Thường Gặp
|
||||
|
||||
1. **Hardcoded Service URLs / Hardcode Service URLs**: Breaks in different environments / Phá vỡ trong các môi trường khác nhau
|
||||
```typescript
|
||||
// ❌ BAD: Hardcoded
|
||||
const url = 'http://user-service:5000';
|
||||
|
||||
// ✅ GOOD: Use discovery or env vars
|
||||
const url = discovery.getServiceUrl('user-service');
|
||||
```
|
||||
|
||||
2. **No Heartbeat / Không Có Heartbeat**: Stale registry entries / Entries registry cũ
|
||||
```typescript
|
||||
// ❌ BAD: Register once
|
||||
await registry.register(service);
|
||||
|
||||
// ✅ GOOD: Periodic heartbeat
|
||||
setInterval(() => registry.register(service), 30000);
|
||||
```
|
||||
|
||||
3. **Missing Graceful Shutdown / Thiếu Graceful Shutdown**: Orphaned registrations / Đăng ký bị bỏ lại
|
||||
```typescript
|
||||
// ✅ Always unregister on shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
await registry.unregister(serviceName);
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
4. **No Fallback / Không Có Fallback**: Fails when registry unavailable / Lỗi khi registry không khả dụng
|
||||
```typescript
|
||||
// ❌ BAD: No fallback
|
||||
const url = await registry.discover('service');
|
||||
|
||||
// ✅ GOOD: Fallback to default
|
||||
const url = await registry.discover('service')
|
||||
?? process.env.SERVICE_FALLBACK_URL;
|
||||
```
|
||||
|
||||
## Quick Reference / Tham Khảo Nhanh
|
||||
|
||||
### Discovery Types / Các Loại Discovery
|
||||
|
||||
| Discovery Type | Implementation | Use Case |
|
||||
|----------------|----------------|----------|
|
||||
| **K8s DNS** | `service.namespace.svc.cluster.local` | Internal services |
|
||||
| **Service Registry** | Database-backed | Dynamic services |
|
||||
| **Service Mesh** | Istio/Linkerd | Complex routing |
|
||||
| **Environment Vars** | `process.env.SERVICE_URL` | Simple/external |
|
||||
|
||||
### Health Check Endpoints / Các Endpoint Health Check
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/health` | Basic health |
|
||||
| `/health/live` | K8s liveness probe |
|
||||
| `/health/ready` | K8s readiness probe |
|
||||
|
||||
### Load Balancing Strategies / Các Chiến Lược Load Balancing
|
||||
|
||||
| Strategy | When to Use |
|
||||
|----------|-------------|
|
||||
| **Round Robin** | Equal capacity servers |
|
||||
| **Least Connections** | Varying request durations |
|
||||
| **Weighted** | Different server capacities |
|
||||
|
||||
### Service Registration Lifecycle / Vòng Đời Đăng Ký Service
|
||||
|
||||
```
|
||||
Startup → Register → Heartbeat (30s) → ... → Shutdown → Unregister
|
||||
```
|
||||
|
||||
## Resources / Tài Nguyên
|
||||
|
||||
- [Deployment Kubernetes](./deployment-kubernetes.md) - Kubernetes deployment patterns / Các pattern deployment Kubernetes
|
||||
- [Observability & Monitoring](./observability-monitoring.md) - Health checks and monitoring / Health checks và giám sát
|
||||
- [Project Rules](./project-rules.md) - GoodGo standards / Các tiêu chuẩn GoodGo
|
||||
- Skill Source: `.cursor/skills/service-discovery-registry/SKILL.md`
|
||||
@@ -1,416 +0,0 @@
|
||||
# Các Pattern Service Layer
|
||||
|
||||
Service layer organization and patterns for GoodGo microservices including business logic implementation, dependency injection, service composition, and separation of concerns.
|
||||
> Tổ chức và các patterns của service layer cho GoodGo microservices bao gồm implement business logic, dependency injection, service composition, và separation of concerns.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
The service layer is where business logic lives in GoodGo microservices. It sits between controllers (HTTP layer) and repositories (data layer), orchestrating business operations, enforcing business rules, and coordinating data access. This guide covers service organization patterns, dependency injection, service composition, and best practices.
|
||||
|
||||
Service layer là nơi chứa business logic trong GoodGo microservices. Nó nằm giữa controllers (HTTP layer) và repositories (data layer), điều phối business operations, thực thi business rules, và phối hợp data access. Hướng dẫn này bao gồm các patterns tổ chức service, dependency injection, service composition, và best practices.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use service layer patterns 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
|
||||
|
||||
Sử dụng service layer patterns khi:
|
||||
- Implement business logic trong services
|
||||
- Tổ chức service layer code
|
||||
- Sử dụng dependency injection patterns
|
||||
- Kết hợp nhiều services với nhau
|
||||
- Tách biệt concerns giữa controllers, services, và repositories
|
||||
- Xử lý business rules và validations
|
||||
- Implement service composition patterns
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Kiến Trúc Service Layer
|
||||
|
||||
Service layer tuân theo mô hình kiến trúc ba tầng, tách biệt các concerns giữa xử lý HTTP, business logic, và truy cập dữ liệu:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "HTTP Layer"
|
||||
Controller["Controller<br/>- HTTP request/response<br/>- Input validation<br/>- Status codes"]
|
||||
end
|
||||
|
||||
subgraph "Business Layer"
|
||||
Service["Service<br/>- Business logic<br/>- Business rules<br/>- Orchestration<br/>- Caching<br/>- Logging"]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
Repository["Repository<br/>- Data access<br/>- CRUD operations<br/>- Database queries"]
|
||||
Database[("Database<br/>Prisma")]
|
||||
end
|
||||
|
||||
Controller -->|"Calls"| Service
|
||||
Service -->|"Uses"| Repository
|
||||
Repository -->|"Queries"| Database
|
||||
|
||||
style Controller fill:#e1f5ff
|
||||
style Service fill:#fff4e1
|
||||
style Repository fill:#e8f5e9
|
||||
style Database fill:#f3e5f5
|
||||
```
|
||||
|
||||
### Trách Nhiệm Service Layer
|
||||
|
||||
The service layer has specific responsibilities that distinguish it from controllers and repositories.
|
||||
|
||||
Service layer có các trách nhiệm cụ thể phân biệt nó với controllers và repositories.
|
||||
|
||||
**Service Layer Responsibilities / Trách Nhiệm**:
|
||||
- **Business Logic / Logic Nghiệp Vụ**: Contains domain-specific business rules and logic
|
||||
- **Orchestration / Điều Phối**: Orchestrates calls to repositories and other services
|
||||
- **Validation / Xác Thực**: Validates business rules (not just input validation)
|
||||
- **Cross-cutting Concerns / Concerns Xuyên Suốt**: Handles caching, logging, auditing
|
||||
- **Coordination / Phối Hợp**: Coordinates multiple repositories or services
|
||||
- **Transport Independence / Độc Lập Transport**: Independent of HTTP transport layer
|
||||
|
||||
**NOT Service Responsibilities / KHÔNG Phải Trách Nhiệm**:
|
||||
- HTTP request/response handling (that's controllers)
|
||||
- Database queries (that's repositories)
|
||||
- Authentication/authorization (that's middleware)
|
||||
|
||||
### Tiêm Phụ Thuộc
|
||||
|
||||
Services use constructor injection to receive dependencies, making them testable and loosely coupled. Luồng dependency injection cho thấy cách các components được kết nối với nhau:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Module Initialization"
|
||||
Prisma[("Prisma Client")]
|
||||
Cache[("Cache Service")]
|
||||
end
|
||||
|
||||
subgraph "Dependency Creation"
|
||||
Repo["UserRepository<br/>(prisma)"]
|
||||
Service["UserService<br/>(repository, cache)"]
|
||||
Controller["UserController<br/>(service)"]
|
||||
end
|
||||
|
||||
subgraph "Router Setup"
|
||||
Router["Router<br/>(controller methods)"]
|
||||
end
|
||||
|
||||
Prisma -->|"Injected"| Repo
|
||||
Cache -->|"Injected"| Service
|
||||
Repo -->|"Injected"| Service
|
||||
Service -->|"Injected"| Controller
|
||||
Controller -->|"Bound to"| Router
|
||||
|
||||
style Prisma fill:#f3e5f5
|
||||
style Cache fill:#f3e5f5
|
||||
style Repo fill:#e8f5e9
|
||||
style Service fill:#fff4e1
|
||||
style Controller fill:#e1f5ff
|
||||
style Router fill:#e1f5ff
|
||||
```
|
||||
|
||||
**Ví Dụ Implementation:**
|
||||
|
||||
Services sử dụng constructor injection để nhận dependencies, làm cho chúng testable và loosely coupled:
|
||||
|
||||
```typescript
|
||||
export class UserService {
|
||||
constructor(
|
||||
private userRepository: UserRepository,
|
||||
private cacheService: CacheService,
|
||||
private logger: Logger
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
### Luồng Request Qua Các Layer
|
||||
|
||||
Sơ đồ sequence sau minh họa cách một request chảy qua các layer Controller → Service → Repository:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Controller
|
||||
participant Service
|
||||
participant Cache
|
||||
participant Repository
|
||||
participant Database
|
||||
|
||||
Client->>Controller: HTTP Request (GET /users/:id)
|
||||
Controller->>Controller: Validate input
|
||||
Controller->>Service: getUserById(id)
|
||||
|
||||
Service->>Cache: Check cache
|
||||
alt Cache Hit
|
||||
Cache-->>Service: Return cached user
|
||||
Service-->>Controller: Return user
|
||||
else Cache Miss
|
||||
Service->>Repository: findById(id)
|
||||
Repository->>Database: Query user
|
||||
Database-->>Repository: User data
|
||||
Repository-->>Service: User entity
|
||||
|
||||
alt User Not Found
|
||||
Service-->>Controller: Throw NotFoundError
|
||||
Controller-->>Client: 404 Not Found
|
||||
else User Found
|
||||
Service->>Cache: Set cache (TTL: 5min)
|
||||
Service-->>Controller: Return user
|
||||
Controller-->>Client: 200 OK + User data
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Pattern Service Cơ Bản
|
||||
|
||||
Basic service implementation with repository dependency.
|
||||
|
||||
Implementation service cơ bản với repository dependency.
|
||||
|
||||
```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;
|
||||
}
|
||||
|
||||
async createUser(data: CreateUserInput) {
|
||||
logger.info('Creating user', { email: data.email });
|
||||
|
||||
const user = await userRepository.create(data);
|
||||
logger.info('User created', { userId: user.id });
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/feature/feature.service.ts`](../../../services/iam-service/src/modules/feature/feature.service.ts)
|
||||
|
||||
### Service Với Caching
|
||||
|
||||
Services implement caching to improve performance for frequently accessed data.
|
||||
|
||||
Services implement caching để cải thiện hiệu suất cho dữ liệu thường xuyên truy cập.
|
||||
|
||||
```typescript
|
||||
export class UserService {
|
||||
constructor(
|
||||
private repository: UserRepository,
|
||||
private cacheService: CacheService
|
||||
) {}
|
||||
|
||||
async getUserById(id: string) {
|
||||
const cacheKey = cacheService.keys.user(id);
|
||||
|
||||
// Thử cache trước
|
||||
const cached = await this.cacheService.get<User>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
// Cache miss - fetch từ repository
|
||||
const user = await this.repository.findById(id);
|
||||
if (!user) {
|
||||
throw new NotFoundError('User');
|
||||
}
|
||||
|
||||
// Cache trong 5 phút
|
||||
await this.cacheService.set(cacheKey, user, 300);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/rbac/rbac.service.ts`](../../../services/iam-service/src/modules/rbac/rbac.service.ts)
|
||||
|
||||
### Kết Hợp Services
|
||||
|
||||
Services can depend on other services to compose complex operations. Sơ đồ sau minh họa service composition:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "AccessRequestService"
|
||||
ARService["AccessRequestService"]
|
||||
ARRepo["AccessRequestRepository"]
|
||||
end
|
||||
|
||||
subgraph "Dependencies"
|
||||
UserService["UserService"]
|
||||
RBACService["RBACService"]
|
||||
end
|
||||
|
||||
subgraph "Supporting Services"
|
||||
UserRepo["UserRepository"]
|
||||
RBACRepo["RBACRepository"]
|
||||
end
|
||||
|
||||
ARService -->|"Uses"| UserService
|
||||
ARService -->|"Uses"| RBACService
|
||||
ARService -->|"Uses"| ARRepo
|
||||
|
||||
UserService -->|"Uses"| UserRepo
|
||||
RBACService -->|"Uses"| RBACRepo
|
||||
|
||||
style ARService fill:#fff4e1
|
||||
style UserService fill:#fff4e1
|
||||
style RBACService fill:#fff4e1
|
||||
style ARRepo fill:#e8f5e9
|
||||
style UserRepo fill:#e8f5e9
|
||||
style RBACRepo fill:#e8f5e9
|
||||
```
|
||||
|
||||
**Ví Dụ Implementation:**
|
||||
|
||||
Services có thể phụ thuộc vào các services khác để kết hợp các operations phức tạp:
|
||||
|
||||
```typescript
|
||||
export class AccessRequestService {
|
||||
constructor(
|
||||
private accessRequestRepository: AccessRequestRepository,
|
||||
private userService: UserService,
|
||||
private rbacService: RBACService
|
||||
) {}
|
||||
|
||||
async createRequest(userId: string, data: CreateRequestDto) {
|
||||
// Sử dụng các services khác
|
||||
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 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Xác Thực Business Logic
|
||||
|
||||
Services validate business rules beyond simple input validation.
|
||||
|
||||
Services validate business rules vượt quá input validation đơn giản.
|
||||
|
||||
```typescript
|
||||
export class UserService {
|
||||
async createUser(data: CreateUserInput) {
|
||||
// Business rule: Kiểm tra email đã tồn tại
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern Service Module
|
||||
|
||||
Organize services into modules with controllers and routers.
|
||||
|
||||
Tổ chức services thành modules với controllers và routers.
|
||||
|
||||
```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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tham Khảo**: [`services/iam-service/src/modules/feature/feature.module.ts`](../../../services/iam-service/src/modules/feature/feature.module.ts)
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
1. **Single Responsibility / Trách Nhiệm Đơn**: Mỗi service xử lý một domain area
|
||||
2. **Dependency Injection / Tiêm Phụ Thuộc**: Sử dụng constructor injection để dễ test
|
||||
3. **Business Logic Only / Chỉ Business Logic**: Giữ HTTP concerns trong controllers
|
||||
4. **Use Repositories / Sử Dụng Repositories**: Không truy cập database trực tiếp
|
||||
5. **Error Handling / Xử Lý Lỗi**: Throw appropriate domain errors
|
||||
6. **Logging / Ghi Log**: Log các operations quan trọng và errors
|
||||
7. **Caching / Caching**: Implement caching trong services, không phải repositories
|
||||
8. **Composition / Kết Hợp**: Kết hợp services cho complex operations
|
||||
9. **Pure Functions / Functions Thuần**: Giữ service methods pure khi có thể
|
||||
10. **Transaction Coordination / Phối Hợp Transaction**: Coordinate transactions cho multi-step operations
|
||||
|
||||
## Lỗi Thường Gặp
|
||||
|
||||
1. **HTTP in Services / HTTP Trong Services**: Sử dụng `req`/`res` trong services
|
||||
2. **Direct Database Access / Truy Cập Database Trực Tiếp**: Truy cập Prisma trực tiếp thay vì repositories
|
||||
3. **Too Many Responsibilities / Quá Nhiều Trách Nhiệm**: Service làm quá nhiều việc
|
||||
4. **No Error Handling / Không Xử Lý Lỗi**: Không throw appropriate errors
|
||||
5. **Business Logic in Controllers / Business Logic Trong Controllers**: Đưa business logic vào controllers
|
||||
6. **Tight Coupling / Kết Hợp Chặt**: Services tightly coupled với specific implementations
|
||||
7. **Missing Validation / Thiếu Validation**: Không validate business rules
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Phụ Thuộc Vòng Tròn
|
||||
|
||||
**Problem / Vấn Đề**: Circular dependencies giữa services
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Refactor để loại bỏ circular dependencies
|
||||
- Extract shared logic vào common service hoặc utility
|
||||
- Use dependency injection properly
|
||||
|
||||
### Service Quá Lớn
|
||||
|
||||
**Problem / Vấn Đề**: Service class quá lớn với quá nhiều methods
|
||||
|
||||
**Solution / Giải Pháp**:
|
||||
- Split service thành multiple smaller services
|
||||
- Extract related functionality vào separate services
|
||||
- Use service composition instead of one large service
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Feature Service](../../../services/iam-service/src/modules/feature/feature.service.ts) - Example service implementation
|
||||
- [User Service](../../../services/iam-service/src/modules/identity/user/user.service.ts) - User management service
|
||||
- [RBAC Service](../../../services/iam-service/src/modules/rbac/rbac.service.ts) - Role-based access control service
|
||||
- [Repository Pattern](./repository-pattern.md) - Repository patterns
|
||||
- [Caching Patterns](./caching-patterns.md) - Caching in services
|
||||
- [Error Handling](./error-handling-patterns.md) - Error handling patterns
|
||||
@@ -1,841 +0,0 @@
|
||||
# Các Pattern Testing
|
||||
|
||||
Comprehensive testing best practices for GoodGo microservices including unit tests, integration tests, E2E tests, Jest configuration, mocking strategies, and debugging techniques.
|
||||
> Các thực hành tốt nhất về testing cho microservices GoodGo bao gồm unit tests, integration tests, E2E tests, cấu hình Jest, strategies mocking, và kỹ thuật debugging.
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
Testing is a critical component of the GoodGo microservices platform. This guide provides comprehensive patterns and best practices for writing maintainable, reliable tests across all services. It covers test types, Jest configuration, mocking strategies, test utilities, and debugging techniques that ensure code quality and reliability.
|
||||
|
||||
Testing là thành phần quan trọng của nền tảng microservices GoodGo. Hướng dẫn này cung cấp các patterns và best practices toàn diện để viết các test có thể bảo trì và đáng tin cậy trên tất cả các services. Nó bao gồm các loại test, cấu hình Jest, strategies mocking, test utilities, và kỹ thuật debugging để đảm bảo chất lượng và độ tin cậy của code.
|
||||
|
||||
## Khi Nào Sử Dụng
|
||||
|
||||
Use these testing patterns 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
|
||||
- Creating test utilities and factories
|
||||
|
||||
Sử dụng các testing patterns này khi:
|
||||
- Viết unit tests cho services, controllers, hoặc repositories
|
||||
- Tạo integration tests cho middleware chains
|
||||
- Xây dựng E2E tests cho API endpoints
|
||||
- Thiết lập cấu hình Jest cho service mới
|
||||
- Mocking các dependencies bên ngoài (Prisma, Redis, Auth SDK)
|
||||
- Debugging test failures
|
||||
- Cải thiện test coverage
|
||||
- Tạo test utilities và factories
|
||||
|
||||
## Khái Niệm Chính
|
||||
|
||||
### Các Loại Test
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
Test individual functions or classes in isolation with all dependencies mocked.
|
||||
|
||||
Test các functions hoặc classes riêng lẻ một cách cô lập với tất cả dependencies được mock.
|
||||
|
||||
**Characteristics / Đặc điểm**:
|
||||
- **Location / Vị trí**: Next to source files (`*.test.ts`) or in `__tests__/` directory
|
||||
- **Scope / Phạm vi**: Single function, method, or class
|
||||
- **Dependencies / Dependencies**: All external dependencies mocked
|
||||
- **Speed / Tốc độ**: Fast (<1s per test)
|
||||
- **Purpose / Mục đích**: Verify logic correctness
|
||||
|
||||
**Example / Ví dụ**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
Test multiple components working together with some real dependencies.
|
||||
|
||||
Test nhiều components làm việc cùng nhau với một số dependencies thật.
|
||||
|
||||
**Characteristics / Đặc điểm**:
|
||||
- **Location / Vị trí**: `__tests__/` directory (`*.test.ts`)
|
||||
- **Scope / Phạm vi**: Multiple components interacting
|
||||
- **Dependencies / Dependencies**: Mix of real and mocked dependencies
|
||||
- **Speed / Tốc độ**: Medium (1-5s per test)
|
||||
- **Purpose / Mục đích**: Verify component integration
|
||||
|
||||
**Example / Ví dụ**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
Test complete request/response cycles through the entire application stack.
|
||||
|
||||
Test các chu kỳ request/response hoàn chỉnh qua toàn bộ application stack.
|
||||
|
||||
**Characteristics / Đặc điểm**:
|
||||
- **Location / Vị trí**: `__tests__/*.e2e.ts`
|
||||
- **Scope / Phạm vi**: Full API workflow from HTTP request to response
|
||||
- **Dependencies / Dependencies**: Test database, mocked external services
|
||||
- **Speed / Tốc độ**: Slow (5-10s per test)
|
||||
- **Purpose / Mục đích**: Verify end-to-end functionality
|
||||
|
||||
**Example / Ví dụ**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
||||
|
||||
### Testing Pyramid / Kim Tự Tháp Testing
|
||||
|
||||
The testing pyramid illustrates the recommended distribution of test types. Most tests should be fast unit tests, with fewer integration tests, and the fewest E2E tests.
|
||||
|
||||
Kim tự tháp testing minh họa phân bố được khuyến nghị của các loại test. Hầu hết tests nên là unit tests nhanh, với ít integration tests hơn, và ít E2E tests nhất.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph pyramid["Testing Pyramid"]
|
||||
E2E["E2E Tests<br/>Fewest<br/>5-10s each<br/>Full stack"]
|
||||
Integration["Integration Tests<br/>Medium<br/>1-5s each<br/>Components"]
|
||||
Unit["Unit Tests<br/>Most<br/><1s each<br/>Isolated"]
|
||||
end
|
||||
|
||||
Unit --> Integration
|
||||
Integration --> E2E
|
||||
|
||||
style E2E fill:#e1f5ff
|
||||
style Integration fill:#b3e5fc
|
||||
style Unit fill:#81d4fa
|
||||
```
|
||||
|
||||
**Key Principles / Nguyên Tắc Chính:**
|
||||
- **Unit Tests (Base) / Unit Tests (Nền)**: Many fast tests covering individual functions/classes / Nhiều tests nhanh bao phủ các functions/classes riêng lẻ
|
||||
- **Integration Tests (Middle) / Integration Tests (Giữa)**: Fewer tests verifying component interactions / Ít tests hơn xác minh tương tác giữa các components
|
||||
- **E2E Tests (Top) / E2E Tests (Đỉnh)**: Fewest tests validating complete workflows / Ít tests nhất xác thực các workflows hoàn chỉnh
|
||||
|
||||
## Cấu Hình Jest
|
||||
|
||||
### Cấu Hình Chuẩn
|
||||
|
||||
The standard Jest configuration for GoodGo services includes TypeScript support, coverage thresholds, and proper test environment setup.
|
||||
|
||||
**Location / Vị trí**: [`services/iam-service/jest.config.ts`](../../../services/iam-service/jest.config.ts)
|
||||
|
||||
```typescript
|
||||
import type { Config } from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: [
|
||||
'**/__tests__/**/*.test.ts',
|
||||
'**/__tests__/**/*.spec.ts',
|
||||
'**/__tests__/**/*.e2e.ts',
|
||||
'**/?(*.)+(spec|test).ts'
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/main.ts',
|
||||
'!src/config/**/*.ts',
|
||||
'!src/**/*.config.ts'
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70
|
||||
}
|
||||
},
|
||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setupTests.ts'],
|
||||
testTimeout: 10000,
|
||||
clearMocks: true,
|
||||
resetModules: true
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Điểm Cấu Hình Quan Trọng
|
||||
|
||||
- **preset: 'ts-jest'**: Enables TypeScript support / Bật hỗ trợ TypeScript
|
||||
- **testEnvironment: 'node'**: Sets Node.js environment (not browser) / Thiết lập môi trường Node.js (không phải browser)
|
||||
- **coverageThreshold**: Enforces minimum 70% coverage across all metrics / Yêu cầu tối thiểu 70% coverage trên tất cả các metrics
|
||||
- **setupFilesAfterEnv**: Loads test setup file before each test suite / Tải file setup test trước mỗi test suite
|
||||
- **clearMocks**: Automatically clears mock calls between tests / Tự động xóa mock calls giữa các test
|
||||
- **resetModules**: Resets module cache for test isolation / Reset module cache để cô lập test
|
||||
|
||||
## Files Thiết Lập
|
||||
|
||||
### File Thiết Lập Test
|
||||
|
||||
The setup file (`setupTests.ts`) configures global mocks and test utilities that are available across all tests.
|
||||
|
||||
**Location / Vị trí**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
||||
|
||||
**Key Features / Tính Năng Chính**:
|
||||
- Mock external services (logger, tracing, database, Redis) / Mock các services bên ngoài
|
||||
- Configure test environment variables / Cấu hình biến môi trường test
|
||||
- Set up global test utilities / Thiết lập test utilities toàn cục
|
||||
- Initialize Prometheus mocks / Khởi tạo Prometheus mocks
|
||||
|
||||
**Example / Ví dụ**:
|
||||
```typescript
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
Mock environment variables for tests
|
||||
// VI: Mock biến môi trường cho tests
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test_db';
|
||||
process.env.REDIS_URL = 'redis://localhost:6379/1';
|
||||
|
||||
Mock external services to avoid real network calls
|
||||
// VI: Mock các service bên ngoài để tránh gọi mạng thật
|
||||
jest.mock('@goodgo/logger', () => ({
|
||||
logger: {
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
Global test utilities
|
||||
// VI: Utilities test toàn cục
|
||||
global.testUtils = {
|
||||
createMockReq: (overrides = {}) => ({
|
||||
body: {},
|
||||
params: {},
|
||||
query: {},
|
||||
headers: {},
|
||||
...overrides,
|
||||
}),
|
||||
createMockRes: () => {
|
||||
const res: any = {};
|
||||
res.status = jest.fn().mockReturnValue(res);
|
||||
res.json = jest.fn().mockReturnValue(res);
|
||||
res.send = jest.fn().mockReturnValue(res);
|
||||
return res;
|
||||
},
|
||||
createMockNext: () => jest.fn(),
|
||||
};
|
||||
```
|
||||
|
||||
### Test Execution Flow / Luồng Thực Thi Test
|
||||
|
||||
The test execution flow shows the lifecycle hooks and execution order in Jest test suites.
|
||||
|
||||
Luồng thực thi test cho thấy các lifecycle hooks và thứ tự thực thi trong các test suites của Jest.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([Test Suite Starts]) --> BeforeAll[beforeAll<br/>Run once before all tests]
|
||||
BeforeAll --> BeforeEach[beforeEach<br/>Run before each test]
|
||||
BeforeEach --> Test[Execute Test<br/>Arrange-Act-Assert]
|
||||
Test --> AfterEach[afterEach<br/>Run after each test]
|
||||
AfterEach --> MoreTests{More Tests?}
|
||||
MoreTests -->|Yes| BeforeEach
|
||||
MoreTests -->|No| AfterAll[afterAll<br/>Run once after all tests]
|
||||
AfterAll --> End([Test Suite Ends])
|
||||
|
||||
style Start fill:#c8e6c9
|
||||
style End fill:#c8e6c9
|
||||
style Test fill:#fff9c4
|
||||
style BeforeAll fill:#e1bee7
|
||||
style AfterAll fill:#e1bee7
|
||||
style BeforeEach fill:#bbdefb
|
||||
style AfterEach fill:#bbdefb
|
||||
```
|
||||
|
||||
**Execution Order / Thứ Tự Thực Thi:**
|
||||
1. `beforeAll`: Setup that runs once for the entire suite (e.g., database connection) / Setup chạy một lần cho toàn bộ suite (ví dụ: kết nối database)
|
||||
2. `beforeEach`: Setup that runs before each test (e.g., reset mocks, create test data) / Setup chạy trước mỗi test (ví dụ: reset mocks, tạo test data)
|
||||
3. **Test Execution / Thực Thi Test**: The actual test code following AAA pattern / Code test thực tế tuân theo pattern AAA
|
||||
4. `afterEach`: Cleanup after each test (e.g., clear mocks, reset state) / Dọn dẹp sau mỗi test (ví dụ: clear mocks, reset state)
|
||||
5. `afterAll`: Final cleanup after all tests (e.g., close database connection) / Dọn dẹp cuối cùng sau tất cả tests (ví dụ: đóng kết nối database)
|
||||
|
||||
## Các Pattern Thường Dùng
|
||||
|
||||
### Pattern Unit Test
|
||||
|
||||
**Structure / Cấu trúc**: Follow the AAA (Arrange-Act-Assert) pattern for clarity / Tuân theo pattern AAA để rõ ràng.
|
||||
|
||||
```typescript
|
||||
describe('FeatureService', () => {
|
||||
let service: FeatureService;
|
||||
let mockRepository: any;
|
||||
|
||||
beforeEach(() => {
|
||||
Clear all mocks before each test
|
||||
// VI: Xóa tất cả mocks trước mỗi test
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockRepository = {
|
||||
findById: jest.fn(),
|
||||
create: jest.fn(),
|
||||
};
|
||||
service = new FeatureService(mockRepository);
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a feature successfully', async () => {
|
||||
Arrange
|
||||
// VI: Chuẩn bị
|
||||
const testData = { name: 'test-feature', title: 'Test Feature' };
|
||||
const mockFeature = {
|
||||
id: 'test-id',
|
||||
name: testData.name,
|
||||
// ... other fields
|
||||
};
|
||||
mockRepository.create.mockResolvedValue(mockFeature);
|
||||
|
||||
Act
|
||||
// VI: Thực hiện
|
||||
const result = await service.create(testData);
|
||||
|
||||
Assert
|
||||
// VI: Kiểm tra
|
||||
expect(mockRepository.create).toHaveBeenCalledWith(testData);
|
||||
expect(result).toEqual(mockFeature);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
||||
|
||||
### Pattern Integration Test
|
||||
|
||||
Integration tests verify that multiple components work together correctly / Integration tests xác minh nhiều components làm việc cùng nhau đúng cách.
|
||||
|
||||
```typescript
|
||||
describe('Auth Middleware', () => {
|
||||
const mockNext = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should authenticate valid token and attach user to request', () => {
|
||||
Arrange
|
||||
// VI: Chuẩn bị
|
||||
const mockReq = createMockReq({
|
||||
headers: { authorization: 'Bearer valid-token' },
|
||||
});
|
||||
const mockRes = createMockRes();
|
||||
|
||||
Act
|
||||
// VI: Thực hiện
|
||||
const middleware = authenticate({ secret: jwtSecret });
|
||||
middleware(mockReq as Request, mockRes as Response, mockNext);
|
||||
|
||||
Assert
|
||||
// VI: Kiểm tra
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
expect(mockReq.user).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
||||
|
||||
### Pattern E2E Test
|
||||
|
||||
E2E tests use supertest to test complete HTTP request/response cycles / E2E tests sử dụng supertest để test các chu kỳ HTTP request/response hoàn chỉnh.
|
||||
|
||||
```typescript
|
||||
import request from 'supertest';
|
||||
import express from 'express';
|
||||
import { createRouter } from '../routes';
|
||||
|
||||
describe('Feature Endpoints E2E', () => {
|
||||
let app: express.Application;
|
||||
|
||||
beforeAll(() => {
|
||||
Set up test environment
|
||||
// VI: Thiết lập môi trường test
|
||||
process.env.NODE_ENV = 'test';
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use(createRouter());
|
||||
});
|
||||
|
||||
describe('POST /api/v1/features', () => {
|
||||
it('should create a feature successfully', async () => {
|
||||
Arrange
|
||||
// VI: Chuẩn bị
|
||||
const featureData = {
|
||||
name: 'test-feature',
|
||||
title: 'Test Feature',
|
||||
description: 'A test feature for E2E testing'
|
||||
};
|
||||
|
||||
Act
|
||||
// VI: Thực hiện
|
||||
const response = await request(app)
|
||||
.post('/api/v1/features')
|
||||
.send(featureData)
|
||||
.expect(201);
|
||||
|
||||
Assert
|
||||
// VI: Kiểm tra
|
||||
expect(response.body).toMatchObject({
|
||||
success: true,
|
||||
message: 'Feature created successfully / Feature đã được tạo thành công',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Real Example / Ví dụ Thực tế**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
||||
|
||||
## Chiến Lược Mocking
|
||||
|
||||
### Mocking Strategy by Test Type / Chiến Lược Mocking Theo Loại Test
|
||||
|
||||
Different test types require different levels of mocking. This diagram shows what should be mocked at each test level.
|
||||
|
||||
Các loại test khác nhau yêu cầu mức độ mocking khác nhau. Biểu đồ này cho thấy những gì nên được mock ở mỗi mức test.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph UnitTests["Unit Tests"]
|
||||
UnitCode[Application Code]
|
||||
UnitMock[All Dependencies Mocked<br/>- Database<br/>- External APIs<br/>- Services<br/>- Repositories]
|
||||
UnitCode --> UnitMock
|
||||
end
|
||||
|
||||
subgraph IntegrationTests["Integration Tests"]
|
||||
IntCode[Application Code]
|
||||
IntReal[Real Internal Components]
|
||||
IntMock[External Dependencies Mocked<br/>- External APIs<br/>- Third-party Services]
|
||||
IntCode --> IntReal
|
||||
IntReal --> IntMock
|
||||
end
|
||||
|
||||
subgraph E2ETests["E2E Tests"]
|
||||
E2ECode[Application Code]
|
||||
E2EReal[Real Internal Stack<br/>- Database<br/>- Services<br/>- Middleware]
|
||||
E2EMock[Only External Services Mocked<br/>- Payment APIs<br/>- Email Services<br/>- Third-party APIs]
|
||||
E2ECode --> E2EReal
|
||||
E2EReal --> E2EMock
|
||||
end
|
||||
|
||||
style UnitTests fill:#ffcdd2
|
||||
style IntegrationTests fill:#fff9c4
|
||||
style E2ETests fill:#c8e6c9
|
||||
style UnitMock fill:#f8bbd0
|
||||
style IntMock fill:#ffe082
|
||||
style E2EMock fill:#a5d6a7
|
||||
```
|
||||
|
||||
**Mocking Guidelines / Hướng Dẫn Mocking:**
|
||||
- **Unit Tests**: Mock all external dependencies to test logic in isolation / Mock tất cả dependencies bên ngoài để test logic một cách cô lập
|
||||
- **Integration Tests**: Use real internal components, mock only external services / Sử dụng các components nội bộ thật, chỉ mock các services bên ngoài
|
||||
- **E2E Tests**: Use real database and internal stack, mock only third-party external services / Sử dụng database thật và internal stack, chỉ mock các services bên ngoài của bên thứ ba
|
||||
|
||||
### Mock Database Prisma
|
||||
|
||||
Mock Prisma client to avoid real database connections in unit tests / Mock Prisma client để tránh kết nối database thật trong unit tests.
|
||||
|
||||
```typescript
|
||||
// In setupTests.ts or individual test files
|
||||
jest.mock('../config/database.config', () => ({
|
||||
connectDatabase: jest.fn(),
|
||||
prisma: {
|
||||
$queryRaw: jest.fn(),
|
||||
$disconnect: jest.fn(),
|
||||
feature: {
|
||||
create: jest.fn(),
|
||||
findMany: jest.fn(),
|
||||
findUnique: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Usage in tests
|
||||
const { prisma } = require('../config/database.config');
|
||||
|
||||
test('should create user', async () => {
|
||||
prisma.feature.create.mockResolvedValue({
|
||||
id: 'test-id',
|
||||
name: 'test-feature',
|
||||
// ... other fields
|
||||
});
|
||||
|
||||
const result = await createFeature({ name: 'test-feature' });
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
```
|
||||
|
||||
### Mock Redis
|
||||
|
||||
Mock Redis client for caching and session management tests / Mock Redis client cho các test caching và quản lý session.
|
||||
|
||||
```typescript
|
||||
jest.mock('../config/redis.config', () => ({
|
||||
getRedisClient: jest.fn().mockReturnValue({
|
||||
call: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
```
|
||||
|
||||
### Mock External APIs
|
||||
|
||||
Mock external HTTP calls using Jest mocks / Mock các HTTP calls bên ngoài sử dụng Jest mocks.
|
||||
|
||||
```typescript
|
||||
jest.mock('axios');
|
||||
import axios from 'axios';
|
||||
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
||||
|
||||
test('should fetch external data', async () => {
|
||||
mockedAxios.get.mockResolvedValue({
|
||||
data: { result: 'success' }
|
||||
});
|
||||
|
||||
const result = await fetchExternalData();
|
||||
expect(result).toEqual({ result: 'success' });
|
||||
});
|
||||
```
|
||||
|
||||
### Mock Auth SDK
|
||||
|
||||
Mock authentication SDK functions for middleware and service tests / Mock các functions của authentication SDK cho middleware và service tests.
|
||||
|
||||
```typescript
|
||||
jest.mock('@goodgo/auth-sdk', () => ({
|
||||
createToken: jest.fn(),
|
||||
verifyToken: jest.fn(),
|
||||
extractTokenFromHeader: jest.fn(),
|
||||
}));
|
||||
|
||||
// Setup mock implementations
|
||||
(verifyToken as jest.Mock).mockImplementation((token) => {
|
||||
if (token === 'valid-token') {
|
||||
return { userId: '123', role: 'user' };
|
||||
}
|
||||
throw new Error('Invalid token');
|
||||
});
|
||||
```
|
||||
|
||||
## Utilities Test
|
||||
|
||||
### Pattern Test Factory
|
||||
|
||||
Create reusable test data factories for consistent test data / Tạo các factories dữ liệu test có thể tái sử dụng để có dữ liệu test nhất quán.
|
||||
|
||||
```typescript
|
||||
export class TestFactory {
|
||||
static createUser(overrides = {}) {
|
||||
return {
|
||||
id: 'test-user-1',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
createdAt: new Date(),
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
static createAuthToken(userId: string) {
|
||||
return jwt.sign({ userId }, 'test-secret');
|
||||
}
|
||||
|
||||
static async cleanDatabase() {
|
||||
await prisma.user.deleteMany();
|
||||
await prisma.feature.deleteMany();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const user = TestFactory.createUser({ name: 'Custom Name' });
|
||||
const token = TestFactory.createAuthToken(user.id);
|
||||
```
|
||||
|
||||
### Helpers Mock Request/Response
|
||||
|
||||
Use global test utilities for creating mock Express request/response objects / Sử dụng test utilities toàn cục để tạo các objects mock Express request/response.
|
||||
|
||||
```typescript
|
||||
// Available via global.testUtils (from setupTests.ts)
|
||||
const mockReq = global.testUtils.createMockReq({
|
||||
headers: { authorization: 'Bearer token' },
|
||||
params: { id: '123' },
|
||||
});
|
||||
|
||||
const mockRes = global.testUtils.createMockRes();
|
||||
const mockNext = global.testUtils.createMockNext();
|
||||
```
|
||||
|
||||
## Các Tình Huống Test Thường Gặp
|
||||
|
||||
### Test Xử Lý Lỗi
|
||||
|
||||
```typescript
|
||||
it('should handle database errors gracefully', async () => {
|
||||
Arrange - Mock database error
|
||||
// VI: Chuẩn bị - Mock lỗi database
|
||||
prismaMock.user.findUnique.mockRejectedValue(
|
||||
new Error('Database connection failed')
|
||||
);
|
||||
|
||||
Act & Assert
|
||||
// VI: Thực hiện & Kiểm tra
|
||||
await expect(userService.findById('123')).rejects.toThrow();
|
||||
|
||||
// Or for HTTP endpoints
|
||||
const response = await request(app)
|
||||
.get('/api/users/123')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'Internal server error / Lỗi máy chủ nội bộ',
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Test Validation
|
||||
|
||||
```typescript
|
||||
describe('Validation', () => {
|
||||
it('should reject invalid email', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({ email: 'invalid-email', password: '123456' })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.error.code).toBe('VALIDATION_ERROR');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Test Authorization
|
||||
|
||||
```typescript
|
||||
it('should deny access for user with incorrect role', () => {
|
||||
const mockReq = createMockReq({
|
||||
user: { userId: 'user-123', role: 'user' },
|
||||
});
|
||||
|
||||
const middleware = authorize('admin');
|
||||
middleware(mockReq as Request, mockRes as Response, mockNext);
|
||||
|
||||
expect(mockNext).not.toHaveBeenCalled();
|
||||
expect(mockStatus).toHaveBeenCalledWith(403);
|
||||
});
|
||||
```
|
||||
|
||||
## Lệnh Test
|
||||
|
||||
Add these scripts to `package.json` / Thêm các scripts này vào `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:unit": "jest --testPathPattern=\\.test\\.ts$",
|
||||
"test:e2e": "jest --testPathPattern=\\.e2e\\.ts$",
|
||||
"test:ci": "jest --coverage --silent --maxWorkers=2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage / Sử dụng**:
|
||||
- `pnpm test`: Run all tests / Chạy tất cả tests
|
||||
- `pnpm test:watch`: Run tests in watch mode / Chạy tests ở chế độ watch
|
||||
- `pnpm test:coverage`: Generate coverage report / Tạo báo cáo coverage
|
||||
- `pnpm test:unit`: Run only unit tests / Chỉ chạy unit tests
|
||||
- `pnpm test:e2e`: Run only E2E tests / Chỉ chạy E2E tests
|
||||
- `pnpm test:ci`: Run tests optimized for CI/CD / Chạy tests tối ưu cho CI/CD
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Cấu Hình Debug VS Code
|
||||
|
||||
Create `.vscode/launch.json` for debugging tests / Tạo `.vscode/launch.json` để debug tests:
|
||||
|
||||
```json
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Jest Tests",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["test", "--", "--runInBand"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Mẹo Debug
|
||||
|
||||
1. **Use `test.only()`** to run a single test / Sử dụng `test.only()` để chạy một test duy nhất:
|
||||
```typescript
|
||||
it.only('should test specific behavior', () => {
|
||||
// Only this test will run
|
||||
});
|
||||
```
|
||||
|
||||
2. **Use `--detectOpenHandles`** for async issues / Sử dụng `--detectOpenHandles` cho các vấn đề async:
|
||||
```bash
|
||||
pnpm test --detectOpenHandles
|
||||
```
|
||||
|
||||
3. **Use `--runInBand`** for sequential execution / Sử dụng `--runInBand` cho thực thi tuần tự:
|
||||
```bash
|
||||
pnpm test --runInBand
|
||||
```
|
||||
|
||||
4. **Add temporary console.log** / Thêm console.log tạm thời:
|
||||
```typescript
|
||||
console.log('Debug value:', someValue);
|
||||
```
|
||||
|
||||
5. **Use debugger breakpoints** in VS Code / Sử dụng breakpoints debugger trong VS Code
|
||||
|
||||
## Thực Hành Tốt Nhất
|
||||
|
||||
### Tổ Chức Test
|
||||
|
||||
- ✅ Each test is independent and isolated / Mỗi test độc lập và cô lập
|
||||
- ✅ Tests follow AAA pattern (Arrange-Act-Assert) / Tests tuân theo pattern AAA
|
||||
- ✅ Use descriptive test names that explain what is being tested / Sử dụng tên test mô tả giải thích điều đang được test
|
||||
- ✅ Group related tests using `describe` blocks / Nhóm các test liên quan sử dụng `describe` blocks
|
||||
- ✅ Use `beforeEach`/`afterEach` for setup/cleanup / Sử dụng `beforeEach`/`afterEach` cho setup/cleanup
|
||||
|
||||
### Mocking
|
||||
|
||||
- ✅ Mock external dependencies (database, APIs, services) / Mock các dependencies bên ngoài
|
||||
- ✅ Use `jest.clearAllMocks()` in `beforeEach` to reset mocks / Sử dụng `jest.clearAllMocks()` trong `beforeEach` để reset mocks
|
||||
- ✅ Verify mock calls to ensure correct behavior / Xác minh mock calls để đảm bảo hành vi đúng
|
||||
- ✅ Keep mocks simple and focused / Giữ mocks đơn giản và tập trung
|
||||
|
||||
### Coverage
|
||||
|
||||
- ✅ Maintain >70% code coverage (as per Jest config) / Duy trì >70% code coverage (theo cấu hình Jest)
|
||||
- ✅ Focus on covering critical business logic / Tập trung vào bao phủ logic nghiệp vụ quan trọng
|
||||
- ✅ Don't sacrifice test quality for coverage percentage / Không hy sinh chất lượng test cho phần trăm coverage
|
||||
- ✅ Review coverage reports regularly / Xem xét báo cáo coverage thường xuyên
|
||||
|
||||
### Dữ Liệu Test
|
||||
|
||||
- ✅ Use factories for creating test data / Sử dụng factories để tạo dữ liệu test
|
||||
- ✅ Keep test data realistic and representative / Giữ dữ liệu test thực tế và đại diện
|
||||
- ✅ Clean up test data after tests (if using real database) / Dọn dẹp dữ liệu test sau tests (nếu dùng database thật)
|
||||
- ✅ Use meaningful test values, not just `'test'` or `123` / Sử dụng giá trị test có ý nghĩa, không chỉ `'test'` hoặc `123`
|
||||
|
||||
### Test Lỗi
|
||||
|
||||
- ✅ Test both success and error scenarios / Test cả kịch bản thành công và lỗi
|
||||
- ✅ Test edge cases and boundary conditions / Test edge cases và điều kiện biên
|
||||
- ✅ Test validation errors / Test lỗi validation
|
||||
- ✅ Test error messages and error codes / Test thông báo lỗi và mã lỗi
|
||||
|
||||
### Hiệu Suất
|
||||
|
||||
- ✅ Keep unit tests fast (<1s each) / Giữ unit tests nhanh (<1s mỗi test)
|
||||
- ✅ Avoid unnecessary async operations in unit tests / Tránh các thao tác async không cần thiết trong unit tests
|
||||
- ✅ Use `--maxWorkers` in CI for parallel execution / Sử dụng `--maxWorkers` trong CI cho thực thi song song
|
||||
- ✅ Don't test implementation details, test behavior / Không test chi tiết implementation, test hành vi
|
||||
|
||||
## Ví Dụ Từ Dự Án
|
||||
|
||||
### Ví Dụ Test Thực Tế
|
||||
|
||||
1. **Unit Test**: [`services/iam-service/src/modules/feature/__tests__/feature.service.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.service.test.ts)
|
||||
2. **Integration Test**: [`services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/auth.middleware.test.ts)
|
||||
3. **E2E Test**: [`services/iam-service/src/__tests__/feature.e2e.ts`](../../../services/iam-service/src/__tests__/feature.e2e.ts)
|
||||
4. **Setup File**: [`services/iam-service/src/__tests__/setupTests.ts`](../../../services/iam-service/src/__tests__/setupTests.ts)
|
||||
5. **Jest Config**: [`services/iam-service/jest.config.ts`](../../../services/iam-service/jest.config.ts)
|
||||
|
||||
### Ví Dụ Cấu Trúc Test
|
||||
|
||||
- **Repository Tests**: [`services/iam-service/src/modules/feature/__tests__/feature.repository.test.ts`](../../../services/iam-service/src/modules/feature/__tests__/feature.repository.test.ts)
|
||||
- **Controller Tests**: [`services/iam-service/src/modules/health/__tests__/health.controller.test.ts`](../../../services/iam-service/src/modules/health/__tests__/health.controller.test.ts)
|
||||
- **Middleware Tests**: [`services/iam-service/src/middlewares/__tests__/correlation.middleware.test.ts`](../../../services/iam-service/src/middlewares/__tests__/correlation.middleware.test.ts)
|
||||
|
||||
## Tham Khảo Nhanh
|
||||
|
||||
### Cây Quyết Định Loại Test
|
||||
|
||||
```
|
||||
Need to test complete HTTP flow?
|
||||
├─ Yes → E2E Test (*.e2e.ts)
|
||||
└─ No → Multiple components interacting?
|
||||
├─ Yes → Integration Test (*.test.ts)
|
||||
└─ No → Unit Test (*.test.ts)
|
||||
```
|
||||
|
||||
### Các Matcher Jest Thường Dùng
|
||||
|
||||
| Matcher | Purpose / Mục đích | Example / Ví dụ |
|
||||
|---------|-------------------|-----------------|
|
||||
| `toBe()` | Exact equality / So sánh chính xác | `expect(value).toBe(5)` |
|
||||
| `toEqual()` | Deep equality / So sánh sâu | `expect(obj).toEqual({a: 1})` |
|
||||
| `toMatchObject()` | Partial match / So sánh một phần | `expect(obj).toMatchObject({a: 1})` |
|
||||
| `toHaveBeenCalled()` | Function called / Function đã được gọi | `expect(mockFn).toHaveBeenCalled()` |
|
||||
| `toHaveBeenCalledWith()` | Called with args / Gọi với args | `expect(mockFn).toHaveBeenCalledWith('arg')` |
|
||||
| `toThrow()` | Throws error / Ném lỗi | `expect(() => fn()).toThrow()` |
|
||||
| `toBeDefined()` | Not undefined / Không undefined | `expect(value).toBeDefined()` |
|
||||
| `toBeNull()` | Is null / Là null | `expect(value).toBeNull()` |
|
||||
| `toContain()` | Array/string contains / Chứa trong array/string | `expect(array).toContain('item')` |
|
||||
|
||||
### Helpers Mock Function
|
||||
|
||||
```typescript
|
||||
// Create mock
|
||||
const mockFn = jest.fn();
|
||||
|
||||
// Set return value
|
||||
mockFn.mockReturnValue('value');
|
||||
mockFn.mockResolvedValue('async value');
|
||||
mockFn.mockRejectedValue(new Error('error'));
|
||||
|
||||
// Set implementation
|
||||
mockFn.mockImplementation((arg) => arg * 2);
|
||||
|
||||
// Clear/reset
|
||||
mockFn.mockClear(); // Clear call history
|
||||
mockFn.mockReset(); // Reset to initial state
|
||||
jest.clearAllMocks(); // Clear all mocks
|
||||
```
|
||||
|
||||
## Skills Liên Quan
|
||||
|
||||
- **[Comment Code](./comment-code.md)**: Writing bilingual comments in tests / Viết comments song ngữ trong tests
|
||||
- **[Security](./security.md)**: Testing security-critical code / Test code bảo mật quan trọng
|
||||
- **[API Design](./api-design.md)**: Testing API endpoints and responses / Test API endpoints và responses
|
||||
- **[Project Rules](./project-rules.md)**: Code organization for tests / Tổ chức code cho tests
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
### Tài Liệu Chính Thức
|
||||
|
||||
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
||||
- [Supertest Documentation](https://github.com/visionmedia/supertest)
|
||||
- [jest-mock-extended](https://github.com/marchaos/jest-mock-extended)
|
||||
|
||||
### Tài Liệu Nội Bộ
|
||||
|
||||
- [Service Development Guide](../guides/development.md)
|
||||
- [Local Development Setup](../guides/local-development.md)
|
||||
- [Troubleshooting Guide](../guides/troubleshooting.md)
|
||||
|
||||
### Công Cụ
|
||||
|
||||
- **Jest**: JavaScript testing framework / Framework testing JavaScript
|
||||
- **Supertest**: HTTP assertion library / Thư viện assertion HTTP
|
||||
- **jest-mock-extended**: Enhanced mocking for TypeScript / Mock nâng cao cho TypeScript
|
||||
- **ioredis-mock**: Redis mock for testing / Mock Redis cho testing
|
||||
Reference in New Issue
Block a user