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

478 lines
16 KiB
Markdown

---
name: api-aggregation
description: API Gateway Aggregation và Backend for Frontend (BFF) patterns. Use for response composition, request routing, và client-specific APIs.
compatibility: ".NET 10+, YARP, Ocelot, GraphQL"
metadata:
author: Velik Ho
version: "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
```csharp
/// <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
```csharp
/// <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
```csharp
/// <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
```csharp
/// <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
```csharp
// ❌ 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
```csharp
// ❌ 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
```csharp
// ❌ 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
- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [API Design](../api-design/SKILL.md) - REST API patterns
- [Redis Caching](../redis-caching/SKILL.md) - Caching strategies
- [Error Handling](../error-handling-patterns/SKILL.md) - Resilience patterns
- [Inter-service Communication](../inter-service-communication/SKILL.md) - HTTP clients