2009년 10월 31일
[번역] 제어 역전 컨테이너와 의존성 주입 패턴 (4/4)
어떤 것을 쓸 것인가?
지금까지는 제가 이 패턴과 변종들을 어떻게 이해하는지를 집중적으로 설명했습니다. 이제, 언제 어떤 것을 쓸면 좋을지 판단하는 데 도움이 될 만한 장단점을 이야기할 수 있게 되었습니다.
서비스 위치탐색기 대 의존성 주입
서비스 위치탐색기와 의존성 주입 사이에서 어떤 것을 선택하느냐가 가장 근본적입니다. 이 두 구현체 모두 기본적으로 객체 간의 결합을 끊어 줍니다. 결합은 '빈약한 예제'의 결함이었습니다. 두 기술을 적용한 사례 모두 사용하는 쪽 코드가 서비스 인터페이스의 구현물에 의존하지 않았습니다. 서비스를 사용하는 응용 클래스에 구현물을 제공하는 방법이 두 패턴의 중요한 차이점입니다. 서비스 위치탐색기 방식에서는 응용 클래스가 명시적으로 위치탐색기를 호출하여 서비스를 요청합니다. 주입 방식에서는 명시적인 요청이 없습니다. 사용하기 원하는 서비스가 응용 클래스에 들어갑니다. 그래서 제어 역전이라고 합니다.
제어 역전은 프레임워크들의 일반적인 특징이지만 비용을 내해야 합니다. 프레임워크는 대체로 이해하기 어렵고 디버깅을 하려고 할 때 문제가 생기기도 합니다. 그래서 전 보통 필요하지 않으면 프레임워크를 쓰지 않는 편입니다. 그렇다고 프레임워크가 나쁘다는 뜻은 아닙니다. 단지 프레임워크들이 보다 직접적인 방식의 대안들에 대해 자신의 가치를 스스로 증명해야 할 필요가 있다는 생각이다.
서비스를 사용하는 모든 객체가 위치탐색기에 의존한다는 것이 서비스 위치탐색기의 가장 큰 차이점입니다. 위치탐색기로 다른 구현물들에 대한 의존성을 숨길 수 있지만 위치탐색기에는 의존해야 합니다. 그래서 위치탐색기와 주입 사이에서의 결정은 의존성이 문제가 되는지 여부에 따릅니다.
의존성 주입을 쓰면 컴포넌트가 무엇에 의존하고 있는지 파악하기 쉽습니다. 의존성 주입에서는 단순히 생성자 같은 주입 메커니즘만 살피면 의존성을 알 수 있습니다. 서비스 위치탐색기에서는 위치탐색기를 호출하는 부분의 소스코드를 찾아 읽어봐야 합니다. 최신 IDE의 참조 찾기 기능을 쓰면 이런 작업을 쉽게 할 수 있지만, 여전히 생성자나 변경 메소드를 보기 만큼 쉬운 건 아닙니다.
서비스를 사용하는 객체의 특징에 따라 대부분이 결정됩니다. 서비스 하나를 사용하는 여러 유형의 클래스로 된 응용 소프트웨어를 만들 때라면 응용 클래스가 위치탐색기에 의존하는 게 큰 문제가 안 됩니다. 친구들에게 준 예의 MovieLister에서는 서비스 위치탐색기가 아주 잘 작동했습니다. 친구들이 해야 할 일이라고는 올바른 서비스 구현물을 집어오도록 위치탐색기를 설정하는 것뿐입니다. 설정을 코드로 하든 아니면 파일로 하든 상관없이 말입니다. 이런 시나리오에서는 주입기의 역전이 그리 매력적으로 보이지 않습니다.
MovieLister가 컴포넌트로서 다른 사람이 작성하는 응용 프로그램에 사용된다면 좀 다릅니다. 이 경우에는 고객들이 사용하려는 서비스 위치탐색기의 API에 대해 그리 잘 알지 못합니다. 고객은 각각 서로 호환이 안 되는 자신만의 서비스 위치탐색기를 가지고 있을 것입니다. 격리된 인터페이스(segregated interface)를 쓰면 어느 정도 이 문제를 해결할 수 있습니다. 고객들이 직접 이 인터페이스로 자신들의 위치탐색기에 맞는 어댑터를 만들 수 있습니다. 그렇지만 어떤 경우라도 여전히 특정 격리 인터페이스를 찾으려면 첫 번째 위치탐색기를 살펴봐야 합니다. 그리고, 일단 어댑터가 등장하면 위치탐색기를 직접 다루는 단순성은 약해지기 시작합니다.
주입에서는 컴포넌트가 주입기에 의존하지 않기 때문에 컴포넌트가 한번 설정되고 나면 주입기에서 추가적인 서비스들을 얻을 수 없습니다.
사람들은 보통 테스트를 더 쉽게 할 수 있어서 의존성 주입을 선호합니다. 여기서 핵심은 테스트하려면 실 서비스 구현을 스텁(stub)이나 목(mock)으로 쉽게 바꿀 수 있어야 한다는 것입니다. 그렇지만 의존성 주입과 서비스 위치탐색기 사이에는 이런 면에서 진짜 어떤 차이도 없습니다. 양쪽 모두 스텁을 쓰기 아주 좋습니다. 서비스 위치탐색기를 쉽게 교체할 수 있도록 노력하지 않은 프로젝트에서 나온 관찰 결과는 의심스럽습니다. 이런 데는 지속적 테스트가 도움이 됩니다. 테스트를 하면서 서비스를 스텁으로 바꾸기 쉽지 않다면 설계에 심각한 문제가 있다는 뜻입니다.
물론 테스트 문제는 자바 EJB 프레임워크 같이 심하게 침투적인(intrusive) 컴포넌트 환경 때문에 악화됩니다. 이런 종류의 프레임워크는 응용 프로그램 코드에 주는 영향이 최소화되어야 한다고 봅니다. 특히, 코드를 수정하고 실행하는 주기가 느려지게 하는 일은 해서는 안 됩니다. 무거운 컴포넌트를 대체하는 플러그인을 쓰면 이런 작업에 큰 도움이 됩니다. 이는 테스트 주도 개발 같은 기법에는 성패가 달린 결정적 조건입니다.
이처럼 핵심은 외부의 응용 프로그램에 쓰일 것으로 예상하는 코드의 작성자와 관련되어 있습니다. 이런 프로그램에는 컴포넌트 제작자가 어떤 제어를 할 수가 없습니다. 이 경우에는 서비스 위치탐색기 도입을 아무리 최소한으로만 하려 한다 해도 문제가 됩니다.
생성자 주입 대 변경자 주입
서비스를 조합하는 데에는 서비스들을 서로 연결하는 항상 어떤 규약이 있어야 합니다. 주입은 매우 간단한 규약만 있으면 되는데 이것이 주입의 본질적인 강점입니다. 최소한 생성자 주입과 변경 주입은 그렇습니다. 컴포넌트에 잡스런 어떤 작업도 하지 않아도 되고 주입기 입장에서도 모든 설정 작업을 상당히 직접적이고 단순하게 할 수 있습니다.
인터페이스 주입은 설정 작업에 필요한 많은 인터페이스를 만들어야 하기 때문에 상대적으로 침투적입니다. 아발론의 방식처럼 몇몇 인터페이스만 있어도 된다면 그렇게 나쁘지만은 않습니다. 그렇지만 인터페이스 주입은 컴포넌트와 의존 객체들을 조립하는데 많은 작업을 해야 합니다. 이런 이유로 요즘 등장하는 경량 컨테이너들은 변경자와 생성자 주입을 합니다.
변경자와 생성자 주입 사이에서 선택하는 문제는 객체지향 프로그래밍의 일반적인 논쟁을 반영하기 때문에 흥미있습니다. 즉 객체의 필드에 값을 생성자에서 채울 것이냐 변경자에서 할 것이냐는 문제이다.
객체와 관련해서 오래전부터 전 기본적으로 가능한 생성 시에 객체를 유효하게 만듭니다. 이 조언은 켄트백의 "스몰톡 우수 기법 패턴(Smalltalk Best Practice Patterns)"에 실린 생성자 메소드와 생성자 매개변수 메소드 패턴까지 거슬러 올라갑니다. 매개변수가 있는 생성자는 적합한 장소에서 유효한 객체를 만들라는 뜻을 명백하게 표현합니다. 객체 초기화 방식이 하나 이상이라면 다른 조합을 표현하는 여러 생성자를 만듭니다.
단순히 변경자 없앰으로써 불변 필드를 확실히 숨길 수 있는 것도 생성자 초기화의 장점입니다. 이 점은 중요합니다. 무엇인가 바뀌지 말아야 할 때도 변경자는 변경사항을 고스란히 전달하는 결함이 있습니다. 초기화에 변경자를 쓰면 이런 특징 때문에 고민하게 됩니다. (실제로 이런 상황에 저는 변경자를 쓰는 방식을 배제하곤 합니다. 전 initFoo 같은 메소드를 써서 생성될 때에만 수행되어야 할 무엇이라는 것을 강조하는 편입니다.)
하지만 모든 상황에는 예외가 있습니다. 생성자 매개변수가 너무 많으면 무척 보기 지저분합니다. 키워드 매개변수가 없는 언어에서는 특히 그렇습니다. 생성자가 길면 대개 객체가 너무 많은 일을 하고 있다는 것을 뜻하기 때문에 나눠야 하는 것이 맞지만 의도적으로 생성자를 길게 만들어야 할 때도 있습니다.
객체를 유효하게 생성하는 방법이 여러 가지 있다면 생성자가 매개변수의 숫자와 타입만으로 구분할 수 있는 이상 생성자로 구분해서 표현하기 어려울 수 있습니다. 이때가 팩토리 메서드가 활약할 때입니다. 팩토리 메서드는 private 생성자와 변경자의 조합으로 유효한 객체를 생성합니다. 컴포넌트 조합에 쓰는 전통적 팩토리 메서드는 보통 정적 메서드(static method) 형태이기 때문에 인터페이스에 둘 수가 없습니다. 팩토리 클래스를 만들 수도 있지만 그러면 주입해야 할 또 다른 서비스 인스턴스가 생길 뿐입니다. 팩토리 서비스가 가끔 좋은 전략일 수 있지만 팩토리를 생성하려면 이 글에서 다루는 IoC 기술 중 하나를 써야만 합니다.
문자열같이 단순한 매개변수를 가진 생성자도 골치입니다. 변경자 주입에서라면 각 변경자의 이름으로 문자열 매개변수가 무엇에 쓰는 것인지 나타낼 수 있습니다. 생성자는 매개변수의 위치에 의존할 뿐이어서 용도를 알기 어렵습니다.
생성자가 여러 개이고 상속을 했다면 특히 곤란해질 수 있습니다. 모든 것을 초기화를 하려면 자신만의 매개변수를 추가하면서도 부모 클래스의 생성자 하나하나를 호출하는 생성자들을 만들어야 합니다. 자칫하면 생성자가 폭발적으로 많아질 수도 있습니다.
이런 단점이 있어도 전 생성자 주입부터 시작하기 좋아합니다. 그렇지만 지금까지 지적한 문제가 진짜 문제가 되기 시작하면 즉시 변경자 주입으로 바꿀 수 있도록 준비를 합니다.
생성자 주입과 변경자 주입을 비교하는 문제 때문에 의존성 주입 기능이 있는 프레임워크의 제작팀 사이에서 많은 논쟁이 있었습니다. 그렇지만, 이들 프레임워크를 만드는 대부분의 사람은 비록 어느 한 쪽을 선호한다 하더라도 두 가지 방식을 모두 지원하는 것이 중요하다는 것을 이해하는 듯합니다.
코드 대 설정 파일
서비스들을 엮을 때 설정 파일을 쓸 것인지 아니면 코드로 API를 써서 할 것인지 선택하는 문제는 개별적이면서도 종종 혼용할 수도 있는 문제입니다. 여러 곳에 배포될 것으로 예상하는 애플리케이션 대부분은 별도 설정 파일 쪽이 대체로 타당합니다. 대부분의 경우 XML 파일일 것이고 XML은 이런 일에 잘 맞습니다. 하지만 프로그램 코드로 서비스를 조립하기가 더 쉬울 때도 있습니다. 한가지 사례로 배포 환경이 비슷비슷한 단순한 응용프로그램을 들 수 있습니다. 이 경우 분리된 XML 파일보다 코드가 조금 더 깨끗할 수 있습니다.
조건에 따라 분기하는 단계가 포함되어 조합이 매우 복잡한 경우에는 차이가 분명합니다. 한번 프로그래밍 언어에 친숙해지기 시작하면 XML은 서서히 쓰지 않을 것이고 진짜 언어를 쓰는 것이 더 나을 겁니다. 프로그래밍 언어는 깨끗한 프로그램을 짜는 데 필요한 모든 문법을 가지고 있습니다. 코드로 조립한다면 빌더 클래스를 만들 것입니다. 독특한 여러 구축 계획이 있다면 빌더 클래스들을 여러 개 마련하고 간단한 파일을 써서 어떤 것을 선택할지 설정할 수 있습니다.
종종 사람들이 필요 이상으로 설정 파일을 정의하는데 열심인 것으로 보입니다. 프로그래밍 언어는 보통 직접적이고 강력한 설정 메커니즘을 만듭니다. 최신 언어들은 대규모 시스템의 일부로 조립해 쓸 수 있는 작은 조립기를 쉽게 컴파일할 수 있습니다. 컴파일하는 게 문제라면 같은 작업을 수행할 수 있는 스크립트 언어도 있습니다.
프로그래머가 아닌 사람이 수정할 수 있어야 하기 때문에 설정에 프로그래밍 언어를 쓰면 안 된다는 말을 듣곤 합니다. 그렇지만 이런 경우가 얼마나 될까요? 사람들이 진짜 복잡한 서버 응용 프로그램의 트렌젝션 격리 수준(transaction isolation level)을 비 프로그래머가 조정하기 원할까요? 프로그래밍 언어가 아닌 설정 파일을 써야만 단순하게 작업할 수 있는 걸까요? 설정 파일이 복잡해지면 적당한 프로그래밍 언어를 사용해볼 것을 생각하기 시작해야 할 시점입니다.
지금 이 순간 자바 세상에는 설정 파일 사이에 불협화음이 있습니다. 컴포넌트마다 다른 것들과 다른 자신만의 고유 설정파일을 가지고 있습니다. 만약 이런 컴포넌트 수십 개를 가지고 있다면 설정파일 수십 개를 서로 맞추느라 금방 질리고 말 것입니다.
여기서 저는 항상 프로그래밍 인터페이스를 써서 쉽게 모든 설정을 할 수 있도록 할 것을 제안합니다. 그러고 나면 별도 설정 파일을 다루는 일은 선택사항이 됩니다. 프로그래밍 인터페이스를 써서 설정 파일을 다루도록 하는 건 쉽습니다. 컴포넌트를 작성한다면 프로그래밍 인터페이스를 쓸지, 정해진 형태의 설정 파일을 쓸지, 자작 설정 양식을 만들고 프로그래밍 인터페이스에 이것을 붙일지 여부를 컴포넌트의 사용자에게 남겨두십시오.
사용과 설정의 분리
서비스를 사용하는 곳에서 설정을 분리하도록 하는 것이 그 무엇보다 중요합니다. 실제로 이는 구현에서 인터페이스를 분리하라는 기본적인 설계 원칙에 들어맞습니다. 이 원칙은 객체지향 프로그래밍에서 조건문으로 어떤 클래스를 생성할지 결정할 때에 볼 수 있습니다. 이렇게 생성을 한 후에는 중복된 조건문으로 조건을 평가하는 일이 다형성으로 대체됩니다.
이런 분리가 단일 코드 베이스에서 유용하다면 서비스나 컴포넌트 같은 외래 요소를 쓸 때는 특히 절대적입니다. 먼저 특정 배포 환경에서 어떤 구현 클래스를 쓸지 선택하는 것을 나중으로 미루고 싶은지 질문해야 합니다. 만약 그렇다면 플러그인의 어떤 구현물을 사용해야 합니다. 일단 플러그인을 쓴다면 이 플러그인들을 조립하는 작업은 반드시 응용 프로그램의 다른 부분들과 분리해야 합니다. 이렇게 해야 다른 배포 환경에 맞는 설정을 쉽게 교체할 수 있습니다. 어떻게 이것을 하느냐는 것은 부차적인 문제입니다. 설정 메커니즘은 서비스 위치탐색기를 설정하는 것일 수도 있고 직접 객체를 설정하도록 주입을 사용하는 것일 수도 있습니다.
그 외 주제들
이 글에서는 의존성 주입과 서비스 위치탐색기로 서비스를 설정하는 기본 문제에 집중했습니다. 여전히 주목해서 다룰만한 주제가 더 있지만 아직 파고들 기회가 없었습니다. 특히 생애 주기 행태(life-cycle behavior)와 관련된 토론 주제가 있습니다. 어떤 컴포넌트는 (멈춤이나 시작 같은) 고유의 생애 주기 이벤트를 가지고 있습니다. 경량 컨테이너드에서 관점 지향을 사용하는 것도 점점 많은 관심을 받는 또 다른 토론 주제입니다. 비록 이 글에서는 관점 지향을 다루지 않았지만 이에 대해 내용을 추가하거나 따로 쓰거나 할 생각입니다.
경량 컨테이너 관련 웹 사이트를 찾아보면 지금까지 다룬 주제에 대해서는 더 많이 찾을 수 있을 것입니다. 피코 컨테이너와 스프링 웹 사이트를 방문하면 이들 주제와 관련된 더 많은 논의와 새로운 주제의 시작을 접할 수 있습니다.
마치면서...
요즘 쇄도하는 경량 컨테이너는 모두 서비스를 조립하는 방법과 관련하여 의존성 주입이라는 공통 패턴에 근거를 두고 있습니다. 의존성 주입은 서비스 위치탐색기의 유용한 대안입니다. 응용 클래스를 만들 때 이 둘은 거의 같습니다. 그렇지만 서비스 위치탐색기가 더 직접적이기 때문에 약간은 앞선다고 생각합니다. 그렇지만 여러 응용 프로그램에서 쓰이는 클래스를 만든다면 의존성 주입을 선택하는 것이 더 낫습니다.
서비스 위치탐색기와 의존성 주입 간의 선택은 응용 프로그램 안에서 서비스를 사용하는 부분과 서비스를 설정하는 부분을 분리한다는 원칙보다 중요하지는 않습니다.
지금까지는 제가 이 패턴과 변종들을 어떻게 이해하는지를 집중적으로 설명했습니다. 이제, 언제 어떤 것을 쓸면 좋을지 판단하는 데 도움이 될 만한 장단점을 이야기할 수 있게 되었습니다.
서비스 위치탐색기 대 의존성 주입
서비스 위치탐색기와 의존성 주입 사이에서 어떤 것을 선택하느냐가 가장 근본적입니다. 이 두 구현체 모두 기본적으로 객체 간의 결합을 끊어 줍니다. 결합은 '빈약한 예제'의 결함이었습니다. 두 기술을 적용한 사례 모두 사용하는 쪽 코드가 서비스 인터페이스의 구현물에 의존하지 않았습니다. 서비스를 사용하는 응용 클래스에 구현물을 제공하는 방법이 두 패턴의 중요한 차이점입니다. 서비스 위치탐색기 방식에서는 응용 클래스가 명시적으로 위치탐색기를 호출하여 서비스를 요청합니다. 주입 방식에서는 명시적인 요청이 없습니다. 사용하기 원하는 서비스가 응용 클래스에 들어갑니다. 그래서 제어 역전이라고 합니다.
제어 역전은 프레임워크들의 일반적인 특징이지만 비용을 내해야 합니다. 프레임워크는 대체로 이해하기 어렵고 디버깅을 하려고 할 때 문제가 생기기도 합니다. 그래서 전 보통 필요하지 않으면 프레임워크를 쓰지 않는 편입니다. 그렇다고 프레임워크가 나쁘다는 뜻은 아닙니다. 단지 프레임워크들이 보다 직접적인 방식의 대안들에 대해 자신의 가치를 스스로 증명해야 할 필요가 있다는 생각이다.
서비스를 사용하는 모든 객체가 위치탐색기에 의존한다는 것이 서비스 위치탐색기의 가장 큰 차이점입니다. 위치탐색기로 다른 구현물들에 대한 의존성을 숨길 수 있지만 위치탐색기에는 의존해야 합니다. 그래서 위치탐색기와 주입 사이에서의 결정은 의존성이 문제가 되는지 여부에 따릅니다.
의존성 주입을 쓰면 컴포넌트가 무엇에 의존하고 있는지 파악하기 쉽습니다. 의존성 주입에서는 단순히 생성자 같은 주입 메커니즘만 살피면 의존성을 알 수 있습니다. 서비스 위치탐색기에서는 위치탐색기를 호출하는 부분의 소스코드를 찾아 읽어봐야 합니다. 최신 IDE의 참조 찾기 기능을 쓰면 이런 작업을 쉽게 할 수 있지만, 여전히 생성자나 변경 메소드를 보기 만큼 쉬운 건 아닙니다.
서비스를 사용하는 객체의 특징에 따라 대부분이 결정됩니다. 서비스 하나를 사용하는 여러 유형의 클래스로 된 응용 소프트웨어를 만들 때라면 응용 클래스가 위치탐색기에 의존하는 게 큰 문제가 안 됩니다. 친구들에게 준 예의 MovieLister에서는 서비스 위치탐색기가 아주 잘 작동했습니다. 친구들이 해야 할 일이라고는 올바른 서비스 구현물을 집어오도록 위치탐색기를 설정하는 것뿐입니다. 설정을 코드로 하든 아니면 파일로 하든 상관없이 말입니다. 이런 시나리오에서는 주입기의 역전이 그리 매력적으로 보이지 않습니다.
MovieLister가 컴포넌트로서 다른 사람이 작성하는 응용 프로그램에 사용된다면 좀 다릅니다. 이 경우에는 고객들이 사용하려는 서비스 위치탐색기의 API에 대해 그리 잘 알지 못합니다. 고객은 각각 서로 호환이 안 되는 자신만의 서비스 위치탐색기를 가지고 있을 것입니다. 격리된 인터페이스(segregated interface)를 쓰면 어느 정도 이 문제를 해결할 수 있습니다. 고객들이 직접 이 인터페이스로 자신들의 위치탐색기에 맞는 어댑터를 만들 수 있습니다. 그렇지만 어떤 경우라도 여전히 특정 격리 인터페이스를 찾으려면 첫 번째 위치탐색기를 살펴봐야 합니다. 그리고, 일단 어댑터가 등장하면 위치탐색기를 직접 다루는 단순성은 약해지기 시작합니다.
주입에서는 컴포넌트가 주입기에 의존하지 않기 때문에 컴포넌트가 한번 설정되고 나면 주입기에서 추가적인 서비스들을 얻을 수 없습니다.
사람들은 보통 테스트를 더 쉽게 할 수 있어서 의존성 주입을 선호합니다. 여기서 핵심은 테스트하려면 실 서비스 구현을 스텁(stub)이나 목(mock)으로 쉽게 바꿀 수 있어야 한다는 것입니다. 그렇지만 의존성 주입과 서비스 위치탐색기 사이에는 이런 면에서 진짜 어떤 차이도 없습니다. 양쪽 모두 스텁을 쓰기 아주 좋습니다. 서비스 위치탐색기를 쉽게 교체할 수 있도록 노력하지 않은 프로젝트에서 나온 관찰 결과는 의심스럽습니다. 이런 데는 지속적 테스트가 도움이 됩니다. 테스트를 하면서 서비스를 스텁으로 바꾸기 쉽지 않다면 설계에 심각한 문제가 있다는 뜻입니다.
물론 테스트 문제는 자바 EJB 프레임워크 같이 심하게 침투적인(intrusive) 컴포넌트 환경 때문에 악화됩니다. 이런 종류의 프레임워크는 응용 프로그램 코드에 주는 영향이 최소화되어야 한다고 봅니다. 특히, 코드를 수정하고 실행하는 주기가 느려지게 하는 일은 해서는 안 됩니다. 무거운 컴포넌트를 대체하는 플러그인을 쓰면 이런 작업에 큰 도움이 됩니다. 이는 테스트 주도 개발 같은 기법에는 성패가 달린 결정적 조건입니다.
이처럼 핵심은 외부의 응용 프로그램에 쓰일 것으로 예상하는 코드의 작성자와 관련되어 있습니다. 이런 프로그램에는 컴포넌트 제작자가 어떤 제어를 할 수가 없습니다. 이 경우에는 서비스 위치탐색기 도입을 아무리 최소한으로만 하려 한다 해도 문제가 됩니다.
생성자 주입 대 변경자 주입
서비스를 조합하는 데에는 서비스들을 서로 연결하는 항상 어떤 규약이 있어야 합니다. 주입은 매우 간단한 규약만 있으면 되는데 이것이 주입의 본질적인 강점입니다. 최소한 생성자 주입과 변경 주입은 그렇습니다. 컴포넌트에 잡스런 어떤 작업도 하지 않아도 되고 주입기 입장에서도 모든 설정 작업을 상당히 직접적이고 단순하게 할 수 있습니다.
인터페이스 주입은 설정 작업에 필요한 많은 인터페이스를 만들어야 하기 때문에 상대적으로 침투적입니다. 아발론의 방식처럼 몇몇 인터페이스만 있어도 된다면 그렇게 나쁘지만은 않습니다. 그렇지만 인터페이스 주입은 컴포넌트와 의존 객체들을 조립하는데 많은 작업을 해야 합니다. 이런 이유로 요즘 등장하는 경량 컨테이너들은 변경자와 생성자 주입을 합니다.
변경자와 생성자 주입 사이에서 선택하는 문제는 객체지향 프로그래밍의 일반적인 논쟁을 반영하기 때문에 흥미있습니다. 즉 객체의 필드에 값을 생성자에서 채울 것이냐 변경자에서 할 것이냐는 문제이다.
객체와 관련해서 오래전부터 전 기본적으로 가능한 생성 시에 객체를 유효하게 만듭니다. 이 조언은 켄트백의 "스몰톡 우수 기법 패턴(Smalltalk Best Practice Patterns)"에 실린 생성자 메소드와 생성자 매개변수 메소드 패턴까지 거슬러 올라갑니다. 매개변수가 있는 생성자는 적합한 장소에서 유효한 객체를 만들라는 뜻을 명백하게 표현합니다. 객체 초기화 방식이 하나 이상이라면 다른 조합을 표현하는 여러 생성자를 만듭니다.
단순히 변경자 없앰으로써 불변 필드를 확실히 숨길 수 있는 것도 생성자 초기화의 장점입니다. 이 점은 중요합니다. 무엇인가 바뀌지 말아야 할 때도 변경자는 변경사항을 고스란히 전달하는 결함이 있습니다. 초기화에 변경자를 쓰면 이런 특징 때문에 고민하게 됩니다. (실제로 이런 상황에 저는 변경자를 쓰는 방식을 배제하곤 합니다. 전 initFoo 같은 메소드를 써서 생성될 때에만 수행되어야 할 무엇이라는 것을 강조하는 편입니다.)
하지만 모든 상황에는 예외가 있습니다. 생성자 매개변수가 너무 많으면 무척 보기 지저분합니다. 키워드 매개변수가 없는 언어에서는 특히 그렇습니다. 생성자가 길면 대개 객체가 너무 많은 일을 하고 있다는 것을 뜻하기 때문에 나눠야 하는 것이 맞지만 의도적으로 생성자를 길게 만들어야 할 때도 있습니다.
객체를 유효하게 생성하는 방법이 여러 가지 있다면 생성자가 매개변수의 숫자와 타입만으로 구분할 수 있는 이상 생성자로 구분해서 표현하기 어려울 수 있습니다. 이때가 팩토리 메서드가 활약할 때입니다. 팩토리 메서드는 private 생성자와 변경자의 조합으로 유효한 객체를 생성합니다. 컴포넌트 조합에 쓰는 전통적 팩토리 메서드는 보통 정적 메서드(static method) 형태이기 때문에 인터페이스에 둘 수가 없습니다. 팩토리 클래스를 만들 수도 있지만 그러면 주입해야 할 또 다른 서비스 인스턴스가 생길 뿐입니다. 팩토리 서비스가 가끔 좋은 전략일 수 있지만 팩토리를 생성하려면 이 글에서 다루는 IoC 기술 중 하나를 써야만 합니다.
문자열같이 단순한 매개변수를 가진 생성자도 골치입니다. 변경자 주입에서라면 각 변경자의 이름으로 문자열 매개변수가 무엇에 쓰는 것인지 나타낼 수 있습니다. 생성자는 매개변수의 위치에 의존할 뿐이어서 용도를 알기 어렵습니다.
생성자가 여러 개이고 상속을 했다면 특히 곤란해질 수 있습니다. 모든 것을 초기화를 하려면 자신만의 매개변수를 추가하면서도 부모 클래스의 생성자 하나하나를 호출하는 생성자들을 만들어야 합니다. 자칫하면 생성자가 폭발적으로 많아질 수도 있습니다.
이런 단점이 있어도 전 생성자 주입부터 시작하기 좋아합니다. 그렇지만 지금까지 지적한 문제가 진짜 문제가 되기 시작하면 즉시 변경자 주입으로 바꿀 수 있도록 준비를 합니다.
생성자 주입과 변경자 주입을 비교하는 문제 때문에 의존성 주입 기능이 있는 프레임워크의 제작팀 사이에서 많은 논쟁이 있었습니다. 그렇지만, 이들 프레임워크를 만드는 대부분의 사람은 비록 어느 한 쪽을 선호한다 하더라도 두 가지 방식을 모두 지원하는 것이 중요하다는 것을 이해하는 듯합니다.
코드 대 설정 파일
서비스들을 엮을 때 설정 파일을 쓸 것인지 아니면 코드로 API를 써서 할 것인지 선택하는 문제는 개별적이면서도 종종 혼용할 수도 있는 문제입니다. 여러 곳에 배포될 것으로 예상하는 애플리케이션 대부분은 별도 설정 파일 쪽이 대체로 타당합니다. 대부분의 경우 XML 파일일 것이고 XML은 이런 일에 잘 맞습니다. 하지만 프로그램 코드로 서비스를 조립하기가 더 쉬울 때도 있습니다. 한가지 사례로 배포 환경이 비슷비슷한 단순한 응용프로그램을 들 수 있습니다. 이 경우 분리된 XML 파일보다 코드가 조금 더 깨끗할 수 있습니다.
조건에 따라 분기하는 단계가 포함되어 조합이 매우 복잡한 경우에는 차이가 분명합니다. 한번 프로그래밍 언어에 친숙해지기 시작하면 XML은 서서히 쓰지 않을 것이고 진짜 언어를 쓰는 것이 더 나을 겁니다. 프로그래밍 언어는 깨끗한 프로그램을 짜는 데 필요한 모든 문법을 가지고 있습니다. 코드로 조립한다면 빌더 클래스를 만들 것입니다. 독특한 여러 구축 계획이 있다면 빌더 클래스들을 여러 개 마련하고 간단한 파일을 써서 어떤 것을 선택할지 설정할 수 있습니다.
종종 사람들이 필요 이상으로 설정 파일을 정의하는데 열심인 것으로 보입니다. 프로그래밍 언어는 보통 직접적이고 강력한 설정 메커니즘을 만듭니다. 최신 언어들은 대규모 시스템의 일부로 조립해 쓸 수 있는 작은 조립기를 쉽게 컴파일할 수 있습니다. 컴파일하는 게 문제라면 같은 작업을 수행할 수 있는 스크립트 언어도 있습니다.
프로그래머가 아닌 사람이 수정할 수 있어야 하기 때문에 설정에 프로그래밍 언어를 쓰면 안 된다는 말을 듣곤 합니다. 그렇지만 이런 경우가 얼마나 될까요? 사람들이 진짜 복잡한 서버 응용 프로그램의 트렌젝션 격리 수준(transaction isolation level)을 비 프로그래머가 조정하기 원할까요? 프로그래밍 언어가 아닌 설정 파일을 써야만 단순하게 작업할 수 있는 걸까요? 설정 파일이 복잡해지면 적당한 프로그래밍 언어를 사용해볼 것을 생각하기 시작해야 할 시점입니다.
지금 이 순간 자바 세상에는 설정 파일 사이에 불협화음이 있습니다. 컴포넌트마다 다른 것들과 다른 자신만의 고유 설정파일을 가지고 있습니다. 만약 이런 컴포넌트 수십 개를 가지고 있다면 설정파일 수십 개를 서로 맞추느라 금방 질리고 말 것입니다.
여기서 저는 항상 프로그래밍 인터페이스를 써서 쉽게 모든 설정을 할 수 있도록 할 것을 제안합니다. 그러고 나면 별도 설정 파일을 다루는 일은 선택사항이 됩니다. 프로그래밍 인터페이스를 써서 설정 파일을 다루도록 하는 건 쉽습니다. 컴포넌트를 작성한다면 프로그래밍 인터페이스를 쓸지, 정해진 형태의 설정 파일을 쓸지, 자작 설정 양식을 만들고 프로그래밍 인터페이스에 이것을 붙일지 여부를 컴포넌트의 사용자에게 남겨두십시오.
사용과 설정의 분리
서비스를 사용하는 곳에서 설정을 분리하도록 하는 것이 그 무엇보다 중요합니다. 실제로 이는 구현에서 인터페이스를 분리하라는 기본적인 설계 원칙에 들어맞습니다. 이 원칙은 객체지향 프로그래밍에서 조건문으로 어떤 클래스를 생성할지 결정할 때에 볼 수 있습니다. 이렇게 생성을 한 후에는 중복된 조건문으로 조건을 평가하는 일이 다형성으로 대체됩니다.
이런 분리가 단일 코드 베이스에서 유용하다면 서비스나 컴포넌트 같은 외래 요소를 쓸 때는 특히 절대적입니다. 먼저 특정 배포 환경에서 어떤 구현 클래스를 쓸지 선택하는 것을 나중으로 미루고 싶은지 질문해야 합니다. 만약 그렇다면 플러그인의 어떤 구현물을 사용해야 합니다. 일단 플러그인을 쓴다면 이 플러그인들을 조립하는 작업은 반드시 응용 프로그램의 다른 부분들과 분리해야 합니다. 이렇게 해야 다른 배포 환경에 맞는 설정을 쉽게 교체할 수 있습니다. 어떻게 이것을 하느냐는 것은 부차적인 문제입니다. 설정 메커니즘은 서비스 위치탐색기를 설정하는 것일 수도 있고 직접 객체를 설정하도록 주입을 사용하는 것일 수도 있습니다.
그 외 주제들
이 글에서는 의존성 주입과 서비스 위치탐색기로 서비스를 설정하는 기본 문제에 집중했습니다. 여전히 주목해서 다룰만한 주제가 더 있지만 아직 파고들 기회가 없었습니다. 특히 생애 주기 행태(life-cycle behavior)와 관련된 토론 주제가 있습니다. 어떤 컴포넌트는 (멈춤이나 시작 같은) 고유의 생애 주기 이벤트를 가지고 있습니다. 경량 컨테이너드에서 관점 지향을 사용하는 것도 점점 많은 관심을 받는 또 다른 토론 주제입니다. 비록 이 글에서는 관점 지향을 다루지 않았지만 이에 대해 내용을 추가하거나 따로 쓰거나 할 생각입니다.
경량 컨테이너 관련 웹 사이트를 찾아보면 지금까지 다룬 주제에 대해서는 더 많이 찾을 수 있을 것입니다. 피코 컨테이너와 스프링 웹 사이트를 방문하면 이들 주제와 관련된 더 많은 논의와 새로운 주제의 시작을 접할 수 있습니다.
마치면서...
요즘 쇄도하는 경량 컨테이너는 모두 서비스를 조립하는 방법과 관련하여 의존성 주입이라는 공통 패턴에 근거를 두고 있습니다. 의존성 주입은 서비스 위치탐색기의 유용한 대안입니다. 응용 클래스를 만들 때 이 둘은 거의 같습니다. 그렇지만 서비스 위치탐색기가 더 직접적이기 때문에 약간은 앞선다고 생각합니다. 그렇지만 여러 응용 프로그램에서 쓰이는 클래스를 만든다면 의존성 주입을 선택하는 것이 더 낫습니다.
서비스 위치탐색기와 의존성 주입 간의 선택은 응용 프로그램 안에서 서비스를 사용하는 부분과 서비스를 설정하는 부분을 분리한다는 원칙보다 중요하지는 않습니다.
이 글과 관련있는 글을 자동검색한 결과입니다 [?]
# by | 2009/10/31 16:38 | 프로그래밍 이야기 | 트랙백(1) | 덧글(0)


![[수입] 요요 마가 연주한 엔니오 모리코네 [CD+DVD 듀얼 디스크]](http://image.aladdin.co.kr/coveretc/music/coveroff/2592437362_1.jpg)
![[SACD] 조수미 (Sumi Jo) - Journey To Baroque (바로크로의 여행)](http://image.aladdin.co.kr/coveretc/music/coveroff/1011203995_1.jpg)







☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
제목 : fupfin의 느낌
[번역] 제어 역전 컨테이너와 의존성 주입 패턴 (4/4)...more