• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

Dependency Injection 부분 질문

23.01.23 11:03 작성 조회수 748

1

 토비님 질문이 있습니다.

디자인 패턴이며 oop며 다들 지향하는게

추상화에 의존하라 즉 인터페이스에 의존하는 내용이 많은데요

그 부분을 스프링 빈 사이의 의존성에 연관을 지으니까 조금 의아한 부분이 있더라구요

이 회차 강좌에서 말씀해주신거 처럼

HelloController가 인터페이스(HelloService)를 의존한다고 해도

결국에는 런타임시 SimpleHelloService에 의존적인거죠?

만약 런타임시 CompleHelloService에 의존으로 하려면

결국에는 HelloController 소스를 수정해야하는거죠?

 

답변 1

답변을 작성해보세요.

3

HelloService 인터페이스에만 의존하도록 코드를 작성한다고 했어도, 실제 의존할 오브젝트를 선택하는 것을 직접 해야 한다면 말씀하신 것처럼 HelloController의 코드가 변경이 되야 합니다. 사실상 클래스에도 의존하는 코드가 되는 것이죠.
바로 이 때문에 어셈블러라는 제3자가 필요한 겁니다. HelloController가 SimpleHelloService 오브젝트를 사용할지, ComplexHelloService를 사용할지를 직접 코드에서 결정하지 않도록, 어셈블러라고 불리는 제3의 오브젝트가 이를 결정해서 생성자에 런타임시에 주입을 해주는 것입니다. 이러면 실제 사용하게 될 오브젝트의 클래스가 바뀌더라도 HelloController의 코드는 전혀 바뀌지 않아도 되는 것이죠.
의존관계주입(Dependency Injection)이란 바로 이걸 말하는 것입니다. 추상화된 인터페이스만 의존하는 것과 의존관계주입(DI)를 통해서 런타임시에 의존 오브젝트를 주입 받아서 사용하는 것, 이 두가지가 다 필요합니다.

ycseol님의 프로필

ycseol

질문자

2023.01.23

image==================================================

 

image==================================================

 

image

==================================================image

안녕하세요 토비님

여기 상기에 보시는 것처럼 SubjectController 는 CallService에 의존성을 갖습니다.

CallService는 인터페이스이구요, 그 구현체는 2개가 있습니다.

저 상태에서 SubjectController 의 소스를 건드리지 않고 상황에 따라

AcallService 를 호출하거나 또는 BcallService를 호출하거나 정할 수 있을까요?

@Primary 또는 @Qualifier(심지어 이 어노테이션 또한 Cotroller에 언급해야하니 ㅠㅠ)를 제외하고 말이죠

 

스프링 빈 의존성관계에서 인터페이스에 의존하면 요청하는 코드를 바꾸지 않고 유연한 상태를 만들수 있다고 하는데 , 자바에서는 이해가 됩니다. 그런데 스프링에서는 이해가 잘 안되서요. 왜냐하면 스프링 빈에서 의존성 관계를 설정할때 인터페이스를 적용해도 명확한 구현체를 사용하겠다라는 언급이 꼭 되어야만 하는거 같아서 말이죠.

만약 그렇지 않다면 아래와 같은 에러는 뜨지 않아야 할거 같기두 하구요

참고로 @Primary 또는 @Qualifier가 없을때 컴파일 시 나는 에러입니다.

image

 

"저 상태에서 SubjectController 의 소스를 건드리지 않고 상황에 따라 AcallService 를 호출하거나 또는 BcallService를 호출하거나 정할 수 있을까요?"

DI는 보통 애플리케이션이 실행되는 시점에 의존관계가 결정됩니다. 따라서 SubjectController의 오브젝트가 처음 만들어질 때 AcallService나 BcallService 중 어떤 걸 사용할지를 SubjectController 코드 외부에서 결정해서 주입을 받는 것이죠. 그래서 애플리케이션이 동작하는 내내 사용할 대상이 유지가 되는 게 일반적입니다.

그런데 "상황에 따라"라는 말씀은 애플리케이션이 실행되는 중에 두 서비스를 선택적으로, 혹은 두 가지 다 사용하고 싶으시다는 말씀으로 보이네요? 이런 경우는 일반 DI로 해결할 수가 없습니다.
자바에서도 예를 들어,
List list = new ArrayList(); 라고 하면 이후에 list는 List 인터페이스 타입이지만 ArrayList 클래스의 구현 방식으로만 동작을 하겠죠.
그런데 이런 식으로 만들어도 어떤 때는 list변수가 ArrayList 처럼 동작하고 어떤 때는 LinkedList 처럼 동작하게 만들고 싶다, 이런 건 쉬운 문제가 아닙니다. 자바의 다형성으로도 간단히 되는 것이 아니죠.

의도하신게 이미 서버가 떠 있는 상황에서 조건이 뭐일지는 모르겠으나, 아무튼 경우에 따라 AcallService, BcallService가 선택적으로 호출되게 만들고 싶다면, 라우팅(routing) 기능을 담당할 또 다른 오브젝트를 하나 더 사용할 수 있습니다. 그래서 SubjectController에게 RoutingCallService를 주입해주고, 여기서 "어떤 상황"을 판단해서 AcallService, BcallService를 골라서 호출하도록 만들어야 합니다. 혹은 Provider라는 걸 이용해서 그때그때 호출할 오브젝트를 다시 가져오도록 SubjectController의 DI 방식을 바꿔야 하는데요. 이건 굉장히 고급 주제이고, 일반적으로 사용할 일이 거의 없습니다.

어떤 이유 때문에 런타임시 오브젝트 호출 대상을 상황에 맞춰 선택하게 하려는지가 궁금합니다. 저라면 상황에 따라 기능이 달라지는 부분은 특정 CallService 안에 코드로 구현을 할 것 같습니다.

일반적인 DI가 적용되는 기술은 애플리케이션의 시작 시점에 의존 관계가 고정이 되는 게 일반적이고 이 방식을 사용하면 충분히 구현이 가능합니다.

"왜냐하면 스프링 빈에서 의존성 관계를 설정할때 인터페이스를 적용해도 명확한 구현체를 사용하겠다라는 언급이 꼭 되어야만 하는거 같아서 말이죠."

자바도 인터페이스를 사용해서 만드는 코드도 어디선가는 어떤 구현체를 사용할 것이다가 명확하게 등장해야 합니다. List 인터페이스를 쓰는데, 어디선가는 이게 ArrayList라는 구현 클래스다라는 게 나와야 하죠.

스프링도 마찬가지입니다. 다만, 그걸 사용하는 쪽(Controller)가 아닌 스프링 컨테이너의 메타정보로 분리시킬 수 있다는 게 스프링이 하는 일이자 DI의 장점입니다.

좀 더 구체적인 시나리오를 가지고 얘기해주시면 더 자세히 설명해드리겠습니다.

ycseol님의 프로필

ycseol

질문자

2023.01.24

친절한 답변 정말 감사드립니다. 이 댓글은 질문이 아닙니다.

 

토비님께서 말씀해주신 부분 보니까 마음이 편안하게 와닿네요

List도 인터페이스지만 결국에는 ArrayList라는 구현체를 언급해줘야 하는거!

완전 와닿습니다.

 

제 질문의 출발은 여기였던거 같아요

==================================================

image==================================================

 

컨트롤러에서 상황에 따라 AcallService 나 BcallService를 사용하고 싶은거였고

또 확장성 고려하면 C,D,E...callService가 생길텐데.. 어떻게 해결을 해야하나

그렇다고 Controller에 몽땅다 의존성 주입을 걸어야 하나??

어떻게 개선하지?

이랬던거 같아요

그래서 Factory Method pattern 을 착안해서 개선을 했는데 말씀해주신 routing 을 위한 오브젝트가 혹시 이게 아닌가 해요

아래와 같이 개선을 했는데 다시 보니 개선이라기 보다는 많은 부담을 단지 뒤로 넘긴거 같은 모양이 된거 같더라구요

==================================================

image

==================================================

image

==================================================

Factory에 C,D Service 가 추가되면 의존성 주입을 계속 걸어 줘야하는가 싶기도 하구요

 

토비님의 DI 부분을 보다가 위와 같이 제가 고민하고 있는 부분이 딱! 떠올라서 질문을 드린거였습니다.^^

 

그리고 뒷부분의 코드를 바꾸었는데도 앞 부분의 코드변화에 전혀 영향이 없습니다. OCP가 지켜진거죠 라는 내용을 여러 강좌나 책에서 본거 같아요. 물론 코드를 작성할때는 공감이 됩니다. 하지만 런타임시에도 발생하는 서비스도 그래야만 하는거겠죠? 뒷부분의 서비스가 바뀌었는데 앞 부분의 서비스에 영향이 없습니다. 처럼 말이죠 그런데 스프링DI 를 하다보니 아예 뒷부분의 서비스가 바뀔일이 없는거 같아서요. 왜냐면 인터페이스와 의존성을 맺어도 명백히 특정 구현체를 사용할꺼야(@primary, @Qualifier) 라고 알려줘야 하니까 말이죠

 

gguu님의 프로필

gguu

2023.01.25

id값을 받아와, 추상 팩토리 패턴도 좋아보입니다.

https://refactoring.guru/ko/design-patterns/abstract-factory