일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- WooWaCon
- gitlab.rb
- MSA
- Vault
- 제어역전
- approle
- backtick
- secretid
- hashicorp
- InheritableThreadLocal
- auth method
- AWS SNS
- 하만카돈 #오라 #스튜디오 #2 #harman #kardon #aura #studio #fix #repair #수리 #shutdown #bluetooth
- json.tool
- 명령어 대체
- Unseal
- 샤미르
- Shamir
- AWS SQS
- shanta #bahadur
- external_url
- 우아콘
- Approval Test
- JSR-330
- gitlab-ctl
- Session invalidate
- 멱등성
- RequestFacade
- KMS
- CQRS
- Today
- Total
인생은 여행
IoC, DI 그리고 의존성 본문
Spring Framework를 다루다 보면 IoC(Inversion of Control; 제어의 역전), DI(Dependency Injection; 의존성 주입) 같은 단어를 많이 보게 된다. 볼 때마다 알듯 모를 듯 헷갈리는 개념이다. 확실히 기억하기 위하여 정리해 본다.
그전에 먼저 소프트웨어에 있어서 의존성이 무엇인지 정리해 보야할 것 같다.
의존성 Dependency
어떤 객체 A의 코드 내부에 다른 객체 B를 적었다면 A는 B를 알고 있다 할 수 있을 것이다. 다른 말로 하자면, A는 B를 참조하고 있다, 또는 A는 B에 의존하고 있다고 표현할 수도 있을 것이다.
class A {
void doThis() {
B.doThat(); // A가 B에 의존하고 있다.
}
}
소프트웨어에서 이런 식으로 다른 객체를 참조하는 것을 피할 수는 없다. 각 모듈을 역할(Responsibility)에 따라 잘 분리하고 내가 할 수 없는 일을 남에게 시키기 위해 참조할 수밖에 없기 때문이다. 이렇듯 객체 간 의존 관계는 당연한 개발 산물이지만, 소프트웨어공학에서 강한 의존성은 대표적인 '악덕'으로 간주한다. 왜 그럴까? 의존 관계의 객체는 변경과 테스트가 어려워지기 때문이다. 위의 코드에서 A는 B를 분명 참조하고 있지만 반대의 경우는 성립하지 않는다. 위 코드만 봐서는 B가 A를 참조하고 있다고 할 수 없는 것이다. 이런 경우 B의 변경에 A는 영향을 받게되지만 반대의 경우는 성립하지 않는다. 그리고 A는 B의 doThat() 메서드의 변화에만 신경 쓰면 되기 때문에 그나마 양호한 의존 관계라고 볼 수 있을 것이다. 그러면 약한 의존 관계와 강한 의존 관계는 어떻게 구분할 수 있을까?
class A {
B b = new B(); // 객체 생성은 대표적인 강한 의존관계이다.
void doThis() {
b.doThat(); // 단순한 메서드 호출은 비난 받을 일은 아니다.
}
void doThis2() {
// ...
}
}
class B {
A a = new A();
void doThat() {
// do something...
a.doThis(); // 순환관계는 최악이다.
}
}
현실 세계로 예을 들어보면, 을이 갑에게 어떤 서비스를 제공하기로 하였을 때 갑과 을은 계약관계라고 할 수 있을 것이다. 계약은 구도로 했을 수도 있고 계약서를 정식으로 작성했을 수도 있을 것이다. 만약 구두로 계약하였다고 하면, 제공되는 서비스가 이발 같은 간단한 일은 별 문제가 없을지도 모른다. 하지만 집짓기 같은 복잡한 일이라면 갑은 을의 일에 사사건건 개입해야 하고 을도 거의 모든 일을 갑에게 확인받으면 진행해야 할 것이다. 강한 의존 관계가 형성된 것이다. 현실적으로 집짓기 같은 일을 계약서 없이 진행하는 사람은 없을 것이다. 계약서를 작성했다면 일은 훨씬 단순해진다. 변하지 않을 내용만을 계약서에 최대한 자세히 담을 것이며 을은 계약서의 내용을 토대로 일을 진행할 것이다. 갑과 을은 이전에 비해 약한 의존 관계라고 볼 수 있을 것이다. 을은 계약서의 테두리 내에서 다양한 방법으로 집을 지을 수 있을 것이다. 병이나 정에게 하청을 줄 수도 있을 것이다. 계약에 위배되지 않는다면 말이다.
다시 프로그램 세계로 돌아와서 소프트웨어 의존성에 위의 계약관계을 대입해보면, 계약서를 자바에서의 인터페이스라고 할 수 있을 것 같다. A와 B의 사이에 인터페이스라는 계약서를 둬서 A와 B를 서로 분리하는 것이다. 이제 A와 B는 서로 모르는 사이가 되었다. B는 인터페이스에 적힌 스펙 테두리 내에서 변경이 자유로울 것이며 아예 B 대신 해당 인터페이스를 구현한 C 나 D가 이 일을 할 수도 있을 것이다.
class A {
final I i;
public A(I i) { // 생성자를 통해 의존성을 주입 받았다. (DI)
this.i = i;
}
void doThis() {
i.doThat();
}
}
interface I {
doThat();
}
class B implements I {
void doThat() {
// do something...
}
}
class C implements I {
void doThat() {
// do something...
}
}
위 코드에서 A와 B 또는 A와 C 간의 직접적인 의존 관계는 성공적으로 분리하였지만 어디에도 객체를 생성하는 코드는 없다. 일이 되기 위해서는 누군가는 객체를 생성해서 A에게 주입시켜 주어야 하는 것이다. 사실 A와 B는 직접적인 계약 관계로 볼 수 없다. 다시 집짓기의 예를 들자면, A는 집을 짓기 위해 공신력 있는 건축가협회나 위원회에 계약을 일임한 것이다. Spring Framework 같은 IoC 컨테이너가 하는 일이다.
IoC, DI, JSR-330 같은 개념에 대해서는 다음에 이어서 다뤄보겠다.