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

16 KiB

name, description, compatibility, metadata
name description compatibility metadata
api-aggregation API Gateway Aggregation và Backend for Frontend (BFF) patterns. Use for response composition, request routing, và client-specific APIs. .NET 10+, YARP, Ocelot, GraphQL
author version
Velik Ho 1.0

API Aggregation & BFF / API Aggregation và BFF Pattern

Patterns cho API Gateway aggregation và Backend for Frontend trong microservices.

When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:

  • Aggregating responses from multiple services / Tổng hợp responses từ nhiều services
  • Creating client-specific APIs / Tạo APIs riêng cho từng client
  • Reducing chatty client-server communication / Giảm giao tiếp "chatty"
  • Implementing API composition / Triển khai API composition

Core Concepts / Khái Niệm Cốt Lõi

Gateway Patterns Overview

┌─────────────────────────────────────────────────────────────┐
│                    GATEWAY PATTERNS                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              GATEWAY ROUTING (Reverse Proxy)          │  │
│  │  Client ──▶ Gateway ──▶ Service A                     │  │
│  │             (Route)     Service B                     │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              GATEWAY AGGREGATION                      │  │
│  │  Client ──▶ Gateway ──┬─▶ Service A ─┐               │  │
│  │                       └─▶ Service B ─┼─▶ Combined    │  │
│  │                       └─▶ Service C ─┘    Response   │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              BACKEND FOR FRONTEND (BFF)               │  │
│  │  Mobile ──▶ Mobile BFF ──▶ Services                   │  │
│  │  Web    ──▶ Web BFF    ──▶ Services                   │  │
│  │  IoT    ──▶ IoT BFF    ──▶ Services                   │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

When to Use What / Khi Nào Dùng Gì

Pattern Use Case Example
Gateway Routing Simple proxy Route /api/users/* to User Service
Gateway Aggregation Combine multiple calls Product page = Product + Reviews + Stock
BFF Client-specific needs Mobile needs different data than Web

Benefits and Trade-offs

Benefit Trade-off
Reduces client complexity Single point of failure
Optimizes for client needs Additional latency hop
Enables caching Gateway becomes bottleneck
Centralizes cross-cutting concerns More complexity to maintain

Key Patterns / Mẫu Chính

YARP Reverse Proxy Configuration

/// <summary>
/// EN: Configure YARP reverse proxy for routing.
/// VI: Cấu hình YARP reverse proxy cho routing.
/// </summary>

// Program.cs
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();
app.MapReverseProxy();
app.Run();

// appsettings.json
{
    "ReverseProxy": {
        "Routes": {
            "users-route": {
                "ClusterId": "users-cluster",
                "Match": {
                    "Path": "/api/users/{**catch-all}"
                },
                "Transforms": [
                    { "PathRemovePrefix": "/api/users" }
                ]
            },
            "orders-route": {
                "ClusterId": "orders-cluster",
                "Match": {
                    "Path": "/api/orders/{**catch-all}"
                }
            }
        },
        "Clusters": {
            "users-cluster": {
                "Destinations": {
                    "user-service": {
                        "Address": "http://user-service:5001"
                    }
                }
            },
            "orders-cluster": {
                "Destinations": {
                    "order-service": {
                        "Address": "http://order-service:5002"
                    }
                }
            }
        }
    }
}

API Aggregation Controller

/// <summary>
/// EN: Aggregation controller combining multiple service responses.
/// VI: Controller aggregation kết hợp responses từ nhiều services.
/// </summary>
[ApiController]
[Route("api/v1/aggregator")]
public class AggregatorController : ControllerBase
{
    private readonly IProductServiceClient _productClient;
    private readonly IReviewServiceClient _reviewClient;
    private readonly IInventoryServiceClient _inventoryClient;
    private readonly ILogger<AggregatorController> _logger;

    public AggregatorController(
        IProductServiceClient productClient,
        IReviewServiceClient reviewClient,
        IInventoryServiceClient inventoryClient,
        ILogger<AggregatorController> logger)
    {
        _productClient = productClient;
        _reviewClient = reviewClient;
        _inventoryClient = inventoryClient;
        _logger = logger;
    }

    /// <summary>
    /// EN: Get product details with reviews and stock info.
    /// VI: Lấy chi tiết sản phẩm với reviews và thông tin tồn kho.
    /// </summary>
    [HttpGet("products/{productId}")]
    public async Task<ActionResult<ProductDetailsDto>> GetProductDetails(
        Guid productId,
        CancellationToken ct)
    {
        // EN: Make parallel calls to services
        // VI: Gọi song song đến các services
        var productTask = _productClient.GetProductAsync(productId, ct);
        var reviewsTask = _reviewClient.GetReviewsAsync(productId, ct);
        var stockTask = _inventoryClient.GetStockAsync(productId, ct);

        await Task.WhenAll(productTask, reviewsTask, stockTask);

        var product = await productTask;
        if (product == null)
            return NotFound();

        var reviews = await reviewsTask;
        var stock = await stockTask;

        // EN: Aggregate responses
        // VI: Tổng hợp responses
        return Ok(new ProductDetailsDto
        {
            Id = product.Id,
            Name = product.Name,
            Description = product.Description,
            Price = product.Price,
            ImageUrl = product.ImageUrl,
            AverageRating = reviews.AverageRating,
            ReviewCount = reviews.TotalCount,
            TopReviews = reviews.Items.Take(3).ToList(),
            InStock = stock.AvailableQuantity > 0,
            AvailableQuantity = stock.AvailableQuantity
        });
    }

    /// <summary>
    /// EN: Get user dashboard data from multiple services.
    /// VI: Lấy dữ liệu dashboard user từ nhiều services.
    /// </summary>
    [HttpGet("dashboard")]
    [Authorize]
    public async Task<ActionResult<DashboardDto>> GetDashboard(CancellationToken ct)
    {
        var userId = User.GetUserId();

        var profileTask = _userClient.GetProfileAsync(userId, ct);
        var ordersTask = _orderClient.GetRecentOrdersAsync(userId, 5, ct);
        var notificationsTask = _notificationClient.GetUnreadCountAsync(userId, ct);
        var walletTask = _walletClient.GetBalanceAsync(userId, ct);

        await Task.WhenAll(profileTask, ordersTask, notificationsTask, walletTask);

        return Ok(new DashboardDto
        {
            Profile = await profileTask,
            RecentOrders = await ordersTask,
            UnreadNotifications = await notificationsTask,
            WalletBalance = await walletTask
        });
    }
}

BFF for Mobile

/// <summary>
/// EN: Mobile BFF with optimized responses.
/// VI: Mobile BFF với responses được tối ưu.
/// </summary>
[ApiController]
[Route("mobile/api/v1")]
public class MobileBffController : ControllerBase
{
    private readonly IMediator _mediator;

    public MobileBffController(IMediator mediator)
    {
        _mediator = mediator;
    }

    /// <summary>
    /// EN: Mobile-optimized product list.
    /// VI: Danh sách sản phẩm tối ưu cho mobile.
    /// </summary>
    [HttpGet("products")]
    public async Task<ActionResult<MobileProductListDto>> GetProducts(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10,
        CancellationToken ct = default)
    {
        // EN: Mobile gets smaller images and fewer fields
        // VI: Mobile nhận images nhỏ hơn và ít fields hơn
        var result = await _mediator.Send(new GetMobileProductsQuery
        {
            Page = page,
            PageSize = pageSize,
            ImageSize = "thumbnail" // Only thumbnails for mobile
        }, ct);

        return Ok(result);
    }

    /// <summary>
    /// EN: Mobile-optimized checkout.
    /// VI: Checkout tối ưu cho mobile.
    /// </summary>
    [HttpPost("checkout")]
    [Authorize]
    public async Task<ActionResult<MobileCheckoutResultDto>> Checkout(
        MobileCheckoutRequest request,
        CancellationToken ct)
    {
        // EN: Single endpoint handles entire checkout for mobile
        // VI: Một endpoint xử lý toàn bộ checkout cho mobile
        var result = await _mediator.Send(new MobileCheckoutCommand
        {
            UserId = User.GetUserId(),
            CartId = request.CartId,
            PaymentMethodId = request.PaymentMethodId,
            ShippingAddressId = request.ShippingAddressId,
            UseWalletBalance = request.UseWalletBalance
        }, ct);

        return Ok(result);
    }
}

GraphQL BFF

/// <summary>
/// EN: GraphQL query for flexible aggregation.
/// VI: GraphQL query cho aggregation linh hoạt.
/// </summary>
public class Query
{
    public async Task<ProductType?> GetProduct(
        [ID] Guid productId,
        ProductDataLoader productLoader,
        ReviewDataLoader reviewLoader,
        StockDataLoader stockLoader)
    {
        return await productLoader.LoadAsync(productId);
    }
}

public class ProductType : ObjectType<Product>
{
    protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
    {
        descriptor.Field(p => p.Id).ID();
        
        descriptor.Field(p => p.Name);
        descriptor.Field(p => p.Price);

        // EN: Lazy load reviews only when requested
        // VI: Lazy load reviews chỉ khi được yêu cầu
        descriptor
            .Field("reviews")
            .ResolveWith<ProductResolvers>(r => r.GetReviews(default!, default!))
            .Type<ListType<ReviewType>>();

        // EN: Lazy load stock only when requested
        // VI: Lazy load stock chỉ khi được yêu cầu
        descriptor
            .Field("stock")
            .ResolveWith<ProductResolvers>(r => r.GetStock(default!, default!))
            .Type<StockType>();
    }
}

public class ProductResolvers
{
    public async Task<IEnumerable<Review>> GetReviews(
        [Parent] Product product,
        ReviewDataLoader loader)
    {
        return await loader.LoadAsync(product.Id);
    }

    public async Task<Stock?> GetStock(
        [Parent] Product product,
        StockDataLoader loader)
    {
        return await loader.LoadAsync(product.Id);
    }
}

Common Mistakes / Lỗi Thường Gặp

1. Sequential Instead of Parallel Calls

// ❌ BAD: Sequential calls
public async Task<ProductDetailsDto> GetProductDetails(Guid productId)
{
    var product = await _productClient.GetAsync(productId);
    var reviews = await _reviewClient.GetAsync(productId);  // Waits for product
    var stock = await _stockClient.GetAsync(productId);     // Waits for reviews
    // Total time = product + reviews + stock
}

// ✅ GOOD: Parallel calls
public async Task<ProductDetailsDto> GetProductDetails(Guid productId)
{
    var productTask = _productClient.GetAsync(productId);
    var reviewsTask = _reviewClient.GetAsync(productId);
    var stockTask = _stockClient.GetAsync(productId);

    await Task.WhenAll(productTask, reviewsTask, stockTask);
    // Total time = max(product, reviews, stock)
}

2. No Fallback on Partial Failure

// ❌ BAD: Fails entirely if one service fails
public async Task<DashboardDto> GetDashboard()
{
    var profile = await _userClient.GetProfileAsync(userId);
    var orders = await _orderClient.GetOrdersAsync(userId);  // If this fails, whole request fails
    var notifications = await _notificationClient.GetAsync(userId);
}

// ✅ GOOD: Graceful degradation
public async Task<DashboardDto> GetDashboard()
{
    var profileTask = _userClient.GetProfileAsync(userId);
    var ordersTask = GetOrdersSafeAsync(userId);
    var notificationsTask = GetNotificationsSafeAsync(userId);

    await Task.WhenAll(profileTask, ordersTask, notificationsTask);

    return new DashboardDto
    {
        Profile = await profileTask,
        Orders = await ordersTask ?? Array.Empty<Order>(),  // Empty if failed
        Notifications = await notificationsTask ?? 0         // Zero if failed
    };
}

private async Task<IEnumerable<Order>?> GetOrdersSafeAsync(string userId)
{
    try
    {
        return await _orderClient.GetOrdersAsync(userId);
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "Failed to get orders for user {UserId}", userId);
        return null;  // Return null, not throw
    }
}

3. Missing Caching

// ❌ BAD: No caching, repeated calls
public async Task<ProductDetailsDto> GetProductDetails(Guid productId)
{
    var product = await _productClient.GetAsync(productId);  // Called every time
    return MapToDto(product);
}

// ✅ GOOD: With caching
public async Task<ProductDetailsDto> GetProductDetails(Guid productId)
{
    var cacheKey = $"product:{productId}";
    
    var cached = await _cache.GetAsync<ProductDetailsDto>(cacheKey);
    if (cached != null)
        return cached;

    var product = await _productClient.GetAsync(productId);
    var dto = MapToDto(product);
    
    await _cache.SetAsync(cacheKey, dto, TimeSpan.FromMinutes(5));
    
    return dto;
}

Quick Reference / Tham Chiếu Nhanh

Pattern Selection Guide

Need Pattern Implementation
Route requests Gateway Routing YARP, Ocelot
Combine data Gateway Aggregation Custom controller
Client-specific API BFF Separate project per client
Flexible queries GraphQL HotChocolate

Performance Checklist

Optimization Applied?
Parallel service calls
Response caching
Graceful degradation
Connection pooling
Timeout configuration

Resources / Tài Nguyên