본문 바로가기
Research/Computer Science

Dependency Injection

by RIEM 2023. 3. 28.

DI(의존성 주입, Dependency Injection)이란

의존성 주입(DI, Dependency Injection)은 디자인 패턴 중 하나로 클래스 내 의존관계를 가진 인스턴스를 생성하는 것 대신 주입을 하는 것이 특징이다.

DI의 유형

의존성 주입의 유형은 여러가지가 있다.

  1. Constructor Injection
  2. Setter Injection
  3. Interface Injection
  4. Field Injection

DI의 장점

  • 코드의 유연성과 재사용성이 높아서 생산성이 높아진다
  • 테스트와 유지보수가 간편해진다
  • 확장성이 좋다
  • 가독성이 좋다

DI의 단점

  • 클래스가 점점 많아지면 복잡도가 커진다
  • 주입된 클래스 추적이 어렵다
  • 프레임워크에 대한 의존성이 커진다

생성자 주입 시나리오 1

일반적인 시나리오는 이렇다.

Normal flow of API to read posts

1. request -> Controller
2. Controller -> Service
3. Service -> Repository
// Controller Layer
import { UserService } from "./UserService";

export class UserController {
  userService: UserService;

  constructor() {
    this.userService = new UserService();
  }

  getUserData = () => {
    this.userService.getUserData();
  }
}
// Service Layer
import { UserRepository }  from "./UserRepository";

export class UserService {
  userRepo: UserRepository;

  constructor() {
    this.userRepo = new UserRepository();
  }

  getUserData = () => {
    this.userRepo.getAll();
  }
}

여기서 서비스 레이어는 리파지토리와 의존 관계를 가진다. 즉, UserRepository - UserService가 서로 묶여있는 상황이다.

이 경우 jest로 테스트 할 시, DB mock 데이터를 생성하기가 까다로워진다.

위 문제는 UserRepository클래스를 UserService의 생성자에 주입함으로서 해결할 수 있다.

Before DI

// Service Layer without DI
import { UserRepository }  from "./UserRepository";

export class UserService {
  userRepo: UserRepository;

  constructor() {
    this.userRepo = new UserRepository();
  }

  getUserData = () => {
    this.userRepo.getAll();
  }
}

After DI

// Service Layer with DI
import { UserRepository } from "./UserRepository";

export class UserService {
  userRepo: UserRepository;

  constructor(userRepo: UserRepository) {
    this.userRepo = userRepo;
  }

  getUserData = () => {
    this.userRepos.getAll();
  }
}

테스팅도 쉬워졌다. Service 인스턴스 생성 시 mockUserRepo를 주입하기만 하면 된다.

import { UserService } from "./UserService";
import { UserRepository } from "./UserRepository";

const mockUserRepo = {
  getAll: jest.fn(),
};

const userService = new UserService(mockUserRepo);

userService.getUserData();

expect(mockUserRepo.getAll).toHaveBeenCalled();

생성자 주입 Scenario 2

// Without dependency Injection

class InviteService {
  sendInvitation(eventName, recipient) {
    console.log(
      `Dear ${recipient}, We are please to inform you that you are invited to join ${eventName}.`
    );
  }
}

class NotificationService {
  constructor() {
    // creating instance inside another instance
    this.inviteService = new InviteService();
  }
  sendNotification(eventName, recipient) {
    this.inviteService.sendInvitation(eventName, recipient);
  }
}

const singleNoti = new NotificationService();

singleNoti.sendNotification('SS22 VIP trunk show', 'Sujan');
// Dear Sujan, We are please to inform you that you are invited to join SS22 VIP trunk show.
// With dependency Injection

class InviteService {
  sendInvitation(eventName, recipient) {
    console.log(
      `Dear ${recipient}, We are please to inform you that you are invited to join ${eventName}.`
    );
  }
}

class NotificationService {
  constructor(inviteService) {
    this.inviteService = inviteService;
  }
  sendNotification(eventName, recipient) {
    this.inviteService.sendInvitation(eventName, recipient);
  }
}

// instantiate InviteService
const inviteService = new InviteService();

// Inject InviteService instance into NotificationService
const singleNoti = new NotificationService(inviteService);

// Use singleNoti method
singleNoti.sendNotification('Public Fashion Show', 'John');
// Dear John, We are please to inform you that you are invited to join Public Fashion Show.

Reference

'Research > Computer Science' 카테고리의 다른 글

Parameter와 Argument의 차이  (0) 2023.03.30
MSA(MicroService Architecture)  (0) 2023.03.29
DB 인덱스  (0) 2023.03.28
REST API  (0) 2023.03.27
OOP(Object-Oriented Programming)  (0) 2023.03.27

댓글