API Composition Pattern: Birden Çok Servisten Veri Toplama
Mikroservis mimarisinde her servis kendi veritabanına ve sorumluluğuna sahiptir. Ancak bir istemci, genellikle tek bir istekte birden fazla servisten gelen verilere ihtiyaç duyar: sipariş detay sayfası; sipariş, müşteri, ürün ve kargo bilgilerini birlikte gösterir. İşte API Composition Pattern, bu ihtiyacı karşılamak için birden fazla servisten veri toplayıp tek bir yanıtta birleştiren çözümü sunar.
1. API Composition Nedir?
Bir Composer (Birleştirici) bileşeni, istemciden gelen tek bir isteği alır; bu isteği karşılamak için gerekli servislere çağrı yapar ve sonuçları birleştirerek istemciye döner.
text
İstemci → API Composer → [Servis A, Servis B, Servis C]
↓
Birleştirilmiş Yanıt → İstemci
Composer'ın Sorumlulukları:
-
Hangi servislerin çağrılacağını belirlemek.
-
Çağrıları paralel veya sıralı yapmak.
-
Yanıtları birleştirmek (merge/join).
-
Hataları yönetmek (fallback, partial response).
-
Sonucu istemciye uygun formatta dönmek.
2. API Gateway, BFF ve Composition İlişkisi
| Desen | Amaç | Veri Birleştirme | Kapsam |
|---|---|---|---|
| API Gateway | Tek giriş noktası, routing, auth | ❌ Genelde yok | Tüm istemciler |
| BFF | İstemciye özel backend | ✅ Var | Tek istemci türü |
| API Composition | Birden fazla servisten veri toplama | ✅ Var (odak) | Belirli iş senaryosu |
İlişki: API Composition genellikle BFF katmanı içinde uygulanır. BFF, istemciye özel optimizasyon yaparken; Composition, veri toplama mantığını üstlenir.
3. Veri Toplama Stratejileri
A. Paralel Çağrılar (Paralel Composition)
Bağımsız servis çağrıları aynı anda yapılır. Toplam süre, en yavaş servisin süresine eşittir.
csharp
public async Task<OrderDetailDto> GetOrderDetailAsync(int orderId)
{
var orderTask = _orderService.GetAsync(orderId);
var customerTask = _customerService.GetByOrderAsync(orderId);
var itemsTask = _productService.GetItemsAsync(orderId);
var shipmentTask = _shipmentService.GetByOrderAsync(orderId);
await Task.WhenAll(orderTask, customerTask, itemsTask, shipmentTask);
return new OrderDetailDto
{
Order = await orderTask,
Customer = await customerTask,
Items = await itemsTask,
Shipment = await shipmentTask
};
}
Avantaj: Toplam gecikme minimumdur.
Dezavantaj: Servisler arasında bağımlılık varsa uygulanamaz.
B. Sıralı Çağrılar (Sequential Composition)
Bir servisin yanıtı diğeri için gerekliyse sıralı yapılır. Toplam süre, tüm servislerin sürelerinin toplamıdır.
C. Hibrit Yaklaşım (En İyi Pratik)
Bağımsız çağrıları paralel, bağımlı çağrıları sıralı yapın.
csharp
// 1. Siparişi al (bağımsız) var order = await _orderService.GetAsync(orderId); // 2. Siparişe bağlı çağrıları paralel başlat var customerTask = _customerService.GetAsync(order.CustomerId); var itemsTask = _productService.GetItemsAsync(orderId); await Task.WhenAll(customerTask, itemsTask);
4. Hata Yönetimi: Dayanıklılık
A. Fail-Fast (Hızlı Başarısız)
Herhangi bir servis çağrısı başarısız olursa tüm istek başarısız olur. Tüm veriler kritikse (ör. ödeme) uygundur.
B. Graceful Degradation (Zarif Bozulma)
Kritik olmayan servisler başarısız olursa kısmi yanıt dönülür.
csharp
var order = await _orderService.GetAsync(orderId); // Kritik var items = await _productService.GetItemsAsync(orderId); // Kritik var customer = await TryGetAsync(() => _customerService.GetAsync(order.CustomerId)); var shipment = await TryGetAsync(() => _shipmentService.GetByOrderAsync(orderId)); // customer/shipment null olabilir; istemci buna göre davranır
C. Polly ile Retry ve Circuit Breaker
csharp
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
var customer = await retryPolicy.WrapAsync(circuitBreaker)
.ExecuteAsync(() => _customerService.GetAsync(order.CustomerId));
5. Performans Optimizasyonları
| Teknik | Açıklama |
|---|---|
| Paralel çağrılar | Bağımsız servisleri Task.WhenAll ile paralel yapın. |
| Caching | Sık kullanılan verileri (müşteri bilgisi) önbelleğe alın. |
| Batch çağrılar | N+1'den kaçının; toplu veri çekin. |
| Timeout | Her çağrıya makul timeout koyun; yavaş servis tüm isteği bloklamasın. |
Caching Örneği:
csharp
var customer = await _cache.GetOrSetAsync(
$"customer:{order.CustomerId}",
() => _customerService.GetAsync(order.CustomerId),
TimeSpan.FromMinutes(10));
6. CQRS ve GraphQL ile İlişkisi
CQRS: Okuma tarafında (Query), veriler farklı servislerden toplanarak bir Read Model oluşturulur. API Composition, bu modeli besleyen mekanizmadır.
GraphQL: GraphQL, API Composition için doğal bir çözümdür. Hot Chocolate ile birden fazla kaynağı tek şema altında birleştirebilirsiniz.
csharp
public class Query
{
public async Task<OrderDetailDto> GetOrderDetail(
int orderId,
[Service] IOrderService orderService,
[Service] ICustomerService customerService)
{
var order = await orderService.GetAsync(orderId);
var customerTask = customerService.GetAsync(order.CustomerId);
// ...
}
}
7. Ne Zaman Kullanılmalı?
| Uygun | Uygun Değil |
|---|---|
| İstemci tek istekte birden fazla servisten veriye ihtiyaç duyuyorsa | Servisler arasında sıkı bağımlılık varsa |
| Mobil istemciler ağ gecikmesini azaltmak istiyorsa | Gerçek zamanlı tutarlılık kritikse |
| BFF katmanı oluşturuluyorsa | Basit CRUD için gereksiz karmaşıklık |
| Veri toplama mantığı merkezileştirilmek isteniyorsa | — |
8. En İyi Pratikler
| Pratik | Açıklama |
|---|---|
| Paralel çağrıları tercih edin | Bağımsız servisleri Task.WhenAll ile paralel yapın. |
| Hata toleransı ekleyin | Polly ile retry, circuit breaker, fallback. |
| Kritik/kritik olmayan veriyi ayırın | Kritik olmayan servis başarısızsa kısmi yanıt dönün. |
| Caching kullanın | Sık kullanılan verileri önbelleğe alın. |
| Batch çağrılar yapın | N+1 probleminden kaçının. |
| Composer'ı ince tutun | Sadece koordinasyon; iş mantığı servislerde. |
| Observability sağlayın | Her servis çağrısını loglayın ve izleyin. |
Sonuç
API Composition Pattern, mikroservis mimarisinde istemcinin tek istekte birden fazla servisten veri almasını sağlayan güçlü bir çözümdür. Paralel çağrılar, hata toleransı (Polly), caching ve batch çağrılar ile performansı optimize edebilir; graceful degradation ile kullanıcı deneyimini koruyabilirsiniz. CQRS ve GraphQL ile birlikte kullanıldığında daha da güçlü hale gelir. Unutmayın: Composer'ı ince tutun, iş mantığını servislerde bırakın ve her zaman hata senaryolarını planlayın.