Back to Blog

Dependency Injection

February 3, 20263 min
Dependency InjectionClean ArchitectureInversion of Control
Dependency Injection

Dependency Injection Mantığı

Dependency Injection’ın temel prensibi şudur:

Bir sınıf bağımlılığını kendisi oluşturmaz, dışarıdan alır.

Yani:

Bağımlılığı yaratmam, bana verilir.

DI Olmayan Durum

class RegisterUser {
  private userRepo = new PrismaUserRepository();

  async execute() {
    // ...
  }
}

Burada:

  • Use case doğrudan DB implementasyonuna bağlı
  • Test yazmak zor
  • Mocklamak zor
  • DB değişirse kod değişmek zorunda

Bu tight coupling’dir.

DI Olan Durum

class RegisterUser {
  constructor(private userRepo: UserRepository) {}

  async execute() {
    // ...
  }
}

Burada:

  • Use case DB’yi bilmez
  • Sadece abstraction (interface) bilir
  • Implementasyon dışarıdan verilir

Bu loose coupling’dir.

DI’nin Gerçek Amacı

DI’nin amacı:

  • Abstraction’a bağımlı olmak
  • Concrete implementasyona bağımlı olmamak

Özetle:

High-level module → low-level module’a değil Her ikisi de abstraction’a bağlıdır.

Bu, aslında SOLID içindeki Dependency Inversion Principle’ın uygulanışıdır.

Hard Dependency Örneği

class UserService {
    constructor() {
        this.emailService = new EmailService();
    }

    register(user) {
        this.emailService.send(user.email, "Hoş geldin!");
    }
}

Burada:

UserService → EmailService’e göbekten bağlıdır.

EmailService değişirse UserService değişmek zorunda.

DI ile Çözüm

class UserService {
    constructor(emailService) {
        this.emailService = emailService;
    }

    register(user) {
        this.emailService.send(user.email, "Hoş geldin!");
    }
}

const gmail = new GmailService();
const userSvc = new UserService(gmail);

UserService artık:

  • Hangi mail servisi olduğunu bilmez
  • Sadece bir “mail gönderen şey” bekler

Bu abstraction’a bağımlılıktır.

DI Neden Kullanılır?

1. Test Edilebilirlik

Gerçek mail servisi yerine fake verilebilir.

const fakeEmailService = {
  send: jest.fn()
};

const service = new UserService(fakeEmailService);

Gerçek sistemlere dokunmadan test yapılır.

2. Esneklik

Gmail → SendGrid değişimi:

UserService değişmez. Sadece en başta verilen dependency değişir.

3. Sorumluluk Ayrımı

Bir sınıf:

  • İşini yapar
  • Bağımlılığını yönetmez

Nesne oluşturma sorumluluğu dışarıdadır.

DI Türleri

1. Constructor Injection (En Doğru Yöntem)

constructor(private repo: UserRepository) {}

Avantaj:

  • Immutable
  • Zorunlu dependency açık
  • Test-friendly

2. Setter Injection

setRepository(repo: UserRepository) {
  this.repo = repo;
}

Riskli:

  • Nesne yarım oluşabilir
  • Zorunlu dependency garanti edilmez

3. Method Injection

execute(repo: UserRepository) {}

Genelde nadir kullanılır.

DI Container Nedir?

Küçük projede:

new A(new B(new C()))

Yönetilebilir.

Ama büyük projede:

  • Yüzlerce dependency
  • Zincir bağımlılık
  • Karmaşık wiring

Bu manuel yapı sürdürülemez.

DI Container:

  • Hangi sınıf neye ihtiyaç duyuyor bilir
  • Zinciri otomatik kurar
  • Lifecycle yönetir (singleton, transient vs.)

Manuel DI (Express Yaklaşımı)

class Logger {
    log(message) { console.log(message); }
}

class UserService {
    constructor(logger) {
        this.logger = logger;
    }

    create(name) {
        this.logger.log(`${name} oluşturuluyor`);
    }
}

const logger = new Logger();
const userService = new UserService(logger);

Bağımlılık en alttan yukarı doğru beslenir.

IoC Container (NestJS Yaklaşımı)

@Injectable()
export class LoggerService {}

@Injectable()
export class UserService {
  constructor(private logger: LoggerService) {}
}

@Controller()
export class UserController {
  constructor(private userService: UserService) {}
}

Burada:

  • new yok
  • Wiring yok
  • Container dependency graph’i yönetir

Bu Inversion of Control’dür.

Kontrol uygulamadan container’a geçer.

DI + Use Case

Clean Architecture’da:

  • Use case → abstraction alır
  • Infrastructure → implement eder
  • Container → bağlar

Örnek test:

const fakeRepo = {
  findByEmail: async () => null,
};

const uc = new RegisterUser(fakeRepo);

DB olmadan test yazılabilir.

Araba Benzetmesi

DI olmayan:

Araba kendi motorunu üretir.

DI olan:

Motor dışarıdan takılır.

Motor değişir, şasi değişmez.

Ne Zaman DI Şarttır?

  • Clean Architecture varsa
  • Use case yapısı varsa
  • Unit test yazılıyorsa
  • Domain izolasyonu isteniyorsa

Küçük script’lerde gerekmez.