Skip to main content

NestJS’te Döngüsel Bağımlılık (Circular Dependency) Çözümü: Kapsamlı Rehber 💡

Meta Açıklaması: NestJS’teki modül ve servisler arası döngüsel bağımlılık sorununu örnek kodlarla ve forwardRef yaklaşımıyla nasıl çözeceğinizi öğrenin.


📘 Bu Rehberde Ne Öğreneceksiniz?

  • NestJS uygulamalarında döngüsel bağımlılığın (circular dependency) ne olduğunu tanıyacaksınız.
  • Modül seviyesindeki döngüsel bağımlılığı forwardRef ile nasıl çözeceğinizi öğreneceksiniz.
  • Servis (provider) seviyesindeki bağımlılıkları @Inject(forwardRef()) kullanarak kırmayı göreceksiniz.
  • Ortak modül (Shared Module) yaklaşımıyla daha sürdürülebilir bir çözüm oluşturmayı keşfedeceksiniz.

🧠 NestJS’te Döngüsel Bağımlılık Nedir?

Döngüsel bağımlılık, iki sınıfın birbirine ihtiyaç duyması durumudur.
Yani A sınıfı çalışmak için B sınıfına, B sınıfı da çalışmak için A sınıfına ihtiyaç duyar.
Bu durum uygulama başlatılırken bir kilitlenmeye (deadlock) neden olur.

NestJS’te bu durum genellikle iki şekilde görülür:

  • Döngüsel modül içe aktarmaları:
    OrdersModule, PaymentModule’ü içe aktarır, o da OrdersModule’ü içe aktarır.
  • Döngüsel servis enjeksiyonları:
    OrdersService, PaymentService’i; PaymentService de OrdersService’i enjekte eder.

💡 Örnek: Bir e-ticaret uygulamasında Sipariş Servisi, ödemeyi başlatmak için Ödeme Servisi’ni çağırır.
Ödeme tamamlanınca Ödeme Servisi, siparişin durumunu güncellemek için tekrar Sipariş Servisi’ni çağırır.
Bu durumda klasik bir döngüsel bağımlılık oluşur.


🔁 Modüller Arasındaki Döngüsel Bağımlılığın Çözümü

İki modül birbirini doğrudan içe aktardığında NestJS bu modülleri başlatamaz.
Her modül diğerinin yüklenmesini bekler ve uygulama çıkmaza girer.

✅ Çözüm: forwardRef() Kullanımı

forwardRef, NestJS’e bağımlılığı hemen çözümlememesi gerektiğini,
uygulamanın diğer parçaları yüklendikten sonra çözümleyeceğini söyler.

1️⃣ Ödeme Modülünde forwardRef Uygulaması

Bu kod, OrdersModule’ün çözümlemesini erteler ve kilitlenmeyi önler.

// src/odeme/odeme.module.ts
import { Module, forwardRef } from "@nestjs/common";
import { PaymentService } from "./payment.service";
import { OrdersModule } from "../siparisler/orders.module";

@Module({
imports: [forwardRef(() => OrdersModule)],
controllers: [PaymentController],
providers: [PaymentService],
exports: [PaymentService],
})
export class OdemeModulu {}

2️⃣ Sipariş Modülünde forwardRef Uygulaması

Bu kod, her iki modülün birbirini beklemeden yüklenmesini sağlar.


// src/siparisler/siparisler.module.ts
import { Module, forwardRef } from "@nestjs/common";
import { OrdersService } from "./orders.service";
import { PaymentModule } from "../odeme/payment.module";

@Module({
imports: [forwardRef(() => PaymentModule)],
controllers: [OrdersController],
providers: [OrdersService],
exports: [OrdersService],
})
export class SiparislerModulu {}

NestJS, tüm modüller yüklendikten sonra bu bağımlılıkları geri dönüp çözer.


⚙️ Servisler Arasındaki Döngüsel Bağımlılığın Çözümü

Servisler (providers) arasındaki döngü, genelde bir servisin diğeriyle karşılıklı constructor enjeksiyonu yapmasıyla oluşur.

✅ Çözüm: @Inject() + forwardRef() Kombinasyonu

Bu kombinasyon, NestJS’in bağımlılığı ihtiyaç duyulana kadar çözümlememesini sağlar.

1️⃣ Sipariş Servisinde Uygulama


// src/siparisler/orders.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { PaymentService } from '../odeme/payment.service';

@Injectable()
export class OrdersService {
constructor(
@Inject(forwardRef(() => PaymentService))
private readonly paymentService: PaymentService,
) {}
}

2️⃣ Ödeme Servisinde Uygulama


// src/odeme/payment.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { OrdersService } from '../siparisler/orders.service';

@Injectable()
export class PaymentService {
constructor(
@Inject(forwardRef(() => OrdersService))
private readonly ordersService: OrdersService,
) {}
}

Bu sayede iki servis de birbirini tam çözülmeden kullanabilir hale gelir.

🧩 Alternatif Yaklaşım: Ortak (Shared) Modül Kullanımı

Bazı durumlarda forwardRef yerine, bağımlılıkları ortak bir yönetim modülüne taşımak daha temizdir. Bu, kodun sorumluluklarını ayırır ve mimariyi sadeleştirir.

💡 Örnek: İade Yönetimi Modülü Bu modül, hem sipariş hem de ödeme işlemleriyle bağımsız olarak çalışabilir.


// src/iade-yonetimi/iade-yonetimi.service.ts
import { Injectable } from '@nestjs/common';
import { OrdersService } from '../siparisler/orders.service';
import { PaymentService } from '../odeme/payment.service';

@Injectable()
export class IadeYonetimiService {
constructor(
private orderService: OrdersService,
private paymentService: PaymentService
) {}

async iadeyiBaslat(orderId: string) {
const uygun = await this.orderService.iadeUygunlugunuKontrolEt(orderId);
if (!uygun) throw new Error('İade için uygun değil');

const basarili = await this.paymentService.iadeyiIsle(orderId);
if (basarili) {
await this.orderService.durumuGuncelle(orderId, 'İade Edildi');
}
}
}

Bu yöntemle modüller birbirinden tamamen bağımsız hale gelir. Kod daha sürdürülebilir ve yönetilebilir olur.


🔍 Döngüsel Bağımlılığı Tespit Etme Aracı: Madge

Madge, modülleriniz arasındaki döngüleri grafiksel olarak analiz eden güçlü bir araçtır.

1️⃣ Madge Kurulumu


npm i madge
# veya
yarn add madge

2️⃣ Döngüsel Bağımlılıkları Bulma


npx madge --circular src/main.ts
# veya
yarn madge --circular src/main.ts

Bu komut, hangi dosyaların birbirine döngüsel olarak bağlı olduğunu listeler.


❓ Sıkça Sorulan Sorular (SSS)

  1. Döngüsel bağımlılık neden bir hatadır?

NestJS’in DI sistemi, bağımlılıkları sırayla çözümler. A, B’yi; B de A’yı beklediğinde kilitlenme olur ve uygulama başlatılamaz.

  1. forwardRef kullanmak kötü bir uygulama mı?

Hayır, ama son çare olmalıdır. Mimariyi yeniden düzenlemek genellikle daha kalıcı bir çözümdür.

  1. Modül ve servis seviyesindeki döngüler aynı mı?

Hayır. Modül seviyesi yükleme sırasındadır, servis seviyesi ise constructor enjeksiyonunda oluşur. Çözüm yöntemleri benzer olsa da kapsam farklıdır.

  1. Ortak (Shared) modül ne zaman kullanılmalı?

İki modül ortak bir iş akışını (ör. iade süreci) paylaşıyorsa, bu modül tek sorumluluk ilkesini korur.


🏁 Sonuç

Bu rehberde, NestJS’te döngüsel bağımlılık hatasının neden oluştuğunu ve forwardRef, @Inject ile nasıl çözülebileceğini öğrendiniz. Ayrıca mimarinizi sadeleştirmek için ortak modül yaklaşımını da keşfettiniz.

🚀 Bu teknikleri Rabisu Bulut platformunuzdaki NestJS projelerinde hemen deneyebilirsiniz!

yaml