IoC, DI란 무엇일까

Posted by Yungwang Ryu on 2019-08-31

Why?

  • Spring 에서 DI를 잘 썼다고 하는데 어느부분에서 잘썼는지 구체적인 사례를 찾아보자

목표

  • Spring IoC, DI가 무엇인지 이해시킨다.
  • Spring DI에 장단점을 이해시킨다.
  • Bean이란 무엇이고 Container에서 어떻게 관리되고 생명주기는 어떻게 되는지 이해시킨다.

IoC (Inversion of Control) 란 무엇인가?

IoC에 용어는 90년 중반에 GoF의 디자인패턴에서도 이용어가 언급되었다고 합니다. 즉, IoC는 Spring에서 나온 용어가 아닙니다. 과거 EJB에서도 WAS에 Servlet Container에서도 사용 된 개념이죠.

IoC에 대한 개념은 굉장히 폭이 넓습니다.
해석하면 제어의 역전입니다. 도대체 어떤 제어를 말하는 것이며 무엇을 역전한다는 말 일까요??

아래 코드부터 바로 보시죠

아래는 개발자가 직접 객체를 생성하여 코드를 제어하는 코드입니다.

1
2
3
4
5
6
7
8
public class A {

private B b;

public A()
b = new B();
}
}

위와 같이 B클래스를 직접 인스턴스하여 의존관계를 나타내고 있습니다. 즉 개발자가 직접 객체를 제어하여 A객체는 B객체에게 의존하고 있어 라고 클래스를 통해 표현하고 있는것 이죠

이것은 개발자가 직접 한 것입니다. 하지만 아래 코드는 어떨까요??

컨테이너에 의해서 생성한 객체를 사용만 하는 코드

1
2
3
4
5
6
public class A {

@Autowired
private B b;

}

스프링 사용자라면 잘알고 있는 표현입니다.
B라는 객체가 스프링 컨테이너에게 관리되고 있는 Bean이라면 @Autowired 를 통해 객체를 주입받을수 있게 되죠
이것은 개발자가 직접 객체를 관리하지 않고 스프링 컨테이너에서 직접(제어) 객체를 생성하여 해당 객체에 주입 시켜준 것입니다.

이것이 바로 제어가 역전되었다. IoC 라는 개념입니다. 또 다른 예를 들어볼까요??

디자인패턴인 템플릿 메소드 패턴에서도 IoC 개념을 찾아 볼 수 있습니다. 이처럼 IoC는 거시적인 개념이 아닌 프로그램을 제어권을 누가 가져갈것인가에 대한 프로그래밍 모델일 뿐입니다. 아래 코드를 한번 보죠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class IronFactory {

private IronMan ironMan;

public IronMan getIronMan () {
return assemble();
}

/**
* 제어권은 상위 클래스에게 있다.
* 하위 클래스에서 구현한 코드는 상위 클래스가 어떻게 되는지 모른다. 단지 구현해야하는 부분을
**/
private IronMan assemble() {
// do assemble.. from head, body, arms, legs
return ironMan;
}

protected abstract void head();
protected abstract void body();
protected abstract void arms();
protected abstract void legs();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public classs HulkBuster extends IronFactory {

@Override
public void head() {
// do something..
}

@Override
public void body() {
// do something..
}

@Override
public void arms() {
// do something..
}

@Override
public void legs() {
// do something..
}
}

제어권은 상위 클래스인 IronFactory에게 있습니다.
하위 클래스에서 구현한 코드는 상위 클래스가 어떻게 되는지 모릅니다. 단지 구현해야하는 부분을 구현하였고 구현한 코드가 언제 어떻게 실행 될지는 모릅니다. 상위 클래스에서 알아서 필요할 때 구현한 메소드를 사용하게 됩니다. 바로 이처럼 코드 흐름이 제3자에게 위임되는 것이 IoC 모델이라고 합니다.

그럼 왜 IoC를 Spring에서 사용 하였을까요? 분명히 이점이 있을텐데요

IoC는 Spring에서만 사용되는 개념이 아닙니다. 프로그래밍 패턴이기때문에 범용적으로 쓰이며 작게는 객체간에 디자인 패턴으로 크게는 컨테이너, 프레임워크 역할에 적합한 구조이기 때문에 상황에 맞게 사용되는 프로그래밍 모델인 것이죠. Spring은 Framework입니다. 그렇기 때문에 흐름을 직접 핸들링 해줘야 하는것이고 자연스럽게 적합한 모델, 패턴을 적용해야 했고 그래서 Framework에 적합한 IoC, DI 개념 모델이 들어가게 된것이조.

그럼 이점은 무엇이 있을까요?
IoC 프로그램 모델은 곧 역할과 책임에 분리라는 내용과 관련이 있다고 생각합니다.
왜 내가 직접 객체를 제어 하지 않고 다른 제 3자가 제어를 위임하게 하고 나는 수동적으로 따라가는 길을 택하였을까요??

이는 객체지향 프로그래밍과 아주 관련이 깊다고 생각합니다. 객체지향 프로그래밍은 각 객체마다 자기의 역할과 책임을 온전히 다하며 서로 협력하며 변경에 유연한 프로그래밍을 할 수 있는 프로그래밍 기법입니다. 즉, 각 객체마다 올바른 캡슐화를 통해 높은 응집도와 낮은 결합도를 이루어나가는 것이 핵심중에 핵심이죠.

이러한 관점에서 제어의 역전으로 인해 제 3자 즉 다른 객체, 다른 컨테이너에게 제어에 대한 역할과 책임을 위임하고 다시말해 신경쓰지 않고 지금 내가 하고자 하는 역할과 책임에 관심이 있는것이죠. 왜 이렇게 할까요? 답은 변경에 유연한 코드 구조를 가져가기 위해서입니다. 내가 작성하고자 하는 코드에서 객체를 생성, 소멸 등에 관리 코드와 함께 비지니스 코드까지 들어가면 어떨까요??

기능은 얼마든지 변경될 수 있습니다. 자, A라는 객체를 생성하고 있었는데 A객체는 삭제하고 B객체를 추가해야 한다고 하면 어떻게 될까요?? 뭐 한 곳에서만 객체 생성을 추가했다면 한 곳에서만 변경하면 되겠죠… 그럼 10곳에서 A라는 객체를 사용하였다고 해보죠. 그럼 10번을 수정해줘야 겠네요?

또 다른 예를 들어보죠. 객체 생명 주기를 직접 관리하기 때문에 객체 생성, 삭제를 직접 할 수 있습니다. 근데 도메인 모델상 객체 삭제는 절대 하면 안된다고 해보죠. 하지만 직접 개발자가 객체를 핸들링 할 수 있기 때문에 삭제하거나 아니면 새로운 객체를 생성 할 수 도 있습니다. 다시 말해서 권한이 너무 많다는 것입니다. 이것은 곧 캡슐화 위반을 일으킨 것입니다.

엔터프라이즈 차원에서 수많은 객체들을 편리하게 관리하기 위함입니다.

현재 설명한 객체는 등장한것이 2~3개 밖에 되지 않습니다.
각 객체마다 협력을 하다보면 IoC역할을 하는 객체들이 계속 생길수 있습니다. 그럼 개발자는 그런 IoC 역할을 하는 객체를 계속 알고 개발을 해야 합니다.
1~2개는 괜찮겠죠 하지만 개발하다 보면 또 유지보수 하다보면 객체를 계속 생길수 있습니다. 요구사항이 변하니까요.

그래서 스프링은 Application Context 로 모든 객체를 일괄 관리하는 IoC를 Wrapping 하는 개념이라 할까요? 아무튼 그렇게 보따리 해서 컨테이너가 되어 객체에 의존관계 주입, 객체 생명 주기 관리, 그 밖에 아직 제가 알지 못하는 영역에서 많은 유용한일을 담당 할 것입니다.

자 그럼 질문을 다시 해보겠습니다. IoC 왜 제어를 역전 시켰을까요? 어떤 이점일까요?

객체를 관리해주는 독립적인 존재 (거시적으로는 컨테이너, 미시적으로는 디지인패턴적용)와 그 외 내가 구현 하고자 하는 부분으로 각각 관심을 분리하고 서로에 역할을 충실히하면서 변경에 유연한 코드를 작성 할 수 있는 구조이기 때문에 제어를 역전 하였다. 저는 이렇게 생각하였습니다.

스프링 컨테이너 차원에서는 엔터프라이즈 개발에 적합하게 수많은 객체 생명주기를 관리 하고 의존 관계를 설정해주고 그 외 많은 기능들을 제공하여 개발자는 비지니스 로직에 집중 할 수 있게 해주는것이죠.

그럼 Spring DI 무엇을 의미하는 걸까요?

Spring Framework란 무엇인가? 장점은 무엇인가? 조사를 하다보면 반드시 나오는 DI입니다.
이녀석은 무엇일까요?? 제가 참고한 토비스프링에서 정보를 얻을 수 있었습니다.

IoC가 매우 느슨하게 정의돼서 폭넓게 사용되는 용어라는 점이다. 때문에 스프링을 IoC 컨테이너라고만 해서는 스프링이 제공히는 기능의 특정을 명확하게 설명하지 못한다. … 생략 … 몇몇 사람의 제안으로 스프링이 제공하는 IoC 방식을 핵심을 짚어주는 의존관계 주입 DependencyIniection이라는, 좀 더 의도가 명확히 드러나는 이름을 사용하 기 시작했다.

위에서 말했다싶이 IoC는 객체 생명 관리, 흐름 제어를 제 3자에게 위임하는 프로그래밍 모델입니다. 디자인 패턴인 템플릿 메소드 패턴에서도 IoC 개념을 찾아 볼 수 있었죠.
하지만 이런 디자인 패턴이 적용된 것을 가지고 Spring 에서 IoC, IoC 할까요? 좀 더 핵심적인 의미가 있지 않을까요? 그 핵심적인 의미는 DI에서 찾아 볼 수 있습니다.

스프링이 여타 프레임워크와 차별화돼서 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명하게 드러난다. 생략…
DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이 핵심

Spring IoC와 DI가 어떤 차이점이 있는지 명확하게 이해가 되지 않았는데 위 문구에서 찾을 수 있게 되었죠.

그리고 DI라는 말은 마틴파울러와 그 주변사람들이 모여 IoC에 범용적인 의미를 객체 주입이라는 의미를 명확히 하기 위해 만들어졌다고 합니다.
그냥 제어의 역전이라 하면 그 차원에서 어느 범위에서 의존성 역전일까요? 단순 객체간의 디자인패턴으로 활용하는것도 제어 역전이고 컨테이너차원에서 수많은 객체들에 의존관계를 파악하고 런타임시점에서도 다이나믹하게 객체를 주임하여 유연한 프로그래밍을 할 수 있도록 하는 패턴에 더 명확한 이름을 부여하기 위해 토론 끝에 Dependency Injection 이라는 용어를 만들었다고 합니다.[원문] [번역]

DI는 IoC 프로그래밍 모델을 구현하는 방식중에 하나입니다.
Spring 에서는 IoC를 구체적으로 DI라는 방식을통해서 의존성 역전 제어를 하고 있는 것이죠. 우선, 용어를 하나하나 분석해 보겠습니다.

의존성
프로그래밍에서 의존한다는 말은 서로 다른 객체간에 레퍼런스 참조가 되어 있다는 말입니다. 이는 A -> B에 의존 관계에 있을 때, B객체에 변경사항이 생겼을 때, A 객체가 영향을 받는 구조인 것이죠.

1
2
3
4
5
6
7
8
public class A {

private B b = new B();

public void anyMethod() {
b.~~
}
}

주입
주입이란 단어를 생각하면 주사 맞는 이미지가 연상되는데요.
외부로부터 객체의 주소(레퍼런스) 값을 전달 받게 되어 객체가 참조 되어지는 방식입니다.

다시 돌아와서 그럼 의존성 주입이라는 말은 어떤 말일까요?
의존관계에 있는 객체들이 있을 때, 외부(스프링컨테이너)에서 객체에 레퍼런스를 전달하여 사용하고자 하는 객체에서 코드를 작성 할 수 있게 한다. 이런의미가 되곘네요

DI에 핵심 키워드는 외부로부터~, 다이나믹한 의존관계 입니다.

토비스프링에서 말하는 DI (의존관계 주입)에 3가지 조건은 다음과 같습니다.

  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

외부로부터 인터페이스타입으로 얼마든지 부품이 교체 될 수 있으니 코드가 유연해 지겠죠? 또한 의존성 제어로 인해 수많은 객체들에 의존관계를 맺어주고 개발하는 클라이언트 입장에서는 편하게 비지니스로직에 집중할 수 있다고 생각합니다. 이러한 DI를 잘 녹인 프레임워크가 스프링이라고 합니다.

토비스프링에서 인용한 문구를 다시 보여드리겠습니다.

DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이 핵심 …생략…
주입받는 메소드 따라미터가 이미 특정 클래스 타입으로 고정되어 있다면 DI가 일어날 수 없다. DI에서 말하는 주입은 다이내믹하게 구현 클래스를 결정해서 제공받을 수 있도록 인터페이스 타입 의 파라미터를 통해 이뤄져야 한다.

핵심은 DI는 클래스타입이 고정되어 있지 않고 인터페이스 타입의 파라미터를 통해 다이나믹하게 구현 클래스를 결정해서 제공 받을수 있어야 한다.

이것이 바로 DI에 핵심개념입니다.

코드를 통해서 바로 알아보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
public class IronManController {

private IronManService ironManService;

public IronManController(IronManService ironManService) {
this.ironManService = ironManService;
}

// 생략

}

IronManService라는 인터페이스를

1
2
3
4
5
public interface IronManService {

void attack();
void flying();
}
1
2
3
4
5
6
7
8
public class HulkBuster implements IronManService {

@Override
public void attack() {
System.out.println("헐크버스터 공격!");
}
// 생략
}
1
2
3
4
5
6
7
8
public class Mark15 implements IronManService {

@Override
public void attack() {
System.out.println("나노입자 아이언맨 공격!");
}
// 생략
}

DI라는 개념 또한 스프링에서 나온 개념이 아닙니다.

그럼 IoC와 DI 무엇이 다른걸까요??

IoC는 객체의 흐름, 생명주기관리등 독립적인 제 3자에게 역할과 책임을 위임하는 방식에 프로그래밍 모델입니다.
디자인패턴에서도 찾아볼수 있고 다른 컨테이너를 가진 프레임워크들에서도 찾아볼 수 있습니다. 범용적인 표현이죠 하지만 DI는 인터페이스를 통해 다이나믹하게 객체를 주입을 하여 유연한 프로그래밍을 가능하게 하는 패턴으로 좀 더 구첵적인 의미인것이죠.