Yazılım Mimarisi Nedir? Design'dan Farkı ve Architect'in Rolü
Yazılım mimarisi, sistemin önemli property'lerini belirleyen ve geri alınması pahalı olan kararlardır; design ile arasında kesin bir çizgi değil, stratejik ve taktik uçlu bir spectrum vardır. SEI, ISO/IEC/IEEE 42010 ve Fowler/Johnson tanımları, FoSA'nın üç yasası, architect'in rolü ve kategori boyunca kullanılacak Bookstore örneği.
Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.
Önce Tahmin Et
Bookstore'un sipariş tarafı bugün kabaca şöyle çalışıyor: sipariş kaydediliyor, müşteriye siparişini takip edebileceği bir link gidiyor, ödeme sağlayıcısına da siparişi tanıyabileceği bir referans numarası yollanıyor. Kodun özü:
Order order = new(0, "Ada", 44.47m);
OrderStore store = new();
int id = store.Place(order);
Console.WriteLine(OrderLinks.ForCustomer(id));
Console.WriteLine(PaymentReference.For(id));
public record Order(int Id, string CustomerName, decimal Total);
public sealed class OrderStore
{
private readonly List<Order> _orders = [];
private int _lastId = 1041;
public int Place(Order order)
{
int id = ++_lastId;
_orders.Add(order with { Id = id });
return id;
}
public Order? Find(int id) => _orders.FirstOrDefault(o => o.Id == id);
}
public static class OrderLinks
{
public static string ForCustomer(int orderId) => $"https://bookstore.example/orders/{orderId}";
}
public static class PaymentReference
{
public static string For(int orderId) => $"BKS-{orderId:D8}";
}
https://bookstore.example/orders/1042
BKS-00001042
Aynı hafta iki iş geliyor. Birincisi: CustomerName property'sinin adı BuyerName olacak, çünkü ürün ekibi artık hediye siparişlerinde alıcıyla ödeyeni ayırıyor. İkincisi: sipariş numarası int yerine Guid olacak. Sıralı numara hem dışarıdan tahmin edilebiliyor hem de rakibe günde kaç sipariş aldığını söylüyor; OWASP (Open Worldwide Application Security Project) Top 10 serisindeki IDOR (Insecure Direct Object Reference) yazısı ve Çözüm Desenleri serisindeki "Id'yi Dışarıya Açmak" yazısı tam bu konuyu anlatıyordu.
İki soru: bu iki işten hangisi bir mimari karar? Ve ikinci değişiklikte derleyici kaç hata verir, hata kalmayınca iş biter mi?
Derleyicinin Gösterdiği ve Göstermediği
Birinci iş bir rename. IDE'de (Integrated Development Environment) property'nin üstünde rename'e basarsın, bütün kullanım yerleri aynı derleme içinde olduğu için hepsi birlikte değişir. Riski de etkisi de bir commit'le sınırlı. İkinci iş için Order ve OrderStore'u Guid'e çevirelim, gerisine dokunmayalım:
Order order = new(Guid.Empty, "Ada", 44.47m);
OrderStore store = new();
Guid id = store.Place(order);
Console.WriteLine(OrderLinks.ForCustomer(id));
Console.WriteLine(PaymentReference.For(id));
public record Order(Guid Id, string CustomerName, decimal Total);
public sealed class OrderStore
{
private readonly List<Order> _orders = [];
public Guid Place(Order order)
{
Guid id = Guid.CreateVersion7();
_orders.Add(order with { Id = id });
return id;
}
public Order? Find(Guid id) => _orders.FirstOrDefault(o => o.Id == id);
}
Program.cs(5,42): error CS1503: Argument 1: cannot convert from 'System.Guid' to 'int'
Program.cs(6,40): error CS1503: Argument 1: cannot convert from 'System.Guid' to 'int'
İki hata. OrderLinks.ForCustomer ve PaymentReference.For'un parametrelerini Guid yapınca proje derleniyor. Çalıştıralım:
https://bookstore.example/orders/01a1b7aa-2de1-7748-9b39-fc2bf5c1f263
Unhandled exception. System.FormatException: Format string can be only "D", "d", "N", "n", "P", "p", "B", "b", "X" or "x".
at System.Guid.ThrowBadGuidFormatSpecification()
...
at PaymentReference.For(Guid orderId) in p01b/Program.cs:line 31
Derleyici {orderId:D8}'deki format string'i kontrol etmiyor; int için anlamlı olan "sekiz haneye sıfırla doldur" Guid için geçersiz ve ilk siparişte runtime'da patlıyor. Ama bu da kolay kısım. Asıl zor olanlar kodda hiç görünmüyor: müşterilerin e-postalarında duran /orders/1042 linkleri, ödeme sağlayıcısının sisteminde BKS-00001042 olarak kayıtlı on binlerce işlem, muhasebenin "sipariş numarası 8 haneli sayıdır" varsayımıyla yazılmış raporu, müşteri hizmetlerinin telefonda "sipariş numaranızı okur musunuz" diye sorduğu süreç. Otuz altı karakterlik bir Guid'i telefonda okutmayı dene.
Demek ki ilk bakışta bir record'daki tip seçimi gibi görünen "sipariş numarası sıralı bir int ve dışarıya olduğu gibi açılıyor" kararı, aslında bir mimari karardı. Nedeni koddaki yeri değil, değiştirmenin bedeli: kararın etkisi uygulamanın sınırını aşıp müşteriye, ödeme sağlayıcısına ve başka ekiplere yayılmış. Rename ise design seviyesinde bir karar. Bu yazının geri kalanı bu ayrımı netleştirmekle ilgili.
Yazılım Mimarisi Nedir? Üç Tanım
Tek bir kabul görmüş tanım yok, ama üç tanımı yan yana koyunca resim netleşiyor.
SEI'nin (Software Engineering Institute, Carnegie Mellon Üniversitesi) tanımı, Bass, Clements ve Kazman'ın Software Architecture in Practice kitabından: bir sistemin mimarisi, o sistem hakkında akıl yürütmek için gereken structure'ların toplamıdır; her structure software element'lerinden, aralarındaki ilişkilerden ve ikisinin property'lerinden oluşur. Bu tanımın iki güzel yanı var. Birincisi çoğul konuşuyor: tek bir mimari diyagram yok; module structure'ı (hangi kod nerede), runtime structure'ı (hangi process hangisini çağırıyor), deployment structure'ı (ne nereye kuruluyor) ayrı ayrı ve hepsi mimarinin parçası. İkincisi "akıl yürütmek" vurgusu: bir structure, sistemin performansı, güvenliği ya da değiştirilebilirliği hakkında bir şey söylemene yardım etmiyorsa mimari değildir.
ISO (International Organization for Standardization), IEC (International Electrotechnical Commission) ve IEEE (Institute of Electrical and Electronics Engineers) ortak architecture description standardı ISO/IEC/IEEE 42010'da daha soyut konuşur: bir sistemin, ortamı içindeki temel kavramları ya da property'leri ve onun hayata geçirilmesine ve evrimine yön veren ilkeler. Buradaki kritik kelime "evrim": mimari bir kez çizilip bırakılan bir şey değil, sistemin nasıl değişeceğini de yönetiyor.
Üçüncüsü, Martin Fowler'ın 2003'te IEEE Software'de yayımlanan "Who Needs an Architect?" yazısından. Fowler orada Ralph Johnson'ın (GoF (Gang of Four: Gamma, Helm, Johnson ve Vlissides'in 1994 tarihli Design Patterns kitabı) yazarlarından biri) bir e-posta listesindeki itirazını aktarır. Johnson'a göre "sistemin en üst seviye kavramı" diye bir şey yok; müşterinin kavramı başka, developer'ınki başka. Mimari, projedeki uzman developer'ların sistem tasarımı hakkındaki ortak anlayışıdır ve bir component'i önemli yapan şey uzmanların onu önemli saymasıdır. Johnson ikinci bir tanım daha önerir: proje başında doğru yapabilmeyi dilediğin kararlar. Fowler bunu bir adım öteye taşır: insanlar bu kararları neden erken doğru yapmak ister? Çünkü değiştirmesi zor olduklarını düşünürler. Mimari böylece "değiştirmesi zor algılanan şeyler" olur. Johnson'ın özeti ise yıllardır alıntılanıyor: "Architecture is about the important stuff. Whatever that is."
Üç tanımın kesişimi pratik bir test veriyor: bir karar, sistemin önemli bir property'sini (performans, güvenlik, değiştirilebilirlik) belirliyorsa ve geri almak pahalıysa mimaridir. Açılıştaki sipariş numarası tam olarak böyleydi.
Architecture ile Design: Bir Spectrum
"Bu mimari mi, design mı?" sorusunun çoğu zaman evet/hayır cevabı yoktur. Mark Richards ve Neal Ford'un Fundamentals of Software Architecture kitabı (2. baskı, O'Reilly, 2025; bundan sonra FoSA) ikisini bir spectrum'un iki ucu olarak anlatır ve bir kararın spectrum'da nerede durduğunu üç soruyla bulmayı önerir: karar stratejik mi taktik mi (ne kadar düşünme ve planlama istiyor, kaç kişiyi ilgilendiriyor), geri almak ne kadar emek ister ve içerdiği trade-off ne kadar büyük.

Soldaki method adı tamamen taktik: tek kişi karar verir, geri almak saniyeler sürer. Repository kullanıp kullanmamak ortaya yakın; ASP.NET Core Web API (Application Programming Interface) serisinin "Katmanlı Yapı: Service Katmanı ve Repository Tartışması" yazısında ve Entity Framework Core (EF Core) serisinin "EF Core ve Mimari" yazısında gördüğümüz gibi, bir ekipte konvansiyon haline gelince değiştirmesi zorlaşır. Sipariş e-postasının senkron mu queue üzerinden mi gideceği ise sistemin hatalara nasıl tepki verdiğini değiştirir; bu serinin dördüncü yazısı tamamen bu karara ayrılmış. En sağda veritabanı motoru ve tek deployment mı birden fazla servis mi sorusu var: bunları geri almak aylar sürer.
Bu bakışın iki pratik sonucu var. Birincisi, "architecture = klasör yapısı" yanılgısından kurtarıyor. Controllers, Services, Repositories klasörleri bir mimari değil, en fazla bir mimari kararın izidir; asıl soru o klasörlerin birbirine nasıl bağımlı olduğu ve bu bağımlılıkların neyi kolay, neyi zor hale getirdiğidir. İkincisi, bir kararın yeri sabit değil. Fowler aynı yazıda, veritabanı şemasını kolayca değiştirmeyi sağlayan migration araçlarının şemayı "mimari olmaktan çıkardığını" anlatır. Architect'in en değerli işlerinden biri, geri alınamazlığı azaltarak kararları spectrum'da sola çekmektir. Açılıştaki örnekte bunun karşılığı, müşteriye açılan sipariş numarasını en baştan veritabanı anahtarından ayırmak olurdu.
.NET'ten Bir Örnek: string Neden Hâlâ UTF-16?
.NET'in kendisi bu ayrımın canlı bir örneği. 2002'de System.String, UTF-16 (Unicode Transformation Format, 16-bit) code unit'lerinden oluşan immutable bir tip olarak tasarlandı. Bugün web'in büyük kısmı UTF-8 konuşuyor ve UTF-16 çoğu metinde iki kat bellek demek:
using System.Text;
string title = "Les Misérables";
ReadOnlySpan<byte> utf8Title = "Les Misérables"u8;
Console.WriteLine($"string: {title.Length} UTF-16 code units, {title.Length * sizeof(char)} bytes");
Console.WriteLine($"u8 literal: {utf8Title.Length} bytes");
Console.WriteLine($"Encoding.UTF8: {Encoding.UTF8.GetByteCount(title)} bytes");
string: 14 UTF-16 code units, 28 bytes
u8 literal: 15 bytes
Encoding.UTF8: 15 bytes
Buna rağmen string'in iç temsili değişmedi ve değişmeyecek. string.Length'in UTF-16 code unit sayısı döndürdüğüne, char'ın 16 bit olduğuna, fixed (char* p = s) ile belleğe doğrudan erişilebileceğine güvenen milyonlarca satır kod var. Karar mimari, çünkü bütün ekosisteme yayılmış. .NET ekibi temsili değiştirmek yerine yanına yeni yapılar ekledi: Span<byte> tabanlı UTF-8 API'leri, System.Text.Json'un baştan UTF-8 üzerine kurulması ve C# 11 ile gelen u8 literal'leri. Bu, mimari kararlarla yaşamanın tipik yolu: kararı geri alamıyorsan, etkisini sınırlayan yeni bir yol açarsın.
Mimarinin Yasaları
FoSA, mimarinin "yasaları" dediği üç gözlemle açılır. İlki: yazılım mimarisinde her şey bir trade-off'tur. Kitabın buna eklediği not da önemli: trade-off'u olmayan bir şey bulduğunu düşünüyorsan, büyük ihtimalle henüz trade-off'u görmemişsindir. Sipariş e-postasını queue'ya almak checkout'u hızlandırır ama e-postanın kaybolma ihtimalini ve sistemin karmaşıklığını artırır. Bunu dördüncü yazıda gerçek ölçümlerle göreceğiz.
İkincisi: neden, nasıldan daha önemlidir. Bir sistemin nasıl kurulduğunu koda bakarak her zaman çıkarabilirsin; neden öyle kurulduğunu çıkaramazsın. Üç yıl sonra projeye katılan biri "sipariş numarası neden int?" diye sorduğunda cevap kodda yoktur. Kararların gerekçesini yazmanın standart yolu olan ADR'leri (Architecture Decision Record) ilerideki Architecture Decision'lar serisinde ayrıntılı işleyeceğiz.
Üçüncüsü ikinci baskıda eklendi: çoğu mimari karar ikili değildir, iki uç arasında bir spectrum'da durur. "Monolith mi microservices mi" diye sorulur, ama arada modular monolith, service-based architecture ve yalnızca bir parçayı ayırmak gibi bir sürü ara nokta vardır. Bu serinin altıncı ve yedinci yazıları bu ara noktaların haritasını çıkaracak.
Architect Ne Yapar?
FoSA bir architect'ten sekiz şey bekler: mimari kararları vermek, mimariyi ve teknoloji ortamını sürekli analiz etmek, güncel trendleri takip etmek, kararlara uyulduğundan emin olmak, farklı teknolojilere maruz kalmış olmak, iş domain'ini bilmek, insan ilişkilerinde güçlü olmak ve kurumun politikasında yol bulabilmek. Listenin yarısının teknik olmadığına dikkat et.
Fowler aynı yazıda iki tür architect'i karşılaştırır. Architectus Reloadus (adı Matrix Reloaded filmindeki Architect karakterinden) bütün önemli kararları tek başına verir; gerekçesi sistemin kavramsal bütünlüğünü korumak ya da ekibe güvenmemektir. Architectus Oryzus ise projenin içindedir: sabah bir developer'la pair programming yapıp ortak bir locking kodunu çıkarır, öğleden sonra requirement toplantısında ürün ekibine bir fikrin teknik bedelini anlatır. Fowler'a göre bu ikinci tipin en önemli işi ekibi yetiştirmektir ve bir architect'in değeri, verdiği karar sayısıyla ters orantılıdır. Ne kadar çok karar ekibe güvenle bırakılabiliyorsa, architect o kadar iyi iş çıkarmış demektir.
İlk tipin sektördeki adı ivory tower architect anti-pattern'idir: koddan kopmuş, diyagram çizen ama sonuçlarını yaşamayan kişi. Belirtileri tanıdıktır: kararlar gerekçesiz gelir, ekip onları sessizce deler, architect'in çizdiği diyagram ile production'daki sistem birbirinden uzaklaşır. Pratikteki panzehiri basit: architect kod yazmaya devam eder, ama kritik yoldaki işleri değil. FoSA'nın önerisi, ekibi bekletmeyecek işleri seçmek: proof of concept'ler, teknik borç ve bug fix işleri, küçük otomasyon araçları ve fitness function'lar (mimariyi otomatik denetleyen testler), code review'lar. Fitness function'ları bu serinin son yazısında göreceğiz.
Bir not daha: "architect" bir unvan olmak zorunda değil. Üç kişilik bir ekipte mimari kararları ekibin kıdemlisi verir, bazen hepsi birlikte verir. Bu seri, kartvizitinde architect yazmayan ama sipariş numarasının tipini seçmek zorunda kalan herkes için.
Serinin Örneği: Bookstore Büyüyor
Bu kategori boyunca tek bir sistem üzerinden gideceğiz: sitenin C#, EF Core, Dapper, OWASP ve Yazılım Tasarımı serilerinden tanıdığın Bookstore. Kitap satan bir online kitapçı; Book, Author, Customer, Order ve OrderItem aynı tipler, namespace'ler yine Bookstore.*. Değişen bakış açısı: şimdiye kadar tek bir class'a ya da tek bir sorguya bakıyorduk, bu seride sistemin bütününe bakacağız.
Bugünkü hali: tek bir ASP.NET Core uygulaması ve tek bir SQL Server veritabanı, haftada bir deploy eden üç developer ve yarı zamanlı bir ops arkadaşı. Kullanıcılar üç grupta. Müşteriler web sitesinden kitap arar, sipariş verir, siparişini takip eder. İç kullanıcılar katalog editörü, depo görevlisi ve müşteri hizmetleri. Dış sistemler ödeme sağlayıcısı, e-posta sağlayıcısı ve kargo firmasının API'si. Sayılar da belli: yaklaşık 40.000 kitap, günde 15.000 tekil ziyaretçi ve 800 sipariş; okul açılışı ve yıl sonu kampanyalarında trafik on katına çıkıyor, zirvede dakikada 120 sipariş geliyor. On sekiz ay hedefi: sipariş hacmini üçe katlamak, ikinci bir ülkeye açılmak ve aynı API'yi kullanan bir mobil uygulama çıkarmak.
Fonksiyonel olarak sistem altı iş alanına ayrılıyor. Kategori boyunca bu adları kullanacağız:
| Alan | Namespace | Sorumluluk |
|---|---|---|
| Catalog | Bookstore.Catalog | Kitap, yazar, kategori, arama ve fiyat gösterimi |
| Ordering | Bookstore.Ordering | Sepetten siparişe, checkout ve sipariş durumu |
| Inventory | Bookstore.Inventory | Stok, rezervasyon ve depo girişleri |
| Payment | Bookstore.Payments | Ödeme sağlayıcısıyla authorize, capture ve iade |
| Shipping | Bookstore.Shipping | Kargo etiketi ve takip numarası |
| Notification | Bookstore.Notifications | Sipariş onayı ve kargo e-postası, SMS (Short Message Service) |
Bu tablo bir mimari değil, bir başlangıç noktası. Bu alanların aynı process'te mi yaşayacağı, birbirleriyle senkron mu konuşacağı, aynı veritabanını mı paylaşacağı, serinin geri kalanında vereceğimiz kararlar. Sıradaki adım, "iyi bir sistem" derken neyi kastettiğimizi netleştirmek: bir sonraki yazının konusu olan quality attribute'lar.
Sık Düşülen Tuzaklar
Mimariyi klasör yapısı ya da proje template'i sanmak. Bir Clean Architecture template'ini kopyalamak sana bir klasör düzeni verir, ama hangi characteristic'leri neden önceliklendirdiğini söylemez. Template'i seçmek de bir karardır ve gerekçesi yazılmalıdır.
Mimari kararları yalnız büyük kararlar sanmak. Sipariş numarasının tipi, bir JSON (JavaScript Object Notation) alanının adı, public bir URL'in (Uniform Resource Locator) şekli küçük görünür; ama sistem sınırını geçtikleri anda geri alınması pahalı hale gelirler. Sınırı geçen her şeyi mimari bir karar gibi düşün.
Her kararı mimari saymak. Tersi de bir tuzak: her method adı için toplantı yapılan ekiplerde iş durur. Bir kararın mimari olup olmadığını geri alma maliyetine bakarak ayır; ucuzsa ekibe bırak.
Architect'in kod yazmayı bırakması. Koddan kopan architect, kararlarının bedelini görmez ve ekip kararları sessizce delmeye başlar. Kritik yolda olmayan ama sistemi tanımanı sağlayan işlerle koda yakın kal.
Kararın "neden"ini yazmamak. Bugün herkes biliyor, altı ay sonra kimse hatırlamıyor. En azından tek paragraflık bir not: ne seçildi, alternatifler neydi, hangi trade-off kabul edildi.
Küçük Bir Deneme
★☆☆ Üzerinde çalıştığın projede son bir ayda verilen beş kararı listele ve her birini spectrum'a yerleştir: kim karar verdi, geri almak kaç gün sürer, hangi trade-off kabul edildi? En sağdaki kararın gerekçesi bir yerde yazılı mı?
★★☆ Açılıştaki örneği, müşteriye açılan sipariş numarasını veritabanı anahtarından ayıracak şekilde yeniden tasarla: Order'a ayrı bir OrderNumber ekle, link ve ödeme referansı yalnız onu kullansın. Sonra Id'nin tipini değiştir ve derleyicinin bu sefer kaç hata verdiğini, dışarıya açılan hiçbir şeyin değişmediğini gör.
★★★ Kullandığın bir .NET API'sinde, geriye uyumluluk yüzünden değiştirilemeyip yanına yeni bir yol açılmış bir karar bul (ipucu: DateTime ve DateTimeOffset, HttpWebRequest ve HttpClient, BinaryFormatter). Eski karar neden mimariydi, yeni yol etkisini nasıl sınırlıyor?
Mimarinin "önemli şeyler" hakkında olduğunu söyledik. Bir sonraki yazıda "önemli"yi somutlaştıracağız: ISO/IEC 25010:2023'ün quality attribute'ları ile Bookstore için hangi architecture characteristic'lerin gerçekten kritik olduğunu belirleyeceğiz.