--- 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 /// /// EN: Configure YARP reverse proxy for routing. /// VI: Cấu hình YARP reverse proxy cho routing. /// // 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 /// /// EN: Aggregation controller combining multiple service responses. /// VI: Controller aggregation kết hợp responses từ nhiều services. /// [ApiController] [Route("api/v1/aggregator")] public class AggregatorController : ControllerBase { private readonly IProductServiceClient _productClient; private readonly IReviewServiceClient _reviewClient; private readonly IInventoryServiceClient _inventoryClient; private readonly ILogger _logger; public AggregatorController( IProductServiceClient productClient, IReviewServiceClient reviewClient, IInventoryServiceClient inventoryClient, ILogger logger) { _productClient = productClient; _reviewClient = reviewClient; _inventoryClient = inventoryClient; _logger = logger; } /// /// 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. /// [HttpGet("products/{productId}")] public async Task> 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 }); } /// /// EN: Get user dashboard data from multiple services. /// VI: Lấy dữ liệu dashboard user từ nhiều services. /// [HttpGet("dashboard")] [Authorize] public async Task> 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 /// /// EN: Mobile BFF with optimized responses. /// VI: Mobile BFF với responses được tối ưu. /// [ApiController] [Route("mobile/api/v1")] public class MobileBffController : ControllerBase { private readonly IMediator _mediator; public MobileBffController(IMediator mediator) { _mediator = mediator; } /// /// EN: Mobile-optimized product list. /// VI: Danh sách sản phẩm tối ưu cho mobile. /// [HttpGet("products")] public async Task> 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); } /// /// EN: Mobile-optimized checkout. /// VI: Checkout tối ưu cho mobile. /// [HttpPost("checkout")] [Authorize] public async Task> 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 /// /// EN: GraphQL query for flexible aggregation. /// VI: GraphQL query cho aggregation linh hoạt. /// public class Query { public async Task GetProduct( [ID] Guid productId, ProductDataLoader productLoader, ReviewDataLoader reviewLoader, StockDataLoader stockLoader) { return await productLoader.LoadAsync(productId); } } public class ProductType : ObjectType { protected override void Configure(IObjectTypeDescriptor 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(r => r.GetReviews(default!, default!)) .Type>(); // EN: Lazy load stock only when requested // VI: Lazy load stock chỉ khi được yêu cầu descriptor .Field("stock") .ResolveWith(r => r.GetStock(default!, default!)) .Type(); } } public class ProductResolvers { public async Task> GetReviews( [Parent] Product product, ReviewDataLoader loader) { return await loader.LoadAsync(product.Id); } public async Task 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 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 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 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 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(), // Empty if failed Notifications = await notificationsTask ?? 0 // Zero if failed }; } private async Task?> 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 GetProductDetails(Guid productId) { var product = await _productClient.GetAsync(productId); // Called every time return MapToDto(product); } // ✅ GOOD: With caching public async Task GetProductDetails(Guid productId) { var cacheKey = $"product:{productId}"; var cached = await _cache.GetAsync(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