Dependency Injection(DI, 의존성 주입)

의존성이란?


public class Person {
  private name;
  public getFormatName() {
    Format format = new Format();
  }
}
  • getFormatName() 메서드는 Format 에 의존성을 가진다.
    = Person 클래스는 Format 클래스를 의존하고 있다.
  • 위와같은 경우는 A 가 B 에 의존하고 있다. (A 는 B 없이 작동 할 수 없음)

의존성이 발생하면 어떤 문제가 발생할까?

  • 긴밀한 결합(tight coupling)이 생기므로, Format 클래스가 변경이 되면 Person 클래스도 변경이 된다.
    왜? A 는 B 가 없으면 작동을 할 수 없다고 하였다. 그런데 B 가 변경이 일어나면 A 는 그전 처럼 정상 동작을 하는가? 결국에는 B 가 변경됨에 따라 A에도 영향을 준다.

개발을 진행하다 보면, 요구사항, 객체 또는 모듈간의 합성은 언제든지 변경 될 수 있다.


이 처럼 의존성이 높아질 경우, 아래와 같은 단점들이 발생한다.

  • 코드 재사용성 감소
  • 유지보수 비용 증가
  • 테스트 코드 작성 어려움

Javascript 에서 DI ?

ES6 에서 ESM 이 추가가 되었다. (import, export)
Node.js 에서는 CommonJS 모듈을 제공한다.(require, module.exports)

위 내용들만 보면, 굳이 DI 가 필요없는거처럼 보인다. 나 또한 ESM 자체가 싱글턴(singleton)으로 생성해 주기 때문에 필요한 코드내에서 import 해서 사용하면 되지 않는가? 생각을 하였다.
틀린 생각은 아니지만, DI 를 적용하면 코드가 유연해지고 결합도를 낮춘 모듈 작성이 가능하다.

// userRepository.js

import axios from 'axios';

export default {
	findUserList() {
    	return axios.get('/v1/users');
    }
}

간단한 예제 코드를 작성하였다. 특별히 문제는 없어보인다.

  • (상황1) 그런데 axios 에서 fetch 로 변경을 할 경우는?
  • (상황2) 리파지토리를 테스트하기 위해서 무엇을 해야 하는가? 지금은 axios 1개의 의존성만 보이는데, logging 이라던가 이것저것 추가 시, mocking 이 쉬운가?

    mock : 테스트를 위한 더미 객체(가짜 객체)
    jest 를 활용하면 axios 도 mocking 이 가능

상황1을 해결하려면 어떻게 해야할까?
만약 저렇게 작성한 리파지토리들이 많다면 위의 코드를 싹 변경을 해줘야한다.
변경이 자주 일어난다는거는 확장성이 떨어지고 결합도가 높다는 의미다.

상황2 같은 경우는 userRepository 에 대한 더미 객체를 만들어야 하는데, 기왕이면 외부 라이브러리를 사용안하는게 훨씬 편한 테스트 방식일거다

// fetchUtils.js
export const api = {
  vaildTargetUrl(targetUrl) {
    return targetUrl === '';
  },
  get(targetUrl) {
    if (!vaildTargetUrl(targetUrl)) return false
    return fetch(targetUrl, { method: 'get' });
  },
  post(targetUrl) {
   //...
  },
  put(targetUrl) {
    //...
  },
  delete(targetUrl) {
    //...
  },
}
// repositoryIndex.js
import { api } from '../utils/fetchUtils';
import userRepository from './user/index';
export const repository = {
  userRepository: userRepository(api),
};
// userRepository.js
export default (api) => ({
	findUserList() {
    	return api.get('/v1/users');
    }
})
// example (사용 예)
const getUserList = async () => {
	const result = await repository.userRepository.findUserList();
};

의존성 주입을 써서 코드를 리팩토링 하였다.
이제 우리는 api 를 직접 인자로 넘겨주기만 하면 된다.(자바 또는 OOP 에서는 생성자 주입, setter 등에 방법으로 의존성을 주입해주기도 한다)
이제 userRepository 는 axios 와 직접 엮여있지 않은 상태다. 이를 통해서 외부 요소의 변경상항으로 부터 격리가 가능하며 테스트가 쉽고 간단해진다.

  • 저렇다고 의존성이 없어진거는 아니다. 우리가 기억할점은 coupling(결합도) 이 낮아짐에 따라 얻는 이점들을 기억해야 한다.

마무리

  • 의존성 주입은 유연성을 제공해준다.
  • 의존성 주입은 다양한 언어에서 적용되고 사용되어지고 있다.
  • Inversify, TypeDI, Awilix 등 DI 컨테이너들이 존재한다.

참고

부족한 내용이나 잘못된 내용 피드백은 항상 감사합니다!🙂

좋은 웹페이지 즐겨찾기