부록 1) DI에 대한 개인적인 생각

Dev StoryTeller·2020년 12월 23일
0

Spring 시리즈!

목록 보기
5/33

0. 개요

DI를 아는 분들은 모두 "필드 주입은 절대 안좋음! 생성자 주입을 써!"라는 소릴 들어봤을 것이다.

그렇게 생성자 주입이 완전 무결한 줄 알았더니, 다른 곳에선 생성자 주입은 좋지 않다는 이야기도 들려온다.

뭐가 맞는 걸까?
필자는 두 의견 모두 맞다라고 생각한다.
그 이유를 적어보겠다.

1. 순환 참조

우선 논란의 중심인 순환 참조부터 알아보자.
간단하다. 서로가 서로를 참조하는 것이다.

class A {
   @Autowired
   private B b;
}

class B { 
   @Autowired
   private A a;
}

저렇게 서로 참조하는 상황에서 자동 주입인 @Autowired를 붙이면, 참조가 무한 루프를 타게 되면서 오류가 뜬다.
(A의 필드 B는 Bean B를 참조하고, Bean B는 자신의 필드인 A를 참조하고, 다시 A는. . . 이렇게 되는 상황이다.)


2. 필드 주입이 왜?

이게 왜 필드 주입의 문제일까?
저 상황은 DI 방법에 상관없이, 아예 코드 자체가 문제인데...??

그 이유는 여러가지가 있다. 찬찬히 알아보자.


1. 필드 주입! 너 너무 안보여!

객체 지향의 중요 원칙인 SOLID의 S에 해당하는 부분이다.
단일 책임의 원칙이라는 것인데, 클래스는 하나의 책임만을 가지며, 이를 완벽히 캡슐화 해야한다는 것이다.

알기 쉽게 말하자면,
필드 주입은 참조 관계를 눈으로 확인하기 어렵다는 것이다.

우리는 클래스를 작성할 때, 암묵적으로 다음과 같이 작성한다.

class A {
   /*
   *  필드1
   *  필드2
   *  필드3
   */
   
   /* 생성자 */
   
   /* getter/setter 및 메소드들 */
}

동의하지 않는 분들도 있겠지만, 어쨌든 필드를 한번에 쓰는 것은 사실이다.

그럼 다음을 보자.

class A {
   @Autowired
   private B b;
   
   @Autowired
   private C c;
}
class B {
   @Autowired
   private C c;
   
   @Autowired
   private A a;
}
class C { 
   @Autowired
   private A a;
   
   @Autowired
   private B b;
}

어떨까? 한 눈에 의존 관계가 쏙쏙 들어오는지?
진짜 끔찍하기 그지 없는 의존 관계이다.
어디서부터 뭘 잘못했는지 파악하려면, 시간 꽤나 걸릴듯 하다.

그럼 이것을 생성자로 바꿔보면 어떨까?
이렇게 된다.

class A {
   private final B b;
   private final C c;
   
   @Autowired
   public A(B b, C c) {
      this.b = b;
      this.c = c;
   }
}
class B {
   private final A a;
   private final C c;
   
   @Autowired
   public B(A a, C c) {
      this.a = a;
      this.c = c;
   }
}
class C { 
   private final A a;
   private final B b;
   
   @Autowired
   public C(A a, B b) {
      this.a = a;
      this.b = cb
   }
}

딱봐도 의존 관계가 보인다.
"A는 B,C를 의존하는데, B는 A,C를 의존하네? 이거 잘못됬네~~"

필드와는 달리, 이렇게 한번에 알아차리기 쉽다.

하지만 이 말은 뭔가 어거지?랄까, 사고의 흐름이 자연스럽지 않다.

"클래스 내에서 보통 책임이 많아지면 위기감을 주는데,
필드 주입은 하나든 여러개든 그런게 없어. 원칙 어겼어!"
라는 것인데, 약간 어거지로 이유를 갖다 붙인 느낌이다.


2. POJO에 위배

이 부분은 공감한다.
사실 자바 객체의 특징을 생각해보면, 좀 말이 안된다.

객체지향의 특징은 캡슐화를 함으로써, 정보를 은닉하는 것이 원칙이다.
그렇기 때문에 필드를 private로 하고, public인 getter/setter로 값을 다룬다.

그런데 필드 주입은 뭘까?
따지고 보면 직접 주입하는 건데, 이건 좋지 않은 방식이다.


3. 의존성 문제

사실 위와 같은 의미이다.

스프링은 POJO의 POJO를 위한 POJO에 의한 프레임워크이다.
진정한 POJO를 생각하면, 의존 관계에 휘둘리지 않는 구조를 가져야 한다.

이것이 무슨 소리인가하면, 현재 코드에서 Spring을 쏙!빼도 말이 되야한단 소리다.
(이것을 확인하는 것이 바로 Spring의 TEST 모듈이다.)

우선 생성자 주입부터 살펴보면,

class A {
   private String name;
}
class B {
   private final A a;
   private String name;
   
   public B(A a) {
      this.a = a;
      System.out.println("쟤 이름은 " +a+ "입니다.");
   }
}

@Autowired를 빼더라도 충분히 말이 된다.
생성자의 인자로 받은 a 객체를 속성으로 정의하고 사용하는 모습을 볼 수 있다.
속성 a의 출처는 생성자의 인자이다.


그럼 필드 주입을 살펴보자.

class A {
   private String name;
}
class B {
   private A a;
   private String name;
   
   public void Hello() {
      // 넌 어디서 온거야..ㅇㅁㅇ?
      System.out.println("쟤 이름은 " +a+ "입니다.");
   }
}

이것이 필드 주입의 단점이다.
쟨 어디서 온 걸까? 출처를 알 수가 없다.
순수 100% Spring의 능력으로 의존성이 주입됬으니, 문제가 생긴 것이다.
이렇게 되면 스프링에 의존한 것이 되므로, 바람직하지 않다.
(스프링의 @Autowired가 없으면 프로그램이 안돌아가므로ㅇㅇ)


4. final 문제

흔히 상수 타입으로 많이 생각하는 final.
필드 주입에는 final을 붙일 수 없기 때문에, 객체가 변화무쌍하다.
객체가 변화무쌍한건 당연한데, 대체 왜?

뭐든간에 주입을 했다는 것은 "난 이걸 사용할 꺼야!"이다.
그런데 중간에 갑자기 "아 미안. 그게 아니었네?" 이러면서 바뀌면, 엄청난 혼란이 올 것이다.
따라서 주입되는 속성은 바뀌어선 안 된다.

이것을 자바에선 final로 정의할 수 있는데,
필드 주입을 그럴 수가 없다.
왜냐면 아무것도 없는 값이기 때문이다.

class A {
   private String name;
}
class B {
   // a가 null인데 무슨 final이야!
   private final A a;
   private String name;
}

이런 점에서도 부적절 하다고 할 수 있다.


3. 그럼 생성자는 또 왜?

필드 주입이 나쁜 놈이고, 생성자가 좋은 놈인건 이제 알겠다.
근데 생성자도 안좋다니 왜?
사실...너무 좋아서 문제다ㅎㅎ

요즘 스프링을 보면, 정말 생성자 주입을 엄청 밀어주는 것을 볼 수 있다.

1. 필드 주입하면, 생성자 주입으로 바꾸라고 한다.
: setter 주입은 거의 쓰는 일이 없어서, 결국 생성자를 주로 쓴다.
(IntelliJ 한정)


2. 그마저도 생략된다
: 자연스러워서 몰랐는데, 생각해보니 @Autowired가 없어졌다. 알아보니 4.3부터 생략 가능하다고 한다.
(생성자가 1개일 경우만)

그렇게 @Autowired를 점점 안 쓰다보니, 의존성 주입이라는 개념이 희미해져 갔다.
이 글을 쓰는데도 "@Autowired를 어떻게 썼지?"라며, 인터넷을 찾아보았다...

코드가 깔끔해지고 하는건 좋은 현상인데, 이건 의존성 주입이야!가 아니라, 원래 이랬었지 이러면서 그냥 넘어가게 되는 것 같다.

이 글로 인해, 다시 한번 생각해보는 계기가 되어 다행이다 : )

profile
개발을 이야기하는 개발자입니다

0개의 댓글