의존성 주입이란?

#SPRING_005
Jan 30, 2024
의존성 주입이란?
 

🥭 개요

의존성 주입 (Dependency Injection)은 소프트웨어 설계의 일반적인 기법 중 하나이다. 이는 객체들 간의 의존성을 줄이고, 소프트웨어의 유연성과 재사용성을 높이기 위해 활용된다. 그럼 이 의존성이라는 것은 무엇을 말하는 것일까???
 

🍏 강한 의존 관계란??

notion image
위의 그림처럼 바리스타가 한곳의 생산지에서 커피를 공급받아서 사용하게 된다면, 이 생산지에서 부득이한 자연 재해등이 발생할 경우 바리스타의 사업에도 차질이 생기게된다. 이렇게 한 곳에서의 문제가 연쇄적으로 문제가 생기는 관계가 의존성이 강한 관계라고 정의할 수 있다.
 
소프트웨어에서도 마찬가지로 한 객체가 다른 객체의 기능이나 데이터에 의존하는 경우를 말한다. 만약 객체 A가 필드값으로 객체B의 인스턴스를 가지게 된다면, 객체 A는 객체 B의 인스턴스 유무에 따라서 객체 A의 존재가 결정되는 것이다. 이런 관계를 ‘객체 A가 객체 B를 의존하는 관계다’ 라고 말한다. 이런 경우에는 다음과 같은 단점들이 있다.
  • 유지보수의 어려움
    • 한 객체가 다른 객체를 강하게 의존하는 경우, 한 객체를 변경하면 의존하는 다른 객체들도 영향을 받을 수 있다. 이로 인해서 작은 변경 사항도 시스템 전체에 파급 효과를 일으킬수 있어 유지 보수가 어려워진다.
  • 낮은 재사용성
    • 강한 의존관계는 객체를 특정 컨텍스트나 다른 객체들과 분리하여 재사용하기 어렵게 만든다. 특히 자바같은 객체 지향적인 언어에서는, 강한 의존성을 보이는 객체를 사용하고 싶을때는 사용할 때마다 객체를 생성하고 그리고 불필요한 의존된 객체도 생성해야되는 경우가 생기므로, 재사용하기가 매우 어렵다.
  • 테스트의 어려움
    • 강한 의존성은 단위 테스트를 더욱 어렵게 만든다. 테스트시 그 객체가 의존하는 객체들도 같이 고려되어야 하며, 이는 테스트를 더욱 복잡하게하고 테스트가 어려워진다.
  • 유연성 감소
    • 시스템 한 부분을 변경할 때 의존성이 높은 만큼 강하게 영향을 받으므로, 새로운 기능의 추가나 기존 기능의 변경을 더욱 어렵게 만든다. 이는 시스템의 진화와 확장성에 제한적이게 된다.
 

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

여기 의존성 주입에서 주입(Injection)때문에 개념이 헷갈릴수도 있지만, 단어를 보기전에 이 개념을 추상적으로 한 번 시각화 해보자.
notion image
이 “주입(Injection)”이라는 단어가 사용된 이유는 객체가 자신이 필요한 의존관계 (바리스타는 원두가 필요하다라는 의존관계)가 필요할 시점에 매번 생성하는 것이 아니라, 외부(IoC 컨테이너 또는 스프링 프레임워크)에서 그 필요한 의존성을 ‘주입’받는 과정을 잘 설명하기 때문이다. 이 의존성주입을 사용함으로서 얻을 수 있는 주요 장점은 다음과 같다.
  • 낮은 결합도
    • DI를 사용하면 클래스들이 서로 긴밀하게 연결되지 않고, 외부에서 의존성을 제공받게 된다. 이는 클래스 간의 결합도를 낮추고, 이 의미는 다른 곳에서의 문제가 현재 객체의 문제가 되지 않는다는 뜻이다.
  • 코드 재사용성 증가
    • 의존성이 외부에서 주입되므로, 동일한 클래스를 다른 곳에서 또는 의존성 구성시에 다시 사용할 수 있다. 이는 코드의 활용도가 높아지고, 중복을 획기적으로 낮춘다.
  • 유지보수성 개선
    • 의존성을 관리하는 주체가 명확하게 정의되고 관리되므로 (주입해주는 주체가 명확하기 때문에), 시스템의 한 부분을 변경해도 다른 부분에 미치는 영향이 최소화 된다. 그리고 문제가 생겼을때 어디서 유지 보수해야되는지 명확해진다.
  • 테스트 용이성
    • DI를 사용하면, 실제 사용 객체대신 테스트용 목(mock)객체나 스텁(stub)을 쉽게 사용할 수 있다. 이는 단위 테스트를 더욱 쉽게할 수 있게 만듬으로서, 중간 점검을 용이하게 하므로 전체 프로세스의 안정성이 높아진다.
  • 유연성 향상
    • 다양한 운영 환경에서 서로 다른 구성이나 의존성을 쉽게 적용해 볼 수 있다. 예를 들어, 개발 환경과 프로덕션 환경에서 서로 다른 의존성을 주입해 볼수 있다.
 

😛 코드 비교

🫤 의존성 주입을 사용하지 않은 경우

일단 먼저 예시로 IOC컨테이너를 사용하지 않고, 의존성을 관리하는 예시를 살펴보자.
notion image
위의 코드에서는 MyController가 라우팅을 역할을 하는 클래스이고, 각 요청에 따라 MySerivce로 안내하는 역할을 한다. 하지만 MyController는 MyService의 객체가 있어야지 존재할 수 있으므로, 강한 의존관계라고 볼수 있다.
 
이 때, 새로운 기능을 추가하고 싶다면은 MyController는 MyService 객체를 생성해서 다른 서비스를 구현하거나, 아니면 새로운 ReadService객체를 생성해야한다.
notion image
이렇게 매버 사용시마다 객체를 생성해야되면 대표적 단점은 부하가 많이 걸리고, 중복되는 코드가 많아지게 된다. 같이 일하기 싫어질 수 있다..
 

☺️ 의존성 주입을 사용한 경우

스프링에서 빈의 생성과 관리는 IoC 컨테이너가 자동으로 처리하지만, 빈을 사용하려면 스프링이 제공하는 의존성 주입 메커니즘을 사용해야 한다.
 

1️⃣ @Service 어노테이션으로 빈 객체로 등록하기

notion image
위 처럼 @Service 어노테이션 덕분에 MyService 클래스는 스프링 IoC(Inversion of Control) 컨테이너에 의해 빈(Bean) 객체로 관리된다. IoC는 MyService 빈의 생명주기를 관리하며, 빈의 생성부터 소멸까지 모든 과정을 관리한다.
 

2️⃣ 빈 사용하기 : MyService 객체

notion image
MyService 빈을 사용하려면, 다른 빈(예: 컨트롤러) 내에서 의존성으로 주입받아야 한다. 위의 예시처럼 MyController 의 생성자에 매개변수로 객체를 넣어주면 주입이 완료가 된다. 스프링은 이 생성자를 호출하며, IoC 컨테이너에 있는 MyService 빈의 인스턴스를 자동으로 전달한다.
 
이렇게 직접적인 객체 생성(new MyService())을 사용하지 않고 스프링 프레임워크가 제공하는 의존성 주입 메커니즘을 통해 빈을 활용하면 된다. 이렇게 함으로써 느슨한 결합과 높은 유연성을 얻을 수 있고 중복되는 코드 또한 줄일 수 있다.
 
Share article

AI_Nomads