Clean Architecture: Katmanlı Mimari Tasarımında Yapılan 5 Ölümcül Hata
Clean Architecture (ve Onion/Hexagonal) size muazzam bir esneklik ve test edilebilirlik vaat eder. Ancak bu vaadin gerçekleşmesi, mimari kurallara sıkı sıkıya bağlı kalmayı gerektirir. Ne yazık ki, çoğu proje bu kuralları "esneterek" aslında mimariyi tamamen çökerten hatalar yapar. İşte Clean Architecture uygulamalarında en sık yapılan 5 ölümcül hata ve bunlardan kaçınma yolları.
Hata 1: Bağımlılık Kuralını Tersine Çevirmek (Dependency Inversion İhlali)
❌ Hatalı Kod: Domain katmanı, Infrastructure katmanına doğrudan bağımlı hale getirilir.
csharp
// Domain Katmanı (YANLIŞ!)
public class Order
{
public void Save()
{
// Infrastructure'daki bir sınıfı doğrudan çağır!
var dbContext = new AppDbContext();
dbContext.Orders.Add(this);
dbContext.SaveChanges();
}
}
Sorun: Domain artık veritabanı detaylarını biliyor. Bu, Domain'in saf (POCO) yapısını bozar, test edilebilirliği öldürür ve teknoloji değişimini imkânsızlaştırır.
✅ Doğru Çözüm: Domain, hiçbir şey bilmez; sadece kendi kurallarını uygular. Veritabanı işlemleri Application katmanı tarafından çağrılan bir Repository interface'i üzerinden yapılır.
csharp
// Domain Katmanı (DOĞRU)
public class Order
{
// Sadece iş kuralları burada
public void AddItem(OrderItem item) { ... }
}
// Application Katmanı (Port)
public interface IOrderRepository
{
Task SaveAsync(Order order);
}
// Infrastructure Katmanı (Adapter)
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public async Task SaveAsync(Order order)
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
}
}
Altın Kural: Domain ve Application katmanları asla Infrastructure'a referans vermez. Bağımlılıklar her zaman içeriden dışarıya (inward) doğrudur.
Hata 2: Domain Katmanına Altyapı (Framework) Sızdırmak
❌ Hatalı Kod: Domain Entity'leri, veritabanı veya UI ile ilgili attribute'lerle süslenir.
csharp
// Domain Katmanı (YANLIŞ!)
using System.ComponentModel.DataAnnotations; // UI/DB ile ilgili!
using Newtonsoft.Json; // Serialization ile ilgili!
public class Order
{
[Key] // Entity Framework Core attribute'ü!
public int Id { get; set; }
[Required] // Validation attribute'ü!
[MaxLength(100)]
public string CustomerName { get; set; }
[JsonIgnore] // Serialization attribute'ü!
public string InternalNote { get; set; }
}
Sorun: Domain katmanı artık EF Core, Newtonsoft ve DataAnnotations'a bağımlı hale gelmiştir. Framework değişikliğinde (ör. EF Core'dan Dapper'a geçiş) Domain katmanı da değişmek zorunda kalır.
✅ Doğru Çözüm: Domain katmanı tamamen POCO (Plain Old CLR Object) olmalıdır. Hiçbir attribute, hiçbir framework referansı içermez.
csharp
// Domain Katmanı (DOĞRU)
public class Order
{
public int Id { get; private set; }
public string CustomerName { get; private set; }
public string InternalNote { get; private set; }
// Hiçbir attribute yok, sadece iş kuralları var.
}
Altın Kural: Domain projesinin .csproj dosyasını açın. Referanslar arasında Microsoft.EntityFrameworkCore, Newtonsoft.Json, System.ComponentModel.Annotations gibi hiçbir altyapı paketi olmamalıdır. Sadece System, System.Collections.Generic gibi temel namespace'ler.
Hata 3: Aşırı Katmanlaşma (Over-Engineering) ve Gereksiz Abstraction
❌ Hatalı Kod: Her işlem için gereksiz yere arayüzler ve katmanlar oluşturmak.
text
- Domain - IOrderService (interface) - Application - OrderService (implementation) - IOrderRepository (interface) - Infrastructure - OrderRepository (implementation) - IEmailService (interface) - Infrastructure.Shared - EmailService (implementation) - WebAPI - OrderController (sadece OrderService çağırır, başka iş yapmaz)
Sorun: Eğer projenizde yalnızca bir tür veritabanı kullanıyorsanız ve değiştirme ihtimaliniz yoksa, her aggregate için repository interface'i oluşturmak gereksiz bir karmaşıklıktır. Ayrıca, IOrderService gibi doğrudan controller'dan çağrılan bir interface, başka bir implementasyonu olmayacağı için tamamen gereksizdir.
✅ Doğru Çözüm: Sadece değişme potansiyeli olan veya testlerde mock'lanması gereken bağımlılıkları soyutlayın. Katman sayısını projenin büyüklüğüne göre ayarlayın.
-
Basit CRUD projesi: Domain, Application (CQRS ile), Infrastructure, WebAPI yeterlidir.
-
IOrderServicegibi tek implementasyonlu interfacelerden kaçının. MediatR kullanıyorsanız, Handler'lar zaten Interface gerektirmez.
Altın Kural: "Kullanmayacağın bir esneklik için kod yazma." (YAGNI - You Ain't Gonna Need It)
Hata 4: Application Katmanında Domain Kurallarını Çiğnemek (Anemic Domain)
❌ Hatalı Kod: Application katmanı, Domain Entity'lerine sadece veri taşıyıcı olarak davranır (getter/setter) ve tüm iş mantığını kendi içinde uygular.
csharp
// Application Katmanı (YANLIŞ!)
public class CreateOrderHandler
{
public async Task Handle(CreateOrderCommand command)
{
var order = new Order(); // Sadece veri tutucu
order.CustomerName = command.CustomerName;
order.Total = 0;
foreach (var item in command.Items)
{
var orderItem = new OrderItem();
orderItem.ProductName = item.ProductName;
orderItem.Quantity = item.Quantity;
orderItem.UnitPrice = item.UnitPrice;
order.Total += item.Quantity * item.UnitPrice; // İş kuralı Application'da!
}
await _orderRepository.SaveAsync(order);
}
}
Sorun: Domain katmanı, içinde hiçbir davranış (metot) barındırmayan kansız (Anemic) bir modele dönüşür. İş kuralları Application'a dağılır ve tekrar eder.
✅ Doğru Çözüm: Domain Entity'leri zengin (Rich) olmalıdır. Tüm iş kuralları Domain'de uygulanır; Application katmanı sadece bu kuralları çağırır ve sonucu kalıcı hale getirir.
csharp
// Domain Katmanı (DOĞRU)
public class Order
{
public void AddItem(string productName, int quantity, Money unitPrice)
{
// Tüm kurallar burada: ürün var mı, stok kontrolü, toplam hesaplama...
var newItem = new OrderItem(productName, quantity, unitPrice);
_items.Add(newItem);
RecalculateTotal(); // Private metot, domain içinde
}
}
// Application Katmanı (DOĞRU)
public class CreateOrderHandler
{
public async Task Handle(CreateOrderCommand command)
{
var order = new Order(command.CustomerName);
foreach (var item in command.Items)
{
order.AddItem(item.ProductName, item.Quantity, new Money(item.UnitPrice, "TRY")); // Domain'i çağır!
}
await _orderRepository.SaveAsync(order);
}
}
Altın Kural: Application katmanı, Domain'in nasıl çalıştığını değil, ne yapacağını (orchestration) organize eder.
Hata 5: Validasyon, Loglama ve Transaction'ı Yanlış Katmana Koymak
❌ Hatalı Kod: Validasyon, loglama ve transaction yönetimi, controller, domain veya veritabanı katmanları arasında dağılmıştır.
Sorun: Cross-cutting concern'ler (validasyon, log, transaction) her yere dağılınca kod tekrarı oluşur ve bakımı zorlaşır.
✅ Doğru Çözüm: Bu işler için MediatR Pipeline Behaviors (AOP) veya benzeri bir mekanizma kullanın.
csharp
// 1. Validasyon Behavior (Application katmanında)
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public async Task<TResponse> Handle(...)
{
// Tüm validasyonları çalıştır
if (failures.Any()) throw new ValidationException(failures);
return await next();
}
}
// 2. Transaction Behavior (Application katmanında)
public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly AppDbContext _context;
public async Task<TResponse> Handle(...)
{
using var tx = await _context.Database.BeginTransactionAsync();
var response = await next();
await tx.CommitAsync();
return response;
}
}
// 3. Logging Behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger _logger;
//...
}
Altın Kural: Validasyon, loglama, transaction, caching gibi cross-cutting concern'leri katmanlara dağıtma; tek bir yerde (Pipeline Behavior) topla.
Doğru Uygulama İçin Kontrol Listesi (Checklist):
- □
Domain projesi altyapı paketlerine referans vermiyor mu? (EF Core, Newtonsoft, DataAnnotations yok)
- □
Application projesi, Domain ve MediatR dışında bir şeye referans vermiyor mu? (Veritabanı yok)
- □
Infrastructure, Application'daki interfaceleri implemente ediyor mu? (Tersine değil)
- □
Domain Entity'leri zengin (Rich) mi? (Sadece property değil, davranış da var)
- □
Validasyon, loglama, transaction tek bir yerde (Pipeline Behavior) mi toplanmış?
- □
Gereksiz yere her servis için interface oluşturmaktan kaçınıldı mı?
- □
Unit testler, Domain ve Application'ı mock'larla test edebiliyor mu? (Veritabanı bağlantısı gerekmiyor)
Sonuç:
Clean Architecture, bir "kural kitabıdır". Bu kuralları esnetmek, mimariyi baştan çökertir. En büyük düşmanınız, "Ama bu seferlik özel, yapabiliriz" diyen geliştiriciler veya yöneticilerdir. Bu beş hatayı bilinçli olarak takip ederseniz, projeniz yıllar boyunca temiz, test edilebilir ve değiştirilebilir kalacaktır. Unutmayın: Kolay olan değil, doğru olanı yapmak uzun vadede her zaman kazandırır.