CS

의존성 주입(DI) 개념 및 Java, JavaScript 사용 방법

cob 2022. 12. 27. 10:25

 

 

DI

 

 

 

의존성이란?
의존성이란 간단하게 두 모듈 간의 연결이다.

 

class A {
	foo() { ... }
}

class B {
    getList() {
        const a = new A();
        const data = a.foo();
        ...
    }
}
  •  B클래스는 A라는 클래스를 사용해서 getList라는 메서드를 사용한다. 이 말은 B는 A에 의존한다고 할 수 있다.

 

 

 

의존성 주입이란?
메인 모듈이 ‘직접’ 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 ‘간접’적으로 의존성을 주입하는 방식이다. 모듈의 확장성과 독립성에 큰 도움을 주고 있다

의존성 주입 구조

  • 이를 통해 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성이 떨어지게 된다. (decoupleing : 비동조화)

 

 

 


1. 의존성 주입의 장단점

1-1) 장점

  • 모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고, 마이그레이션 하기도 수월하다.
마이그레이션이란? 
데이터를 한 위치에서 다른 위치로, 한 형식에서 다른 형식으로 또는 한 앱에서 다른 앱으로 이동하는 프로세스이다.
  • 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 쉽게 추론할 수 있으며, 모듈 간의 관계가 명확해진다.

 

1-2) 단점

  • 모듈들이 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며, 약간의 런타임 페널티가 생기기도 한다.

 

 


2. 의존성 주입의 원칙

  • 상위 모듈은 하위모듈에서 어떠한 것도 가져오지 않아야 한다.
  • 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 한다.

 

 

 

 


3. JavaScript 의존성 주입 모듈 예

3-1) 의존성 주입 전

사용자 추가하고, 사용자들의 리스트 조회
' 사용자 데이터에 의존 '
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
}
  • service가 특정 repository와 연결되어있어 다른 repository로 바꾸고 싶다면, 위의 코드를 싹 다 바꿔야 한다.(확장성이 떨어짐)
  • 모듈의 테스트가 힘들어진다. getUser 메서드가 잘 작동하는지 확인하려면, usersRepository에 대한 가짜 객체를 만들어줘야 한다. 

 

 

3-2) 의존성 주입 후

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);
  });
});
  • service와 repository를 decoupleing 하게 되면서 다른 repository로 언제든지 편하게 적용할 수 있다.
  • 의존성을 주입할 때 개별 의존성을 하나하나 인자로 전달하는 것보다 객체로 감싸서 한 번에 주는 게 더 좋다.
  • describe() : 여러 테스트 케이스를 묶어준다.
  • it() : 테스트 케이스

 

 

3-3) TypeScript function을 사용한 의존성 주입 예

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
});
  • function은 parameter로 의존성을 주입한다. 
  • 의존성들을 모두 미리 세팅해야 하기 때문에 복잡하지만( usersRepository, mailer, logger)  외부 라이브러리로 해결할 수 있다(Awilix, TypeDI, Inversify) 

 

 

 

 


4. Java 의존성 주입 모듈 예

4-1) 의존성 주입 전

햄버거 레시피가 변화하게 되었을 때, 변화된 레시피에 따라서 요리사는 햄버거 만드는 방법을 수정
' 햄버거 가게 요리사는 햄버거 레시피에 의존 '
class BurgerChef {
    private BurgerRecipe burgerRecipe;

    public BurgerChef() {
        burgerRecipe = new HamBurgerRecipe();
        //burgerRecipe = new CheeseBurgerRecipe();
        //burgerRecipe = new ChickenBurgerRecipe();
    }
}

interface BurgerRecipe {
    newBurger();
    // 이외의 다양한 메소드
} 

class HamBurgerRecipe implements BurgerRecipe {
    public Burger newBurger() {
        return new HamBerger();
    }
    // ...
}
  • 의존관계를 인터페이스로 추상화하게 되면, 다양한 의존 관계 맺을 수 있다.

 

 

4-2) 의존성 주입 후

/* 생성자를 이용하는 방법 */
class BurgerChef {
    private BurgerRecipe burgerRecipe;

    public BurgerChef(BurgerRecipe burgerRecipe) {
        this.burgerRecipe = burgerRecipe;
    }
}

class BurgerRestaurantOwner {
    private BurgerChef burgerChef = new BurgerChef(new HamburgerRecipe());

    public void changeMenu() {
        burgerChef = new BurgerChef(new CheeseBurgerRecipe());
    }
}
  • 생성자를 이용한 의존성 주입

 

/* 메서드를 이용하는 방법 (대표적으로 Setter 메서드) */
class BurgerChef {
    private BurgerRecipe burgerRecipe = new HamburgerRecipe();

    public void setBurgerRecipe(BurgerRecipe burgerRecipe) {
        this.burgerRecipe = burgerRecipe;
    }
}

class BurgerRestaurantOwner {
    private BurgerChef burgerChef = new BurgerChef();

    public void changeMenu() {
        burgerChef.setBurgerRecipe(new CheeseBurgerRecipe());
    }
}
  • set 메서드를 이요한 의존성 주입
반응형