feat(docs): Update architecture and caching documentation

- Added detailed sections on distributed caching architecture, including caching overview, ICacheService interface, and caching strategies for tokens and sessions.
- Enhanced documentation with mermaid diagrams to illustrate caching flows and key patterns.
- Updated Redis configuration instructions in the README, including environment variable setup and usage examples for caching operations.
- Removed outdated notes from the NOTE.MD file to streamline content and focus on relevant tasks and proposals.
This commit is contained in:
Ho Ngoc Hai
2026-01-12 18:48:50 +07:00
parent bb4cf4884c
commit ba928022cb
4 changed files with 410 additions and 15 deletions

View File

@@ -8,15 +8,6 @@
Có Cached chưa
⚠️ Vấn đề phát hiện:
/connect/token endpoint không phản hồi - OAuth2 token endpoint bị treo khi gửi request. Có vẻ OpenIddict Server middleware không xử lý request đúng cách.
Bạn muốn tôi làm gì tiếp:
Fix OAuth2 /connect/token endpoint - Debug và sửa lỗi OpenIddict configuration
Chỉ cần database connection - Nếu chỉ cần verify database connection thì đã hoàn thành
Tiếp tục với task khác - Cần hướng dẫn cụ thể
Đề xuất cần implement:
Redis Connection - Đăng ký IConnectionMultiplexer trong DI
Distributed Caching Service - Sử dụng IDistributedCache

View File

@@ -367,12 +367,163 @@ All errors return Problem Details format:
}
```
## Distributed Caching Architecture
### Caching Overview
```mermaid
graph TB
subgraph "Application"
SVC[Services]
CACHE[ICacheService]
end
subgraph "Caching Layer"
REDIS[(Redis Server)]
TOKEN[Token Cache]
SESSION[Session Cache]
DATA[Data Cache]
end
SVC --> CACHE
CACHE --> REDIS
REDIS --> TOKEN
REDIS --> SESSION
REDIS --> DATA
style CACHE fill:#e74c3c,stroke:#c0392b,color:#fff
style REDIS fill:#d35400,stroke:#a04000,color:#fff
style TOKEN fill:#9b59b6,stroke:#7d3c98,color:#fff
style SESSION fill:#3498db,stroke:#2980b9,color:#fff
style DATA fill:#2ecc71,stroke:#27ae60,color:#fff
```
### ICacheService Interface
The service implements a generic `ICacheService` interface for distributed caching:
| Method | Purpose |
|--------|---------|
| `GetAsync<T>` | Retrieve cached item by key |
| `SetAsync<T>` | Store item with optional TTL |
| `RemoveAsync` | Delete cached item |
| `ExistsAsync` | Check if key exists |
| `GetOrSetAsync<T>` | Cache-aside pattern |
| `BlacklistAsync` | Add to token blacklist |
| `IsBlacklistedAsync` | Check token blacklist |
### Token Caching Strategy
```mermaid
sequenceDiagram
participant Client
participant AuthController
participant CacheService
participant Redis
participant Database
Note over Client,Database: Token Validation with Cache
Client->>AuthController: Request with JWT
AuthController->>CacheService: IsBlacklistedAsync(tokenId)
CacheService->>Redis: GET blacklist:token:{id}
Redis-->>CacheService: null (not blacklisted)
CacheService-->>AuthController: false
AuthController->>AuthController: Validate JWT Claims
AuthController-->>Client: Response
Note over Client,Database: Token Revocation (Logout)
Client->>AuthController: POST /logout
AuthController->>CacheService: BlacklistAsync(tokenId, 7 days)
CacheService->>Redis: SETEX blacklist:token:{id} 604800 "1"
Redis-->>CacheService: OK
AuthController-->>Client: 200 OK
```
### Token Cache Keys
| Key Pattern | Purpose | TTL |
|-------------|---------|-----|
| `blacklist:token:{tokenId}` | Revoked tokens | Token remaining lifetime |
| `blacklist:refresh:{tokenId}` | Revoked refresh tokens | 7 days |
| `user:{userId}:tokens` | User's active tokens | 15 minutes |
### Session Caching Strategy
```mermaid
graph LR
subgraph "Session Data"
UID[User ID]
ROLES[User Roles]
PERMS[Permissions]
CLAIMS[Claims]
end
subgraph "Cache Keys"
K1[session:{userId}]
K2[user:{userId}:roles]
K3[user:{userId}:permissions]
end
UID --> K1
ROLES --> K2
PERMS --> K3
style K1 fill:#3498db,stroke:#2980b9,color:#fff
style K2 fill:#9b59b6,stroke:#7d3c98,color:#fff
style K3 fill:#2ecc71,stroke:#27ae60,color:#fff
```
### Session Cache Keys
| Key Pattern | Purpose | TTL |
|-------------|---------|-----|
| `session:{userId}` | User session data | 30 minutes |
| `user:{userId}:roles` | Cached user roles | 15 minutes |
| `user:{userId}:permissions` | Computed permissions | 15 minutes |
| `user:{userId}:profile` | User profile data | 10 minutes |
### Cache-Aside Pattern
```mermaid
sequenceDiagram
participant Service
participant CacheService
participant Redis
participant Database
Service->>CacheService: GetOrSetAsync(key, factory)
CacheService->>Redis: GET key
alt Cache Hit
Redis-->>CacheService: cached data
CacheService-->>Service: return data
else Cache Miss
Redis-->>CacheService: null
CacheService->>Database: factory() - fetch data
Database-->>CacheService: data
CacheService->>Redis: SETEX key ttl data
CacheService-->>Service: return data
end
```
### Cache Invalidation Strategies
| Strategy | When to Use | Implementation |
|----------|-------------|----------------|
| **Time-based (TTL)** | General data | `SetAsync(..., TimeSpan.FromMinutes(15))` |
| **Event-based** | User updates | Remove cache on update event |
| **Pattern-based** | Bulk invalidation | `RemoveByPatternAsync("user:*")` |
## Performance Considerations
1. **Connection Pooling**: EF Core with Npgsql resilience
2. **Token Caching**: Redis for token validation
3. **Async Operations**: All I/O operations are async
4. **Database Indexes**: Configured in EntityConfigurations
2. **Token Caching**: Redis for token validation and blacklist
3. **Session Caching**: User sessions and permissions cached
4. **Async Operations**: All I/O operations are async
5. **Database Indexes**: Configured in EntityConfigurations
6. **Cache-Aside Pattern**: Reduce database load for frequent reads
## References

View File

@@ -367,12 +367,163 @@ Tất cả lỗi trả về định dạng Problem Details:
}
```
## Kiến Trúc Distributed Caching
### Tổng Quan Caching
```mermaid
graph TB
subgraph "Application"
SVC[Services]
CACHE[ICacheService]
end
subgraph "Tầng Caching"
REDIS[(Redis Server)]
TOKEN[Token Cache]
SESSION[Session Cache]
DATA[Data Cache]
end
SVC --> CACHE
CACHE --> REDIS
REDIS --> TOKEN
REDIS --> SESSION
REDIS --> DATA
style CACHE fill:#e74c3c,stroke:#c0392b,color:#fff
style REDIS fill:#d35400,stroke:#a04000,color:#fff
style TOKEN fill:#9b59b6,stroke:#7d3c98,color:#fff
style SESSION fill:#3498db,stroke:#2980b9,color:#fff
style DATA fill:#2ecc71,stroke:#27ae60,color:#fff
```
### ICacheService Interface
Service implement interface `ICacheService` cho distributed caching:
| Method | Mục Đích |
|--------|----------|
| `GetAsync<T>` | Lấy item từ cache theo key |
| `SetAsync<T>` | Lưu item với TTL tùy chọn |
| `RemoveAsync` | Xóa item từ cache |
| `ExistsAsync` | Kiểm tra key tồn tại |
| `GetOrSetAsync<T>` | Pattern cache-aside |
| `BlacklistAsync` | Thêm vào blacklist token |
| `IsBlacklistedAsync` | Kiểm tra blacklist token |
### Chiến Lược Token Caching
```mermaid
sequenceDiagram
participant Client
participant AuthController
participant CacheService
participant Redis
participant Database
Note over Client,Database: Validate Token với Cache
Client->>AuthController: Request với JWT
AuthController->>CacheService: IsBlacklistedAsync(tokenId)
CacheService->>Redis: GET blacklist:token:{id}
Redis-->>CacheService: null (không bị blacklist)
CacheService-->>AuthController: false
AuthController->>AuthController: Validate JWT Claims
AuthController-->>Client: Response
Note over Client,Database: Thu Hồi Token (Logout)
Client->>AuthController: POST /logout
AuthController->>CacheService: BlacklistAsync(tokenId, 7 ngày)
CacheService->>Redis: SETEX blacklist:token:{id} 604800 "1"
Redis-->>CacheService: OK
AuthController-->>Client: 200 OK
```
### Token Cache Keys
| Mẫu Key | Mục Đích | TTL |
|---------|----------|-----|
| `blacklist:token:{tokenId}` | Tokens đã thu hồi | Thời gian còn lại của token |
| `blacklist:refresh:{tokenId}` | Refresh tokens đã thu hồi | 7 ngày |
| `user:{userId}:tokens` | Tokens đang hoạt động của user | 15 phút |
### Chiến Lược Session Caching
```mermaid
graph LR
subgraph "Session Data"
UID[User ID]
ROLES[User Roles]
PERMS[Permissions]
CLAIMS[Claims]
end
subgraph "Cache Keys"
K1[session:{userId}]
K2[user:{userId}:roles]
K3[user:{userId}:permissions]
end
UID --> K1
ROLES --> K2
PERMS --> K3
style K1 fill:#3498db,stroke:#2980b9,color:#fff
style K2 fill:#9b59b6,stroke:#7d3c98,color:#fff
style K3 fill:#2ecc71,stroke:#27ae60,color:#fff
```
### Session Cache Keys
| Mẫu Key | Mục Đích | TTL |
|---------|----------|-----|
| `session:{userId}` | Dữ liệu session user | 30 phút |
| `user:{userId}:roles` | Cached user roles | 15 phút |
| `user:{userId}:permissions` | Computed permissions | 15 phút |
| `user:{userId}:profile` | Dữ liệu profile user | 10 phút |
### Pattern Cache-Aside
```mermaid
sequenceDiagram
participant Service
participant CacheService
participant Redis
participant Database
Service->>CacheService: GetOrSetAsync(key, factory)
CacheService->>Redis: GET key
alt Cache Hit
Redis-->>CacheService: dữ liệu cached
CacheService-->>Service: trả về dữ liệu
else Cache Miss
Redis-->>CacheService: null
CacheService->>Database: factory() - lấy dữ liệu
Database-->>CacheService: dữ liệu
CacheService->>Redis: SETEX key ttl data
CacheService-->>Service: trả về dữ liệu
end
```
### Chiến Lược Invalidation Cache
| Chiến Lược | Khi Nào Sử Dụng | Implementation |
|------------|-----------------|----------------|
| **Time-based (TTL)** | Dữ liệu chung | `SetAsync(..., TimeSpan.FromMinutes(15))` |
| **Event-based** | Cập nhật user | Xóa cache khi có event cập nhật |
| **Pattern-based** | Invalidation hàng loạt | `RemoveByPatternAsync("user:*")` |
## Cân Nhắc Hiệu Năng
1. **Connection Pooling**: EF Core với Npgsql resilience
2. **Token Caching**: Redis cho token validation
3. **Async Operations**: Tất cả I/O operations đều async
4. **Database Indexes**: Cấu hình trong EntityConfigurations
2. **Token Caching**: Redis cho token validation và blacklist
3. **Session Caching**: User sessions và permissions được cache
4. **Async Operations**: Tất cả I/O operations đều async
5. **Database Indexes**: Cấu hình trong EntityConfigurations
6. **Cache-Aside Pattern**: Giảm tải database cho các truy vấn thường xuyên
## Tài Liệu Tham Khảo

View File

@@ -228,6 +228,108 @@ Sau khi chạy service, truy cập Swagger UI tại:
| `ASPNETCORE_ENVIRONMENT` | Môi trường | `Development` |
| `DATABASE_URL` | PostgreSQL connection | - |
| `JWT_SECRET` | Secret ký JWT (32+ ký tự) | - |
| `REDIS_HOST` | Redis server host | `localhost` |
| `REDIS_PORT` | Redis server port | `6379` |
| `REDIS_PASSWORD` | Redis password | - |
| `REDIS_DATABASE` | Redis database number | `0` |
## Redis Caching
Service sử dụng Redis cho distributed caching thông qua interface `ICacheService`.
### Cấu Hình Redis
Thêm cấu hình Redis trong `appsettings.json`:
```json
{
"Redis": {
"Host": "localhost",
"Port": 6379,
"Password": "",
"Database": 0,
"ConnectTimeout": 5000,
"SyncTimeout": 5000
}
}
```
Hoặc sử dụng biến môi trường:
```bash
REDIS_HOST=your-redis-host
REDIS_PORT=6379
REDIS_PASSWORD=your-password
REDIS_DATABASE=0
```
### ICacheService Interface
```csharp
public interface ICacheService
{
// Các thao tác cơ bản
Task<T?> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
Task RemoveAsync(string key);
Task<bool> ExistsAsync(string key);
// Get hoặc tạo mới pattern
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
// Hỗ trợ blacklist token
Task BlacklistAsync(string key, TimeSpan expiration);
Task<bool> IsBlacklistedAsync(string key);
}
```
### Ví Dụ Sử Dụng
**Get/Set cơ bản:**
```csharp
public class MyService
{
private readonly ICacheService _cache;
public MyService(ICacheService cache) => _cache = cache;
public async Task<User?> GetUser(string userId)
{
return await _cache.GetAsync<User>($"user:{userId}");
}
public async Task CacheUser(User user)
{
await _cache.SetAsync($"user:{user.Id}", user, TimeSpan.FromMinutes(15));
}
}
```
**Get or Set Pattern (Cache-Aside):**
```csharp
public async Task<User> GetUserById(string userId)
{
return await _cache.GetOrSetAsync(
$"user:{userId}",
async () => await _repository.GetByIdAsync(userId),
TimeSpan.FromMinutes(15)
);
}
```
**Blacklist Token (cho Logout):**
```csharp
public async Task Logout(string tokenId)
{
// Blacklist refresh token trong thời gian còn lại của token
await _cache.BlacklistAsync($"token:{tokenId}", TimeSpan.FromDays(7));
}
public async Task<bool> IsTokenRevoked(string tokenId)
{
return await _cache.IsBlacklistedAsync($"token:{tokenId}");
}
```
## Kiểm Thử