
Şöyle bir senaryo düşünelim. Sisteminiz PostgreSQL kullanıyor, her şey güzel çalışıyor. Bir gün karar verildi: "MongoDB'ye geçiyoruz." TransferOrchestrator sınıfını açıyorsunuz ve şunu görüyorsunuz:
class TransferOrchestrator {
private PostgreSQLAccountRepository repo = new PostgreSQLAccountRepository();
private KafkaEventPublisher publisher = new KafkaEventPublisher();
private RuleBasedFraudDetector fraud = new RuleBasedFraudDetector();
}TransferOrchestrator her somut sınıfı doğrudan içinde yaratmış. MongoDB'ye geçmek için iş mantığına dokunmak zorundasınız. Kafka'yı RabbitMQ ile değiştirmek için yine aynı şekilde iş mantığını değiştirmeliyiz.
Peki, bir iş mantığı sınıfı neden altyapı detaylarını bilsin ki?
Nedir bu prensip aslında?
Dependency Inversion Principle iki şey söylüyor:
Yüksek seviye modüller düşük seviye modüllere bağımlı olmamalı. Her ikisi de soyutlamalara bağımlı olmalı.
Sade bir dille: TransferOrchestrator PostgreSQL'i, Kafka'yı, ML modelini tanımamalı. Yalnızca arayüzleri tanımalı. Hangi somut sınıfın arkada çalıştığını umursamamalı.
Bunu sağladığınızda altyapı değişir ama iş mantığına tek satır dokunmazsınız. Üstelik test yazarken gerçek DB veya Kafka'ya ihtiyaç duymadan sahte implementasyonlarla rahatça çalışırsınız.
❌ Önce yanlışı görelim
TransferOrchestrator her somut sınıfı doğrudan yaratıyor:
class TransferOrchestrator {
// Somut sınıflara doğrudan bağımlı — DIP ihlali
private PostgreSQLAccountRepository repo
= new PostgreSQLAccountRepository();
private KafkaEventPublisher publisher
= new KafkaEventPublisher();
private RuleBasedFraudDetector fraud
= new RuleBasedFraudDetector();
void transfer(String fromId, String toId, BigDecimal amount) {
if (fraud.isSuspicious(amount)) {
publisher.publish("FRAUD_ALERT: " + amount);
throw new IllegalStateException("Şüpheli işlem — reddedildi");
}
Account source = repo.findById(fromId);
Account target = repo.findById(toId);
source.debit(amount);
target.credit(amount);
repo.save(source);
repo.save(target);
publisher.publish("TRANSFER_OK: " + amount);
}
}Bu yapıda ne değişirse TransferOrchestrator'a dokunmak zorundasınız:
PostgreSQL → MongoDB geçişi?
TransferOrchestratordeğişir.Kafka → RabbitMQ geçişi?
TransferOrchestratordeğişir.Kural tabanlı fraud → ML fraud?
TransferOrchestratordeğişir.Test yazmak istiyorsunuz? Gerçek DB ve Kafka ayağa kaldırmak zorundasınız.
İş mantığı altyapıya zincirlenmiş. İkisi birbirinden ayrılamıyor.
✅ Şimdi doğrusunu yapalım
Önce soyutlamalarımızı tanımlıyoruz — bunlar artık sistemin kontratları:
// Hesap deposu kontratı
interface AccountRepository {
Account findById(String id); // Hesap getir
void save(Account account); // Hesap kaydet
}
// Dolandırıcılık dedektörü kontratı
interface FraudDetector {
boolean isSuspicious(BigDecimal amount); // Şüpheli mi?
}
// Olay yayıncısı kontratı
interface EventPublisher {
void publish(String event); // Olay yayınla
}TransferOrchestrator artık yalnızca bu kontratları biliyor — arkada ne olduğunu bilmiyor:
// DIP: hiçbir somut sınıfı tanımıyor, sadece arayüzlere bağımlı
class TransferOrchestrator {
private final AccountRepository repo;
private final FraudDetector fraud;
private final EventPublisher events;
private final FeeEngine feeEngine;
private final AuditLogger logger;
// Constructor injection — bağımlılıklar dışarıdan veriliyor
TransferOrchestrator(
AccountRepository repo,
FraudDetector fraud,
EventPublisher events,
FeeEngine feeEngine,
AuditLogger logger) {
this.repo = repo;
this.fraud = fraud;
this.events = events;
this.feeEngine = feeEngine;
this.logger = logger;
}
void transfer(String fromId, String toId,
BigDecimal amount, String feeType) {
if (fraud.isSuspicious(amount)) { // Fraud kontrolü
events.publish("FRAUD_ALERT: " + amount);
throw new IllegalStateException("Şüpheli işlem — reddedildi");
}
BigDecimal fee = feeEngine.calculate(feeType, amount);
BigDecimal total = amount.add(fee);
Account source = repo.findById(fromId);
Account target = repo.findById(toId);
((Debitable) source).debit(total); // Gönderenden çek
((Creditable) target).credit(amount); // Alıcıya yatır
repo.save(source);
repo.save(target);
logger.log(fromId + " → " + toId + " : " + amount + " (ücret: " + fee + ")");
events.publish("TRANSFER_OK: " + amount);
}
}Şimdi production ve test implementasyonlarını yazıyoruz:
// Production — gerçek veritabanı
class JpaAccountRepository implements AccountRepository {
public Account findById(String id) {
// DB sorgusu
return null;
}
public void save(Account account) {
// DB'ye yaz
}
}
// Production — gerçek Kafka
class KafkaEventPublisher implements EventPublisher {
public void publish(String event) {
System.out.println("[KAFKA] " + event);
}
}
// Production — ML tabanlı fraud dedektörü
class MLFraudDetector implements FraudDetector {
public boolean isSuspicious(BigDecimal amount) {
// ML modeli burada çalışır
return amount.compareTo(new BigDecimal("50000")) > 0;
}
}
// Test — DB gerektirmez, bellekte çalışır
class InMemoryAccountRepository implements AccountRepository {
private final Map<String, Account> store = new HashMap<>();
public void put(String id, Account account) { store.put(id, account); }
public Account findById(String id) {
if (!store.containsKey(id))
throw new IllegalArgumentException("Hesap bulunamadı: " + id);
return store.get(id);
}
public void save(Account account) { /* zaten bellekte */ }
}
// Test — her işlemi güvenli sayar
class AlwaysSafeFraudDetector implements FraudDetector {
public boolean isSuspicious(BigDecimal amount) { return false; }
}
// Test — sadece konsola yazar
class LogEventPublisher implements EventPublisher {
public void publish(String event) {
System.out.println("[EVENT] " + event);
}
}
Kullanalım
Production ortamı — gerçek DB ve Kafka:
TransferOrchestrator prod = new TransferOrchestrator(
new JpaAccountRepository(),
new MLFraudDetector(),
new KafkaEventPublisher(),
feeEngine,
new AuditLogger()
);Test ortamı — hiçbir altyapıya ihtiyaç yok:
InMemoryAccountRepository testRepo = new InMemoryAccountRepository();
testRepo.put("A1", new CheckingAccount(new BigDecimal("2000"), new BigDecimal("500")));
testRepo.put("A2", new SavingsAccount(new BigDecimal("1000")));
FeeEngine feeEngine = new FeeEngine();
feeEngine.register("STANDARD", new StandardFee());
TransferOrchestrator test = new TransferOrchestrator(
testRepo,
new AlwaysSafeFraudDetector(), // DB yok, Kafka yok
new LogEventPublisher(), // gerçek altyapı yok
feeEngine,
new AuditLogger()
);
test.transfer("A1", "A2", new BigDecimal("300"), "STANDARD");
// [LOG] A1 → A2 : 300 (ücret: 0.30)
// [EVENT] TRANSFER_OK: 300
System.out.println(testRepo.findById("A1").getBalance()); // 1699.70
System.out.println(testRepo.findById("A2").getBalance()); // 1300.00MongoDB'ye geçiş senaryosu
Karar verildi, PostgreSQL'den MongoDB'ye geçiyorsunuz. Tek yapmanız gereken:
// Yeni implementasyon — başka hiçbir şeye dokunmadık
class MongoAccountRepository implements AccountRepository {
public Account findById(String id) {
// MongoDB sorgusu
return null;
}
public void save(Account account) {
// MongoDB'ye yaz
}
}
// Production'da sadece bu satır değişti
TransferOrchestrator prod = new TransferOrchestrator(
new MongoAccountRepository(), // ← tek değişiklik
new MLFraudDetector(),
new KafkaEventPublisher(),
feeEngine,
new AuditLogger()
);TransferOrchestrator'a dokunmadınız. MLFraudDetector'a dokunmadınız. KafkaEventPublisher'a dokunmadınız. Mevcut testleriniz hâlâ yeşil.
Kısaca
Yüksek seviye iş mantığı altyapı detaylarını bilmemeli. Arayüzler bu ikisi arasındaki duvarı oluşturuyor. Duvar sayesinde her iki taraf bağımsızca değişebiliyor.
Seriyi tamamladık
Beş bölüm boyunca tek bir banking modülü üzerinden SOLID'in her prensibini gördük:
S — Her sınıf tek iş yapar. E-posta şablonu değişince Account'a dokunmayız.
O — Yeni ücret modeli için FeeEngine'e dokunmayız, yeni bir strateji sınıfı yazarız.
L — SavingsAccount ve CheckingAccount her yerde Account yerine sorunsuz geçer.
I — InvestmentAccount kullanmadığı metodları implement etmek zorunda kalmaz.
D — TransferOrchestrator PostgreSQL'i, Kafka'yı, ML modelini tanımaz. Yalnızca arayüzleri tanır.
Bu prensipler tek tek de değerli ama hepsi birlikte uygulandığında asıl gücünü gösteriyor. Değişim izole kalıyor, test kolaylaşıyor, ekip büyüdükçe çakışmalar azalıyor.
Umarım bu seri işe yarar. Sorularınız varsa yorumlarda buluşalım.
