본 글은 제가 NestJS 프레임 워크를 통해 개발하면서 깨달은 노하우를 기록한 것입니다. 제가 제시한 방법보다 더 좋은 방법이 있을 수도 있습니다. 지적은 언제나 환영입니다 :)

7 minute read

본 글은 제가 NestJS 프레임 워크를 통해 개발하면서 깨달은 노하우를 기록한 것입니다. 제가 제시한 방법보다 더 좋은 방법이 있을 수도 있습니다. 지적은 언제나 환영입니다 :)



Dependency Injection은 NestJS 프레임워크에서 단골로 등장하는 용어다.

Dependency Injection(DI)는 소프웨어 설계의 “Design Pattern” 중 하나다.

DI는 ‘의존성(Dependency)’과 ‘주입(Injection)’을 함께 사용하는 설계 방법이다. 그래서 두 개념에 대한 이해가 선행되어야 한다!

본편에 들어가기 전에 DI에 대한 간단한 짤을 보고 가자 :smile:

Material from here



의존성; Dependency

  “Dependency is a relationship between components or classes that can be thought of as a ‘uses’ relationship.”

Class $A$ uses Class $B$
$\equiv$ Class $A$ is dependent on Class $B$
$\equiv$ Class $B$ is a dependency of Class $A$

직관적으로 말하면 Class $A$가 Class $B$를 내부 변수로 가진다고 말할 수 있을 것 같다.

이번엔 코드 레벨에서 살펴보자!

public class PetOwner{
  private AnimalType animal;

  public PetOwner() {
    this.animal = new Dog();
  }
}

// code fragment from here

이 코드에서 PetOwner 객체는 AnimalType의 객체를 만들기 위해 Dog 객체에 “의존”한다.

소프트웨어를 만드는 과정에서 ‘의존성’은 피할 수 없다! (의존성 없는 소프트웨어가 어디 있겠는가)

하지만, PetOwnerDog 사이에서는 PetOwner가 사용할 animal이라는 객체 생성과 기능이 모두 Dog에 의존한다. 즉, 아주 긴밀하게 연결(tight coupling) 되어 있다고 볼 수 있다.

이런 상황은 디자인 패러다임 중 하나인 “Low Coupling”에 부합하지 않는다.

왜냐하면, Coupling이 존재한다면 어떤 모듈의 API나 행동이 바뀌다면, 그 모듈에 의존한 다른 모듈까지 코드를 변경해줘야 할 가능성이 생기기 때문이다.

즉, Dog 객체를 수정하면, PetOwner까지 덩달아 수정해야 할지도 모른다.

주입; Injection

“주입”은 객체 생성이 내부가 아니라 “외부”에서 진행하여 그것을 사용할 객체에 주입하는 것을 말한다.

위의 예시 코드에서는 this.animal 객체의 생성을 PetOwner 객체의 “내부”에서 진행했다.

그래서 이번엔 “주입”으로 코드를 수정해보자.

public class PetOwner{
  private AnimalType animal;

  public PetOwner(AnimalType animal) {
    this.animal = animal;
  }
}

수정 이후에는 PetOwner의 생성자에서 animal에 대한 인자를 받는 방식으로 바뀌었다.

이는 객체 내부가 아닌 “외부”에서 AnimalType의 객체을 만들어 PetOwner 객체에 주입하는 꼴이다!

이렇게 만들 경우, PetOwnerDog 사이의 couping은 옅어지게 된다!!

오히려 Dog 대신에 Cat이나 Rabbit 같은 새로운 형태의 AniamlType 클래스를 정의 해줄 수 있다!! (짱인데?)


사실 위와 같이 “의존성”이 발견되는 부분을 “주입”으로 해결해주면 “의존성-주입“이 된다!! :smile:

위의 코드에서는 간단하게 함수 인자로 설정했지만, 객체를 생성의 Factory를 사용할 수도 있을 것 같다!!



여기까지만 알면, DI에 대해 충분히 이해한 것이다.

이제 조금 소프트웨어 설계를 곁들인 개념들을 살펴보자. Design Principle에 대해 익숙하다면, 당연한 것들을 기술하는 것과 불과하다고 느낄 것이다.

제어의 역전 / 의존관계 역전


제어의 역전 Inversion of Control; IoC

  Principle that promotes components that relinquish control of aspects of the code execution to external modules to obtiain “week coupling”.

* relinquish: give up

예를 들어, Sorting 함수의 Comparator가 대표적인 “제어의 역전”이다!

List<T>.Sort(IComparator<T>);

List의 Sort 함수는 실제 Sorting을 수행할 구체적인 방법/기준을 Sort API를 사용하는 Client 단에서 맡김으로써 Control의 일부를 포기하였다!

Client는 IComparator dependency에 대한 implementation, creation, lifetime을 모두 직접 관리한다!

이 “제어의 역전”과 DI에 대한 관계는 아래 문장에서 소개된다.

  “Dependecny injection is a software design pattern that implements inverseion of control for resolving dependencies. A ‘dependency’ is an object that can be used. An ‘injection’ is the passing of a dependency to a dependent object that would use it.

- Wikipedia




의존관계 역전의 원칙 Dependency Inversion Principle; DIP

의존관계 역전의 원칙(DIP)“는 객체 사이에 의존관계를 맺을 때의 가이드 라인이다.

DIP는 의존관계를 맺을 때 변화하기 쉬운 것 또는 자주 변하는 것 보다는 변화하기 어려운 것, 거의 변하지 않는 것에 의존하라는 원칙이다!

이때, 변하기 쉬운 것과 잘 변하지 않는 것을 구분하는 기준은 interface인지와 implementation이다 정도로 이해하면 될 것 같다.

앞에서의 예제를 빌려오자면, Dog과 같은 구체적인 AnimalType에 의존하는 것이 아니라 AnimalType 자체와 같은 추상적인 interface에 의존하라는 것이다!!

interface에 의존함으로써 소프트웨어는 좀더 유연한 시스템이 된다.

좀더 체계를 갖춰 기술해보자면,

A. 하이-레벨 모듈은 로우-레벨 모듈에 의존해서는 안 된다. 둘다 추상에 의존해야 한다.
A. High-level module should not depend upon low-level module. Both should depend upon abstractions.

B. 추상은 상세를 의존해서는 안 된다. 상세는 추상을 의존해야 한다.
B. Abstractions should not depend upon details. Details should depend upon abstraction.

“의존 관계 역전”에서 ‘역전inversion‘의 의미는 모든 것을 로우-레벨 모듈에 의존하는 전통적인 설계에서 하이-레벨 모듈에 의존하는 방식으로 ‘역전’되었다는 의미를 담고 있다.


모델 디자인이 DIP를 만족하는 설계라면, ‘의존성 주입’을 쉽게 수용하는 코드를 작성할 수 있다.

의존성이 필요한 부분이 implementation이 아니라 interface에 의존되어 있기 때문에 그 interface를 기반으로 한다면 의존성을 손쉽게 주입할 수 있다.



자! 이제 처음에 봤던 짤을 다시 한번 보자.

Material from here



참고자료

Categories:

Updated: