DI

appti·2023년 5월 8일
0

학습 로그

목록 보기
6/8

로드맵 질문

DI란 무엇인가?

토비의 스프링(p.114)에서는 다음과 같은 세 가지의 조건을 만족해야 DI라고 표현하고 있습니다.

  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다.
    • 그러기 위해서는 인터페이스만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

이 조건을 토대로, DI를 정의하면 다음과 같을 것입니다.

  • 특정 이 동작하기 위해 필요한 의존 관계를 내부적으로 직접 생성하지 않음
  • 컴파일 시점에서는 인터페이스로 의존하고 있음
  • 런타임 시점에는 필요한 구현체들을 컨테이너나 팩토리 같은 제 3의 존재가 주입

을 생성하기 위한 의존 관계를 내부가 아닌 외부에서 주입받게 되므로, 스프링에서는 이를 제어의 역전(Inversion of Control, IoC)이라고도 표현합니다.

DI를 사용하면 무엇이 좋은가?

DI를 사용하면 다음과 같은 이점을 얻을 수 있습니다.

  • 코드의 가독성이 좋아집니다.
  • 단일 책임 원칙(SRP)를 지킬 수 있습니다.
    • DI를 받지 않고 내부에서 필요한 객체를 직접 생성하는 경우, 해당 클래스의 비즈니스 로직에 대한 책임 + 필요한 객체를 생성하는 책임 모두를 가지고 있다고 판단할 수 있습니다.
  • 의존 관계를 가지고 있는 객체들 간의 결합도를 낮출 수 있습니다.
    • 컴파일 시점의 의존 관계는 유지한 채로, 런타임 시점에서의 실제 의존 관계()을 손쉽게 변경할 수 있습니다.
    • 기능 확장에 유리하며, 변경에 대한 유연성을 얻을 수 있습니다.
      • 즉 유지보수성이 뛰어납니다.
  • 테스트 하기 수월해집니다.
    • 외부에서 의존 관계()를 주입해주기 때문에, 컴파일 시점의 의존 관계를 그대로 유지한 채로, 테스트의 런타임 시점의 의존 관계를 변경할 수 있습니다.

의존성을 주입하는 방법에는 무엇이 있는가?

스프링에서는 @Autowired를 통해 의존성을 주입합니다.

이렇게 의존성을 주입하는 방법은 다음과 같이 4가지가 존재합니다.

  • 생성자 주입
  • setter 주입
  • 필드 주입
  • 메소드 주입

생성자 주입

public class MyBean {
    
    private final A a;
    
    @Autowired
    public MyBean(A a) {
        this.a = a;
    }
}
  • 장점
    • 주입받고자 하는 필드에 fianl 키워드를 추가할 수 있습니다.
      • 해당 필드를 변경 불가능한 불변으로 설정할 수 있습니다.
    • 필요한 의존 관계가 null이 되지 않도록 보장할 수 있습니다.
    • 항상 완전히 초기화된 상태로 빈이 생성됩니다.
    • 단 한 번만 의존관계 주입이 진행됨을 확신할 수 있습니다.
      • 생성자를 통해 의존관계 주입을 진행하기 때문입니다.
  • 단점
    • 동적으로 의존 관계를 변경할 수 없습니다.
    • 순환 참조를 처리할 수 없습니다.
  • 특이사항
    • 단점 두 가지는 관점에 따라 모두 장점으로 볼 수 있습니다.

수정자(Setter) 주입

public class MyBean {
    
    private A a;
    
    @Autowired
    public void setA(A a) {
        this.a = a;
    }
}
  • 장점
    • 의존 관계를 빈 생성 이후에도 변경할 수 있습니다.
  • 단점
    • final 키워드를 추가할 수 없습니다.
      • 불변이 아니며, 초기 값이 없으면 null이 될 수 있습니다.
      • 적절한 기본 값을 할당할 수 있을 때에만 사용해야 합니다.

필드 주입

public class MyBean {
    
    @Autowired
    private A a;
}
  • 장점
    • 간단합니다.
  • 단점
    • 개발자가 직접 의존 관계를 주입할 수 없어, 테스트를 수행하기 힘듭니다.
    • 프레임워크에 지나치게 의존적입니다.
    • final 키워드를 추가할 수 없습니다.

메소드 주입

public class MyBean {
    
    private A a;
    private B b;
    
    @Autowired
    public void init(A a, B b) {
        this.a = a;
    }
}
  • 장점
    • 한 번에 여러 Bean을 받아 주입할 수 있습니다.
  • 단점
    • final 키워드를 추가할 수 없습니다.
  • 특이 사항
    • 수정자 주입은 메소드 주입의 방식 중 하나라고 볼 수 있습니다.

어떤 기준으로 나눠서 사용하는가?

다음과 같은 기준으로 사용할 수 있을 것이라 생각합니다.

  • Production 환경
    • 생성자 주입
  • Test 환경
    • 필드 주입
  • 나머지 DI 방식은 사용하지 않음

생성자 주입을 사용해야 하는 이유 두 가지는 다음과 같습니다.

  • final 키워드 추가(불변 및 null을 허용하지 않음)
  • 순환참조 불가능(방지)

이는 다른 방식에 비해 월등히 뛰어난 방식이기 때문에, 다른 방식을 사용할 이유가 없다고 볼 수 있습니다.

다만 Test 환경에서는 필드 주입을 사용할 것이라 명시했는데, 이는 편의성 때문입니다.

궁금증

  • 의존 관계가 무엇일까?
    • 의존 관계란 하나의 객체가 다른 객체를 사용하는 것을 의미합니다.
    • AB를 사용하는 경우, B가 변경되면 A가 영향을 받게 됩니다.
    • 이러한 영향을 최소화하기 위해 외부에서 의존 관계를 주입해주는 것입니다.

  • 인터페이스를 사용하지 않으면 DI가 아닌걸까?
    • 이는 DI의 목적을 생각해봐야 할 것 같습니다.
    • DI는 결국, AB를 사용할 때, B의 변경에 영향을 최소화하기 위해 사용하는 것입니다.
    • 이를 위해 B를 인터페이스로 추상화하고, B의 구현체를 제 3의 존재가 주입해주는 것입니다.
    • 여기서 B를 추상화하지 않고 B의 구현체를 그대로 사용한다면, 외부에서 주입받는다고 하더라도 AB에게 계속 영향을 받게 됩니다.
    • 이로 인해 DI를 수행하는 이유가 없어지므로, 엄밀히 따지자면 DI가 아니라고 볼 수 있습니다.
    • 다만 으로 등록할 모든 클래스에 대해 추상화를 적용하는 것은 비용이 커질 수 있기 때문에, 환경에 따라 적절히 선택해야 할 것입니다.

  • IoC? DI?
    • IoC는 객체의 생성과 구성에 대한 제어 권한을 객체 자체에서 외부로 이동하는 디자인 패턴입니다.
    • 즉, 특정 객체 입장에서 볼 때 외부에서 자신에게 필요한 구성을 전달 받는 경우, 이 외부가 컨테이너인지, 팩토리인지 상관 없이 이를 모두 IoC라고 볼 수 있습니다.
    • 그래서 토비의 스프링에서 DI 조건 중 하나로 런타임 시점에는 필요한 구현체들을 컨테이너나 팩토리 같은 제 3의 존재가 주입이 있는 것입니다.
    • DI는 이러한 IoC를 구현하기 위한 방법 중 하나입니다.
    • 다만 DI는 주로 스프링에서 많이 표현되기 때문에 컨테이너, 이라는 단어를 통해 설명이 되었다고 볼 수 있습니다.

  • 결합도? 응집도?
    • 이는 인터페이스를 사용하지 않으면 DI가 아닌걸까?와 연관이 있는 항목으로 보입니다.
    • AB를 사용할 때, B의 변경에 A가 영향을 받으므로 AB의 결합도가 강한 상태입니다.
    • DI와 같은 IoC 없이 AB를 직접 생성해주는 경우, A에서 필요한 비즈니스 로직과는 별개로 A의 로직을 수행하기 위해 필요한 B를 생성해주는 과정까지 책임을 지니고 있습니다.
      • A의 비즈니스 로직 책임 + B를 생성해주는 책임
    • 그러므로 현재 A의 상태는 B를 생성해주는 책임으로 인해 결합도가 높고(B의 변경에 영향을 받기 쉬워짐), 응집도가 낮습니다.(A의 비즈니스 로직뿐만 아니라 B의 생성 책임까지 지니고 있음)
    • 그러므로 DI의 장점 중의 하나로 결합도를 낮출 수 있으며, 이 결합도를 낮추는 과정이 B의 생성 책임을 외부에게 위임하는 것이므로 응집도를 높일 수 있게 되는 것입니다.

  • 왜 생성자 주입을 사용하면 null이 되지 않을까? 다른 주입 방법은 null일 수 있다는 것일까?
    • 생성자 주입의 경우, 객체 생성 시점에 DI가 이루어지기 때문에 null이 되지 않습니다.
    • 이는 유일하게 생성자 주입만이 final 키워드를 붙일 수 있는 이유기도 합니다.
    • 필드 주입은 객체를 생성한 직후에 의존 관계가 주입되며, 메소드 주입(일반 메소드 + setter)는 객체를 생성한 이후 의존 관계가 주입됩니다.
    • 즉, 모두 생성 이후에 의존 관계 주입이 이루어지므로 특정 상황에서는 null일 수 있습니다.

  • 왜 필드 주입은 프레임워크에 지나치게 의존적일까?
    • 생성자나 setter가 없는 상태에서 필드 주입을 사용하게 되면, 스프링의 DI Container에 의해서만 의존관계 주입이 가능합니다.
    • 스프링 외부에서 해당 객체에 의존 관계를 주입하기 위해서는 리플렉션을 사용할 수 밖에는 없습니다.
profile
안녕하세요

0개의 댓글