Mert Özen Her Satırda Daha İleri
Yazılım Mimarisi Mimariye Giriş — #07

Mimari Stillerin Haritası: Layered'dan Microservices'e Hangi Stil Ne İçin?

Mert Özen 7 Kas 2026 14 dk 4 görüntülenme
Mimari Stillerin Haritası: Layered'dan Microservices'e Hangi Stil Ne İçin?

Architecture style bir constraint setidir; deployment unit sayısı ve technical/domain partitioning eksenleriyle layered, modular monolith, pipeline, microkernel, service-based, event-driven, space-based, SOA, microservices ve Web-Queue-Worker tek haritaya oturur. Pipeline ve AssemblyLoadContext'li microkernel örnekleri, Bookstore bağlamında kendi stil tablomuz ve Clean/Vertical Slice'ın neden stil değil kod organizasyonu olduğu.

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

Önce Tahmin Et

Bookstore'un mimari tartışmasında şu altı ifade aynı cümlelerde, sanki birbirinin alternatifiymiş gibi geçiyor: Microservices, Clean Architecture, Event-driven architecture, Vertical Slice Architecture, Modular monolith, CQRS (Command Query Responsibility Segregation). "Clean Architecture mı kullanalım, microservices mi?" sorusu toplantıda ciddi ciddi soruluyor.

Bu altı ifadeden hangileri bir architecture style, hangileri değil? "Değil" dediklerin neyi anlatıyor? Bir kâğıda iki liste yap. Bir de şu soruyu düşün: aynı sistem aynı anda hem modular monolith hem Clean Architecture olabilir mi?

Cevap yazının ortasında. Önce "stil" kelimesini netleştirelim, çünkü sorunun yarısı kelimenin gevşek kullanımından çıkıyor.

Stil Bir Constraint Setidir

Azure Architecture Center'ın Architecture Styles sayfası stili ortak characteristic'leri paylaşan bir mimariler ailesi olarak tanımlıyor ve önemli bir ayrıntı ekliyor: stil, tasarıma constraint'ler koyar. Hangi element'lerin bulunabileceğini ve aralarında hangi ilişkilere izin verildiğini kısıtlar. Bu kısıtlara uyduğunda bazı istenen sonuçlar kendiliğinden ortaya çıkar. Sayfadaki örnek microservices: her service tek bir sorumluluğu temsil eder, service'ler birbirinden bağımsızdır, veri sahibi service'e özeldir ve paylaşılmaz. Bu constraint'lerin karşılığında bağımsız deploy, hata izolasyonu ve sık release elde edersin.

Bu bakışın pratik sonucu şu: bir stili adıyla değil, constraint'leriyle seçersin. "Microservices kullanıyoruz" diyen ama bütün service'leri tek veritabanına bağlayan bir ekip, stilin adını almış ama constraint'ini bırakmış, dolayısıyla faydasını da kaybetmiştir. Bir önceki yazıdaki distributed monolith tam olarak buydu.

Stilleri bir haritaya yerleştirmek için iki eksen yeter. FoSA (Fundamentals of Software Architecture, Mark Richards ve Neal Ford, 2. baskı, O'Reilly, 2025) kitabının kullandığı bu iki eksenin ilki deployment unit sayısı: tek birim (monolithic) ya da çok birim (distributed). İkincisi partitioning: kodun teknik rollere (technical) ya da iş alanlarına (domain) göre bölünmesi. Haritadaki yerleşim kendi okumamız; bazı stiller (microkernel gibi) iki tarafa da yatkın.

07-style-map-tr.png

Monolithic Stiller: Layered, Modular Monolith ve Pipeline

Layered Architecture ve modular monolith'i önceki iki yazıda gördük. Layered, kodu presentation, business ve data gibi teknik rollere ayırıyor ve bir isteğin layer'ları sırayla geçmesini şart koşuyor. Modular monolith ise tek deployment unit'i koruyup üst seviyede iş alanlarına göre bölüyor. İkisi de Bookstore'un bugünkü ve yakın geleceğindeki aday stiller.

Pipeline (pipes and filters) daha az konuşulan ama her gün kullandığımız bir stil. Constraint'leri şunlar: iş, birbirini tanımayan küçük filter'lara bölünür; her filter girdisini bir pipe'tan alır, çıktısını bir sonrakine verir; veri tek yönde akar. Filter'lar rollerine göre ayrışır: veriyi üreten bir kaynak, veriyi dönüştürenler, koşulu tutmayanı eleyenler ve sonucu tüketen bir son halka. Unix shell'deki | operatörü bu fikrin en bilinen hâli. .NET'te LINQ (Language Integrated Query) zincirleri, System.IO.Pipelines ve System.Threading.Channels ile kurulan producer/consumer hatları aynı stilin farklı ölçekleri; C# Async serisindeki "C# Channels ile Producer/Consumer: Backpressure, Bounded Channel ve Pipeline" yazısı bunun async tarafını anlatıyor.

Bookstore'da pipeline'ın doğal yeri gece gelen fiyat dosyası. Distribütör her gece bir CSV (Comma-Separated Values) dosyası gönderiyor; satırlar parse ediliyor, doğrulanıyor, stok durumuna göre marj ekleniyor ve normalize ediliyor:

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

// Distribütörün gece gönderdiği fiyat dosyası: isbn;title;listPrice;stock
string[] feed =
[
    "978-0441013593;Dune;10.00;14",
    "978-0141439518; Emma ;4.80;3",
    "not-an-isbn;Broken Row;3.00;1",
    "978-0261103344;The Hobbit;9.20;0",
    "978-0141439549;Middlemarch;-1;5"
];

List<string> rejected = [];

// Pipe'lar IEnumerable<T>, filter'lar birbirini tanımayan küçük fonksiyonlar
IEnumerable<PriceRow> pipeline = feed
    .Pipe(Filters.Parse(rejected))
    .Pipe(Filters.Validate(rejected))
    .Pipe(Filters.ApplyMargin)
    .Pipe(Filters.Normalize);

foreach (PriceRow row in pipeline)
    Console.WriteLine($"OK   {row.Isbn} {row.Title,-12} {row.Price,6:F2} EUR  stock {row.Stock}");
foreach (string reason in rejected)
    Console.WriteLine($"SKIP {reason}");

public sealed record PriceRow(string Isbn, string Title, decimal Price, int Stock);

public static class PipeExtensions
{
    public static IEnumerable<TOut> Pipe<TIn, TOut>(this IEnumerable<TIn> source, Func<IEnumerable<TIn>, IEnumerable<TOut>> filter) =>
        filter(source);
}

public static class Filters
{
    public static Func<IEnumerable<string>, IEnumerable<PriceRow>> Parse(List<string> rejected) => lines => ParseCore(lines, rejected);

    public static Func<IEnumerable<PriceRow>, IEnumerable<PriceRow>> Validate(List<string> rejected) => rows => ValidateCore(rows, rejected);

    // Stokta yoksa marj düşük, azsa yüksek
    public static IEnumerable<PriceRow> ApplyMargin(IEnumerable<PriceRow> rows) =>
        rows.Select(r => r with { Price = Math.Round(r.Price * (r.Stock switch { 0 => 1.10m, < 5 => 1.35m, _ => 1.25m }), 2) });

    public static IEnumerable<PriceRow> Normalize(IEnumerable<PriceRow> rows) =>
        rows.Select(r => r with { Isbn = r.Isbn.Replace("-", ""), Title = r.Title.Trim() });

    private static IEnumerable<PriceRow> ParseCore(IEnumerable<string> lines, List<string> rejected)
    {
        foreach (string line in lines)
        {
            if (line.Split(';') is [var isbn, var title, var price, var stock]
                && decimal.TryParse(price, CultureInfo.InvariantCulture, out decimal listPrice)
                && int.TryParse(stock, out int count))
                yield return new PriceRow(isbn, title, listPrice, count);
            else
                rejected.Add($"parse: {line}");
        }
    }

    private static IEnumerable<PriceRow> ValidateCore(IEnumerable<PriceRow> rows, List<string> rejected)
    {
        foreach (PriceRow row in rows)
        {
            if (!row.Isbn.StartsWith("978")) rejected.Add($"isbn: {row.Isbn}");
            else if (row.Price <= 0) rejected.Add($"price: {row.Isbn} {row.Price}");
            else yield return row;
        }
    }
}
OK   9780441013593 Dune          12.50 EUR  stock 14
OK   9780141439518 Emma           6.48 EUR  stock 3
OK   9780261103344 The Hobbit    10.12 EUR  stock 0
SKIP isbn: not-an-isbn
SKIP price: 978-0141439549 -1

Pipeline'ın gücü modifiability'de: yeni bir kural ("yayınevi kara listesindekileri ele") mevcut filter'lara dokunmadan araya yeni bir filter olarak girer, her filter tek başına test edilir. Zayıf tarafı ise akışın tek yönlü olması: kullanıcıyla etkileşen, durumu sık değişen bir checkout akışı bu kalıba sığmaz. Bu örnekte bilinçli bir kestirme de var: reddedilen satırlar ortak bir List<string>'e yazılıyor. Saf bir pipeline'da hatalar da ayrı bir pipe'tan akar; kestirme küçük bir import için kabul edilebilir, ama filter'lar paralel çalışmaya başladığında ilk kırılacak yer burası. Pipeline'ın kod seviyesindeki karşılığını Design Patterns serisindeki "GoF Dışında Bilmen Gereken Kod Seviyesi Pattern'ler: Null Object, Specification, Result ve Pipeline" yazısı anlatıyor (GoF: Gang of Four, yani Gamma, Helm, Johnson ve Vlissides'in 1994 tarihli Design Patterns kitabı).

Microkernel: Plugin ile Genişleyen Core

Microkernel (plugin) stilinde sistem iki parçadan oluşur: minimal ama kararlı bir core ve core'un tanımladığı contract'ları uygulayan plugin'ler. Core plugin'leri tanımaz, yalnız contract'ı tanır; yeni davranış eklemek yeni bir plugin bırakmak demektir. .NET dünyasında bu stilin yaygın örnekleri var: derleyiciye takılan Roslyn analyzer'ları ve source generator'lar, MSBuild'in task assembly'leri, ASP.NET Core'un IHostingStartup ile dışarıdan yüklenen startup assembly'leri.

Bookstore'da kargo ücreti hesabı bu kalıba uyuyor: kargo firmaları ve tarifeleri değişiyor, Shipping module'ünün geri kalanı değişmiyor. Contract ayrı bir assembly'de:

namespace Bookstore.Shipping.Contracts;

// Core ile plugin'ler arasındaki tek sözleşme
public interface IShippingRateProvider
{
    string Name { get; }
    decimal Quote(Parcel parcel);
}

public sealed record Parcel(int BookCount, decimal WeightKg, string City);

Her plugin kendi project'inde ve core'u hiç tanımıyor:

using Bookstore.Shipping.Contracts;

namespace Bookstore.Shipping.FlatRate;

public sealed class FlatRateProvider : IShippingRateProvider
{
    public string Name => "flat-rate";
    public decimal Quote(Parcel parcel) => parcel.BookCount <= 3 ? 4.90m : 7.90m;
}
using Bookstore.Shipping.Contracts;

namespace Bookstore.Shipping.WeightBased;

public sealed class WeightBasedProvider : IShippingRateProvider
{
    public string Name => "weight-based";
    public decimal Quote(Parcel parcel) => 2.50m + Math.Ceiling(parcel.WeightKg) * 1.20m;
}

Core, Microsoft Learn'deki "Create a .NET Core application with plugins" rehberinin yaklaşımıyla her plugin'i kendi AssemblyLoadContext'inde yüklüyor. AssemblyDependencyResolver, plugin'in .deps.json dosyasına bakarak dependency'lerini plugin'in kendi klasöründen çözüyor; böylece iki plugin aynı kütüphanenin farklı sürümlerini kullanabiliyor:

using System.Reflection;
using System.Runtime.Loader;

namespace Bookstore.Shipping.Host;

// Her plugin kendi load context'inde, kendi dependency'leriyle yüklenir
internal sealed class PluginLoadContext(string pluginPath) : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver = new(pluginPath);

    protected override Assembly? Load(AssemblyName assemblyName) =>
        _resolver.ResolveAssemblyToPath(assemblyName) is { } path ? LoadFromAssemblyPath(path) : null;
}
using System.Globalization;
using System.Reflection;
using Bookstore.Shipping.Contracts;
using Bookstore.Shipping.Host;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

string pluginRoot = Path.GetFullPath(args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "plugins"));
List<IShippingRateProvider> providers = [];

foreach (string dll in Directory.GetFiles(pluginRoot, "Bookstore.Shipping.*.dll", SearchOption.AllDirectories).Order())
{
    if (Path.GetFileName(dll) == "Bookstore.Shipping.Contracts.dll") continue;

    PluginLoadContext context = new(dll);
    Assembly assembly = context.LoadFromAssemblyPath(dll);
    IEnumerable<Type> candidates = assembly.GetTypes().Where(t => t is { IsClass: true, IsAbstract: false }
        && typeof(IShippingRateProvider).IsAssignableFrom(t));
    int found = 0;
    foreach (Type type in candidates)
    {
        providers.Add((IShippingRateProvider)Activator.CreateInstance(type)!);
        found++;
    }
    Console.WriteLine($"{Path.GetFileName(dll)}: {found} provider(s)");
}

Parcel parcel = new(BookCount: 4, WeightKg: 2.3m, City: "Izmir");
foreach (IShippingRateProvider provider in providers.OrderBy(p => p.Name))
    Console.WriteLine($"{provider.Name,-13} {provider.Quote(parcel),6:F2}");
Bookstore.Shipping.FlatRate.dll: 1 provider(s)
Bookstore.Shipping.WeightBased.dll: 1 provider(s)
flat-rate       7.90
weight-based    6.10

Stilin en tipik tuzağı plugin'in project dosyasında saklı. Plugin, contract project'ine şöyle referans vermeli:

<PropertyGroup>
  <EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>

<ItemGroup>
  <ProjectReference Include="..\Bookstore.Shipping.Contracts\Bookstore.Shipping.Contracts.csproj">
    <Private>false</Private>
    <ExcludeAssets>runtime</ExcludeAssets>
  </ProjectReference>
</ItemGroup>

Private ve ExcludeAssets satırlarını FlatRate plugin'inden silip aynı host'u çalıştırınca, build hatasız geçiyor ama sonuç şu:

Bookstore.Shipping.FlatRate.dll: 0 provider(s)
Bookstore.Shipping.WeightBased.dll: 1 provider(s)
weight-based    6.10

Plugin klasörüne Bookstore.Shipping.Contracts.dll'in bir kopyası düştü. Resolver onu plugin'in load context'ine yükledi; plugin'deki class o kopyadaki IShippingRateProvider'ı uyguluyor, host ise kendi default context'indekini arıyor. Runtime için bunlar adı aynı ama farklı iki type. Hata da yok, exception da yok; plugin sessizce kayboluyor. Microkernel'de contract assembly'si core'a aittir ve plugin'le birlikte dağıtılmamalı.

İki uyarı daha. Microsoft'un rehberi açıkça belirtiyor: güvenilmeyen kod, güvenilen bir .NET process'ine güvenli şekilde yüklenemez. AssemblyLoadContext bir sürüm izolasyonu sağlar, güvenlik ya da hata izolasyonu sağlamaz; plugin'deki bir StackOverflowException bütün process'i düşürür. İkincisi, core'u minimal tutmak zor: her yeni plugin ihtiyacı contract'a "bir parametre daha" ekletir ve contract'ın her değişikliği bütün plugin'leri etkiler.

Distributed Stiller: Service-Based'den Microservices'e

Haritanın üst yarısındaki stiller birden fazla deployment unit kullanır ve bir önceki yazıdaki network bedelini öder. Hangisini seçtiğin, o bedelin karşılığında ne aldığına bağlı.

Service-based architecture, monolith'ten sonraki en yumuşak adım. Sistem birkaç kaba taneli domain service'ine bölünür (FoSA tipik sayıyı 4 ile 12 arasında veriyor), service'ler ayrı deploy edilir ama çoğu zaman tek veritabanını paylaşır. Bookstore için Catalog, Ordering ve Fulfillment gibi üç dört service'lik bir ara durum olabilir. Bağımsız deploy kısmen kazanılır, veritabanı ortak kaldığı için şema değişiklikleri hâlâ koordinasyon ister.

Event-driven architecture'da component'ler birbirini çağırmaz, event yayınlar ve event'lere tepki verir. Broker topolojisinde merkezi bir koordinatör yoktur, event'ler bir broker üzerinden zincirlenir; mediator topolojisinde ise bir mediator adımları yönetir. Kazanç ölçeklenme, responsiveness ve hata izolasyonu; bedel eventual consistency, mesaj sırası, tekrarlanan mesajlar ve hata ayıklamanın zorlaşması. İlerideki Messaging ve Event-Driven Architecture serisinin konusu.

Space-based architecture, veritabanının darboğaz olduğu çok yüksek ve dalgalı yük için tasarlanmış. Veri, işlem birimlerinin belleğindeki replike bir data grid'de tutulur, veritabanına arka planda asenkron yazılır. Bilet satışı ya da flash sale gibi ani zirvelerde elasticity sağlar; karşılığında karmaşıklığı ve maliyeti yüksek, veri tutarlılığı zor. Bookstore'un kampanya zirvesi (dakikada yaklaşık 120 sipariş) bunu gerektirecek seviyenin çok altında.

Orchestration-driven SOA (Service-Oriented Architecture), 2000'lerin kurumsal yaklaşımı: service'ler teknik bir taksonomiye göre ayrılır ve merkezi bir orchestration engine ya da ESB (Enterprise Service Bus) aralarındaki akışı yönetir. Hedef yeniden kullanımdı; sonuç çoğu zaman her şeyin merkezi ürüne bağlandığı, değişikliğin yavaşladığı sistemler oldu. Bugün daha çok tarihsel bir referans ve microservices'in neden "paylaşmak yerine kopyala" dediğini anlamak için okunmaya değer.

Microservices, bağımsız deploy edilebilen, her biri tek bir iş yeteneğine sahip ve verisinin sahibi olan küçük service'ler. En yüksek bağımsızlığı ve ölçeklenebilirliği verir, en yüksek operasyonel bedeli de ister: service discovery, distributed tracing, dağıtık veri tutarlılığı, olgun bir CI (Continuous Integration) ve CD (Continuous Delivery) hattı. İlerideki Microservices serisi bu bedeli ayrıntısıyla ele alıyor.

Azure Architecture Center'ın listesinde FoSA'da ayrı başlık olmayan bir stil daha var: Web-Queue-Worker. Bir web front end, bir message queue ve arka planda ağır işleri yapan bir worker. Front end ve worker ayrı ölçeklenir. Görece basit domain'li ama bazı işleri ağır olan uygulamalar için iyi bir başlangıç; Bookstore'un dördüncü yazıda sipariş sonrası e-postayı queue'ya bırakma kararı da bu stilin küçük, in-process bir hâli.

Bookstore Gözüyle Stil Tablosu

Aşağıdaki tablo bizim değerlendirmemiz ve bilinçli olarak bağlama bağlı: Bookstore'un bugünkü hâli (3 developer, haftalık deploy, günde yaklaşık 800 sipariş, kampanyada 10 kat trafik) ve önümüzdeki 18 ayın hedefleri göz önünde tutularak verildi. ★ sayısı arttıkça stil o kolonda Bookstore için daha iyi. Başka bir ekip, başka bir yükte aynı tabloyu farklı doldurur; doldurmalıdır da.

StilSadelik ve işletme maliyetiİş değişikliğine uyumBağımsız deployÖlçeklemeHata izolasyonuDerinleştiği yer
Layered★★★★★★★★★★★Bu serinin 05. yazısı; Clean, Onion ve Hexagonal Architecture
Modular monolith★★★★★★★★★★★★Modular Monolith serisi
Pipeline★★★★★★★★★★★★C# Async serisi, Channels yazısı
Microkernel★★★★★★★★★★★Bu yazı
Web-Queue-Worker★★★★★★★★★★★★★★★★System Design serisi, asenkron işleme
Service-based★★★★★★★★★★★★★★★★Modular Monolith serisi, module'ü service'e çıkarmak
Event-driven★★★★★★★★★★★★★★★★★★Messaging ve Event-Driven Architecture serisi
Space-based★★★★★★★★★★★★★★System Design serisi, caching ve veri katmanı
Orchestration-driven SOA★★★★★★★★★Tarihsel referans
Microservices★★★★★★★★★★★★★★★★★★★★Microservices serisi

Tabloyu okurken iki şeye dikkat et. Birincisi "İş değişikliğine uyum" kolonu: Bookstore'a gelen isteklerin çoğu iş alanından geldiği için domain partitioning'li stiller burada öne çıkıyor; teknik değişikliklerin baskın olduğu bir sistemde layered daha yüksek puan alırdı. Microkernel'in üç yıldızı da yalnız plugin şeklindeki değişiklikler için geçerli; kargo tarifesi eklemek kolay, checkout'u değiştirmek değil. İkincisi "Sadelik ve işletme maliyeti" kolonu: 3 kişilik bir ekip için bu kolon diğerlerinden ağır basıyor, çünkü tek bir ops kişisiyle on service'i izlemek availability hedefini tek başına tehlikeye atar. Microservices'in diğer bütün kolonlardaki yıldızları, bu kolondaki tek yıldızı ödeyecek bir ekip yoksa kâğıt üzerinde kalır.

Bookstore için okuma şu: bugün layered, kısa vadede modular monolith ve ağır işler için Web-Queue-Worker benzeri bir arka plan işleme. 18 ay sonra dört ekip olduğunda (sekizinci yazının konusu) service-based ya da seçici olarak microservices masaya gelir. Haritadaki yeşil ok bu yolu gösteriyor.

Stil Değil: Clean, Hexagonal ve Vertical Slice Nerede Durur?

Açılıştaki listeye dönelim. Microservices, event-driven architecture ve modular monolith birer architecture style: deployment unit'leri ve aralarındaki iletişimi kısıtlıyorlar. Clean Architecture, Onion, Hexagonal (Ports and Adapters) ve Vertical Slice ise bir deployment unit'in içindeki kodun nasıl organize edileceğini anlatıyor: dependency'lerin hangi yöne aktığını, use case'lerin nerede durduğunu, kodun layer'a mı feature'a mı göre gruplandığını. CQRS bir pattern'dir: okuma ve yazma modellerini ayırır, herhangi bir stilin içinde uygulanabilir. DDD (Domain-Driven Design) ise bir stil değil, domain'i modelleme ve sınırları bulma yaklaşımı; bounded context'ler hem modular monolith'te module sınırını hem microservices'te service sınırını belirlemekte kullanılır.

Bu yüzden "Clean Architecture mı microservices mi?" sorusu yanlış kurulmuş. Bir modular monolith'in her module'ü içeride Clean Architecture'la ya da Vertical Slice'la yazılabilir; bir microservice da içeride layered olabilir. İki karar farklı seviyelerde verilir ve farklı sorulara cevap verir. Stil, sistemin parçalarının nasıl deploy edilip nasıl konuştuğunu; kod organizasyonu, bir parçanın içinde değişikliğin nereye dokunduğunu belirler.

Bu kod organizasyonu yaklaşımları kategoride kendi serilerine sahip: Clean, Onion ve Hexagonal Architecture serisi ile Vertical Slice Architecture serisi. CQRS ve Event Sourcing ile Domain-Driven Design serileri de aynı kategoride. Mimariye Giriş serisi stilleri yalnız tanıtıyor; her birinin "nasıl"ı ilgili seride.

Sık Düşülen Tuzaklar

Stili adıyla seçmek. "Event-driven olalım" kararı, hangi constraint'leri kabul ettiğini ve karşılığında hangi characteristic'i aldığını söylemiyorsa bir karar değil, bir slogandır. Seçimi ikinci ve üçüncü yazıdaki characteristic listesinden başlat.

Tek stil aramak. Gerçek sistemler hibrittir: Bookstore'un ana uygulaması modular monolith, gece fiyat import'u bir pipeline, kargo hesaplama microkernel, e-posta gönderimi Web-Queue-Worker. Her parça kendi problemine uyan stili alabilir; önemli olan sınırların net olması.

Tabloyu bağlamından koparmak. Bir kitaptaki ya da bu yazıdaki yıldızları kendi sistemine kopyalamak, quality attribute'ları kendi domain'inden koparmakla aynı hata. Tabloyu kendi ekip büyüklüğün, yükün ve değişiklik tiplerinle yeniden doldur.

Plugin'i güvenlik sınırı sanmak. AssemblyLoadContext aynı process içinde sürüm izolasyonu sağlar; kötü niyetli ya da hatalı bir plugin process'i düşürebilir, belleği okuyabilir. Güvenilmeyen kod için ayrı process ya da container gerekir.

Kod organizasyonunu stil sanmak. Klasörleri Clean Architecture template'ine göre dizmek deployment'ı, ölçeklemeyi ya da hata izolasyonunu değiştirmez; Clean Architecture'lı bir monolith hâlâ bir monolith'tir.

Küçük Bir Deneme

★☆☆ Çalıştığın sistemi haritaya yerleştir: kaç deployment unit var, kod technical mi domain mi bölünmüş? Sonra sistemin içindeki bir pipeline'ı ve bir plugin noktasını bul; neredeyse her sistemde ikisi de vardır.

★★☆ Fiyat pipeline'ına bir filter ekle: başlığı boş olan satırları reddetsin. Sonra rejected listesini kaldırıp her filter'ın Result benzeri bir type döndürdüğü, hataların da pipe'tan aktığı bir sürüm yaz. Hangi filter'ların imzası değişti?

★★★ Microkernel örneğine üçüncü bir plugin ekle ve bu plugin'in bir NuGet paketine (örneğin JSON (JavaScript Object Notation) okuyan bir tarife dosyası için) dependency'si olsun. Plugin'i EnableDynamicLoading olmadan build edip yükle, sonra açıp tekrar dene. Farkı .deps.json dosyasında ve plugin klasöründe gör.

★★★ Stil tablosunu kendi sistemin için doldur. En az bir kolonda bu yazıdakinden farklı bir sonuca varmanı bekliyorum; varamıyorsan bağlamı yeterince somut tanımlamamışsın demektir.

Bu yazıda stilleri teknik constraint'ler üzerinden okuduk. Ama haritadaki yeşil oku belirleyen asıl şey teknik değil: Bookstore'un ne zaman service-based'e geçeceğini kod değil, ekiplerin sayısı ve nasıl konuştukları belirleyecek. Bir sonraki yazıda Conway's Law'a ve Team Topologies'in ekip tiplerine bakıp, aynı commit geçmişinin farklı ekip yapılarında ne kadar farklı bir koordinasyon maliyeti ürettiğini ölçeceğiz.