Dependency Injection Nedir Express İçin Nasıl Kullanılır?

Erdem Köşk
Kodcular
Published in
3 min readMar 14, 2023
Photo by Christopher Gower on Unsplash

Herkese selamlar,
Bugun ki konumuz başlıktada söylediğimiz gibi Dependency injection ve bunun bir örneğini oluşturmak.

Öncelikle temeller ile başlayalım.

Dependency Injection (Bağımlılık Enjeksiyonu), bir yazılım tasarım desenidir ve bir bileşenin (servis, modül, sınıf vb.) gereksinim duyduğu diğer bileşenleri (bağımlılıklar) otomatik olarak sağlamak için kullanılır. Bu desen, bileşenlerin bağımlılıklarını elle oluşturmaktan ve yönetmekten kaçınarak, daha esnek, değiştirilebilir ve test edilebilir kod oluşturmayı amaçlar.

Photo by Christina @ wocintechchat.com on Unsplash

Bunu genel olarak düşününce ,Dependency Injection kullanmak, uygulama geliştiricilerinin bileşenler arasındaki bağımlılıkları yönetmelerine ve uygulamanın performansını ve ölçeklenebilirliğini artırmalarına yardımcı olabilir. Node.js, üçüncü taraf paketlerin kullanımıyla bileşenlerin birbirine bağımlı hale gelmesini sağlar. Bu bağımlılıkların yönetimi, her bileşenin diğer bileşenlere doğrudan erişmesi yerine, Dependency Injection kullanarak gerçekleştirilir. Böylece, her bileşenin kendine özgü işlevselliğinin yanı sıra, tek tek bileşenlerin değiştirilmesi veya test edilmesi daha kolay hale gelir.

Ayrıca, DI sayesinde kodun tekrar kullanılabilirliği de artar. Bir bağımlılık değiştirildiğinde, bunun diğer bağımlılıkları etkilemediğinden emin olmak için testler yapmak yeterlidir. Bu da, yazılım geliştiricilerin kodlarını daha az riskli hale getirir.

Üstte belirttiğim üzere bu işi yapabileceğiniz oldukça fazla paket bulunuyor. Bunlar kabaca şu şekilde (tüm hepsini yazamam ama önemli yıldız sayısı olanları ekliyorum)

  1. InversifyJS
  2. awilix
  3. tsyringe

Bugun biz konumuza tsyringe ile devam edeceğiz.

Şimdi Örneklerle devam edelim,

Photo by Tai Bui on Unsplash

Elimizde bir modül yapısı olduğunu düşünelim. Tam örneği görmek isterseniz sizi buraya alalım.

Bunlar,

  1. foo.controller.ts
  2. foo.repository.ts
  3. foo.route.ts
  4. foo.service.ts

controller:

import { Request, Response } from 'express';
import FooService from './foo.service';
import { autoInjectable } from 'tsyringe';

@autoInjectable()
export default class FooController {
fooService: FooService;

constructor(fooService: FooService) {
this.fooService = fooService;
}

public getFoo = async (req: Request, res: Response): Promise<void> => {
const foo = await this.fooService.getFoos();
res.status(200).json(foo);
}

public getError = async (req: Request, res: Response): Promise<void> => {
const error = await this.fooService.getError();
res.status(200).json(error);
}

public getCustomError = async (req: Request, res: Response): Promise<void> => {
const error = await this.fooService.getCustomError();
res.status(200).json(error);
}
}

repository:

export default class FooRepository {
public async findAll() {
const foos = [{
id : 1,
name : 'erdem'
},
{
id : 2,
name : 'kosk'
}];

return foos;
}
}

route:

import * as express from 'express'

import IRouteBase from '../../interfaces/IRouteBase.interface'
import FooController from './foo.controller';

import { autoInjectable } from 'tsyringe';

@autoInjectable()
export default class FooRoute implements IRouteBase {
private fooController: FooController;

constructor(fooController: FooController) {
this.fooController = fooController;
this.initializeRoutes();
}

public router = express.Router();

initializeRoutes() {
this.router.get('/foos', this.fooController.getFoo);
this.router.get('/error', this.fooController.getError);
this.router.get('/custom-error', this.fooController.getCustomError);
}
}

service:

import FooRepository from './foo.repository';
import { autoInjectable } from 'tsyringe';

import { ERROR_CLASSES } from '../../util/error.util';


@autoInjectable()
export default class FooService {
fooRepository: FooRepository;

constructor(fooRepository: FooRepository) {
this.fooRepository = fooRepository;
}

async getFoos() {
return this.fooRepository.findAll();
}

async getError() {
throw new Error('This is not custom error');
}

async getCustomError() {
throw new ERROR_CLASSES.ExampleError();
}
}

Burada gördüğümüz bir tüm bu sınıflar birbirlerine bağımlı.
Yani bunları tek tek newlamamız gerektiğini düşünelim. Yani üstteki kodda @autoInjectable() gibi yapıyı kullanmadığımızı düşünelim.

Bunu normalde nasıl yapabiliriz?

const fooRepository = new FooRepository();
const fooService = new FooService(fooRepository); // Servis oluşabilmesi için önce repostory newlanmalı
const fooController = new FooController(fooService); // Controller oluşabilmesi için önce servis newlanmalı
const fooRoute = new FooRoute(fooController); // Route oluşabilmesi için önce Controller newlanmalı

Gördüğünüz üzere herşey birbirinle bağımlı ve örneğin FooRepository() newlamadan Route oluşturamaz hale geldik…

İşte tamda bu nedenle dependcy injection’a ihtiyacımız var.

Üstteki kodda gösterdiğim üzere

import { autoInjectable } from 'tsyringe';
@autoInjectable()

Bu annotation kullanarak birbirinle ilişkili tüm sınıfların bir container içine yerleştirilip o containerdan gerekli yerlerde kullanılmasını sağlıyor.

Yani elimizde bir kutu var ve herşeyi o kutuya koyuyoruz. Ve gereklilikler oradan alınıyor bu sebeple sürekli newlama işinden kurtuluyoruz.

Sonuç olarak güncel mimarilerde oldukça popüler olan bu sistemin tam olarak örnek serviste nasıl kullanıldığını görmek isterseniz sizi buraya alalım!

Sevgilerle,

--

--