Giriş
Modern yazılım geliştirmede mimari seçimi, projenin başarısını doğrudan etkileyen kritik bir karardır. N-Tier ve Onion Architecture gibi farklı mimari yaklaşımlar, geliştiricilere farklı avantajlar sunar ve farklı senaryolarda öne çıkar.
Bu yazıda neler bulacaksınız:
- N-Tier ve Onion Architecture’ın temel prensipleri
- Detaylı karşılaştırma ve analiz
- Gerçek proje senaryolarında kullanım örnekleri
- Hangi durumda hangisini tercih etmeniz gerektiği
- .NET Core ile pratik uygulama örnekleri
N-Tier Architecture Nedir?
Temel Yapı ve Prensipler
N-Tier Architecture (Çok Katmanlı Mimari), uygulamanın farklı sorumluluklarını ayrı katmanlara bölen geleneksel bir mimari yaklaşımıdır.
Klasik 3-Tier Yapısı:
- Presentation Layer (Sunum Katmanı): UI, Controllers, Views
- Business Layer (İş Katmanı): Business Logic, Validation, Rules
- Data Access Layer (Veri Erişim Katmanı): Database operations, Repositories
Avantajları
- Basit ve Anlaşılır: Öğrenmesi kolay, ekip için anlaşılır
- Yaygın Kullanım: Geniş developer community desteği
- Hızlı Geliştirme: Prototipleme ve MVP için ideal
- Proven Pattern: Yıllardır test edilmiş ve güvenilir
Dezavantajları
- Tight Coupling: Katmanlar arası sıkı bağlılık
- Database Dependency: Üst katmanlar database’e bağımlı
- Test Zorluğu: Unit testing için mock’lama kompleks
- Scalability Sorunları: Büyük projelerde bakım zorluğu
Onion Architecture Nedir?
Temel Yapı ve Prensipler
Onion Architecture, Jeffrey Palermo tarafından 2008’de önerilen, Dependency Inversion prensibine dayanan modern bir mimari yaklaşımdır.
Katman Yapısı (İçten Dışa):
- Core (Domain): Entities, Value Objects, Domain Services
- Application: Use Cases, Interfaces, Application Services
- Infrastructure: Data Access, External Services, Frameworks
- Presentation: UI, Web API, Controllers
Temel Prensipler
- Dependency Inversion: Dış katmanlar iç katmanlara bağımlı
- Separation of Concerns: Her katmanın tek sorumluluğu
- Testability: Kolay unit testing
- Framework Independence: Framework’e bağımlılık minimum
Avantajları
- Loose Coupling: Katmanlar arası gevşek bağlılık
- High Testability: Mükemmel test edilebilirlik
- Flexibility: Teknoloji değişikliklerine uyum
- Maintainability: Uzun vadeli bakım kolaylığı
Dezavantajları
- Complexity: Öğrenme eğrisi daha yüksek
- Over-engineering: Küçük projeler için fazla kompleks
- Initial Setup: Başlangıç kurulum süresi uzun
- Team Learning: Ekip eğitimi gerekli
Detaylı Karşılaştırma
Teknik Karşılaştırma
| Özellik | N-Tier Architecture | Onion Architecture |
|---|---|---|
| Dependency Direction | Top-down (Yukarıdan aşağıya) | Inside-out (İçten dışa) |
| Coupling Level | Tight Coupling | Loose Coupling |
| Testability | Orta seviye | Çok yüksek |
| Learning Curve | Kolay | Orta-Zor |
| Flexibility | Düşük | Yüksek |
| Development Speed | Hızlı başlangıç | Yavaş başlangıç |
| Long-term Maintenance | Zorlaşır | Kolay |
Kod Yapısı Karşılaştırması
N-Tier Approach
// N-Tier: Business Layer
public class ProductService
{
private readonly ProductRepository _repository;
public ProductService()
{
_repository = new ProductRepository(); // Tight coupling
}
public void CreateProduct(Product product)
{
// Business logic
if (product.Price <= 0)
throw new ArgumentException("Price must be positive");
_repository.Add(product);
}
}
Onion Architecture Approach
// Onion: Application Layer
public class ProductService
{
private readonly IProductRepository _repository;
public ProductService(IProductRepository repository)
{
_repository = repository; // Dependency injection
}
public async Task CreateProductAsync(Product product)
{
// Business logic
if (product.Price <= 0)
throw new ArgumentException("Price must be positive");
await _repository.AddAsync(product);
}
}
// Domain Layer - Interface
public interface IProductRepository
{
Task AddAsync(Product product);
Task<Product> GetByIdAsync(int id);
}
Gerçek Dünya Senaryoları
N-Tier İçin Uygun Projeler
E-Ticaret Prototipi
// Basit CRUD operasyonları
public class OrderController : Controller
{
private readonly OrderService _orderService;
public OrderController()
{
_orderService = new OrderService();
}
[HttpPost]
public ActionResult CreateOrder(OrderViewModel model)
{
var order = new Order
{
CustomerId = model.CustomerId,
Total = model.Total
};
_orderService.CreateOrder(order);
return RedirectToAction("Success");
}
}
Uygun Durumlar:
- Küçük-orta ölçekli projeler
- Hızlı prototipleme gereken durumlar
- Basit CRUD operasyonları ağırlıklı
- Sınırlı budget ve zaman
- Junior developer ağırlıklı ekipler
Onion Architecture İçin Uygun Projeler
Enterprise Banking Sistemi
// Domain Layer - Core Business Logic
public class Account
{
public int Id { get; private set; }
public decimal Balance { get; private set; }
public void Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive");
if (Balance < amount)
throw new InsufficientFundsException();
Balance -= amount;
}
}
// Application Layer - Use Case
public class WithdrawMoneyUseCase
{
private readonly IAccountRepository _accountRepository;
private readonly INotificationService _notificationService;
public WithdrawMoneyUseCase(
IAccountRepository accountRepository,
INotificationService notificationService)
{
_accountRepository = accountRepository;
_notificationService = notificationService;
}
public async Task<WithdrawResult> ExecuteAsync(WithdrawRequest request)
{
var account = await _accountRepository.GetByIdAsync(request.AccountId);
account.Withdraw(request.Amount);
await _accountRepository.UpdateAsync(account);
await _notificationService.SendWithdrawNotificationAsync(account.Id, request.Amount);
return new WithdrawResult { Success = true };
}
}
Uygun Durumlar:
- Büyük ölçekli enterprise projeler
- Karmaşık business logic
- Yüksek test coverage gereken durumlar
- Uzun vadeli bakım gerekli
- Teknoloji değişikliği olasılığı yüksek
Performance ve Scalability
N-Tier Performance Özellikleri
- Basit Deployment: Tek deployment unit
- Monolithic Structure: Vertical scaling
- Database Bottleneck: Tüm katmanlar aynı DB’ye bağlı
- Memory Usage: Düşük memory footprint
Onion Architecture Performance Özellikleri
- Modular Deployment: Microservices’e uygun
- Horizontal Scaling: Katmanlar bağımsız scale
- Caching Strategy: Katmanlar arası cache
- Resource Optimization: Interface-based optimization
Test Edilebilirlik
N-Tier Testing Challenges
// N-Tier: Test etmesi zor
[Test]
public void CreateProduct_ShouldSaveToDatabase()
{
// Problem: Gerçek database bağlantısı gerekli
var service = new ProductService();
var product = new Product { Name = "Test", Price = 100 };
service.CreateProduct(product);
// Database'den kontrol etmek gerekli
Assert.IsTrue(DatabaseHelper.ProductExists(product.Id));
}
Onion Architecture Testing Advantages
// Onion: Test etmesi kolay
[Test]
public async Task CreateProduct_ShouldCallRepository()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var service = new ProductService(mockRepository.Object);
var product = new Product { Name = "Test", Price = 100 };
// Act
await service.CreateProductAsync(product);
// Assert
mockRepository.Verify(r => r.AddAsync(product), Times.Once);
}
Hangi Durumda Hangisini Seçmeli?
N-Tier Architecture Tercih Edilmeli
- Proje Büyüklüğü: Küçük-orta ölçekli projeler
- Zaman Kısıtı: Hızlı delivery gerekli
- Ekip Deneyimi: Junior developer ağırlıklı
- Budget Sınırı: Sınırlı geliştirme zamanı
- Basit Requirements: CRUD ağırlıklı işlemler
Onion Architecture Tercih Edilmeli
- Proje Büyüklüğü: Büyük ölçekli enterprise projeler
- Karmaşık Business Logic: Sophisticated domain rules
- Yüksek Test Coverage: %80+ test coverage hedefi
- Uzun Vadeli Bakım: 5+ yıl active development
- Teknoloji Flexibility: Framework/database değişikliği olasılığı
Geçiş Stratejisi
N-Tier’dan Onion’a Geçiş
1. Aşama: Interface Extraction
// Önce: Direct dependency
public class OrderService
{
private readonly OrderRepository _repository = new OrderRepository();
}
// Sonra: Interface-based
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
}
2. Aşama: Domain Model Separation
// Domain katmanını ayırma
public class Order // Domain Model
{
public int Id { get; private set; }
public List<OrderItem> Items { get; private set; }
public void AddItem(OrderItem item)
{
// Business logic here
Items.Add(item);
}
}
3. Aşama: Use Case Implementation
// Application layer use case
public class PlaceOrderUseCase
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
public async Task<OrderResult> ExecuteAsync(PlaceOrderRequest request)
{
// Orchestration logic
var order = new Order(request.CustomerId);
foreach (var item in request.Items)
{
order.AddItem(item);
}
await _paymentService.ProcessPaymentAsync(order.Total);
await _orderRepository.SaveAsync(order);
return new OrderResult { OrderId = order.Id };
}
}
Sonuç
N-Tier Architecture ve Onion Architecture arasındaki seçim, projenin gereksinimlerine, ekip deneyimine ve uzun vadeli hedeflere bağlıdır.
Önemli Çıkarımlar:
- Basit projeler için N-Tier hala geçerli ve pratik
- Karmaşık business logic için Onion Architecture kaçınılmaz
- Test edilebilirlik kritik ise Onion Architecture tercih edilmeli
- Hızlı prototipleme için N-Tier daha uygun
Gelecek Adımlar:
- Mevcut projenizi analiz edin: Hangi kategoride olduğunu belirleyin
- Ekip kapasitesini değerlendirin: Öğrenme eğrisi için zaman ayırın
- Pilot proje ile başlayın: Küçük bir module ile test edin
- Gradual migration planlayın: Büyük projeler için aşamalı geçiş
Her iki mimari de doğru kullanıldığında başarılı sonuçlar verir. Önemli olan, projenizin ihtiyaçlarına en uygun olanını seçmek ve tutarlı bir şekilde uygulamaktır.