IoC? DIP? IoC Container? DI? DI Framework? 도대체 그게 뭔데?

Ryan Yang·2019년 4월 20일
27
post-thumbnail

여러분이 자바 혹은 C#을 사용한다면 제목과 같은 용어들을 봐왔을겁니다. 하지만 보면 볼수록 매우 혼란스럽게 느껴질 것 입니다. 이렇게 복잡하게 느껴지는 이유는 이러한 용어들이 긴 시간에 걸쳐 하나의 용어에 다른 별명이 만들어지거나 세분화되고 혼용되었기 때문이죠. 그러므로 시간 순서에 따라 각 용어들을 살펴보려고 합니다.

먼저 IoC(Inversion of Control) 의 사전적 의미를 살펴봅시다.


Inversion of Control

  • Inversion of Control (제어의 역전): 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 프로그래밍 원칙을 말한다

이 용어 및 개념은 Ralph E. Johnson & Brian Foote가 1988년에 저술한 논문인 Designing Reusable Classes에서 처음으로 등장합니다. 이 논문에서 블랙박스 프레임워크는 상속을 통해서가 아닌 인터페이스 프로토콜에 따라 사용자가 정의한 메서드를 프레임워크 동작을 변경하는데 사용한다고 설명하고 있습니다.

아래 자바 코드에서 스레드를 사용하는 방법은 블랙박스 프레임워크의 전형적인 예시입니다. 여기서 Thread 클래스는 블랙박스 프레임워크이며 Runnable은 인터페이스 입니다. 그리고 run은 사용자가 오버라이드 하여 정의한 메서드죠.

new Thread(new Runnable() {
    @Override
    public void run() {
    	// Do something
    }
}).start();

이후 1993년 발표된 Richard Sweet의 논문 The Mesa Programming Environment과 1994년 발표된 GoF Design Pattern 에서는 Hollywood Principle 이라는 용어로 불리기도 합니다.

  • Hollywood Principle (헐리우드 법칙): Don't call us, we'll call you (우리에게 연락하지마쇼. 알아서 불러줄테니)

Dependency Inversion Principle (DIP)

Robert C. Martin은 1995, 1996년에 The Dependency Inversion Principle을 비롯한 몇편의 아티클을 작성합니다.

  • Dependency Inversion Principle (의존관계 역전 원칙): 상위 모듈은 하위 모듈에 종속되어서는 안되며 인터페이스(추상화)에 의존해야합니다.

얼핏 보기에는 IoC랑 비슷한듯 보입니다. 하지만 한가지 차이점이 있다면, IoC는 사용자가 구현한 인터페이스를 의존하는 하위 모듈로써 참조하든 말든 상관하지 않습니다. 하지만 DIP는 의존 관계에 대한 것이기 때문에 하위 모듈은 상위 모듈의 has 관계(맴버 변수)가 되어야 합니다.

interface IHeater {
    public on();
    public off();
}

interface IPump {
    public pump();
}

public class CoffeeMaker { // 상위 모듈
    private final IHeater heater; // 하위 모듈
    private final IPump pump; // 하위 모듈

    public CoffeeMaker(IHeater heater, IPump pump) {
        this.heater = heater;
        this.pump = pump;
    }

    public void brew() {
        heater.on();
        pump.pump();
        heater.off();
    }
}

DIP는 2003년에 발표된 SOLID principles (소프트웨어를 더 이해하기 쉽고, 유연하게 하기위한 다섯가지 디자인 원칙)D로 알려져 있습니다.


J2EE Design Patterns

그와 동시에 2000년대 초반 Context, Locator, Registers 등과 같은 이름을 가진 거대한 팩토리들이 소개되기 시작했습니다. 많은 사람들이 J2EE 표준에 따라 이러한 코드들을 작성했습니다.

public class ServiceLocatorTester {
    public static void main(String[] args) {
        ServiceLocator serviceLocator = ServiceLocator.getInstance();

        try {
            ProjectHome projectHome = (ProjectHome) serviceLocator.getHome(ServiceLocator.Services.PROJECT);
        } catch (ServiceException ex) {
            // client handles exception
            System.out.println(ex.getMessage());
        }
    }
}

이러한 거대 팩토리는 리펙토링 및 테스트 코드 작성을 어렵게 한다는 비난을 들어야 했습니다.
서비스 로케이터는 안티패턴이다


IoC Container

많은 개발자들이 거대한 팩토리를 사용하는 동안 1998년 한쪽에서는 Stefano Mazzocchi는 Avalon (1998~2005)이라는 Java Apache Server Framework를 발표합니다. 이 Avalon은 IoC를 적극적으로 차용해 컴포넌트 종속성 및 라이프사이클, 설정, 리소스 등을 외부에서 자동으로 해결주는 IoC Bandwagon(혹은 IoC-Oriented Container) 개념을 최초로 소개했습니다.

아래는 Avalon에서 하고자 했던 것들 입니다.

IoC 프레임워크는 다음을 반전하는 데 관심이 있습니다.

이 기술은 IoC Container라는 이름으로 알려져 OSGi, Spring, Pico, Hivemind, Guice 등 많은 프로젝트에 영향을 끼치게 됩니다.

※ Avalon은 후에 다시 언급할 Martin Fowler의 Inversion of Control Containers and the Dependency Injection pattern 글에서 인터페이스 주입 (Interface Injection, IoC 유형 1) 이라고 불리는 방식으로써 인터페이스 사용을 강제합니다.


Spring Framework

2003년 Rod Johnson이 Spring Framework를 발표합니다. XML에 기술한대로 Setter 인젝션을 수행하는 스프링 프레임워크는 Java 진영에서 대세 웹 프레임워크로 자리 잡았으며, IoC Container라는 개념을 대중화하는데 기여합니다.

Spring 또한 Avalon의 영향을 받아 받아서 DI를 수행하는 BeanFactory 이외에도 다국어를 지원하는 MessageSource, 이벤트를 처리하는 ApplicationEventPublisher, 설정을 담당하는 EnvironmentCapable, 리소스를 담당하는 ResourceLoader 등 많은 기능을 수행합니다.

Pico Container, Google Guice와 서로 영향을 받으며 현재와 같은 모습이 되었습니다.


DI Framework

2004년 1월 Martin Fowler의 Inversion of Control Containers and the Dependency Injection pattern 글에서 IoC Container에서 IoC가 매우 포괄적인 이름이라 혼동을 줄 수 있다며, 대신 Dependency Injection 이라는 용어를 사용하는것이 어떻겠나고 제안합니다.

Inversion of Control은 너무 일반적인 용어이므로 사람들이 혼란스러워합니다. 다양한 IoC 옹호자들과 많은 논의를 거친 결과 우리는 Dependency Injection 이라는 이름을 결정했습니다.

Martin Fowler 제안에 대한 Avalon 창시자인 Stefano Mazzocchi 반응

마틴 파울러는 IoC를 Dependency Injection으로 이름을 바꾸려 하지만 내 생각에 이 주장은 요점을 놓친 것 같습니다. IoC는 종속성을 주입하는 것이 아니라 격리를 시행하는 것입니다. 종속성을 주입해야 하는 필요성은 재사용을 개선하기 위해 격리를 증가시켜야 할 필요성의 결과이며 원인이 아닙니다.

IoC는 디자인 패턴도 아니고 프레임워크에서 API를 분리하는 일반적인 원칙입니다. 그러나 Dependency Injection 이라는 용어는 독자로 하여금 IoC가 객체를 구성하는 또 다른 방법이라고 오해하게 만든다는 것 입니다. 나는 Martin Fowler를 비난하는 것이 아니라, IoC 유형 1,2,3을 분류함으로써 요점을 놓친 사람들에 대한 비난을 하고자 합니다. IoC는 아발론이 사용하고 있는 단순한 원칙이지, 아발론이 하고 있는 무언가가 아닙니다.

※ Avalon의 IoC Framework가 역전하고자 하는 대상은 Dependency 뿐만 아니라 Resource, Configuration, Lifecycle과 같은 요소들도 포함한다고 이해하면 될 것 같습니다.


정리

따라서 현재 커뮤니티에서 교통 정리(?)된 바에 따르면, 하위 모듈을 생성자, 프로퍼티, 메서드(Setter)를 통해 주입하는것을 Dependency Injection(DI)이라 하고, 설정에 따라 의존성을 자동으로 resolve 하여 주입해주는 기술을 통칭하여 DI Framework라고 부릅니다.

  • DI는 DI Framework 없이 직접 주입하는 것도 포괄하는 일반적인 개념입니다.
    - Pure DI, Bastard Injection, Poor Man's Injection
  • DI는 DIP 나 IoC 없이 콘크리트(구체) 클래스의 인스턴스를 주입하는 것 또한 포함합니다.
  • 확실히 합의된 것은 아니라 IoC Container는 좀 더 많은 기능을 가진 포괄적인 개념으로써 여전히 DI Framework와 혼용되어 사용되고 있습니다.

DI Framework에 대한 논란

Java, C#, Angular, Android 등 커뮤니티에 DI Framework는 베스트 프렉티스로 받아들여져 왔습니다. 반면 여전히 DI Framework를 왜 사용해야 하는지 의문을 갖는 개발자들도 있습니다. 그 중 대표적인 사람이 스택 오버플로의 창립자이자 조엘 온 소프트웨어의 저자인 조엘 스폴스키입니다. SOLID와 TDD를 설파하는 마틴 파울러와 온라인 상에서 마찰을 빚어왔죠.

조엘 스폴스키는 왜 IoC 컨테이너가 필요한지 묻는 스택 오버플로우 질문에서 IoC 컨테이너를 "간단하고 우아하며 유용한 개념을 이틀동안 공부 해야하는 200 페이지 분량의 메뉴얼로 만든다"며 비판했습니다. 하지만 이에 반대하여 조엘이 아래의 코드보다 위의 코드를 선호한다는 사실을 믿을수 없다는 답변 또한 조엘의 답변의 3배가 넘는 지지를 받았습니다.

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));
var svc = IoC.Resolve<IShippingService>();

DI Framework의 미래

다른 한쪽에서는 DI를 빌트인 한 실험적인 언어인 newspeak (소설 1984에 나오는 신어에서 따온 이름)도 길라드 브라차(Gilad Bracha) 주도로 연구되고 있습니다. newspeak에서는 String, Array를 비롯한 모든 것이 추상화 될 수 있으며, 실제 구현은 실행시 외부로부터 넘겨집니다. 논의에 그칠지도 모르지만 코틀린에서는 컴파일 타임에 의존성을 해결해주는 방법 또한 논의되고 있습니다.


기타

  • 2007년 Google 내부적으로 사용되던 Guice 프레임워크가 외부에 공개되었으며 DI Framework에 대한 JSR 330 표준이 확립됩니다.
  • AugularJS 및 Augular는 DI를 최초로 도입한 JS 프론트엔드 프레임워크 입니다.
  • C# 에서는 Ninject, Autofac, Unity와 같은 DI Framework를 사용합니다.
  • 타입스크립트에서는 reflect-meta 기능을 사용한 inversify가 있습니다.
  • Dagger2는 안드로이드에서 성능을 위해 리플랙션 대신 어노테이션 프로세싱을 사용합니다. (컴파일 타임을 희생하여)
  • 필드 주입(Field Injection) 대신 생성자 주입(Constructor Injection)을 사용해야 하는 이유

1개의 댓글

comment-user-thumbnail
2022년 9월 3일

좋은 글 감사합니다

답글 달기