의존성이란?
public class Person {
private name;
public getFormatName() {
Format format = new Format();
}
}
의존성이 발생하면 어떤 문제가 발생할까?
- 긴밀한 결합(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');
}
}
간단한 예제 코드를 작성하였다. 특별히 문제는 없어보인다.
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 와 직접 엮여있지 않은 상태다. 이를 통해서 외부 요소의 변경상항으로 부터 격리가 가능하며 테스트가 쉽고 간단해진다.
Inversify, TypeDI, Awilix
등 DI 컨테이너들이 존재한다.