당니의 개발자 스토리
생성자 주입을 선택해라! 본문
생성자 주입을 선택해라!
이번 시간에는 '생성자 주입을 선택해라!' 라고 굉장히 단언적으로 얘기를 하죠. 이전에 봤던 여러가지 주입 중에서, 왜 생성자 주입을 선택하는 게 좋은지 설명을 해드리겠습니다.

과거에는 수정자 주입과 필드 주입을 많이 사용을 했어요. 그런데 최근에는 스프링 뿐만 아니라, 다른 DI 프레임워크들도 대부분이 생성자 주입을 권장합니다. 그 이유는 다음과 같아요.

첫번째 불변입니다. 앞에서도 말씀드렸는데 대부분의 의존관계 주입은 처음에 딱 애플리케이션 조립할 때, 마치 비유를 하자면 공연을 하기 전에 배역이 다 정해지는 거예요. 그러면 애플리케이션이 종료할 때까지 의존관계를 변경할 일은 거의 없어요. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안 돼요.
그래서 목적 자체가 애플리케이션이 시작하고 끝날 때까지 바꾸지 않을 거야! 라고 하면 생성자 주입이 최고의 선택이죠. 처음에 딱 세팅이 되면, 바꿀 수 있는 방법이 없으니까요. 스프링 빈 자체가 싱글톤이고, 생성자를 호출해서 값을 새로 넣고 싶다고 하더라도, 그건 생성자를 호출하는 순간부터 이미 다른 객체인 거죠.
이미 값이 세팅됐던 그 객체 인스턴스를 바꿀 수 있는 방법이 아예 없습니다.
그런데 이처럼 생성자 주입이 아니고 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 돼요. 그럼 누군가 호출을 해버려서 실수로 바꿀 수도 있고 이런 문제들이 생겨요.
그래서 누군가 실수로 변경할 수도 있고, 변경하면 안 되는 method 자체를 열어두는 게 좋은 설계 방법이 아니죠.
그래서 생성자 주입은 객체를 생성할 때, 딱 한 번만 호출되기 때문에 이후에 호출되는 일이 없습니다. 따라서 불변하게 설계할 수가 있습니다.
그 다음 두 번째, 누락입니다.

사실 프레임워크 없이, 순수한 Java 단위 테스트 코드를 작성하는 경우가 진짜 많거든요. 이런 게 좋은 테스트에요. 그러니까 스프링을 막 엮고, DB 엮고 이런 테스트도 되게 중요한데, 나는 OrderServiceImpl 자체만 딱 테스트하고 싶은 거에요. 그러면 MemberRepository나 DiscountPolicy 에는 가짜 객체를 주입해서 쓸 수도 있거든요. 아무튼 이건 테스트를 좀 공부하셔야 이해가 되실 수 있는데, 코드를 한번 보여드릴게요.

나는 순수한 Java 코드로 이것만 테스트를 하고 싶어요. 그런데 만약에 생성자 의존관계가 아니라, 수정자 의존관계를 써요.

이렇게 해서 수정자 의존관계를 쓴단 말이에요.
일단 이 OrderServiceImpl에 대한 테스트 코드를 만들어볼게요.

cmd + shift + T 하면,

OrderServiceImplTest 를 만들겠습니다. 얘는 순수한 Java로 테스트를 짜보겠습니다.

지금 OrderServiceImpl 자체를 잘 만들었는지 테스트 해보는 거예요. 그 다음에 createOrder를 바로 호출해볼게요.

이렇게 하면 문제가 생길까요? 실행해 볼게요.

생성자가 갑자기 사라졌기 때문에 AppConfig에서 걸립니다. 얘는 일단,

return null; 로 바꾸고, 다시 돌리면

NullPointerException이 납니다.


순수한 java 코드 테스트임에도 왜 NullPointerException이 나냐면,

OrderServiceImpl에서 memberRepository랑 discountPolicy에다가 값을 세팅해줘야 되는데, 세팅된 게 없기 때문에 그렇습니다.
내가 아무리 createOrder만 테스트하고 싶다고 해도,

createOrder를 보면, 어쨌든 memberRepository랑 discountPolicy가 필요하잖아요. 그러면 가짜 memberRepository라도 만들어서 넣어줘야 되는 거예요. 임의의 dummy라도.

그런데 얘는 누락을 해버렸어요. 이런 수정자 메서드를 두면, 누락 경험을 많이 해볼텐데, 왜냐면 내가 테스트를 하는 입장에서는

이것만 보고서는 의존관계가 뭐가 들어가는지 눈에 안 보입니다. 코드를 까봐야 아는거예요. 그래서 테스트 할 때는 안 보여서 실수를 하게 됩니다.
그런데 이걸 생성자 주입을 사용하면 어떻게 돼요?

다시 set 메서드를 지우고, 생성자 주입을 살립니다.

이렇게 실행하면, 생성자 파라미터 때문에 컴파일 오류가 날 거예요. 세상에서 제일 좋은 오류죠.

생성자에다가 뭘 넣을지 지정을 하면, 필수 값처럼 되기 때문에 개발자가 new OrderServiceImpl(); 하는 순간,

'아 참! 얘는 memberRepository랑 discountPolicy가 필요하지??' 라고 바로 인지할 수가 있어요. 굉장히 유용합니다.

그럼 테스트니까 그냥 내가 임의로 막 집어넣어도 괜찮겠죠. 지금은 순수한 java로 테스트하는 거니까, 가짜 객체를 프레임워크로 만들어서 넣어주는 거죠.
돌려보면, 아직 회원가입을 안해줘서 오류가 날텐데 회원을 임의로 넣어주면 되겠죠.

돌려보면 성공합니다.
값까지 맞는지 검증을 해봅시다.

돌리면 성공하죠.
이건 정말 스프링 없이 순수하게 내가 Java 코드로 필요한 것들을 테스트 해서 조립을 하는 거죠. 테스트 코드 상에서 내가 필요한 구현체들을 직접 조합해서 테스트 코드를 짤 거야! 라고 할 수가 있죠.
이런 식으로 해서 순수한 Java 코드로 테스트를 만들 수 있습니다.
이렇게 해서 생성자로 해야 빨리 detect 할 수 있다는 걸 말씀드렸구요.

그 다음에 한가지 더, 생성자를 쓰면 좋은 게 final 키워드를 넣을 수가 있어요.

final 키워드는 뭔가요? final을 쓰면 딱 한번 생성할 때 정해지면, 그 뒤로 절대 안 바뀐다는 거죠.
얘의 장점은 초기화 할 때 오른쪽에다가 = new 이런 식으로 넣어주거나, 그게 아니면 생성자에서만 값을 세팅할 수 있다는 거에요. 나머지는 final이 붙은 애의 값을 바꿀 수가 없어요.

그래서 이런 데에서 오류가 납니다. 생성자에서 세팅이 되고 더이상 값을 바꿀 수가 없기 때문이죠. 이건 이제 안쓰니까 지워 버릴게요.
어쨌든 생성자 주입을 쓰면, final 키워드를 넣을 수 있다는 장점이 있는데요.

final을 일단 지우고, 만약 개발자가 이렇게 실수로 코드를 누락할 수 있겠죠.

그러면 테스트를 짤 때까지는 문제가 없는데 테스트를 실행하면 문제가 되죠.

'어랏? 난 분명히 의존관계까지 다 생각해서 넣어줬는데 왜..?' 라고 생각했는데 막상 들어가보면,

코드가 누락된 겁니다. 이걸 막을 수 있는 기가 막힌 방법이 있습니다.

여기다가 final을 넣어주시는 겁니다. 이렇게 하면, 오류가 나요.
final을 넣어주면 초기화 단계에 생성자나 new로 값이 들어와야 되는데, 지금 안 들어왔다! 라고 Java 컴파일러가 나한테 알려주는 겁니다.

실수로 이렇게 적어도 여전히 컴파일 오류납니다. 그래서 까먹지 않고 생성자 주입을 할 수 있겠죠. 돌려보면 성공합니다.
그래서 왜 final까지 넣으면 좋은지 이제 아시겠죠?
그래서 생성자 주입을 사용하면 좋은게 불변, 그리고 누락을 막을 수 있는거, 또 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있는 게 장점입니다.
생성자 주입이 아니면 final 키워드가 불가능하겠죠. setter 이런 메서드는 객체가 생성된 다음에 호출이 되는 거니까요.
그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 다 막아줍니다.

그리고 항상 기억하시면 좋은게 컴파일 오류가 세상에서 가장 빠르고 좋은 오류입니다.

참고로 수정자 주입을 포함한 나머지 주입 방식은 모두, 생성자 호출 이후에 호출되기 때문에 필드에 final 키워드를 사용하는게 불가능합니다. Java 문법인 거죠.
오직 생성자 주입 방식만 final 키워드를 사용할 수가 있습니다.
정리를 해보면,

생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 Java 언어의 특징을 잘 살리는 방법이기도 합니다.
기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하시면 돼요. 그래서 생성자 주입과 수정자 주입을 동시에 사용할 수도 있습니다.
정리하면 항상 생성자 주입을 선택하세요. 그리고 가끔 옵션이 필요하면 수정자 주입을 선택하시면 됩니다. 필드 주입은 사용하지 않는 게 좋습니다.
필드 주입을 쓰는 순간 아예 테스트 같은 데서 값을 넣을 수 있는 방법 자체가 없고, 애플리케이션이 굉장히 딱딱해지고, 스프링 컨테이너 없이는 테스트 조차 할 수 없게 됩니다.
이렇게 해서 생성자 주입을 선택하는 것에 대해서 말씀드렸습니다.
쭉 듣고 보면 그냥 생성자 주입을 어떻게든 쓰시면 대부분의 문제가 해결이 됩니다.
이제 그 다음 시간에는 롬복과 최신 트렌드 이런 주입에 대해서 어떻게 하면 좋은지 말씀드리겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 조회 빈이 2개 이상 - 문제 (0) | 2024.01.24 |
|---|---|
| 롬복과 최신 트랜드 (0) | 2024.01.24 |
| 옵션 처리 (0) | 2024.01.24 |
| 다양한 의존관계 주입 방법 (0) | 2024.01.24 |
| 중복 등록과 충돌 (0) | 2024.01.24 |