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

Microservices Nedir, Ne Değildir? Independent Deployability ve Information Hiding

Mert Özen 8 Kas 2026 11 dk 3 görüntülenme
Microservices Nedir, Ne Değildir? Independent Deployability ve Information Hiding

Microservice, bir business domain etrafında modellenmiş ve diğerlerine dokunmadan deploy edilebilen service'tir; ölçüt boyut değil, independent deployability ve information hiding'dir. Lewis ve Fowler'ın dokuz özelliği, Newman'ın tanımı, contract ile iç modeli ayırmanın C#'taki karşılığı, SOA farkı ve Bookstore'un service adayları.

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

Önce Tahmin Et

Bookstore ekibi bir yıl önce "microservices'e geçtik" diye bir duyuru yaptı. Bugün elde altı ayrı container var: Catalog, Ordering, Inventory, Payment, Shipping ve Notification. Her biri ayrı bir ASP.NET Core projesi, aralarında HTTP (Hypertext Transfer Protocol) çağrıları var. Ama altısı da aynı SQL (Structured Query Language) Server veritabanına bağlanıyor, entity class'ları Bookstore.Domain adlı ortak bir NuGet paketinden geliyor ve her release günü altısı birlikte, belli bir sırayla deploy ediliyor.

İki soru. Birincisi: bu sistem microservices mi? İkincisi: ekipten biri kitap fiyatını biçimlendiren 50 satırlık bir PriceFormatter service'i öneriyor, "bu gerçek bir microservice, Catalog 40.000 satır, o kadar büyük şeye microservice denmez" diyor. Hangisi daha "micro"? Cevaplarını bir kenara yaz; yazının sonunda ikisinin de neden yanlış soruya cevap verdiğini göreceğiz.

Lewis ve Fowler'ın Tanımı: Dokuz Ortak Özellik

Terimin bugünkü anlamı büyük ölçüde James Lewis ve Martin Fowler'ın 25 Mart 2014'te martinfowler.com'da yayımladığı "Microservices" makalesinden geliyor. Makale microservice architecture'ı, tek bir uygulamayı her biri kendi process'inde çalışan, birbiriyle hafif mekanizmalarla (çoğunlukla HTTP üzerinden bir API (Application Programming Interface) ile) konuşan küçük service'lerden oluşan bir bütün olarak kurmak diye tarif ediyor. Service'ler business capability'lere göre ayrılıyor ve otomasyonla bağımsız deploy ediliyor.

Yazarlar bunun resmî bir tanım olmadığını açıkça söylüyor; yaptıkları şey, bu stili kullanan ekiplerde gördükleri ortak özellikleri listelemek:

  • Componentization via Services: component, library olarak değil, process dışında çalışan service olarak ayrılıyor.
  • Organized around Business Capabilities: ekip ve service, teknik katmana değil iş yeteneğine göre kuruluyor.
  • Products not Projects: ekip yazdığı yazılımın production'daki ömrü boyunca sahibi oluyor.
  • Smart endpoints and dumb pipes: iş mantığı service'lerde, aradaki iletişim altyapısı yalnız taşıyor.
  • Decentralized Governance: her ekip kendi teknolojisini ve standardını seçebiliyor.
  • Decentralized Data Management: her service kendi verisini yönetiyor.
  • Infrastructure Automation: build, test ve deploy otomatik.
  • Design for failure: başka bir service'in çökmesi normal kabul ediliyor ve tasarıma giriyor.
  • Evolutionary Design: sistem parça parça değiştirilip parça parça atılabiliyor.

Listenin en kritik cümlesi ilk maddenin içinde saklı. Makale component'i bağımsız olarak değiştirilip yükseltilebilen parça diye tanımlıyor ve service kullanmanın ana sebebini şöyle koyuyor: bir service'teki değişiklik çoğu zaman yalnız o service'in yeniden deploy edilmesini gerektirir. Aynı paragraf bir uyarı da yapıyor: service interface'leri değişirse koordinasyon yine gerekir. Microservices'in bütün vaadi bu iki cümlenin arasındaki gerilimde yaşıyor.

Newman'ın Merkezi: Independent Deployability

Sam Newman, Building Microservices kitabının ikinci baskısında (O'Reilly, 2021) tanımı daha da sadeleştiriyor: microservice'ler, bir business domain etrafında modellenmiş ve bağımsız olarak release edilebilen service'lerdir. Kitabın ilk bölümü bunu birkaç kavrama açıyor: independent deployability, domain etrafında modellemek, kendi state'ine sahip olmak, boyut ve esneklik. Newman bunların içinde merkeze bir tanesini koyuyor: bir service'i değiştirip, başka hiçbir şeyi değiştirmeden production'a çıkarabiliyor musun?

Bu test, açılıştaki soruyu hemen cevaplıyor. Altı container'ın altısı birlikte deploy ediliyorsa, elinde Mimariye Giriş serisinin "Monolith'i Doğru Anlamak: Single-Process, Modular ve Distributed Monolith" yazısında anlattığımız distributed monolith var: microservices'in bütün network maliyeti, ama karşılığında bağımsız deploy yok. Container sayısı, repository sayısı ya da Kubernetes kullanıp kullanmadığın bu sorunun cevabını değiştirmiyor.

Independent deployability'yi kıran şeyler genelde aynı üç yerden geliyor. Ortak veritabanı: Catalog bir kolonun adını değiştirdiğinde o tabloyu okuyan herkes kırılıyor. Ortak domain library'si: Bookstore.Domain paketinin yeni sürümü çıkınca herkes yeniden build ve deploy ediyor. Kırılgan contract'lar: service'ler birbirinin iç modelini biliyor, bu yüzden birinin iç değişikliği diğerine sızıyor. Üçünün ortak kökü aynı: service'in içi dışarıdan görünüyor. Bu da bizi 1972'ye götürüyor.

Information Hiding: Parnas'tan Service Sınırına

David Parnas, 1972'de Communications of the ACM (ACM: Association for Computing Machinery) dergisinde yayımlanan "On the Criteria To Be Used in Decomposing Systems into Modules" makalesinde, bir sistemi module'lere bölerken ölçütün iş akışının adımları değil, değişmesi muhtemel kararlar olması gerektiğini savundu. Her module, değişebilecek bir tasarım kararını içinde saklamalı; dışarıya yalnız o karardan bağımsız, dar bir interface açmalı. Newman bu fikri microservice'lerin temeline koyuyor: service sınırı, bir information hiding sınırıdır. Ne kadar az şey dışarı açılırsa, içeride o kadar çok şey serbestçe değişebilir.

Bookstore'da bunun ne demek olduğunu Catalog üzerinde görelim. Catalog'un iç modelinde tedarikçi maliyeti, depo rafı, arama anahtar kelimeleri gibi Catalog ekibinin her an değiştirebileceği bilgiler var. Dışarı açılan contract ise yalnız başkalarının gerçekten ihtiyaç duyduğu kısım:

namespace Bookstore.Catalog;

// Catalog'un iç modeli: dışarıdan görünmez, Catalog ekibi istediği gibi değiştirir
internal sealed class Book(Guid id, string isbn, string title, string author, decimal listPrice)
{
    public Guid Id { get; } = id;
    public string Isbn { get; } = isbn;
    public string Title { get; set; } = title;
    public string Author { get; } = author;
    public decimal ListPrice { get; set; } = listPrice;
    public decimal SupplierCost { get; set; }
    public string WarehouseBin { get; set; } = "";
    public string SearchKeywords { get; set; } = "";
}

// Dışarıya verilen contract: yalnız başkalarının gerçekten ihtiyaç duyduğu property'ler
public sealed record BookSummaryV1(Guid Id, string Isbn, string Title, decimal Price, string Currency);

internal static class BookMapping
{
    public static BookSummaryV1 ToContract(this Book book) =>
        new(book.Id, book.Isbn, book.Title, book.ListPrice, "EUR");
}

Ordering, Catalog'un class'larına referans vermiyor. Cevabı kendi tanımladığı, kendi ihtiyacı kadar property'si olan bir tipe okuyor:

namespace Bookstore.Ordering;

// Ordering, Catalog'un cevabını kendi tipine okur; Catalog'un class'larına referans vermez
public sealed record CatalogBook
{
    public required Guid Id { get; init; }
    public required string Title { get; init; }
    public required decimal Price { get; init; }
}

Şimdi üç Catalog sürümünü Ordering'in gözünden deneyelim. İlki bugünkü contract, ikincisi yeni bir property eklenmiş hâli, üçüncüsü bir property'nin adı değiştirilmiş hâli:

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
JsonSerializerOptions web = new(JsonSerializerDefaults.Web);

Book dune = new(Guid.Parse("0199f0c4-5c1e-7000-8000-000000000001"), "978-0441013593", "Dune", "Frank Herbert", 12.50m)
{
    SupplierCost = 7.10m,
    WarehouseBin = "B-14-03",
    SearchKeywords = "arrakis spice desert"
};

// 1) Catalog v1 cevabı: iç property'ler (SupplierCost, WarehouseBin) dışarı çıkmıyor
string v1 = JsonSerializer.Serialize(dune.ToContract(), web);
Console.WriteLine($"Catalog v1 : {v1}");
Console.WriteLine($"Ordering   : {JsonSerializer.Deserialize<CatalogBook>(v1, web)}");

// 2) Catalog contract'a yeni bir property ekliyor: Ordering'e dokunmadan deploy
string v2 = """{"id":"0199f0c4-5c1e-7000-8000-000000000001","isbn":"978-0441013593","title":"Dune","subtitle":"Deluxe Edition","price":12.50,"currency":"EUR"}""";
Console.WriteLine($"Catalog v2 : additive change -> {JsonSerializer.Deserialize<CatalogBook>(v2, web)}");

// 3) Catalog "price" property'sinin adını "listPrice" yapıyor: breaking change
string v3 = """{"id":"0199f0c4-5c1e-7000-8000-000000000001","isbn":"978-0441013593","title":"Dune","listPrice":12.50,"currency":"EUR"}""";
try
{
    JsonSerializer.Deserialize<CatalogBook>(v3, web);
}
catch (JsonException ex)
{
    Console.WriteLine($"Catalog v3 : {ex.Message}");
}
Catalog v1 : {"id":"0199f0c4-5c1e-7000-8000-000000000001","isbn":"978-0441013593","title":"Dune","price":12.50,"currency":"EUR"}
Ordering   : CatalogBook { Id = 0199f0c4-5c1e-7000-8000-000000000001, Title = Dune, Price = 12.50 }
Catalog v2 : additive change -> CatalogBook { Id = 0199f0c4-5c1e-7000-8000-000000000001, Title = Dune, Price = 12.50 }
Catalog v3 : JSON deserialization for type 'Bookstore.Ordering.CatalogBook' was missing required properties including: 'price'.

İlk satır information hiding'in kendisi: SupplierCost ve WarehouseBin network'e hiç çıkmıyor. Catalog yarın maliyet hesabını tamamen değiştirse, depo rafı yerine barkod kullansa, kimseye haber vermesine gerek yok. İkinci satırda Catalog contract'a subtitle ekledi ve Ordering hiçbir şey yapmadan çalışmaya devam etti. Bu, Catalog'un tek başına deploy edilebildiği an. Üçüncü satırda ise Catalog'un yaptığı küçük bir isim değişikliği Ordering'i kırdı. Bu değişikliği yapmak için iki ekibin aynı gün deploy etmesi gerekiyor; yani independent deployability tam burada kayboluyor.

required keyword'ünün burada bir rolü var: Ordering'in gerçekten ihtiyaç duyduğu property gelmezse sessizce 0 fiyatla sipariş açmak yerine yüksek sesle hata veriyor. Contract'ı geriye uyumlu değiştirmenin teknikleri, expand-contract ve consumer-driven contract test bu serinin yedinci yazısının konusu. Burada çıkarmamız gereken ders daha temel: bir service'in dışarı açtığı her property, o service'in artık tek başına değiştiremeyeceği bir şeydir.

"Küçük Service" Efsanesi: Boyut Değil Sınır

"Micro" ön eki en çok yanlış anlaşılan kısım. Lewis ve Fowler makalede bir boyut vermiyor; gördükleri en büyük örneklerin Amazon'un "two pizza team" (iki pizzayla doyurulabilecek, en fazla bir düzine kişilik ekip) fikrine uyduğunu, küçük uçta ise altı kişilik bir ekibin altı kadar service'e baktığını aktarıyorlar. Newman da satır sayısını anlamlı bir ölçü olarak görmüyor ve James Lewis'in "bir microservice kafam kadar büyük olmalı" şakasını aktarıyor: anlaşılabilecek kadar küçük, ama tek bir iş yeteneğini bütünüyle taşıyacak kadar büyük.

Açılıştaki PriceFormatter önerisi bu yüzden yanlış. Fiyat biçimlendirmek bir iş yeteneği değil, bir yardımcı fonksiyon; kendi verisi yok, kendi kararı yok, onu değiştirmek isteyen ayrı bir ekip yok. Onu ayrı bir process'e almak her fiyat gösteriminde bir network çağrısı, bir deploy pipeline'ı ve bir alarm daha eklemek demek; karşılığında hiçbir şey kazanmıyorsun. Bu tür aşırı küçük parçalara bazen nanoservice deniyor ve bir anti-pattern olarak anılıyor. Öte yandan 40.000 satırlık Catalog kendi verisine, kendi kurallarına ve büyüdüğünde kendi ekibine sahip olabilecek bir iş alanı; satır sayısı onu service adayı olmaktan çıkarmıyor.

Doğru soru "ne kadar küçük?" değil, "bu sınırın içinde birlikte değişen şeyler mi var, dışında ise birbirinden bağımsız değişen şeyler mi?". Bu, cohesion ve coupling'in service ölçeğindeki karşılığı; SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) ve Design Principles serisindeki "Coupling ve Cohesion: İyi Tasarımın İki Ölçüsü, Connascence ve Law of Demeter" yazısının class'lar için anlattığı şeyin aynısı, yalnız bedeli çok daha yüksek. Sınırın nasıl çizileceği bu serinin üçüncü yazısının konusu.

SOA ile Farkı

"Bu SOA (Service-Oriented Architecture) değil mi?" sorusu 2014'ten beri soruluyor. Lewis ve Fowler'ın cevabı dikkatli: microservices, SOA'yı savunan bazı insanların tarif ettiği şeye benziyor, ama SOA terimi o kadar farklı anlama gelmiş ki tek başına işe yaramıyor. Pratikte 2000'lerin kurumsal SOA projeleri çoğunlukla merkezi bir ESB (Enterprise Service Bus) etrafında, merkezi yönetişimle ve ağır standartlarla kuruldu. Bazı microservices savunucuları SOA etiketini reddediyor, bazıları ise microservices'i "doğru yapılmış SOA" diye görüyor.

BoyutKurumsal SOA (yaygın uygulama)Microservices
İletişim altyapısıESB: routing, dönüşüm, orkestrasyon bus'taDumb pipe: HTTP ya da broker, mantık service'te
YönetişimMerkezi standart, ortak canonical modelEkip başına karar, contract üzerinden anlaşma
VeriÇoğu zaman ortak kurumsal veritabanıHer service kendi verisinin sahibi
Sınır ölçütüÇoğu zaman teknik ya da entegrasyon odaklıBusiness capability ve bounded context
DeployBüyük, koordineli release'lerService başına, bağımsız ve sık

Tablo genelleme yapıyor ve her SOA projesi böyle değildi. Ama fark çizgisi net: microservices, mantığı merkezi bir bus'a toplamayı ve ortak bir kurumsal modeli reddediyor. Information hiding'i ve bağımsız değişimi her şeyin üstüne koyuyor.

Bookstore'un Service Adayları

Mimariye Giriş serisinde Bookstore'un mimari seviyedeki altı parçasını tanımlamıştık. Bunları şimdilik yalnız service adayı olarak okuyalım; karar değil, aday. Her birinin sakladığı bilgi ve sahip olduğu veri farklı:

  • Catalog: kitap, yazar, kategori, arama ve fiyat gösterimi. Saklar: arama index'i, tedarikçi bilgisi. Yayınlar: BookPriceChanged.
  • Ordering: sepet, checkout, sipariş durumu ve kampanya kuralları. Saklar: fiyatlandırma ve iade kuralları. Yayınlar: OrderPlaced, OrderCancelled.
  • Inventory: stok, rezervasyon, depo girişleri. Saklar: hangi nüshanın nerede olduğu. Yayınlar: StockReserved, OutOfStock.
  • Payment: ödeme sağlayıcısıyla authorize, capture ve refund. Saklar: sağlayıcının modeli ve token'lar. Yayınlar: PaymentCaptured, PaymentFailed.
  • Shipping: kargo etiketi ve takip numarası. Saklar: kargo firmasının API'si ve eski SOAP (Simple Object Access Protocol) entegrasyonu. Yayınlar: OrderShipped.
  • Notification: sipariş ve kargo e-postaları, SMS (Short Message Service). Saklar: e-posta sağlayıcısı, template'ler, gönderim tercihleri.

Bu altı parça üç farklı şekle sokulabilir ve aradaki fark kodun klasör düzeninden çok, kaç deployment unit olduğu ve verinin kime ait olduğu:

01-three-shapes-tr.png

Bu adaylar DDD (Domain-Driven Design) serisinde bounded context olarak çizdiğimiz sınırlarla örtüşüyor; bunun tesadüf olmadığını üçüncü yazıda göreceğiz. .NET dünyasında bu şeklin resmî bir referansı da var: Microsoft'un dotnet/eShop örnek uygulaması (.NET 10 ve Aspire üzerinde) Catalog, Basket, Ordering, Identity gibi ayrı API'lere bölünmüş; Catalog, Ordering ve Identity'nin ayrı veritabanları var, Basket Redis kullanıyor ve service'ler arası bildirimler RabbitMQ üzerinden integration event olarak gidiyor. Ama altı adayın altısının da service olması gerektiği anlamına gelmiyor. Bookstore bugün 3 developer'lık tek bir ekip ve independent deployability bilinçli olarak "şimdilik değil" listesinde. Bu adaylardan hangisinin ne zaman service olmayı hak ettiği, bir sonraki yazının konusu olan bedel hesabına bağlı.

Sık Düşülen Tuzaklar

Microservices'i deployment teknolojisiyle tanımlamak. Docker, Kubernetes ya da ayrı repository'ler bir sistemi microservices yapmaz. Ölçüt, bir service'in diğerlerine dokunmadan değişip production'a çıkabilmesi.

Boyutu satır sayısıyla ölçmek. "Küçük olsun" diye bir iş yeteneğini birden fazla service'e bölmek, her değişikliği birden fazla deploy'a çevirir. Küçük olan sınır değil, dışarı açılan contract olmalı.

Entity'yi contract yapmak. Catalog'un Book entity'sini olduğu gibi JSON (JavaScript Object Notation) olarak dışarı vermek, iç modeldeki her değişikliği bir breaking change'e çevirir. İç model ile contract ayrı tipler olmalı, arada açık bir mapping olmalı.

Ortak domain paketi. Service'ler arasında entity paylaşan bir NuGet paketi, information hiding'i derleme zamanında öldürür. Paylaşılabilecek şey küçük ve kararlı olmalı; Newman da, DDD serisindeki Shared Kernel tartışması da bunu söylüyor.

Microservices'i varsayılan başlangıç noktası sanmak. Bu stil bir hedef değil, belli bir problemin (birbirini bekleyen ekipler, farklı ölçeklenen parçalar) çözümü. Problem yoksa bedel kalır.

Küçük Bir Deneme

★☆☆ Bu yazıdaki CatalogBook tipinden required keyword'ünü kaldır ve üçüncü senaryoyu tekrar çalıştır. Ordering ne görüyor? Bu davranış production'da hangi hataya dönüşür ve kim ne zaman fark eder?

★★☆ Çalıştığın bir sistemde "service" denen iki parçayı seç. Son üç ayda birini değiştirmek için diğerini de aynı gün deploy etmeniz gereken kaç durum oldu? Sebeplerini ortak veritabanı, ortak library ve contract değişikliği diye üç gruba ayır.

★★★ Bookstore'un Payment adayı için bir contract taslağı yaz: Ordering'in bilmesi gerekenler (ödeme sonucu, tutar, para birimi) ile Payment'ın saklaması gerekenler (sağlayıcının işlem numarası, kart token'ı, retry sayısı) ayrı tiplerde olsun. Sonra sağlayıcıyı değiştirdiğini varsay: contract'ta ne değişti?

Bu yazıda microservices'in ne olduğunu iki kavrama indirdik: independent deployability ve information hiding. Bir sonraki yazıda madalyonun öbür yüzüne bakıyoruz: network'ün, operasyonun ve ekiplerin bu stile ödediği bedel, Prime Video vakasının gerçekte ne anlattığı ve "bizim microservices'e ihtiyacımız var mı?" sorusu için bir karar checklist'i.