아이디어: 객체의 인스턴스를 만들어서 사용하는 곳에서 매개변수로 값을 줄 수 있게 하고 객체는 고정값을 받아쓰지 말자.
의존성 주입의 정의: 클래스간 의존성을 클래스 외부에서 주입하는 것
그래서 의존성 주입을 하면 무엇이 좋을까?
의존성 주입을 하지 않았을 때 코드를 살펴보며 얘기를 해보자.
//users-service.js
const User = require('./User');
const UsersRepository = require('./users-repository');
async function getUsers() {
return UsersRepository.findAll();
}
async function addUser(userData) {
const user = new User(userData);
return UsersRepository.addUser(user);
}
module.exports = {
getUsers,
addUser
}
해당 코드의 문제는 ./users-repository에서 import해왔기 떄문에 다른 곳에서 이들을 사용할 때 유동적으로 사용하지 못하고 결과값이 정해진다.
다른 곳에서 userRepository의 내용말고 userDB에서 가져오고 싶어도 사용할 수 없는 것이다.그럼 이 문제를 해결해보자
const User = require('./User');
function UsersService(usersRepository) { // check here
async function getUsers() {
return usersRepository.findAll();
}
async function addUser(userData) {
const user = new User(userData);
return usersRepository.addUser(user);
}
return {
getUsers,
addUser
};
}
module.exports = UsersService
이렇게 코드를 고치면 다른 곳에서 UserService.getUsers(아무거나) 를 통해 다양한 값을 줘
user.js, userRepository.js → UserService(내부는 정해져 있음 무조건 userRepo)였던 부분을
user.js → UserService(userDb)으로 바꿀 수 있게 되는 것이다.
user.js → UserService(userRepo)
user.js → UserService(kakaoUserRepo)
user.js → UserService(naverUserRepo)
user.js → UserService(githubUserRepo)
의존성 주입의 장점은 테스트코드를 작성할 때 더더욱 들어난다.
위의 의존성주입을 적용하지 않은 코드의 테스트 코드를 보면
const UsersRepository = require('./users-repository');
const UsersService = require('./users');
const sinon = require('sinon') ;
const assert = require('assert');
describe('Users service', () => {
it('gets users', async () => {
const users = [{
id: 1,
firstname: 'Joe',
lastname: 'Doe'
}];
sinon.stub(UsersRepository, 'findAll', () => {
return Promise.resolve(users)
});
assert.deepEqual(await UsersService.getUsers(), users);
});
});
서버 호출은 비용이 높은 작업이다.
이를 테스트하기 위해 위의코드는 userRepository를 모킹하여 완전한 UsersService를 만들어야한다.
의존성을 잘 주입한 코드는 usersRepository를 모킹 라이브러리 없이 모킹할 수 있다.
const UsersService = require('./users');
const assert = require('assert');
describe('Users service', () => {
it('gets users', async () => {
const users = [{
id: 1,
firstname: 'Joe',
lastname: 'Doe'
}];
const usersRepository = {
findAll: async () => {
return users
}
};
const usersService = new UsersService(usersRepository);
assert.deepEqual(await usersService.getUsers(), users);
});
});
타입스크립트로 더욱 편해지는 의존성 주입
type UsersDependencies = { // Here is all dependencies.
usersRepository: UserRepository
mailer: Mailer
logger: Logger
};
export class UserService {
constructor(
private dependencies: UsersDependenceis // looks better isnt it?
) {}
async findAll() {
return this.dependencies.usersRepository.findAll();
}
async addUser(user) {
await this.dependencies.usersRepository.addUser(user); // more easy to access dependencies
this.dependencies.logger.info(`User created: ${user}`);
await this.dependencies.mailer.sendConfirmationLink(user); // more easy to access dependencies
this.dependencies.logger.info(`Confirmation link sent: ${user}`);
}
}
const usersService = new UserService({
usersRepository,
mailer,
logger
});
함수형을 사용해도 다르지 않다!
type UsersDependencies = {
usersRepository: UsersRepository
mailer: Mailer
logger: Logger
};
export const usersService = (dependencies: UsersDependencies) => {
const findAll = () => dependencies.usersRepository.findAll();
const addUser = user => {
await dependencies.usersRepository.addUser(user)
dependencies.logger.info(`User created: ${user}`)
await dependencies.mailer.sendConfirmationLink(user)
dependencies.logger.info(`Confirmation link sent: ${user}`)
};
return {
findAll,
addUser
};
}
const service = usersService({
usersRepository,
mailer,
logger
});
댓글