본문 바로가기
JS&TS

의존성 주입 With JS Code

by Luke K 2022. 7. 29.

아이디어: 객체의 인스턴스를 만들어서 사용하는 곳에서 매개변수로 값을 줄 수 있게 하고 객체는 고정값을 받아쓰지 말자.

의존성 주입의 정의: 클래스간 의존성을 클래스 외부에서 주입하는 것

그래서 의존성 주입을 하면 무엇이 좋을까?
의존성 주입을 하지 않았을 때 코드를 살펴보며 얘기를 해보자.

//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
});

출처:

https://tsh.io/blog/dependency-injection-in-node-js/

댓글