Clean Architecture ve SOLID İlkeleri

Yazılım mühendisliğinde Clean Architecture (Temiz Mimari) ve SOLID prensipleri nedir? Bağımlılıkların tersine çevrilmesi, test edilebilirlik ve teknik borç.

Yozel

12 dakika

Clean Architecture ve SOLID İlkeleri
İçindekiler (23)

Modern kurumsal yazılım projelerinin en büyük düşmanı ne yetersiz sunucu donanımıdır ne de yanlış programlama dili seçimidir; projeleri zamanla geliştirilemez, test edilemez ve ölçeklenemez bir kaosa sürükleyen asıl tehdit kontrolsüz artan karmaşıklık (spaghetti code) ve teknik borçtur (technical debt). Bir yazılım projesinin ilk aylarında yeni bir özellik eklemek yalnızca birkaç saat sürerken, ikinci yılın sonunda aynı büyüklükteki bir özelliğin haftalar sürmesi ve canlı ortamda (production) beklenmedik hatalara yol açması, mimari sınırların ihlal edildiğinin en somut göstergesidir. İşte Robert C. Martin (Uncle Bob) tarafından kavramsallaştırılan Clean Architecture (Temiz Mimari) ve nesne yönelimli tasarımın temel taşlarını oluşturan SOLID İlkeleri, yazılım sistemlerini dış dünyadaki veritabanı, kullanıcı arayüzü (UI) veya üçüncü parti kütüphane bağımlılıklarından tamamen izole ederek uzun ömürlü ve esnek tutmak için geliştirilmiş mühendislik disiplinleridir.

Günümüzde Uygulama ve Yazılım ekosisteminde mikroservislerden büyük ölçekli monolitik kurumsal sistemlere kadar her başarılı ürün, iş kurallarını (business logic) altyapı detaylarından ayırma felsefesine dayanır. Bu kapsamlı teknik rehberde; Clean Architecture katmanlarını, Hexagonal Architecture (Ports & Adapters) ve Onion Architecture ile olan sinerjisini, Bağımlılık Kuralını (The Dependency Rule) ve SOLID prensiplerinin gerçek dünya senaryolarında nasıl uygulandığını tüm mühendislik derinliğiyle inceliyoruz.

1. Yazılım Mimarisi Neden Çöker? Geleneksel Katmanlı Mimarinin Tuzakları

Yıllar boyunca yazılım dünyasında standart kabul edilen geleneksel 3 katmanlı mimari (Presentation / UI Katmanı → Business Logic Katmanı → Data Access / Veritabanı Katmanı), bağımlılık oklarının yukarıdan aşağıya doğru akmasına dayanıyordu. Bu geleneksel modelde İş Mantığı (Business Logic) katmanı, doğrudan Veri Erişim (Data Access) katmanına bağımlıdır. İlk bakışta mantıklı görünen bu yapı, proje büyüdükçe ölümcül bir mimari kusur yaratır: İş kurallarınız, veritabanı teknolojinizin ve ORM (Object-Relational Mapping) kütüphanenizin esiri haline gelir.

Örneğin SQL tabanlı bir ORM (Hibernate, Entity Framework veya Prisma) nesnelerini doğrudan iş kurallarınızın içinde kullandığınızda, veritabanı şemasındaki tek bir sütun değişikliği tüm ticari hesaplama algoritmalarınızı bozmaya başlar. Daha da kötüsü, basit bir indirim hesaplama kuralını birim testine (unit test) tabi tutmak istediğinizde gerçek bir veritabanı bağlantısı ayağa kaldırmak zorunda kalırsınız. Clean Architecture, bu geleneksel bağımlılık yönünü tersine çevirerek (Dependency Inversion) veritabanını sistemin merkezi olmaktan çıkarır ve onu yalnızca değiştirilebilir bir "detay" (plugin) seviyesine indirger.

2. Clean Architecture (Temiz Mimari) Nedir ve Temel Felsefesi Neye Dayanır?

Clean Architecture, bir yazılım sistemini iç içe geçmiş eşmerkezli halkalar (concentric circles) şeklinde organize eden ve sistemin en değerli varlığı olan iş kurallarını en içteki çekirdeğe yerleştiren mimari yaklaşımdır. Bu mimarinin kırılmaz anayasası Bağımlılık Kuralıdır (The Dependency Rule):

Bağımlılık Kuralı (The Dependency Rule): Kaynak kodu bağımlılıkları yalnızca ve yalnızca dıştan içe doğru işaret edebilir. İç halkadaki hiçbir sınıf, fonksiyon veya değişken; dış halkadaki bir veritabanını, web çatısını (framework), UI bileşenini veya harici API servisini tanıyamaz, import edemez ve adını dahi bilemez.

Bu kural sayesinde, en içteki çekirdek (Domain), uygulamanın bir web uygulaması mı, bir komut satırı aracı (CLI) mı yoksa Composable Commerce (Birleştirilebilir Ticaret) altyapısında çalışan bir mikroservis mi olduğundan tamamen habersizdir. Veritabanınızı PostgreSQL'den MongoDB'ye geçirmek veya REST API yerine GraphQL/gRPC kullanmak istediğinizde, en içteki iş kurallarında tek bir satır kod bile değiştirmeniz gerekmez.

Clean Architecture Bağımlılık Kuralı ve Hexagonal Mimari Ports and Adapters Yapısı
Clean Architecture ve Hexagonal (Ports & Adapters) mimaride tüm bağımlılık okları dış dünyadaki altyapı detaylarından içteki saf iş kurallarına (Domain) doğru yönelir.

3. Clean Architecture'ın 4 Ana Katmanı

Robert C. Martin'in çizdiği eşmerkezli halkalar modelinde içten dışa doğru dört temel katman bulunur. Her katmanın sorumluluğu keskin sınırlarla ayrılmıştır:

3.1. Entities (Varlıklar / Kurumsal İş Kuralları - En İç Halka)

Sistemin en saf, en değişmez ve en yüksek değerli katmanıdır. Bir Entity, kurumsal iş kurallarını (Enterprise Business Rules) kapsülleyen bir nesne veya veri yapılarını işleyen saf fonksiyonlar bütünüdür. Dikkat edilmesi gereken en kritik nokta şudur: Buradaki Entity, bir ORM kütüphanesinin veritabanı tablosunu temsil eden @Entity veya @Table anotasyonlu sınıfı değildir. İçerisinde hiçbir framework kütüphanesi barındırmayan, tamamen saf programlama dili (Plain Old Java Object - POJO, POCO veya saf TypeScript/Go struct) ile yazılmış iş mantığıdır. Örneğin bir bankacılık sisteminde kredi faiz hesaplama formülü veya kredi risk skoru doğrulaması doğrudan bu katmanda yaşar.

3.2. Use Cases / Application Layer (Uygulama İş Kuralları - İkinci Halka)

Sistemin gerçekleştirdiği spesifik uygulama senaryolarını (Application-Specific Business Rules) barındırır. CreateOrderUseCase, ApproveLoanInteractor veya TransferFundsHandler gibi sınıflar bu katmandadır. Use Case katmanı, verinin Entities katmanına nasıl gireceğini ve oradan nasıl çıkacağını orkestre eder. Veritabanına kayıt yapılması gerektiğinde doğrudan SQL çalıştırmaz; bunun yerine kendi katmanında tanımladığı bir OrderRepositoryInterface (Port) arayüzünü çağırır. Bu arayüzün nasıl doldurulacağı (implement edileceği) dış katmanların sorunudur.

3.3. Interface Adapters (Arayüz Adaptörleri - Üçüncü Halka)

Bu katman bir tercüman (çevirmen) görevi görür. Veriyi, Use Case ve Entities katmanları için en uygun formattan; veritabanı veya web sunucusu gibi harici sistemler için en uygun formata dönüştürür. MVC/MVP/MVVM mimarilerindeki Controller'lar, Presenter'lar, Gateway'ler ve veritabanı Repository implementasyonları bu halkada yer alır. Dış dünyadan gelen bir HTTP JSON isteği burada karşılanır, bir DTO (Data Transfer Object) nesnesine çevrilerek Use Case'e iletilir.

3.4. Frameworks & Drivers (Çatılar ve Sürücüler - En Dış Halka)

En dışta yer alan bu katman, tüm teknik detayların ve üçüncü parti araçların bulunduğu yerdir: Web framework'leri (Spring Boot, ASP.NET Core, NestJS, FastAPI), veritabanı motorları (PostgreSQL, Redis), mesaj kuyrukları, UI çatıları (React, Vue) ve harici ödeme geçitleri (Stripe, Iyzico). Clean Architecture felsefesine göre bu katman "yapıştırıcı kod" (glue code) dışında fazla mantık içermemelidir. Dış dünyadaki araçlar ne kadar popüler olursa olsun mimari açıdan değiştirilebilir birer eklentidir.

4. Hexagonal Architecture (Ports & Adapters) ve Onion Architecture İlişkisi

Yazılım mühendisliği literatüründe sıklıkla karşılaşılan Hexagonal Architecture (Altıgen Mimari / Ports and Adapters - Alistair Cockburn, 2005) ve Onion Architecture (Soğan Mimarisi - Jeffrey Palermo, 2008), aslında Clean Architecture ile aynı temel hedefi paylaşan kardeş mimarilerdir:

  • Ports (Portlar): Uygulama çekirdeğinin dış dünya ile iletişim kurmak için tanımladığı soyut arayüzlerdir (Interfaces). İki tür port vardır: Uygulamayı tetikleyen Inbound / Driving Ports (örneğin Use Case arayüzleri) ve uygulamanın dış dünyadan talep ettiği Outbound / Driven Ports (örneğin PaymentGatewayPort veya UserRepositoryPort).
  • Adapters (Adaptörler): Bu soyut portları gerçek dünya teknolojileriyle buluşturan somut sınıflardır. Örneğin StripePaymentAdapter sınıfı PaymentGatewayPort arayüzünü implemente eder. Yarın Stripe yerine farklı bir ödeme sistemine geçildiğinde yalnızca yeni bir adaptör yazılır; uygulama çekirdeği değişmez.

Özellikle eski ve karmaşık kurumsal yazılımları modernize ederken veya yapay zeka destekli tersine mühendislik (Reverse Engineering Architecture) süreçleriyle monolitik sistemleri parçalarken Hexagonal ve Clean Architecture kalıpları kurtarıcı rol oynar.

5. SOLID İlkeleri: Temiz Mimarinin Tuğlaları

Clean Architecture sistemin makro ölçekteki (bileşen ve katman düzeyindeki) yapısını düzenlerken, SOLID ilkeleri mikro ölçekte (sınıf, modül ve fonksiyon düzeyinde) temiz, esnek ve bakımı kolay kod yazmanın 5 temel kuralını belirler. SOLID olmadan Clean Architecture kurgulamak imkansızdır:

SOLID Prensipleri ve Yazılımda Teknik Borç Azaltma Refactoring Süreci
SOLID prensipleri sınıf düzeyinde bağımlılıkları yöneterek teknik borcun birikmesini önler ve birim test (unit test) yazımını kolaylaştırır.

5.1. S — Single Responsibility Principle (Tek Sorumluluk İlkesi - SRP)

"Bir sınıfın veya modülün değişmek için yalnızca tek bir nedeni (tek bir aktörü) olmalıdır." Çoğu geliştirici SRP'yi "her fonksiyon tek bir iş yapmalı" şeklinde dar yorumlar; oysa Uncle Bob bu ilkeyi kurumsal aktörler üzerinden tanımlar. Örneğin bir Employee (Çalışan) sınıfı içinde hem muhasebe departmanını ilgilendiren calculatePay() (maaş hesaplama), hem insan kaynaklarını ilgilendiren reportHours() (mesai raporlama), hem de DBA'leri ilgilendiren save() (veritabanı kaydı) metotları bulunuyorsa SRP ihlal edilmiş demektir. Muhasebe departmanının vergi dilimi değişikliği talebi yüzünden yapılan bir kod düzenlemesi, İK departmanının raporlama algoritmasını yanlışlıkla bozabilir. Bu sorumluluklar ayrı sınıflara (PayCalculator, HourReporter, EmployeeRepository) bölünmelidir.

5.2. O — Open/Closed Principle (Açık/Kapalı İlkesi - OCP)

"Yazılım varlıkları (sınıflar, modüller, fonksiyonlar) gelişime (extension) AÇIK, ancak değişime (modification) KAPALI olmalıdır." Mevcut, çalışan ve test edilmiş bir koda dokunmadan sisteme yeni davranışlar ekleyebilmeliyiz. Örneğin kargo ücreti hesaplayan bir sınıfta if (type == 'STANDARD') ... else if (type == 'EXPRESS') ... şeklinde uzayan koşul blokları varsa, her yeni kargo firması eklendiğinde mevcut sınıfı açıp değiştirmek zorunda kalırsınız. Bunun yerine bir ShippingStrategy arayüzü tanımlayarak her yeni kargo tipini ayrı bir sınıf olarak sisteme dahil etmek OCP'nin tam karşılığıdır.

5.3. L — Liskov Substitution Principle (Liskov Yerine Geçme İlkesi - LSP)

Barbara Liskov tarafından ortaya konan bu ilkeye göre: "Türetilmiş sınıflar (alt sınıflar), türedikleri üst sınıfların (base class / interface) yerine, programın doğruluğunu ve davranışını bozmadan doğrudan geçebilmelidir." Klasik "Kare bir Dikdörtgendir" matematiksel önermesi, nesne yönelimli programlamada LSP ihlaline yol açar; çünkü Rectangle sınıfından türeyen Square sınıfında genişliği değiştirdiğinizde yükseklik de değişir ve üst sınıfın beklediği alan hesaplama sözleşmesi (contract) kırılır. Eğer bir alt sınıfta throw new NotImplementedException() fırlatıyorsanız, LSP'yi ihlal ediyorsunuz demektir.

5.4. I — Interface Segregation Principle (Arayüz Ayrımı İlkesi - ISP)

"İstemciler (sınıflar), kullanmadıkları metotları içeren şişman arayüzlere (fat interfaces) bağımlı olmaya zorlanmamalıdır." Tek bir devasa IMachine arayüzü içine print(), scan() ve fax() metotlarını koyarsanız, yalnızca baskı yapabilen basit bir yazıcı sınıfı scan() ve fax() metotlarını boş bırakmak zorunda kalır. Bunun yerine arayüzleri IPrinter, IScanner ve IFax olarak küçük, odaklı parçalara ayırmak hem esnekliği artırır hem de gereksiz derleme bağımlılıklarını önler.

5.5. D — Dependency Inversion Principle (Bağımlılıkların Tersine Çevrilmesi İlkesi - DIP)

Clean Architecture'ın kalbini oluşturan ilkedir:
1. Yüksek seviyeli modüller (iş kuralları), düşük seviyeli modüllere (veritabanı, dosya sistemi, ağ) bağımlı olmamalıdır. Her ikisi de soyutlamalara (interfaces / abstractions) bağımlı olmalıdır.
2. Soyutlamalar detaylara bağımlı olmamalıdır. Detaylar soyutlamalara bağımlı olmalıdır.

Bu ilke sayesinde çalışma zamanındaki (runtime) kontrol akışı ile kaynak kodundaki (compile-time) bağımlılık yönü birbirinden ayrılır. İş kuralı katmanı kendi ihtiyaç duyduğu arayüzü tanımlar; altyapı katmanı ise bu arayüzü implemente ederek bağımlılık okunu tersine çevirir. Tıpkı modern Web3 ve merkezsiz web mimarilerinde akıllı kontrat arayüzlerinin (ABI) ön yüz uygulamalarından bağımsız çalışması gibi, DIP de kurumsal sistemlerde tam bağımsızlık sağlar.

6. Geleneksel Katmanlı Mimari vs. Clean Architecture Karşılaştırması

Bir projede geleneksel yaklaşım ile Clean Architecture arasındaki farkları mühendislik metrikleri üzerinden aşağıdaki tabloda net biçimde görebilirsiniz:

Değerlendirme Kriteri Geleneksel 3 Katmanlı Mimari (N-Tier) Clean / Hexagonal Architecture
Merkezdeki Bileşen Veritabanı (Data-Centric) ve Tablo Şemaları İş Kuralları ve Domain Modeli (Domain-Centric)
Bağımlılık Yönü UI → Business → Data Access (Yukarıdan Aşağıya) Tüm dış katmanlar → Use Cases → Entities (Dıştan İçe)
Birim Test (Unit Test) Hızı Yavaş (Veritabanı veya ağır mock kurulumu gerektirir) Milisaniyeler içinde (Saf bellek içi nesnelerle binlerce test)
Framework / DB Değişimi Çok Yüksek Maliyetli (İş kuralları yeniden yazılır) Düşük Maliyetli (Yalnızca yeni bir Adapter sınıfı yazılır)
Başlangıç Karmaşıklığı (Boilerplate) Düşük (Hızlı prototipleme ve CRUD için kolay) Orta-Yüksek (DTO, Mapper, Port ve Adapter tanımları gerekir)
İdeal Kullanım Senaryosu Basit CRUD uygulamaları, kısa ömürlü MVP projeleri Karmaşık iş kurallarına sahip, uzun ömürlü kurumsal sistemler

7. Pratik Uygulama Mimarisi: Klasör Yapısı ve Paketleme Stratejileri (Package by Layer vs. Package by Feature)

Clean Architecture'ı teoriden pratiğe dökerken yazılım ekiplerinin en sık tartıştığı konulardan biri de kaynak kodun dizin (klasör) yapısının nasıl organize edileceğidir. Geleneksel projelerde tüm dosyalar teknik katmanlarına göre gruplanır (controllers/, services/, repositories/, models/). Package by Layer (Katmana Göre Paketleme) adı verilen bu yöntem, küçük projelerde anlaşılır olsa da sistem büyüdüğünde Robert C. Martin'in Screaming Architecture (Haykıran Mimari) ilkesiyle çelişir. Screaming Architecture ilkesine göre bir yazılım projesinin klasör yapısına ilk kez bakan bir mühendis, "Bu bir Spring Boot veya NestJS projesi" demek yerine "Bu bir Bankacılık Kredi ve Ödeme Sistemi" diyebilmelidir; yani mimari, kullanılan framework'ü değil iş alanını haykırmalıdır.

Bu nedenle modern Clean Architecture uygulamalarında Package by Feature (Özelliğe / İş Yeteneğine Göre Paketleme) veya Vertical Slice (Dikey Dilim) organizasyonu tercih edilir. Örneğin modules/orders/, modules/payments/ ve modules/inventory/ şeklinde iş birimleri ayrılır ve her iş biriminin kendi içinde domain/, application/ (use cases), infrastructure/ ve presentation/ alt dizinleri bulunur. Bu yapı şu kritik mühendislik avantajlarını beraberinde getirir:

  • Yüksek Kohezyon ve Düşük Bağımlılık (High Cohesion, Low Coupling): Bir sipariş özelliği üzerinde çalışan geliştirici, birbirinden uzak 5 farklı klasör arasında gezinmek yerine yalnızca orders modülü içinde kalarak tüm değişikliği tamamlar.
  • Mimari Sınırların Otomatik Korunması (ArchUnit / Dependency Cruiser): CI/CD boru hattına eklenen mimari test araçları (Java için ArchUnit, .NET için NetArchTest, TypeScript için Dependency Cruiser) sayesinde domain paketinin yanlışlıkla infrastructure paketinden bir sınıf import etmesi derleme aşamasında (build-time) otomatik olarak engellenir.
  • Mikroservislere Sıfır Sürtünmeli Geçiş: Dikey dilimler halinde izole edilmiş bir modül, iş hacmi arttığında kendi klasöründen kopyalanarak bağımsız bir mikroservis olarak dakikalar içinde dışarıya taşınabilir.

8. Ne Zaman Clean Architecture Kullanılmamalıdır? (Over-Engineering Tuzağı)

Her güçlü mühendislik aracında olduğu gibi Clean Architecture'ı da yanlış yerde kullanmak aşırı mühendislik (over-engineering) maliyetine yol açar. Eğer geliştirdiğiniz proje yalnızca veritabanındaki tabloları ekranda listeleyen ve güncelleyen basit bir CRUD (Create, Read, Update, Delete) uygulamasıysa veya içinde karmaşık ticari kurallar barındırmayan bir iç yönetim paneliyse, 4 ayrı katman ve onlarca DTO/Mapper sınıfı oluşturmak zaman kaybıdır. Hatta bu tür standart veri giriş-çıkış senaryolarında No-Code / Low-Code uygulama geliştirme platformları kullanmak çok daha yüksek yatırım getirisi (ROI) sağlar. Clean Architecture; finans, e-ticaret, sigortacılık, sağlık, ERP ve lojistik gibi iş kurallarının sürekli evrimleştiği ve yıllarca yaşayacak çekirdek sistemlerde gerçek gücünü gösterir.

Sıkça Sorulan Sorular (SSS)

1. Clean Architecture ile SOLID ilkeleri arasındaki temel fark nedir?

SOLID ilkeleri sınıf, nesne ve fonksiyon seviyesinde (mikro mimari) temiz kod tasarımını yönlendiren 5 temel prensiptir. Clean Architecture ise SOLID ilkelerinin (özellikle Bağımlılıkların Tersine Çevrilmesi - DIP ilkesinin) tüm sistem bileşenleri, modüller ve katmanlar düzeyinde (makro mimari) uygulanmış halidir.

2. Clean Architecture kullanmak uygulama performansını düşürür mü?

Katmanlar arasında veri taşırken yapılan nesne dönüşümleri (DTO → Domain Entity → Persistence Model mapping) işlemci ve bellekte nanosaniyelik bir ek yük oluşturur. Ancak modern JVM, .NET CLR veya V8 motorlarında bu maliyet ağ gecikmesi (network latency) ve disk I/O süreleri yanında tamamen ihmal edilebilir düzeydedir (binde birin altında). Buna karşılık kazanılan bakım kolaylığı ve hatasızlık paha biçilemezdir.

3. Domain katmanında ORM anotasyonları (Entity Framework, Hibernate, TypeORM) kullanabilir miyim?

Saf Clean Architecture kurallarına göre Domain katmanında hiçbir üçüncü parti kütüphane veya ORM anotasyonu bulunmamalıdır; çünkü bu durum Domain modelinizi veritabanı teknolojisine bağımlı hale getirir. Ancak orta ölçekli projelerde pragmatik bir yaklaşım olarak, davranışsal kuralları bozmamak şartıyla hafif anotasyonlara izin verildiği hibrit modeller de görülmektedir.

4. Clean Architecture yalnızca mikroservisler için mi uygundur?

Hayır. Clean Architecture hem mikroservislerde hem de Modüler Monolit (Modular Monolith) mimarilerde mükemmel çalışır. Hatta bir projeye Clean Architecture ile modüler monolit olarak başlamak, ileride sistem büyüdüğünde ilgili modülleri sıfır kod değişikliğiyle bağımsız mikroservislere ayırmayı (extract etmeyi) son derece kolaylaştırır.

5. Dependency Injection (DI) ile Dependency Inversion Principle (DIP) aynı şey midir?

Hayır, sıklıkla karıştırılan iki farklı kavramdır. Dependency Inversion (DIP), yüksek seviyeli modüllerin soyutlamalara bağımlı olmasını söyleyen mimari bir tasarım ilkesidir. Dependency Injection (DI) ise bir sınıfın ihtiyaç duyduğu bağımlılıkların dışarıdan (Constructor veya Setter üzerinden) verilmesini sağlayan bir tasarım deseni ve tekniğidir. DIP hedefi, DI ise o hedefe ulaşmak için kullanılan araçlardan biridir.

Paylaş

Yozel

Codex haberleri en az iki bağımsız kaynakla doğrulanır; düzeltmeler yazının altında tarih ve saatle belirtilir. Yayın ilkelerimiz