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 daOrdersModule’ü içe aktarır. - Döngüsel servis enjeksiyonları:
OrdersService,PaymentService’i;PaymentServicedeOrdersService’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)
- 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.
- 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.
- 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.
- 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