Dependency

JungChihoon·2021년 1월 27일
6

개념

목록 보기
1/3

Dependency 란?

  • 어떤 클래스 A가 다른 클래스(또는 인터페이스)B를 이용할 때 A가 B에 의존한다고 한다.
  • A는 B없이 작동할 수 없고 B를 재사용하지 않으면 A또한 재사용 할 수 없다.
  • 여기에서 클래스 A는 Dependant라고 하고 클래스 B는 Dependency라고 한다.
  • Dependant는 자신의 Dependency들에게 의존한다.
  • 서로를 이용하는 두 클래스는 각각 coupled라 한다.
  • 클래스 사이의 coupling은 느슨할 수 도 타이트 할 수도 있다.
  • tightness의 정도는 연속적이다.
  • 의존성 또한 강함/약함으로 나타낼 수 있다.
  • 타이트한 커플링은 강한 의존성을 만들고, 느슨한 커플링은 약한 의존성을 만들거나 의존성을 만들지 않을 수도 있다.
  • 의존성, 커플링은 방향이 존재한다.
    - A가 B에게 의존한다고 해서 B 또한 A에게 의존적인 것은 아니다.

높은 의존성이 좋지 않은 이유

높은 의존성은 모듈의 재사용을 감소: 독립적이지 않으므로 다른 곳에 사용하기 어려움

의존성 규칙

모든 소스코드 의존성은 반드시 outer에서 inner로, 저수준에서 고수준 정책을 향해야 한다.

고수준 : 상위 수준의 개념, 추상화된 개념
    ex. 업무로직: 데이터를 저장한다, 견적을 계산한다
저수준 : 추상화된 개념을 실제 어떻게 구현할지에 대한 세부적인 개념
    ex. 세부사항: MySQL에 데이터를 저장한다, 선택 프로젝트의 견적을 계산한다

의존성 규칙의 설명에 대한 추가 설명

  • 고수준의 모듈은 저수준의 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존 해야 한다.

  • 추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.

  • 자주 변경되는 구현 클래스에 의존하지 마라. 만약 어떤 클래스의 참조를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라. 만약 어떤 함수를 호출해야 한다면, 호출 되는 함수를 추상 함수로 만들어라.

  • 일반적으로, 추상 클래스와 인터페이스는 자신에게서 유도된 구체적인 클래스 보다 훨씬 덜 변한다.

이러한 의존성 규칙을 지키기 위해서는 "What data crosses the boundaries", "Crossing boundaries"와 같이 각 계층의 경계에서 어떤 것을 주의해야하는 지를 알아야 한다.

What Data crosses the boundaries

: "경계간의 테이터를 전달할 때 무엇을 전달해야하는가"

  • 단순하고, 독립된 형태의 데이터 구조 사용 필요
  • DB형식의 데이터 구조 또는 Framework에 종속적인 데이터 구조가 사용된다면 저수준의 데이터형식을 고수준에서 알아야하므로 의존성 규칙에 위반하게 됨

Crossing boundaries

: "제어의 흐름은 내부에서 외부로 향할 수 있는데 이는 의존성 규칙을 위배하게 되므로 의존성 역전의 원칙을 이용하여 이를 해결해야한다"

  • 제어의 흐름이 고수준에서 저수준으로 의존하게 되는 경우의 예

  • 의존성 역전의 원칙을 적용하여 수정한 예
    1) 추상적인 Repository 인터페이스를 두어 Service가 이를 참조
    2) 구체적인 RDBRepository가 이러한 인터페이스를 구현하여 추상적인 Repository 인터페이스를 참조
    3) 1), 2)를 통해 소스코드의 의존성을 역전시킬 수 있으며 Service는 DB의 세부사항을 알 수 없게되고, 따라서 DB의 변경에도 영향을 받지 않게 된다.


Dependency Injection(의존성 주입)이란?

용어

  • 의존성

//수정필요
 B 클래스에서
A 클래스를 내부에 변수로 사용하게 됨으로써
B 클래스는 A 클래스에 의존관계가 생기게 됩니다.
  • 주입

    내부가 아닌 외부에서 객체를 생성해서 넣어주는 것

  • 의존성 주입

    의존성 주입은 하나의 패턴 : 의존성들을 인자들로 전달해준다면 모듈안에서 의존성들을 불러오거나 새로 만드는 것을 피할 수 있음.

DI의 장점

  • 모듈을 독립적으로 만들 수 있게 해줌
  • 코드를 단순화 시켜줌
  • 테스트에 용이
  • 재사용성을 높여줌
  • 종속적인 코드의 수 감소
  • 종속성이 감소 > 코드변경/수정에 용이
  • 가독성이 좋아짐 (왜 사용하는지 파악하기가 수월해지므로)
  • 결합도(coupling)는 낮추면서 유연성과 확장성은 향상 시킬 수 있음
  • 객체간의 의존관계를 설정할 수 있음
  • 객체 간의 의존 관계를 없애거나 줄일 수 있음

의존성 주입 방법

의존성 주입 전 vs 후

참조 : https://velog.io/@moongq/Dependency-Injection

아래코드는 의존성 주입 전 일반적인 코드이다.

//user-service.js
const User = require('./User_);
const UsersRepository = require('./users-repository);
                     
async function getUsers() {
  return UserRepository.findAll();
}

async function addUser(userData) {
  const user = new User(userData);
  
  return UserRepository.addUser(user):
}

module.exports = {
  getUsers,
  addUser
}

위 코드에서 user-service.js는 비즈니스 로직, user repository는 데이터들에 대해 책임지고 있다. 하지만 두가지의 문제점이 있다.

  1. service가 특정 repository와 연결되어있다.
    다른 repository로 바꾸게 되면 코드를 모두 수정해야 함 > 확장성이 떨어짐을 의미

  2. 모듈의 테스트가 힘들어진다.
    테스트를 위해 외부라이브러리를 사용해야함 > 의존성 주입은 외부라이브러리 이용을 최소화 할 수 있게 해줌

아래 코드는 의존성 주입(userRepository를 인자로 전달하는)을 쓴 코드이다.

const User = require('./User);

function UsersService(usersRepository) {
  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

의존성 주입 : Class vs function

참조 : https://velog.io/@moongq/Dependency-Injection

1. 클래스 형태의 의존성 주입

: 클래스의 생성자(constructor)에 의존성 주입, 개별의존성을 개별인자로 전달하는 것보다 객체로 감싸서 한번에 전달이 좋음

class UsersService {
  constructor({ usersRepository, mailer, logger }) {
    this.usersRepository = usersRepository;
    this.mailer = mailer;
    this.logger = logger;
  }
  async findAll() {
    return this.usersRepository.findAll();
  }
  async addUser(user) {
    await this.usersRepository.addUser(user);
    this.logger.info(`User created ${user}`);
    await this.mailer.sendConfirmationLink(user);
    this.logger.info(`Confirmation link sent!: ${user}`);
  }
}

module.exports = UsersService;

//ex. userController.js
require('./userService.js)
const usersService = new UsersService({
  usersRepository,
  mailer,
  logger
}); 

2. 함수 형태의 의존성 주입

export const userService = ({ usersRepository, mailer, logger }) => {
  const fildAll  = () => usersRepository.findAll();
  addUser = (user) => {
    await usersRepository. addUser(user);
    logger.info(`User created: ${user}`);
    await mailer.sendConfirmationLink(user);
    logger.info(`Confirmation link sent ${user}`);
  };
  
  return {
    findAll,
    addUser
  };
}

const service = usersService({
  usersRepository,
  mailer,
  logger})       

의존성 세팅

의존성 주입의 단점은 이용하려는 의존성들을 모두 미리 세팅, 구조화 해둬야한다는 것이다.
아래 코드와 같이 userService를 생성하기 전에 의존성들을 모아두는 곳을 흔히 container라고 부른다.

//container.js
const UsersRepository = require('./users-repository');
const Mailer = require('./mailer');
const Logger = require('./logger');
const UsersService = require('./users-service');
const InMemoryDataSource = require('./users-repository/data-source/in-memory');

const logger = new Logger({
  level: process.env || 'dev'
});

const dataSource = new InMemoryDataSource();

const mailer = new Mailer({
  templates: '/emails',
  logger
});

const usersRepository = new UsersRepository({
  logger,
  dataSource
});

const usersService = new UsersService({
  usersRepository,
  mailer,
  logger
});

module.exports = {
  usersService
}

위 코드와 같이 container를 일일이 세팅하는 것이 번거로울 때는 알아서 의존성을 찾고 가져와주는 AwilixTypeDI와 같은 라이브러리를 사용 할 수도 있다.


참조

용어 참조

추상 클래스, 구현클래스 정의 : https://zetawiki.com/wiki/%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4,_%EA%B5%AC%EC%B2%B4%ED%81%B4%EB%9E%98%EC%8A%A4

  • 추상 클래스 (abstrate class)
    : 추상적인 클래스. 인스턴스화 불가능
    : ex. 이동수단
  • 구현 클래스 (concrete class)
    : 구체적인 클래스. 인스턴스화 가능
    : ex. 트럭, 자전거, 배, 비행기...

: 추상 클래스
https://itewbm.tistory.com/entry/%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4abstract-class%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0

의존성 참조

https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f
https://getchan.github.io/til/node_DI/
https://velog.io/@moongq/Dependency-Injection
https://getchan.github.io/til/node_DI/

profile
주니어 개발자

0개의 댓글