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

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


Inversion of Control

  • Inversion of Control (제어의 역전): 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다

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

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

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

이 용어는 후에 객체-지향(OOP) 커뮤니티에 영감을 주어 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로 다시금 재조명되어 발표됩니다. 하지만 조금만 어플리케이션이 커지면 아래와 같은 객체 생성 코드를 작성해야 했죠.

ShippingService shippingService = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

J2EE Design Patterns

그와 동시에 2000년대 초반 Context, Locator, Registers 등과 같은 이름을 가진 거대한 팩토리들이 소개되기 시작했습니다. 많은 사람들이 J2EE 표준에 따라 이러한 코드들을 작성했습니다. (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 Container라는 이름으로 알려져 OSGi, Spring, Pico, Hivemind, Guice 등 많은 프로젝트에 영향을 끼치게 됩니다.

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


Spring Framework

1004.1536743076.png

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

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

DI Framework

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

oYukX.jpg

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

  • DI는 DI Framework 없이 직접 주입하는 것도 포괄하는 일반적인 개념입니다.
  • 확실히 합의된 것은 아니라 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는 reflection과 같은 런타임 기술 요소에 의존하기 때문에 초기 동작을 느리게하고 컴파일 타임에 검증 할 수 없다는 점도 지적 받고 있습니다.


DI Framework의 미래

다른 한쪽에서는 DI를 빌트인 한 실험적인 언어인 newspeak (소설 1984에 나오는 신어에서 따온 이름)도 길라드 브라차(Gilad Bracha) 주도로 연구되고 있습니다. newspeak에서는 String, Array를 비롯한 모든 것이 추상화 될 수 있으며, 실제 구현은 실행시 외부로부터 넘겨집니다. 또한 그가 참여한 Dart 언어에서는 DI에는 한참 모자라지만 factory라는 키워드로 객체의 생성을 후킹하여 변경할 수도 있죠. 어쩌면 우리는 아직 개선할 여지를 남겨둔 것일지도 혹은 후대에 안티 패턴으로 남겨질 무언가를 붙잡고 있는 것인지도 모르겠습니다.


기타

  • 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)을 사용해야 하는 이유