Files
pos-system/microservices/.agent/skills/api-design/SKILL.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

9.7 KiB

name, description, compatibility, metadata
name description compatibility metadata
api-design RESTful API design standards for GoodGo microservices. Use for new API endpoints, DTOs, controllers, OpenAPI documentation, or standardized responses. .NET 10+, ASP.NET Core, MediatR, Swashbuckle, Asp.Versioning
author version
Velik Ho 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

/// <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

/// <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

/// <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

/// <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

[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

// ❌ 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

// ❌ 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ữ

// ❌ 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

// ❌ 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

// ❌ 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

// Success response
{ "success": true, "data": {...}, "pagination": {...} }

// Error response
{ "success": false, "error": "Error message" }

Resources / Tài Nguyên