API Gateway ve Backend for Frontend: Edge'de Ne Olur, Ne Olmamalı?
API Gateway her client için aynı olan edge işlerini (routing, TLS termination, token doğrulama, rate limiting) yapar; bir kullanıcı deneyimine özgü aggregation, response şekillendirme ve token handling ise o deneyimin ekibine ait Backend for Frontend'e aittir, iş kuralı ikisine de girmez. Kitap detay sayfası için üç service'i paralel çağıran ve Inventory çöktüğünde sayfayı düşürmeyen gerçek bir BFF endpoint'i, RFC 10017'nin BFF önerisi ve god gateway tuzağı.
Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.
Önce Tahmin Et
Bookstore'un Angular storefront'unda kitap detay sayfası üç bilgi gösteriyor: kitabın adı, yazarı ve fiyatı (Catalog), stok durumu (Inventory) ve "bu kitabı daha önce aldın" rozeti (Ordering). Service'ler ayrıldıktan sonra ilk sürümde Angular uygulaması bu üç service'e tarayıcıdan doğrudan gidiyor: üç ayrı host, üç ayrı CORS (Cross-Origin Resource Sharing) ayarı, tarayıcıda tutulan bir access token ve her service'te ayrı ayrı yazılmış token doğrulama kodu.
Mobil uygulama da aynı sayfayı istiyor, ama mobil ağda her round trip yüzlerce milisaniye ve mobil ekip Catalog'un 40 alanlık kitap JSON'unun (JavaScript Object Notation) üçünü kullanıyor. Bir gün Inventory yavaşlıyor ve detay sayfası hiç açılmıyor. Sence bu tablodaki sorunların kaçı bir API (Application Programming Interface) Gateway ile çözülür, kaçı başka bir şey ister? Ve "bu kitabı daha önce aldın" rozetinin kuralı (iade edilen siparişler sayılır mı?) nerede yaşamalı: gateway'de mi, service'te mi?
Client Service'lere Doğrudan Konuşursa
Microsoft'un Azure Architecture Center'daki API gateway rehberi, client'ın service'lerle doğrudan konuşmasının bedellerini sıralıyor: client kodu karmaşıklaşır çünkü birden fazla endpoint'i ve her birinin hatasını yönetir; client service'lerin nasıl bölündüğünü bilir, yani service sınırını değiştirmek client'ı kırar; tek bir ekran için birden fazla network round trip gerekir; her public service authentication, TLS (Transport Layer Security) ve rate limiting'i kendisi yapar; saldırı yüzeyi büyür. Bunların hepsi Önce Tahmin Et'teki tabloda var.
Bu sorunlar iki farklı türden. Bir kısmı her client için aynı olan edge işleri: TLS, kimin geldiğinin kontrolü, rate limiting, istek hangi service'e gidecek. Bir kısmı ise tek bir kullanıcı deneyimine özgü: detay sayfası hangi üç çağrıyı birleştirir, mobil hangi alanları ister, Inventory çökerse sayfa ne gösterir. Birinciler API Gateway'in, ikinciler Backend for Frontend'in (BFF) işi. İkisini aynı component'e yığmak, bu yazının sonundaki tuzakların çoğunun kaynağı.
API Gateway: Edge'in Sorumlulukları
API Gateway, client'larla service'ler arasında duran bir reverse proxy. Microsoft'un pattern kataloğu onu üç pattern'le tarif ediyor. Gateway Routing: client tek bir host'a gider, gateway layer-7 routing ile isteği doğru service'e yollar; /catalog/* Catalog'a, /orders/* Ordering'e. Client service topolojisini bilmez, bir service ikiye bölündüğünde yalnız route değişir. Gateway Offloading: her service'in tekrar tekrar yapacağı edge işleri tek yerde yapılır; TLS termination, mTLS (mutual TLS), token doğrulama, IP (Internet Protocol) allow list, rate limiting, WAF (Web Application Firewall), response compression. Gateway Aggregation: birden fazla service çağrısı tek isteğe birleştirilir; bu üçüncüsüne birazdan itiraz edeceğim.
Authentication offloading'in sınırını iyi çizmek gerekiyor. Gateway "bu token geçerli mi, imzası doğru mu, süresi dolmuş mu?" sorusunu cevaplayabilir ve geçersiz istekleri service'lere hiç ulaştırmaz. Ama "Ada bu siparişi iptal edebilir mi?" sorusu gateway'in değil Ordering'in sorusu, çünkü cevabı Ordering'in verisine bağlı. Edge'de coarse-grained authentication, service'te fine-grained authorization; bunun ayrıntısı serinin güvenlik yazısında. Rate limiting için de benzer bir ayrım var: gateway client başına toplam istek sayısını sınırlar (ASP.NET Core'un yerleşik AddRateLimiter middleware'i ya da gateway ürününün kendi özelliği), "bir kupon dakikada bir kez denenebilir" gibi iş kuralı ise service'te kalır.
.NET dünyasında gateway için iki yol öne çıkıyor: hazır bir ürün (Nginx, Envoy, bulutun managed gateway'i ya da Kubernetes ingress controller'ı) ya da Microsoft'un açık kaynak reverse proxy library'si YARP (Yet Another Reverse Proxy) ile ASP.NET Core üzerinde kendi gateway'ini kurmak. YARP'ın konfigürasyonu, route ve cluster tanımları, transform'lar ilerideki Backend kategorisindeki "API Gateway ve YARP" serisinin konusu; serinin son yazısında YARP'ı strangler fig için kısaca kullanacağız.
Backend for Frontend: Her Deneyime Bir Backend
Sam Newman, 2015'te yazdığı "Backends For Frontends" makalesinde pattern'in adını SoundCloud'da çalışmış Phil Calçado'ya bağlıyor. Fikir basit: herkese hizmet eden tek bir genel amaçlı API yerine, her kullanıcı deneyimine ayrı bir backend. Angular web için bir Web BFF, mobil uygulama için bir Mobile BFF. Newman'ın gözlemi, genel amaçlı API'nin zamanla her client'ın özel ihtiyaçlarıyla şiştiği, birçok ekibin aynı artifact'ı değiştirmesiyle bir deploy darboğazına döndüğü ve çoğu zaman arada ayrı bir "API ekibi" doğurduğu yönünde. BFF'in sahibi ise o deneyimi geliştiren ekip: Web BFF'i Angular ekibi yazar ve deploy eder, mobil ekip kendi BFF'ini. Böylece "detay sayfasına bir alan ekleyelim" değişikliği tek ekipte başlar ve biter. Bu, Mimariye Giriş serisinin "Conway's Law: Ekip Yapısı Mimariyi Nasıl Şekillendirir?" yazısındaki fikrin edge'deki karşılığı: kaç BFF olacağını ekip yapısı belirler.

Figürdeki düzen, bugün sık görülen bir kurulum: TLS, rate limiting ve kaba token doğrulaması gibi herkese ortak işler edge'deki gateway'de, deneyime özgü birleştirme BFF'lerde, iş kuralları service'lerde. Kargo firmasının Shipping'e gönderdiği webhook gibi bir kullanıcı deneyimi olmayan trafik BFF'e uğramaz, gateway'den doğrudan service'e gider. Küçük bir sistemde gateway ile BFF aynı process olabilir; Web BFF'in kendisi edge'de durur ve TLS'i de o sonlandırır. Ayrım fiziksel değil sorumluluk ayrımı.
BFF'de Aggregation: Kitap Detay Sayfası
Web BFF'in detay sayfası endpoint'i, üç service'i paralel çağırıp sayfanın ihtiyacına göre biçimlenmiş tek bir cevap döndürüyor. Örneği tek bir console uygulamasında kurdum: Catalog, Inventory ve Ordering birer stub (sırasıyla 120, 80 ve 100 ms gecikmeyle cevap veriyor), BFF de ayrı bir Kestrel host'u:
// Web storefront'un kitap detay sayfası için tek endpoint
bff.MapGet("/bff/web/books/{id:guid}", async (Guid id, IHttpClientFactory http, CancellationToken ct) =>
{
string customerId = "ada"; // gerçekte BFF'in session cookie'sinden gelir
Task<CatalogBook?> book = http.CreateClient("catalog").GetFromJsonAsync<CatalogBook>($"/api/books/{id}", ct);
Task<int?> stock = TryGetAvailableAsync(http.CreateClient("inventory"), id, ct);
Task<Purchase?> purchase = http.CreateClient("ordering")
.GetFromJsonAsync<Purchase>($"/api/customers/{customerId}/purchases/{id}", ct);
await Task.WhenAll(book, stock, purchase);
if (book.Result is not { } b)
return Results.NotFound();
return Results.Ok(new BookDetailsView(
b.Title,
b.Author,
$"{b.ListPrice.Amount:0.00} {b.ListPrice.Currency}",
stock.Result switch { null => "unknown", 0 => "out-of-stock", < 5 => "few-left", _ => "in-stock" },
purchase.Result?.PurchasedBefore ?? false));
});
static async Task<int?> TryGetAvailableAsync(HttpClient inventory, Guid bookId, CancellationToken ct)
{
// Stok bilgisi sayfanın olmazsa olmazı değil: hata olursa "unknown" göster
try
{
StockLevel? level = await inventory.GetFromJsonAsync<StockLevel>($"/api/stock/{bookId}", ct);
return level?.Available;
}
catch (HttpRequestException)
{
return null;
}
}
// Angular sayfasının ihtiyacına göre biçimlenmiş view model
public sealed record BookDetailsView(string Title, string Author, string Price, string Availability, bool PurchasedBefore);
Önce Inventory ayaktayken, sonra 503 dönerken aynı isteği atınca:
inventory up=True, 122 ms: {"title":"Dune","author":"Frank Herbert","price":"14.90 EUR","availability":"few-left","purchasedBefore":true}
inventory up=False, 122 ms: {"title":"Dune","author":"Frank Herbert","price":"14.90 EUR","availability":"unknown","purchasedBefore":true}
Bu kısa kodda üç karar var. Birincisi paralellik: üç çağrı birbirine bağımlı değil, Task.WhenAll ile toplam süre en yavaş çağrıya (Catalog'un 120 ms'si) yaklaşıyor; sırayla await edilseydi en az 120 + 80 + 100 = 300 ms olurdu. Tarayıcıdan üç ayrı çağrı yerine tek çağrı, mobil ağda bu farkı daha da büyütür, çünkü BFF ile service'ler arasındaki network veri merkezinin içinde ve hızlı.
İkincisi, kısmi hata için bilinçli bir karar. Newman'ın BFF makalesindeki örnek de bu: stok service'i çökerse stok bilgisini sayfadan düşür, sayfayı düşürme. Catalog olmadan sayfanın anlamı yok, o yüzden Catalog hatası sayfa hatası; Inventory olmadan sayfa yine işe yarar, o yüzden "unknown". Bu karar teknik değil ürün kararı ve deneyimin sahibi ekip verir; BFF'in o ekipte olmasının bir sebebi de bu. Dikkat: örnekte Inventory 503'ü hızlı döndürdüğü için süre değişmedi. Inventory cevap vermeden asılı kalsaydı, BFF de asılı kalırdı; her downstream çağrının bir timeout'u olmalı. Timeout, retry ve circuit breaker'ın service seviyesindeki anlamı serinin "Production'da Microservice" yazısında, Polly ile kodu ise ilerideki Backend kategorisindeki "External Service Integration ve Resilience" serisinde.
Üçüncüsü, BFF'in ne yapmadığı. "Daha önce aldın mı?" sorusunun cevabını Ordering veriyor; iade edilmiş siparişlerin sayılıp sayılmayacağı Ordering'in kuralı. BFF yalnız PurchasedBefore'u alıp sayfaya koyuyor. Stok sayısını "few-left" gibi bir etikete çevirmek ise sunum kararı: web ekibi eşiği 5 seçmiş, mobil ekip kendi BFF'inde başka bir eşik seçebilir. Çizgi şu: aynı soruya iki client farklı cevap alırsa iş tutarsızlığı oluyorsa kural service'te, yalnız görünüm farklıysa BFF'te.
BFF ve Token Handling
BFF'in ikinci büyük işi güvenlikle ilgili. Angular gibi tamamen tarayıcıda çalışan bir uygulama access token'ı, çoğunlukla bir JWT (JSON Web Token), tarayıcıda tutarsa, sayfaya sızan her XSS (Cross-Site Scripting) açığı token'ı çalabilir. IETF'in (Internet Engineering Task Force) OAuth çalışma grubunun yıllarca draft olarak geliştirdiği "OAuth 2.0 for Browser-Based Applications" dokümanı Ağustos 2026'da RFC (Request for Comments) 10017 olarak, best current practice statüsünde yayımlandı ve tarayıcı tabanlı uygulamalar için mimari seçenekleri karşılaştırıyor. En güçlü önerdiği seçenek BFF: OAuth client'ı tarayıcı değil BFF'tir, authorization code flow'u sunucu tarafında BFF yürütür ve token'ları kendi tarafında, kullanıcının session'ına bağlı tutar. Tarayıcıya yalnız HttpOnly, Secure, SameSite=Strict ve __Host- önekli bir session cookie gider. Angular uygulaması BFF'e cookie'yle gelir, BFF cookie'yi token'a çevirip isteği downstream'e access token'la yollar.
Bu düzenin bedeli de var: BFF artık session tutan, ölçeklenirken session'ı paylaşması gereken stateful bir component; cookie'ye dayandığı için CSRF (Cross-Site Request Forgery) korumasını da üstlenir. OWASP (Open Worldwide Application Security Project) serisinin "OWASP A07: Authentication Failures (2) — Cookie Flag'leri, JWT Doğrulama ve CSRF" yazısında cookie flag'lerinin ve CSRF'in ayrıntısını işledik. Ama kazanç büyük: token hiç tarayıcıya inmediği için XSS ile token çalınması büyük ölçüde ortadan kalkar. BFF'in downstream'e hangi token'ı taşıyacağı (kullanıcının token'ı mı, değiştirilmiş bir token mı) serinin güvenlik yazısının konusu.
Gateway mi BFF mi, İkisi Birden mi?
| Soru | API Gateway | BFF |
|---|---|---|
| Kime hizmet eder? | Bütün client'lara aynı şekilde | Tek bir kullanıcı deneyimine |
| Tipik işler | Routing, TLS, token doğrulama, rate limiting, WAF | Aggregation, response şekillendirme, session ve token handling |
| Sahibi | Platform ekibi | Deneyimin ekibi (web, mobil) |
| Kod mu konfigürasyon mu? | Çoğunlukla configuration | Uygulama kodu |
| Değişme sıklığı | Seyrek | UI (user interface) kadar sık |
| İçinde olmaması gereken | İş kuralı, client'a özel birleştirme | İş kuralı, başka deneyimlerin ihtiyacı |
Gateway Aggregation pattern'ine yukarıda itiraz edeceğimi söylemiştim; sebebi bu tablo. Aggregation client'a özgü bir iş ve client'a özgü bir iş platform ekibinin sahip olduğu ortak gateway'e girdiğinde her UI değişikliği platform ekibinin backlog'una düşer. Newman da Building Microservices'in (2. baskı, 2021) "Implementing Microservice Communication" bölümünde gateway'in davranışının generic kalmasını, call aggregation ve protocol rewriting'in gateway'e konmamasını öneriyor; "User Interfaces" bölümünde de aggregation'ı tek bir merkezi gateway'de toplamanın, onu sahiplenen ayrı bir ekip ve bir darboğaz doğurduğunu anlatıp bu işi BFF'lere bırakıyor.
Sık Düşülen Tuzaklar
God gateway
"Nasılsa her istek oradan geçiyor" diye gateway'e iş kuralı eklemek: sipariş toplamını gateway'de doğrulamak, kampanya fiyatını gateway'de hesaplamak, response'ları gateway'de yeniden şekillendirmek. Birkaç ay sonra gateway, service'lerin hepsinin bildiği her şeyi bilen tek bir component olur; 2000'lerin ESB'lerinin (Enterprise Service Bus) içine iş mantığı gömülüp herkesin bağımlı olduğu bir darboğaza dönmesinin bugünkü versiyonu.
Tek ekip darboğazı
Gateway'in route tablosu ya da aggregation kodu tek bir platform ekibinin elindeyse, her yeni endpoint bir ticket ve bir bekleme demek. Route'lar ve policy'ler service ekiplerinin pull request açabildiği, otomatik doğrulanan bir configuration olmalı.
BFF'i yeni bir genel amaçlı API'ye çevirmek
"Mobil de bu endpoint'i kullansın" diye Web BFF'i paylaşmak, BFF'in var olma sebebini ortadan kaldırır: yine iki deneyimin ihtiyacıyla şişen tek bir API. İki deneyim gerçekten çok benziyorsa Newman tek BFF'e izin veriyor; ama bu karar bilinçli olmalı, alışkanlıktan değil.
BFF'ten BFF'e çağrı
Mobile BFF'in bir veriyi Web BFF'ten alması, deneyimler arasında yeni bir coupling yaratır. Ortak ihtiyaç varsa o veri bir service'e aittir; iki BFF de o service'i çağırır.
Her downstream çağrıyı zorunlu saymak
Kısmi hata kararı verilmemiş bir aggregation, en kırılgan service'in availability'sini bütün sayfanın availability'si yapar. Hangi parçanın olmazsa olmaz olduğunu sayfa sayfa yaz.
Ne Zaman Kullanma
Tek bir Angular uygulaması, tek bir ekip ve bir modular monolith varsa, ayrı bir gateway ve BFF katmanı yalnız bir network hop'u ve bir deploy edilecek component daha ekler; monolith zaten deneyimin backend'idir. Gateway'in değeri, birden fazla service'in dışarıya açıldığı anda başlar: TLS ve token doğrulamayı her service'te tekrar etmek istemezsin. BFF'in değeri ise birden fazla, ihtiyaçları belirgin biçimde ayrışan deneyim ve onlara sahip ayrı ekipler olduğunda ortaya çıkar. Bookstore'un 18 aylık planında mobil uygulama ve ikinci ülke var; o noktaya kadar tek bir Web BFF (ya da tarayıcının yalnız tek bir host gördüğü bir gateway) yeterli.
Küçük Bir Deneme
★☆☆ Örnekteki Inventory stub'ına 503 yerine await Task.Delay(5000) ekle ve BFF'in süresini ölç. Sonra Inventory çağrısına 300 ms'lik bir timeout ekleyip (CancellationTokenSource.CancelAfter) tekrar dene. Sayfa ne gösteriyor?
★★☆ Aynı üç service için bir Mobile BFF endpoint'i tasarla: mobil detay ekranı yalnız başlık, fiyat ve "stokta var/yok" gösteriyor. Hangi çağrı tamamen düşer, response hangi alanları içerir, eşik kararını kim verir?
★★★ Bookstore'un ilk gateway route tablosunu yaz: hangi path hangi service'e ya da BFF'e gider, hangi route'ta rate limiting var, kargo firmasının webhook'u nasıl doğrulanır? Tabloda iş kuralı gibi görünen bir satır var mı?
Client artık tek bir adres biliyor. Ama BFF'in kendisi hâlâ http://localhost:5301 gibi sabit adreslerle konuşuyor; Inventory üç instance'a çıktığında ya da Kubernetes'te yeniden başladığında bu adresler ne olacak? Bir sonraki yazıda service discovery'ye bakacağız.