Mert Özen Her Satırda Daha İleri
Yazılım Mimarisi Microservices — #02

Microservice'lerin Bedeli: Ne Zaman Gereksiz, Ne Zaman Kaçınılmaz?

Mert Özen 8 Kas 2026 12 dk 4 görüntülenme
Microservice'lerin Bedeli: Ne Zaman Gereksiz, Ne Zaman Kaçınılmaz?

Microservices tek ekipli, ön koşulları (otomatik provisioning, monitoring, hızlı deploy) olmayan bir sistem için gereksizdir; birbirini bekleyen ekipler ve farklı ölçeklenen parçalar ölçülebilir hâle geldiğinde kaçınılmaz olur. Network çağrısının gerçek maliyeti ölçülerek, fallacies of distributed computing, Fowler'ın prerequisite'leri, Prime Video vakasının doğru okunuşu ve Bookstore için bir karar checklist'i.

Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore'da checkout sırasında kitabın fiyatı bugün sıradan bir method çağrısıyla okunuyor: prices.Find(isbn). Catalog ayrı bir service olursa aynı bilgi bir HTTP (Hypertext Transfer Protocol) isteğiyle gelecek. İkisi aynı makinede, aynı process'te bile çalışsa (yani gerçek bir network kablosu hiç devreye girmese) HTTP çağrısı method çağrısından kaç kat yavaş olur? 2 kat mı, 20 kat mı, 2.000 kat mı?

İkinci soru daha önemli: method çağrısı hangi şekillerde başarısız olabilir, HTTP çağrısı hangi yeni şekillerde? Catalog'u deploy ettiğin 15 saniye boyunca checkout'a ne olur? Tahminini yap; ardından ölçelim.

Network Çağrısı Bir Method Çağrısı Değildir

Aşağıdaki program aynı PriceList'i iki şekilde kullanıyor: doğrudan, bir de aynı process içinde ayağa kaldırılan küçük bir Minimal API (Application Programming Interface) arkasından, 127.0.0.1 üzerinden. Fiziksel network yok, TLS (Transport Layer Security) yok, load balancer yok; HTTP ve JSON (JavaScript Object Notation) maliyetinin en iyimser hâli:

namespace Bookstore.Catalog;

public sealed record BookPrice(string Isbn, decimal Amount, string Currency);

public sealed class PriceList
{
    private readonly Dictionary<string, decimal> _prices = new() { ["978-0441013593"] = 12.50m };

    public BookPrice? Find(string isbn) =>
        _prices.TryGetValue(isbn, out decimal amount) ? new BookPrice(isbn, amount, "EUR") : null;
}
using System.Diagnostics;
using System.Globalization;
using System.Net.Http.Json;
using Bookstore.Catalog;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
PriceList prices = new();

// Aynı PriceList'i bir de HTTP arkasında, ayrı bir "Catalog service" gibi yayınlıyoruz
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.WebHost.UseUrls("http://127.0.0.1:5087");
WebApplication catalog = builder.Build();
catalog.MapGet("/books/{isbn}/price", (string isbn) =>
    prices.Find(isbn) is { } price ? Results.Ok(price) : Results.NotFound());
catalog.MapGet("/slow/books/{isbn}/price", async (string isbn) =>
{
    await Task.Delay(TimeSpan.FromSeconds(3)); // GC duraklaması, dolu connection pool, yavaş disk...
    return Results.Ok(prices.Find(isbn));
});
await catalog.StartAsync();

using HttpClient http = new() { BaseAddress = new Uri("http://127.0.0.1:5087"), Timeout = TimeSpan.FromSeconds(1) };
const int calls = 10_000;
for (int i = 0; i < 1_000; i++) await http.GetFromJsonAsync<BookPrice>("/books/978-0441013593/price"); // ısınma

// 1) Process içi method çağrısı (JIT ısınsın diye önce bir tur)
for (int i = 0; i < 1_000_000; i++) prices.Find("978-0441013593");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1_000_000; i++) prices.Find("978-0441013593");
double inProcess = sw.Elapsed.TotalMicroseconds / 1_000_000;

// 2) Aynı makinede, loopback üzerinden HTTP + JSON
sw.Restart();
for (int i = 0; i < calls; i++) await http.GetFromJsonAsync<BookPrice>("/books/978-0441013593/price");
double overHttp = sw.Elapsed.TotalMicroseconds / calls;

Console.WriteLine($"In-process call : {inProcess,10:F3} µs");
Console.WriteLine($"HTTP (loopback) : {overHttp,10:F3} µs  (~{overHttp / inProcess:N0}x)");

// 3) Network çağrısının yeni hata türü: cevap gelmiyor
try
{
    await http.GetFromJsonAsync<BookPrice>("/slow/books/978-0441013593/price");
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    Console.WriteLine($"Slow Catalog    : {ex.Message}");
}

// 4) Catalog deploy ediliyor: process yok
await catalog.StopAsync();
try
{
    await http.GetFromJsonAsync<BookPrice>("/books/978-0441013593/price");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Catalog down    : {ex.HttpRequestError} - {ex.Message}");
}

M5 (Apple Silicon) işlemcili bir Mac'te, Release build ile:

In-process call :      0.028 µs
HTTP (loopback) :     51.610 µs  (~1,860x)
Slow Catalog    : The request was canceled due to the configured HttpClient.Timeout of 1 seconds elapsing.
Catalog down    : ConnectionError - Connection refused (127.0.0.1:5087)

Sayının kendisi makineye göre değişir, sıra büyüklüğü değişmez. Dictionary'den fiyat okumak 28 nanosaniye, en iyimser HTTP çağrısı 50 mikrosaniye civarında: yaklaşık iki bin kat. Gerçek bir network'te, TLS, service mesh ve farklı bir makine eklenince bu milisaniyelere çıkar. Bir sayfada 40 kitap listeliyorsan ve her biri için fiyat ayrı bir çağrıyla geliyorsa, bu fark artık kullanıcının hissettiği bir şeydir.

Asıl önemli olan son iki satır. Method çağrısının "cevap gelmedi" diye bir hata türü yok; process ayaktaysa cevap gelir. Network çağrısının ise iki yeni hali var: karşı taraf yavaş (ve sen ne kadar bekleyeceğine karar vermek zorundasın) ya da karşı taraf hiç yok. İkincisi bir arıza bile değil: Catalog her deploy edildiğinde birkaç saniye o adreste kimse olmayacak. Bu satırları yazmadığın sürece checkout, Catalog'un her deploy'unu müşteriye hata olarak gösterecek. Timeout, retry ve circuit breaker'ı doğru kurmak ilerideki Backend serisinin "External Service Integration ve Resilience" yazılarının konusu; burada fark etmemiz gereken şey, bu kodun microservice'e geçtiğin gün yazılmak zorunda olduğu. Örnekte kısalık için HttpClient'ı doğrudan oluşturduk; gerçek bir service'te IHttpClientFactory kullanılır.

Fallacies of Distributed Computing'in Günlük Karşılığı

1990'larda Sun Microsystems'te dağıtık sistem yazan mühendislerin yeni başlayanlarda tekrar tekrar gördüğü yanlış varsayımlar, L. Peter Deutsch'un 1994'te yedi maddeye tamamladığı ve James Gosling'in sekizinciyi eklediği bir listeye dönüştü: fallacies of distributed computing. Liste 30 yaşında, ama her maddenin Bookstore'da bugün bir karşılığı var:

Yanlış varsayımBookstore'daki günlük karşılığı
Network güvenilirdirInventory'ye giden istek kayboldu: stok ayrıldı mı, ayrılmadı mı? Tekrar denersen iki kez mi ayrılır?
Latency sıfırdırSepet sayfası 40 kitap için 40 ayrı fiyat çağrısı yapıyor; method çağrısında görünmeyen maliyet sayfa süresine dönüşüyor
Bandwidth sonsuzdurCatalog'un tam kitap listesini her istekte Ordering'e taşımak, kampanya günü hattı dolduruyor
Network güvenlidirService'ler arası trafik de şifrelenmeli ve kimlik doğrulamalı; "içerisi güvenli" varsayımı bu serinin onuncu yazısının konusu
Topology değişmezInventory'nin adresi deploy'dan deploy'a değişiyor; sabit URL'ler (Uniform Resource Locator) service discovery ihtiyacını doğuruyor
Tek bir yönetici vardırÖdeme sağlayıcısı ve kargo firması senin kontrolünde değil; bakım pencerelerini sana sormadan seçiyorlar
Taşıma maliyeti sıfırdırHer çağrı serialization, CPU (Central Processing Unit) ve bulutta veri transferi faturası demek
Network homojendirBir service .NET, öbürü başka bir dil; tarih formatı, decimal hassasiyeti ve timeout davranışı farklı

Monolith'te bu sekiz maddenin çoğu görünmez, çünkü method çağrıları aynı process'te kalır. Microservices'te her biri bir tasarım kararına dönüşür: timeout ne olacak, retry güvenli mi, aynı istek iki kez gelirse ne olacak, adresi kim bilecek. Bu soruların cevabı kod, test ve operasyon olarak geri gelir.

Fowler'ın Ön Koşulları ve Microservice Premium

Martin Fowler, Ağustos 2014'teki MicroservicePrerequisites yazısında microservices'e başlamadan önce sahip olunması gereken üç yeteneği sayıyor. Hızlı provisioning: yeni bir sunucuyu ya da ortamı saatler içinde, büyük ölçüde otomasyonla ayağa kaldırabilmek. Temel monitoring: hem teknik sorunları (hata oranı, service'in ayakta olup olmadığı) hem de iş sorunlarını (siparişlerde ani düşüş) hızla fark edebilmek ve gerekirse geri alabilmek. Hızlı uygulama deploy'u: bir service'i test ve production ortamına, tercihen birkaç saat içinde biten bir pipeline ile çıkarabilmek. Fowler bunların arkasında bir kültür değişikliği de görüyor: developer'larla operasyon ekibinin, deploy'dan incident incelemesine kadar yakın çalışması.

Bookstore'un bugünkü hâliyle karşılaştıralım: 3 developer, yarı zamanlı 1 ops kişisi, haftada bir deploy. Üç ön koşulun hiçbiri bugün tam olarak yok. Bu, Bookstore'un hiçbir zaman service'lere bölünmeyeceği anlamına gelmiyor; bölünmeden önce bu yeteneklerin monolith üzerinde kazanılması gerektiği anlamına geliyor. Bu üçü monolith için de değerli: tek bir uygulamayı dakikalar içinde güvenle deploy edebilen bir ekip, microservices'e geçse de geçmese de kazançlı.

Fowler Mayıs 2015'te bunu MicroservicePremium yazısıyla bir adım öteye taşıdı: microservices'in deploy, monitoring ve hata yönetimi için ödenen bir "prim"i var ve bu prim yalnız monolith olarak yönetilemeyecek kadar karmaşık sistemlerde karşılığını veriyor. Basit bir sistemde prim, verimliliği düşürür. Çoğu sistem iyi tasarlanmış bir modular monolith olarak daha iyi kurulur.

Operasyonel ve Bilişsel Maliyet

Primin içinde ne olduğunu somutlaştıralım. Bookstore'u altı service'e böldüğünü düşün. Bir monolith'te bir kez yaptığın şeyler artık altı kez yapılacak ya da altısını birden kapsayan bir platformla çözülecek:

  • Altı build ve deploy pipeline'ı, altı container image'ı ve sürüm geçmişi.
  • Altı veritabanı (ya da schema): ayrı migration, backup ve restore planı.
  • Altı service'in log'unu tek bir yerde toplamak ve bir isteği service'ler arasında izleyebilmek için distributed tracing.
  • Her service için health check, alarm, dashboard ve "gece 3'te kim uyanacak" sorusunun cevabı.
  • Service'ler arası contract'lar, versioning ve geriye uyumluluk testleri.
  • Service'ler arası kimlik doğrulama, secret'lar ve sertifikalar.
  • Lokal geliştirme: bir özelliği denemek için altı service'i ve bir message broker'ı ayağa kaldırmak.

Bunlar bir kez kurulup biten işler değil, sürekli işletilen işler. Üstüne bilişsel bir maliyet geliyor: bir developer'ın bir hatayı bulmak için kafasında tutması gereken şey artık bir call stack değil, birkaç process, birkaç veritabanı ve arada kaybolabilen message'lar. Mimariye Giriş serisinin "Conway's Law: Ekip Yapısı Mimariyi Nasıl Şekillendirir?" yazısında anlattığımız cognitive load kavramı burada doğrudan uygulanıyor: 3 kişilik bir ekibin altı service'i, bir broker'ı ve bir tracing altyapısını aynı anda taşıması, ekibin bütün zihinsel kapasitesini ürüne değil altyapıya harcaması demek.

Bu maliyetlerin bir kısmı araçlarla azalıyor. .NET tarafında ilerideki Backend serisinin ".NET Aspire ile Dağıtık Uygulamalar" yazıları lokal geliştirme, service discovery ve telemetry'nin büyük kısmını hazır getiriyor. Ama araç maliyeti sıfırlamıyor; yalnız tek seferlik kurulum maliyetini düşürüyor. İşletme maliyeti, yani her gün ayakta tutulan şeyin sayısı, service sayısıyla büyümeye devam ediyor.

Prime Video Vakasını Doğru Okumak

Mart 2023'te Prime Video Tech blogunda Marcin Kolny imzalı bir yazı yayımlandı: ekip, video yayınlarındaki görüntü ve ses kalitesi sorunlarını tespit eden bir monitoring aracını dağıtık bir yapıdan tek bir process'e taşıyarak altyapı maliyetini %90'ın üzerinde düşürdüklerini anlatıyordu. Yazı birkaç hafta içinde "Amazon microservices'ten monolith'e döndü" başlıklarıyla dolaştı. Orijinal yazı artık yayında değil, adres yönlendiriliyor; bu yüzden aşağıdaki özet DevClass'ın 5 Mayıs 2023 tarihli haberine ve The New Stack'in Haziran 2023 tarihli değerlendirmesine dayanıyor.

Anlatılan sistemde component'ler AWS (Amazon Web Services) Step Functions ile orkestre ediliyor, çözümlenen video kareleri component'ler arasında S3 (Simple Storage Service) üzerinden taşınıyordu. İki sorun çıktı. Step Functions her state geçişi için ücretlendiriyordu ve yayının her saniyesi için birden fazla geçiş vardı; ölçek büyüyünce hesap limitlerine takıldılar. Ara verileri S3'e yazıp okumak da ciddi bir maliyet kalemiydi. Ekip component'leri tek bir process'te topladı, ara veriyi bellekte taşıdı ve sistemi EC2 (Elastic Compute Cloud) ve ECS (Elastic Container Service) üzerinde çalıştırdı.

Bu vakadan "microservices öldü" sonucu çıkmıyor; çıkan ders daha dar ve daha kullanışlı. Birincisi, konsolide edilen şey bütün Prime Video değil, tek bir alt sistemdi. Netflix'te ve AWS'de cloud architecture'dan sorumlu yöneticilik yapmış Adrian Cockcroft, The New Stack'teki değerlendirmede değişikliği "microservices'ten monolith'e" değil, "Step Functions'tan tek bir microservice'e" geçiş olarak okudu; Prime Video sözcüsü de projenin bu şekilde başlayıp ölçeklenebilir bir service olarak bittiğini doğruladı. İkincisi, sorun dağıtık yapının kendisinde değil, büyük hacimde veriyi her adımda network ve depolama üzerinden taşımaktaydı: fallacies listesindeki "taşıma maliyeti sıfırdır" maddesi faturaya dönüşmüştü. Üçüncüsü, ekip component'leri yanlış yerden böldüğünü fark edip birleştirdi. Bu bir başarısızlık değil; Newman'ın da, Fowler'ın da söylediği şey zaten service sınırlarının ilk seferde doğru çizilemeyeceği ve geri alınabilir olması gerektiği.

Ne Zaman Gereksiz, Ne Zaman Kaçınılmaz?

Microservices'e geçişin gerekçesi bir moda ya da bir şirketin hikâyesi değil, ölçülebilen bir sorun olmalı. Aşağıdaki checklist her soruyu bir sinyale bağlıyor; son sütun Bookstore'un bugünkü cevabı:

SoruService lehine sinyalBookstore bugün
Kaç ekip aynı kod tabanında çalışıyor?Birden fazla ekip ve birbirinin release'ini bekleyen ekiplerTek ekip, 3 developer: hayır
Release'ler çakışıyor mu?Bir ekibin değişikliği, başka bir ekibin test edilmemiş kodu yüzünden bekliyorHaftalık tek release, çakışma yok
Parçaların yük profili farklı mı?Bir parça diğerlerinden çok farklı ölçekleniyor ve bu ciddi maliyet farkı yaratıyorKampanyada katalog araması 10 kat: izlenmeli
Hata izolasyonu iş için gerekli mi?Bir parçanın çökmesi, kritik akışı durdurmamalıCheckout'u e-posta düşürmemeli: queue yetiyor
Compliance kapsamı daraltılmalı mı?Kart verisi gibi denetime tabi kısmı ayrı bir sınırda tutmak denetim kapsamını küçültüyorKart verisi ödeme sağlayıcısında: hayır
Ön koşullar hazır mı?Otomatik provisioning, monitoring ve dakikalar içinde deployHenüz değil

Bookstore'un bugünkü cevabı açık: microservices gereksiz, iyi tasarlanmış bir modular monolith yeterli. Ama Mimariye Giriş serisinde koyduğumuz 18 aylık hedef tabloyu değiştiriyor: dört stream-aligned ekip, iki ülke, mobil uygulama ve kampanyada on kat trafik. O noktada ilk iki satır "evet"e döner ve katalog aramasının ayrı ölçeklenmesi gerçek bir maliyet konusu olur. Kaçınılmaz an, ekiplerin birbirini beklemesinin teslim süresine ölçülebilir biçimde yansıdığı andır. O zaman bile ilk adım bütün sistemi bölmek değil, en bağımsız parçayı (Bookstore'da büyük ihtimalle Catalog araması ya da Notification) dışarı almak olmalı; bu geçişin teknikleri serinin son yazısında.

Sık Düşülen Tuzaklar

CV (curriculum vitae, özgeçmiş) odaklı mimari, literatürdeki adıyla CV-driven architecture. Bir mimariyi problem gerektirdiği için değil, ekibin özgeçmişinde iyi duracağı için seçmek. Belirtisi kolay: kararın gerekçesinde Bookstore'un bir sorunu değil, bir teknoloji adı ya da bir şirketin adı geçiyor.

Tek ekip, on beş service. Üç developer'ın on beş service'e baktığı bir sistemde independent deployability'nin bir alıcısı yok: deploy'u bekleyen başka bir ekip olmadığı için bağımsızlığın faydası sıfır, maliyeti ise on beş katı. Newman'ın da altını çizdiği gibi, microservices'in en büyük faydası birden fazla ekibin birbirini beklemeden çalışabilmesi.

Ekip yapısını değiştirmeden service'e bölmek. Kod altı service'e bölündü ama aynı ekip hâlâ her özelliği uçtan uca yapıyor ya da tam tersine ekipler hâlâ katmanlara göre (frontend, backend, veritabanı) kurulu. Conway's Law burada acımasız: iletişim yapısı değişmezse service sınırları zamanla ekip sınırlarına geri çekilir ve ortaya bir distributed monolith çıkar. Bunun mekanizmasını Mimariye Giriş serisinin "Conway's Law: Ekip Yapısı Mimariyi Nasıl Şekillendirir?" yazısında ölçerek göstermiştik.

Prime Video'yu tersinden kullanmak. "Amazon bile monolith'e döndü" cümlesi, "Netflix microservices kullanıyor" cümlesi kadar zayıf bir gerekçe. İki vaka da başka bir sistemin, başka bir ölçeğin ve başka bir ekibin kararı.

Ön koşulları sonraya bırakmak. "Önce service'lere bölelim, pipeline ve monitoring'i sonra kurarız" planı, ilk production incident'ında kimin neyi bozduğunu bilmeden saatler kaybetmek demek.

Küçük Bir Deneme

★☆☆ Yazıdaki programı kendi makinende çalıştır ve oranı not et. Sonra BookPrice'a 2 KB'lık bir açıklama property'si ekle ve tekrar ölç. Oran nasıl değişti? Bu sana "bandwidth sonsuzdur" maddesi hakkında ne söylüyor?

★★☆ /slow endpoint'ini 3 saniye yerine 900 milisaniye beklet ve timeout'u 1 saniyede bırak. Çağrı başarılı oluyor mu? Şimdi bu çağrının bir checkout zincirindeki beş çağrıdan biri olduğunu düşün: kullanıcının toplam beklediği süre için bir bütçe koy ve her çağrıya ne kadar pay düştüğünü hesapla.

★★★ Kendi ekibin için checklist tablosunu doldur. Her satır için cevabını destekleyen bir veri bul: son üç ayın release takvimi, bir ekibin diğerini beklediği gün sayısı, parçaların CPU ve bellek kullanımı. Veri bulamadığın satırlar, kararı vermeden önce ölçmen gereken şeyler.

Bedeli gördük; peki bedel ödenmeye değerse sınırları nereden çizeceğiz? Bir sonraki yazıda business capability ve bounded context'ten service sınırına gidiyoruz: Newman'ın coupling türleri, "entity service" anti-pattern'i, "Customer"ın Ordering, Payment ve Notification'da neden üç ayrı model olduğu ve her service'in tek sahibi olan ekip.