- Enhanced the architecture documentation to recommend direct upload over legacy proxy upload for improved performance and scalability. - Added detailed comparisons of upload patterns, including throughput, memory usage, and latency. - Updated API endpoint documentation to reflect new direct upload methods and their benefits. - Included examples for direct upload flow and bucket directory structure to aid developers in implementation.
12 KiB
12 KiB
Kiến Trúc Storage Service
Tài liệu kiến trúc chi tiết cho microservice Storage Service.
Tổng Quan Kiến Trúc
graph TB
subgraph "API Layer"
C[Controllers]
CMD[Commands]
Q[Queries]
B[Behaviors]
end
subgraph "Domain Layer"
SF[StorageFile]
SQ[UserStorageQuota]
DE[Domain Events]
RI[Repository Interfaces]
end
subgraph "Infrastructure Layer"
SP[Storage Providers]
IAM[IAM Client]
R[Repositories]
CTX[DbContext]
end
subgraph "External Services"
MINIO[(MinIO)]
OSS[(Aliyun OSS)]
IAMS[IAM Service]
DB[(PostgreSQL)]
end
C --> CMD
C --> Q
CMD --> B --> SF
Q --> R
R --> CTX --> DB
SF --> DE
CMD --> SP
SP --> MINIO
SP --> OSS
C --> IAM --> IAMS
style C fill:#4a90d9,stroke:#2d5986,color:#fff
style SF fill:#50c878,stroke:#2d8659,color:#fff
style MINIO fill:#c73b3b,stroke:#922b2b,color:#fff
style OSS fill:#ff6b35,stroke:#cc5500,color:#fff
style IAMS fill:#9b59b6,stroke:#7d3c98,color:#fff
Trách Nhiệm Từng Layer
1. Domain Layer (StorageService.Domain)
Trái tim của ứng dụng chứa business logic thuần túy.
| Component | Mục đích |
|---|---|
| StorageFile | Aggregate root cho metadata file và lifecycle |
| UserStorageQuota | Aggregate root cho giới hạn và usage storage của user |
| StorageProvider | Enum: MinIO, AliyunOSS |
| FileAccessLevel | Enum: Private, Public, Shared |
| Domain Events | FileUploadedDomainEvent, FileDeletedDomainEvent, UserQuotaUpdatedDomainEvent |
2. Infrastructure Layer (StorageService.Infrastructure)
Triển khai kỹ thuật và tích hợp bên ngoài:
| Component | Mục đích |
|---|---|
| MinioStorageProvider | Thao tác storage tương thích MinIO S3 |
| AliyunOssStorageProvider | Thao tác Alibaba Cloud OSS |
| StorageProviderFactory | Chọn provider runtime dựa trên config |
| HttpIamServiceClient | Giao tiếp inter-service với IAM |
| FileRepository | EF Core repository cho StorageFile |
| QuotaRepository | EF Core repository cho UserStorageQuota |
3. API Layer (StorageService.API)
Entry point ứng dụng và triển khai CQRS:
| Component | Mục đích |
|---|---|
| FilesController | Endpoints CRUD file (proxy upload cũ) |
| SignedUrlController | Endpoints direct upload (khuyến nghị) |
| QuotaController | Endpoints quota của user |
| SignUploadCommand | Tạo pre-signed upload URLs |
| ConfirmUploadCommand | Xác nhận direct uploads và lưu metadata |
| UploadFileCommand | Xử lý proxy uploads (cũ) |
| DeleteFileCommand | Xử lý xóa file |
| Query Handlers | Xử lý các thao tác đọc |
Kiến Trúc Direct Upload (Khuyến Nghị)
Cho hệ thống với hàng triệu users, pattern Direct Client Upload được khuyến nghị thay vì proxy upload.
So Sánh Upload Patterns
| Khía cạnh | Proxy Upload (Cũ) | Direct Upload (Khuyến nghị) |
|---|---|---|
| Throughput | ~100-500/giây | ~10,000+/giây |
| Memory mỗi request | 100MB (kích thước file) | ~10KB (chỉ metadata) |
| Latency (file 100MB) | 30-60 giây | 10-20 giây |
| Tải backend | Cao | Tối thiểu |
Luồng Direct Upload
sequenceDiagram
participant Client
participant Storage_Service as Storage Service
participant MinIO
rect rgb(200, 230, 200)
Note over Client,Storage_Service: 1. Yêu cầu Upload URL (nhẹ)
Client->>Storage_Service: POST /api/v1/storage/sign-upload
Storage_Service->>Storage_Service: Validate JWT, Kiểm tra Quota
Storage_Service-->>Client: Pre-signed PUT URL + ObjectKey
end
rect rgb(200, 200, 230)
Note over Client,MinIO: 2. Upload trực tiếp (bỏ qua backend)
Client->>MinIO: PUT file binary vào Pre-signed URL
MinIO-->>Client: 200 OK
end
rect rgb(230, 230, 200)
Note over Client,Storage_Service: 3. Xác nhận Upload (nhẹ)
Client->>Storage_Service: POST /api/v1/storage/confirm-upload
Storage_Service->>MinIO: Xác minh file tồn tại
Storage_Service->>Storage_Service: Lưu metadata, Cập nhật quota
Storage_Service-->>Client: File metadata
end
Kiểm Soát Truy Cập Theo Path
Files được tổ chức với prefix theo access level:
storage-bucket/
├── public/{userId}/{date}/{fileId}_{filename} → Truy cập công khai
├── private/{userId}/{date}/{fileId}_{filename} → Yêu cầu pre-signed URL
└── shared/{userId}/{date}/{fileId}_{filename} → Kiểm soát bằng quy tắc
Components Direct Upload
| Component | Mục đích |
|---|---|
| SignUploadCommand | Validate quota, tạo object key với path prefix, tạo pre-signed PUT URL |
| SignUploadCommandHandler | Xử lý yêu cầu sign-upload |
| ConfirmUploadCommand | Xác minh file tồn tại, lưu metadata, cập nhật quota |
| ConfirmUploadCommandHandler | Xử lý confirm-upload với idempotency |
| SignedUrlController | Endpoints /sign-upload và /confirm-upload |
Kiến Trúc Storage Provider
graph TD
subgraph "Storage Provider Factory"
F[StorageProviderFactory]
C[Configuration]
end
subgraph "Providers"
MP[MinioStorageProvider]
AP[AliyunOssStorageProvider]
end
subgraph "Storage Backends"
MINIO[(MinIO)]
OSS[(Aliyun OSS)]
end
C --> |STORAGE_PROVIDER=minio| F
C --> |STORAGE_PROVIDER=aliyun| F
F --> |GetProvider| MP
F --> |GetProvider| AP
MP --> MINIO
AP --> OSS
style F fill:#4a90d9,stroke:#2d5986,color:#fff
style MP fill:#c73b3b,stroke:#922b2b,color:#fff
style AP fill:#ff6b35,stroke:#cc5500,color:#fff
Interface Storage Provider
public interface IStorageProvider
{
Task<UploadResult> UploadAsync(Stream stream, string objectKey, ...);
Task<Stream> DownloadAsync(string bucketName, string objectKey);
Task DeleteAsync(string bucketName, string objectKey);
Task<bool> ExistsAsync(string bucketName, string objectKey);
Task<string> GetPreSignedDownloadUrlAsync(string bucketName, string objectKey, int expirationSeconds);
Task<string> GetPreSignedUploadUrlAsync(string bucketName, string objectKey, int expirationSeconds);
}
Giao Tiếp Inter-Service
sequenceDiagram
participant Client
participant Storage as Storage Service
participant Cache as In-Memory Cache
participant IAM as IAM Service
Client->>Storage: Upload File (JWT)
Storage->>Cache: Kiểm tra cache user
alt Cache Hit
Cache-->>Storage: Thông tin user
else Cache Miss
Storage->>IAM: GET /api/v1/users/me
Note over Storage,IAM: Headers: Authorization, X-Service-Name
IAM-->>Storage: Thông tin user
Storage->>Cache: Lưu (5 phút TTL)
end
Storage->>Storage: Validate quota
Storage->>Storage: Upload lên provider
Storage-->>Client: Kết quả upload
Tính Năng IAM Client
| Tính năng | Mô tả |
|---|---|
| Caching | In-memory cache cho user info (5 phút TTL) |
| Health Check | Kiểm tra IAM availability với caching (1 phút TTL) |
| Polly Resilience | Retry (3x exponential) + Circuit Breaker |
| Permission Check | HasPermissionAsync, HasRoleAsync |
Các Phương Thức IAM Client
// Thao tác User
Task<IamUserInfo?> ValidateUserAsync(string accessToken);
Task<IamUserInfo?> GetUserByIdAsync(string userId, string accessToken);
Task<bool> UserExistsAsync(string userId, string accessToken);
Task<IReadOnlyList<string>> GetUserRolesAsync(string userId, string accessToken);
Task<IReadOnlyList<string>> GetUserPermissionsAsync(string userId, string accessToken);
Task<bool> HasPermissionAsync(string userId, string permission, string accessToken);
Task<bool> HasRoleAsync(string userId, string role, string accessToken);
// Health Check
Task<IamHealthStatus> CheckHealthAsync();
Task<bool> IsAvailableAsync();
// Quản lý Cache
void InvalidateUserCache(string userId);
void ClearCache();
Database Schema
erDiagram
storage_files {
uuid id PK
varchar file_name
varchar bucket_name
varchar object_key
varchar content_type
bigint file_size_bytes
varchar user_id
varchar tenant_id
int provider
int access_level
timestamp uploaded_at
timestamp expires_at
varchar checksum
boolean is_deleted
timestamp deleted_at
}
user_storage_quotas {
uuid id PK
varchar user_id UK
bigint max_storage_bytes
bigint used_storage_bytes
int max_file_count
int current_file_count
varchar quota_tier
timestamp last_updated_at
timestamp created_at
}
API Endpoints
Files (Proxy Upload Cũ)
| Method | Endpoint | Mô tả |
|---|---|---|
POST |
/api/v1/files/upload |
Upload file qua backend (tối đa 100MB) |
GET |
/api/v1/files |
Danh sách file với phân trang |
GET |
/api/v1/files/{id} |
Lấy metadata file |
GET |
/api/v1/files/{id}/download-url |
Lấy pre-signed download URL |
DELETE |
/api/v1/files/{id} |
Xóa file (soft delete) |
Direct Upload (Khuyến Nghị)
| Method | Endpoint | Mô tả |
|---|---|---|
POST |
/api/v1/storage/sign-upload |
Lấy pre-signed PUT URL để upload trực tiếp |
POST |
/api/v1/storage/confirm-upload |
Xác nhận upload và lưu metadata |
Quota
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/quota |
Lấy quota storage của user |
Health Checks
graph TD
HC[Health Check Endpoint]
HC --> |/health/live| L[Liveness]
HC --> |/health/ready| R[Readiness]
R --> DB[(PostgreSQL)]
R --> MINIO[(MinIO)]
R --> IAM[IAM Service]
style HC fill:#3498db,stroke:#2980b9,color:#fff
style L fill:#2ecc71,stroke:#27ae60,color:#fff
style R fill:#f39c12,stroke:#d68910,color:#fff
Kiến Trúc Deployment
Docker Compose (Local)
services:
storage-api:
build: .
ports: ["5002:8080"]
depends_on:
- postgres
- redis
- minio
environment:
- Storage__Provider=minio
- Storage__MinIO__Endpoint=minio:9000
minio:
image: minio/minio:latest
ports: ["9000:9000", "9001:9001"]
Tích Hợp Traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.storage-service.rule=PathPrefix(`/api/v1/files`) || PathPrefix(`/api/v1/quota`)"
- "traefik.http.services.storage-service.loadbalancer.server.port=8080"
Cân Nhắc Bảo Mật
- Authentication: Xác thực JWT Bearer qua IAM Service
- Authorization: Kiểm tra quyền sở hữu trên files
- Input Validation: Giới hạn kích thước file, xác thực content type
- Pre-signed URLs: Truy cập file có thời hạn
- Soft Delete: Files được đánh dấu xóa, không xóa ngay lập tức
Tối Ưu Hiệu Suất
- Caching: In-memory cache cho IAM user info
- Pre-signed URLs: Client download trực tiếp từ storage
- Streaming Upload: Xử lý file dựa trên stream
- Async Operations: Tất cả thao tác I/O đều async
- Connection Pooling: HTTP client với Polly policies