350 lines
9.7 KiB
Markdown
350 lines
9.7 KiB
Markdown
---
|
|
name: api-design
|
|
description: RESTful API design standards for GoodGo microservices. Use for new API endpoints, DTOs, controllers, OpenAPI documentation, or standardized responses.
|
|
compatibility: ".NET 10+, ASP.NET Core, MediatR, Swashbuckle, Asp.Versioning"
|
|
metadata:
|
|
author: Velik Ho
|
|
version: "2.0"
|
|
---
|
|
|
|
# RESTful API Design Standards / Tiêu Chuẩn Thiết Kế RESTful API
|
|
|
|
Standards for designing consistent, maintainable APIs in GoodGo microservices.
|
|
|
|
## When to Use This Skill / Khi Nào Sử Dụng
|
|
|
|
Use this skill when:
|
|
- Creating new API endpoints / Tạo API endpoints mới
|
|
- Designing request/response DTOs / Thiết kế DTOs
|
|
- Implementing controllers with MediatR / Triển khai controllers với MediatR
|
|
- Writing OpenAPI/Swagger documentation / Viết tài liệu OpenAPI/Swagger
|
|
- Standardizing error responses / Chuẩn hóa error responses
|
|
- Implementing pagination, filtering / Triển khai pagination, filtering
|
|
|
|
## Core Concepts / Khái Niệm Cốt Lõi
|
|
|
|
### Clean Architecture Layers / Các Tầng Clean Architecture
|
|
|
|
```
|
|
src/
|
|
├── ServiceName.API/ # Controllers, DTOs, Middleware
|
|
│ ├── Controllers/ # API Controllers
|
|
│ └── Application/ # Commands, Queries, Handlers
|
|
├── ServiceName.Domain/ # Entities, Aggregates, Interfaces
|
|
└── ServiceName.Infrastructure/ # Repositories, External Services
|
|
```
|
|
|
|
### API Response Wrapper / Wrapper Response Chuẩn
|
|
|
|
```csharp
|
|
/// <summary>
|
|
/// EN: Standard API response wrapper.
|
|
/// VI: Wrapper response API chuẩn.
|
|
/// </summary>
|
|
public class ApiResponse<T>
|
|
{
|
|
public bool Success { get; set; }
|
|
public T? Data { get; set; }
|
|
public string? Error { get; set; }
|
|
public PaginationInfo? Pagination { get; set; }
|
|
}
|
|
|
|
public record PaginationInfo(
|
|
int Page,
|
|
int Limit,
|
|
int Total,
|
|
int TotalPages);
|
|
```
|
|
|
|
### URL Structure / Cấu Trúc URL
|
|
|
|
```
|
|
/api/v{version}/{resource}/{id?}/{sub-resource?}
|
|
|
|
GET /api/v1/files # List files
|
|
POST /api/v1/files # Create file
|
|
GET /api/v1/files/{id} # Get file by ID
|
|
PUT /api/v1/files/{id} # Update file
|
|
DELETE /api/v1/files/{id} # Delete file
|
|
GET /api/v1/files/{id}/versions # Get file versions
|
|
```
|
|
|
|
## Key Patterns / Mẫu Chính
|
|
|
|
### Controller with MediatR / Controller với MediatR
|
|
|
|
```csharp
|
|
/// <summary>
|
|
/// EN: Controller for file operations.
|
|
/// VI: Controller cho các thao tác file.
|
|
/// </summary>
|
|
[ApiController]
|
|
[ApiVersion("1.0")]
|
|
[Route("api/v{version:apiVersion}/files")]
|
|
[SwaggerTag("File Management - Upload, download, and manage files")]
|
|
public class FilesController : ControllerBase
|
|
{
|
|
private readonly IMediator _mediator;
|
|
|
|
public FilesController(IMediator mediator)
|
|
{
|
|
_mediator = mediator;
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Get file by ID.
|
|
/// VI: Lấy thông tin file theo ID.
|
|
/// </summary>
|
|
[HttpGet("{fileId:guid}")]
|
|
[Authorize]
|
|
[SwaggerOperation(Summary = "Get file by ID")]
|
|
[SwaggerResponse(200, "File retrieved successfully")]
|
|
[SwaggerResponse(404, "File not found")]
|
|
public async Task<ActionResult<ApiResponse<FileDto>>> GetFile(
|
|
Guid fileId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var userId = GetUserId();
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Unauthorized(new ApiResponse<FileDto>
|
|
{
|
|
Success = false,
|
|
Error = "User ID not found"
|
|
});
|
|
|
|
var query = new GetFileQuery(fileId, userId);
|
|
var result = await _mediator.Send(query, cancellationToken);
|
|
|
|
if (result == null)
|
|
return NotFound(new ApiResponse<FileDto>
|
|
{
|
|
Success = false,
|
|
Error = "File not found"
|
|
});
|
|
|
|
return Ok(new ApiResponse<FileDto> { Success = true, Data = result });
|
|
}
|
|
|
|
private string? GetUserId() =>
|
|
User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
}
|
|
```
|
|
|
|
### DTO with Records / DTO với Records
|
|
|
|
```csharp
|
|
/// <summary>
|
|
/// EN: DTO for file information.
|
|
/// VI: DTO cho thông tin file.
|
|
/// </summary>
|
|
public record FileDto(
|
|
Guid Id,
|
|
string UserId,
|
|
string FileName,
|
|
string ContentType,
|
|
long FileSizeBytes,
|
|
string AccessLevel,
|
|
DateTime UploadedAt);
|
|
|
|
/// <summary>
|
|
/// EN: Result for user files query with pagination.
|
|
/// VI: Kết quả query files với phân trang.
|
|
/// </summary>
|
|
public record UserFilesResult(
|
|
IReadOnlyList<FileDto> Files,
|
|
int TotalCount);
|
|
|
|
/// <summary>
|
|
/// EN: Mapper from domain entities to DTOs.
|
|
/// VI: Mapper từ domain entities sang DTOs.
|
|
/// </summary>
|
|
public static class FileDtoMapper
|
|
{
|
|
public static FileDto ToDto(this StorageFile file) => new(
|
|
file.Id,
|
|
file.UserId,
|
|
file.FileName,
|
|
file.ContentType,
|
|
file.FileSizeBytes,
|
|
file.AccessLevel.ToString(),
|
|
file.UploadedAt);
|
|
}
|
|
```
|
|
|
|
### Request Validation / Validation Request
|
|
|
|
```csharp
|
|
/// <summary>
|
|
/// EN: Command for file upload.
|
|
/// VI: Command để upload file.
|
|
/// </summary>
|
|
public record UploadFileCommand(
|
|
Stream FileStream,
|
|
string FileName,
|
|
string ContentType,
|
|
long FileSize,
|
|
string UserId,
|
|
Guid? FolderId,
|
|
FileAccessLevel AccessLevel) : IRequest<UploadFileResult>;
|
|
|
|
/// <summary>
|
|
/// EN: Query for user files with pagination.
|
|
/// VI: Query lấy files với phân trang.
|
|
/// </summary>
|
|
public record GetUserFilesQuery(
|
|
string UserId,
|
|
int Skip = 0,
|
|
int Take = 20,
|
|
string? Search = null) : IRequest<UserFilesResult>;
|
|
```
|
|
|
|
### List Endpoint with Pagination / Endpoint List với Phân Trang
|
|
|
|
```csharp
|
|
[HttpGet]
|
|
[Authorize]
|
|
[SwaggerOperation(Summary = "Get user files")]
|
|
public async Task<ActionResult<ApiResponse<UserFilesResult>>> GetFiles(
|
|
[FromQuery] int skip = 0,
|
|
[FromQuery] int take = 20,
|
|
[FromQuery] string? search = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var userId = GetUserId();
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Unauthorized(new ApiResponse<UserFilesResult>
|
|
{
|
|
Success = false,
|
|
Error = "User ID not found"
|
|
});
|
|
|
|
var query = new GetUserFilesQuery(userId, skip, take, search);
|
|
var result = await _mediator.Send(query, cancellationToken);
|
|
|
|
return Ok(new ApiResponse<UserFilesResult>
|
|
{
|
|
Success = true,
|
|
Data = result,
|
|
Pagination = new PaginationInfo(
|
|
Page: skip / take + 1,
|
|
Limit: take,
|
|
Total: result.TotalCount,
|
|
TotalPages: (int)Math.Ceiling(result.TotalCount / (double)take))
|
|
});
|
|
}
|
|
```
|
|
|
|
## Common Mistakes / Lỗi Thường Gặp
|
|
|
|
### 1. Inconsistent Response Format / Response Format Không Nhất Quán
|
|
|
|
```csharp
|
|
// ❌ BAD: Trả về trực tiếp data
|
|
return Ok(result);
|
|
|
|
// ✅ GOOD: Dùng ApiResponse wrapper
|
|
return Ok(new ApiResponse<FileDto> { Success = true, Data = result });
|
|
```
|
|
|
|
### 2. Missing API Versioning / Thiếu API Versioning
|
|
|
|
```csharp
|
|
// ❌ BAD: Không có versioning
|
|
[Route("api/files")]
|
|
|
|
// ✅ GOOD: Có API versioning
|
|
[ApiVersion("1.0")]
|
|
[Route("api/v{version:apiVersion}/files")]
|
|
```
|
|
|
|
### 3. No Bilingual XML Comments / Thiếu Comment Song Ngữ
|
|
|
|
```csharp
|
|
// ❌ BAD: Chỉ tiếng Anh
|
|
/// <summary>Get file by ID.</summary>
|
|
|
|
// ✅ GOOD: Song ngữ EN/VI
|
|
/// <summary>
|
|
/// EN: Get file by ID.
|
|
/// VI: Lấy thông tin file theo ID.
|
|
/// </summary>
|
|
```
|
|
|
|
### 4. Missing Swagger Annotations / Thiếu Swagger Annotations
|
|
|
|
```csharp
|
|
// ❌ BAD: Không có Swagger annotations
|
|
[HttpGet("{fileId:guid}")]
|
|
public async Task<ActionResult<FileDto>> GetFile(Guid fileId)
|
|
|
|
// ✅ GOOD: Đầy đủ Swagger annotations
|
|
[HttpGet("{fileId:guid}")]
|
|
[SwaggerOperation(Summary = "Get file by ID")]
|
|
[SwaggerResponse(200, "File retrieved successfully")]
|
|
[SwaggerResponse(404, "File not found")]
|
|
public async Task<ActionResult<ApiResponse<FileDto>>> GetFile(Guid fileId)
|
|
```
|
|
|
|
### 5. Returning 200 for Errors / Trả 200 Cho Lỗi
|
|
|
|
```csharp
|
|
// ❌ BAD: 200 cho error
|
|
return Ok(new ApiResponse<FileDto> { Success = false, Error = "Not found" });
|
|
|
|
// ✅ GOOD: Đúng status code
|
|
return NotFound(new ApiResponse<FileDto> { Success = false, Error = "File not found" });
|
|
```
|
|
|
|
## Quick Reference / Tham Chiếu Nhanh
|
|
|
|
### HTTP Methods & Status Codes
|
|
|
|
| Method | Action | Success | Error Codes |
|
|
|--------|--------|---------|-------------|
|
|
| **GET** | Retrieve | 200 | 404 |
|
|
| **POST** | Create | 200/201 | 400, 409 |
|
|
| **PUT** | Full update | 200 | 400, 404 |
|
|
| **PATCH** | Partial update | 200 | 400, 404 |
|
|
| **DELETE** | Remove | 200/204 | 404 |
|
|
|
|
### Common Error Codes
|
|
|
|
| Code | Meaning | When to Use |
|
|
|------|---------|-------------|
|
|
| 400 | Bad Request | Validation errors |
|
|
| 401 | Unauthorized | Missing/invalid token |
|
|
| 403 | Forbidden | No permission |
|
|
| 404 | Not Found | Resource doesn't exist |
|
|
| 409 | Conflict | Duplicate resource |
|
|
| 422 | Unprocessable | Business rule violation |
|
|
| 429 | Too Many Requests | Rate limited |
|
|
|
|
### Controller Attributes
|
|
|
|
| Attribute | Purpose |
|
|
|-----------|---------|
|
|
| `[ApiController]` | Enable API behaviors |
|
|
| `[ApiVersion("1.0")]` | API versioning |
|
|
| `[Authorize]` | Require authentication |
|
|
| `[SwaggerTag("Description")]` | Swagger grouping |
|
|
| `[SwaggerOperation]` | Endpoint documentation |
|
|
| `[SwaggerResponse]` | Response documentation |
|
|
|
|
### ApiResponse Format
|
|
|
|
```csharp
|
|
// Success response
|
|
{ "success": true, "data": {...}, "pagination": {...} }
|
|
|
|
// Error response
|
|
{ "success": false, "error": "Error message" }
|
|
```
|
|
|
|
## Resources / Tài Nguyên
|
|
|
|
- [API Versioning Strategy](../api-versioning-strategy/SKILL.md) - Versioning patterns
|
|
- [Error Handling Patterns](../error-handling-patterns/SKILL.md) - Error handling
|
|
- [Middleware Patterns](../middleware-patterns/SKILL.md) - Request handling
|
|
- [Project Rules](../project-rules/SKILL.md) - Coding standards
|
|
- [Skill Authoring](../skill-authoring/SKILL.md) - How to write skills
|