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 메서드를 이요한 의존성 주입
반응형