[번역] 제어 역전 컨테이너와 의존성 주입 패턴 (1/4)

마틴 파울러(Martin Fowler)

자바 커뮤니티에서는 줄곧 경량 컨테이너들이 경쟁적으로 출시 되고 있습니다. 이들은 다른 프로젝트에서 가지고 온 S/W 부품을 조립해서 하나로 통합된 응용 S/W를 만드는 데 도움이 됩니다. 이 컨테이너들은 S/W 부품들을 연결하는 방법에 대한 공통 패턴을 근간으로 하고 있습니다. 이 개념을 흔히 제어 역전(Inversion of Control)이라고 부릅니다. 이 글에서는 이 패턴을 의존성 주입(Dependency Injection)과 서비스 위치탐색기(Service Locator)로 세분화하고 이들을 비교함으로 이 패턴의 작동 방식을 파고들어보려고 합니다. 이 두 가지 방식 중 어떤 것을 선택할지 논하는 것보다 S/W 부품를 이용하는 코드에서 설정을 분리하자는 원칙이 더 중요합니다.

주류 J2EE의 대안을 만들려는 엄청난 규모의 활동은 기업용 자바 세상에서 흥미있는 일 중 하나입니다.그 중 많은 수가 오픈 소스에서 일어나고 있습니다. 상당수는 무겁고 복잡한 주류 J2EE 세상에 대한 반발이지만 다수는 대안을 찾으려는 탐구와 창의적인 시도이기도 합니다. 어떻게 각기 다른 요소를 함께 연결할 것인지를 다루는 것, 예를 들어 서로를 잘 알지 못하는 여러 팀에서 각자 만든 웹 컨트롤러 아키덱처와 데이터베이스 인터페이스를 어떻게 하나로 묶을 것인지가 이것들이 해결하려는 공통 사안입니다. 다수의 프레임워크가 이 문제를 해결했고 그중 몇몇은 다른 계층의 컴포넌트들을 조립하는 일반적인 기능까지 제공하는데 이르렀습니다. 이들을 종종 경량 컨테이너라고 부르는데 피코 컨테이너와 스프링을 예로 들 수 있습니다.

이 컨테이너들은 인상적인 몇가지 설계 원칙에 기초하고 있습니다. 이 원칙들은 특정 컨테이너는 물론 자바라는 플렛폼까지 넘어서 적용될 수 있습니다. 여기서 이 원칙 중 몇가지를 탐구해보려 합니다. 예로 사용한 언어는 자바이지만 내가 썼던 다른 원칙들 대부분이 그런 것처럼 다른 객체지향 환경(특히 .Net)에도 적용할 수 있습니다.

S/W 구성 요소들을 하나로 묶는 주제를 다루려면 서비스와 컴포넌트라는 두 단어를 둘러싼 껄끄러운 용어 문제 먼저 해결해야 할 것 같습니다. 이 둘의 정의와 관련된 길고 모호한 글들을 쉽게 찾을 수 있습니다. 저는 여기서 제가 이 단어를 어떤 의미로 사용하는지 말하려고 합니다.

전 컴포넌트를 재사용되는 소프트웨어 덩어리를 의미할 때 사용합니다. 컴포넌트는 컴포넌트 제작자가 어떤 영향도 끼칠 수 없는 응용 소프트웨어에 변경 없이 재사용 할 수 있습니다. 여기서 '변경 없이'라고 한 건 응용 소프트웨어가 컴포넌트 제작자가 허용한 방법으로 컴포넌트를 확장해서 행위를 바꾸어 사용할 수는 있을지언정  컴포넌트의 소스 코드를 고치지는 않는다는 것을 뜻합니다.

서비스는 외래 애플리케이션이 사용한다는 측면에서는 컴포넌트와 비슷합니다. 주요 차이점이라면 컴포넌트가 (jar 파일, 조립, DLL, 소스코드 삽입등 같이) 지역적으로 사용될 것이라 보는 반면에 서비스는 동기 방식이든 (웹 서비스, 전문 시스템, RPC, 소켓 등) 비동기 방식이든 모종의 인터페이스를 사용해 원격에서 사용 되리라는 것입니다.

이 글에서는 대부분 서비스라고 쓰겠지만 상당수의 논리는 지역적 컴포넌트에도 똑같이 적용될 수 있습니다. 사실 원격 서비스를 쉽게 사용하려면 흔히 로컬 컴포넌트 프레임워크 같은 것이 필요합니다. 컴포넌트냐 서비스냐에 대해 논하는 글은 읽는 것도 쓰는 것도 피곤한 일입니다. 그리고 요즘은 서비스가 훨씬 더 인기있습니다.

순진한 예제

모든 것을 분명하게 하고자 실행 가능한 예제 하나를 사용하려 합니다. 제가 흔히 쓰는 예제가 그렇듯 이 예제도 엄청나게 단순합니다.  비현실적으로 보일 만큼 작지만 복잡한 실제 예의 수렁에 빠지는 일 없이 뭐가 어찌 돌아가는지 눈으로 확인할 수 있는 정도는 되리라 생각합니다.

예제는 특정 감독이 연출한 영화의 목록을 찾아주는 컴포넌트 입니다. 이 놀랍도록 유용한 기능이 메소드 하나로 구현되어 있습니다.

class MovieLister...
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        }

        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
    }

기능 구현이 극단적으로 단순합니다. 보유하고 있는 모든 필름 정보를 달라고 (조금 있다 얻게 될) 찾기 객체에게 요청합니다. 그리고 목록을 뒤쳐 반환할 특정 감독의 작품을 뒤집니다. 이 순진한 코드는 단지 이 글에서 진짜 다루려고 하는 내용을 위한 골격에 지나지 않으므로 손대지 않을 겁니다.

이 글에서 진짜로 관심 있는 것은 찾기 객체, 특히 MovieLister와 특정 찾기 객체를 연결하는 방법입니다. 이것이 흥미있는 이유는 이 멋진 movieDirectedBy 메소드가 영화를 보관하는 방식과 전혀 상관 없이 움직였으면 하기 때문입니다. 그래서 메소드는 단지 찾기 객체를 참조하기만 하면 되고 그 파인더는 findAll 메소드 호출에 어떻게 응답해야하는지 알기만 해도 되었으면 합니다. 찾기 객체의 인터페이스를 정의하는 것으로 이것을 실현할 수 있습니다.

public interface MovieFinder {
    List findAll();
}

이제 이렇게 해서 결합이 잘 분리되었습니다. 하지만 어느 지점에선가 실제 영화 목록을 얻으려면 구상 클래스를 얻어야 합니다. 한번 생성자에 이 코드를 넣어보겠습니다.

class MovieLister...
  private MovieFinder finder;
  public MovieLister() {
    finder = new ColonDelimitedMovieFinder("movies1.txt");
  }

구현 클래스의 이름은 콜론으로 구분되어 있는 텍스트 파일에서 목록을 얻으려고 한다는 뜻입니다. 단지 어떤 구현체가 있다는 사실만이 중요하므로 상세한 부분은 생략하겠습니다.

이 클래스를 혼자서만 쓴다면 모든 것이 훌륭하고 멋집니다. 그런데 친구들이 이 멋진 기능을 쓰고 싶은 열망에 압도되어 이 프로그램를 복사해달라고 할 때 어떤 일이 일어날까요? 그 친구들도 "movies1.txt"라는 파일에 콜론으로 구별된 형태로 영화 목록을 보관하고 있다면 모든 것이 환상적이겠죠. 만약 영화 목록 파일의 이름이 다르다면 설정 파일에 파일 이름을 넣도록 해서 쉽게 해결할 수 있습니다. 그런데 만약 완전히 다른 형태로 영화 목록을 저장하고 있다면 어떨까요? SQL 데이터베이스나 XML이나 웹 서비스나 형식이 다른 텍스트 파일이면 말입니다. 이 경우에는 다른 클래스로 데이터를 얻어 와야 합니다. 이미 MovieFinder 인터페이스를 정의 했기 때문에 moviesDirectedBy 메소드를 바꾸지 않아도 됩니다. 그렇지만 여전히 올바른 finder 구현물의 인스턴스를 얻어 제자리에 두는 어떤 방법이 필요합니다.

그림 1 : 목록 클래스에서 단순히 생성했을 때 의존성 관계

그림 1에서 지금의 의존 관계를 볼 수 있습니다. MovieLister 클래스는 MovieFinder 인터페이스와 그 구현물에 종속되어 있습니다. 인터페이스에만 종속되어 있으면 좋겠는데 어떻게하면 인스턴스를 가져와 쓸 수 있을까요?

P of EAA에는 이런 상황을 Plugin 패턴으로 설명하고 있습니다. 컴파일 시점에는 찾기 객체의 구현 클래스가 프로그램에 연결되어 있지 않습니다. 친구들이 어떤 것을   쓰려는지 모르기 때문입니다. 대신 목록 객체가 어떤 구현물과도 작동되고 나중에 언젠가 그 구현물을 별 조작없이 장착할 수 있으면 좋겠습니다. 문제는 어떻게 우리의 목록 객체가 구현 클래스에 대해 잘 모르면서도 여전히 인스턴스와 협력하여 해야 할일을 할 수 있도록 하느냐는 것입니다.

이것을 실제 시스템으로 확대해서 보겠습니다. 실제 시스템에서는 이런 서비스와 컴포넌트가 수십개나 있을 것입니다. 이 컴포넌트들의 사용하는 곳마다 인터페이스를 통해 호출하도록 추상화 할 수 있습니다. 만약 컴포넌트가 그 인터페이스에 맞지 않다면 어댑터를 써야겠죠. 하지만 만약 이 시스템을 여러가지 다른 방식으로 배포하기 원한다면 이 서비스들과의 상호작용할 수 있도록 처리하기 위해 플러그인을 사용해야 합니다. 그러면 여러 다른 구현물은 여러 다른 배포 환경에서 사용할 수 있습니다.

이제 핵심 문제는 이 여러 플러그인들을 어떻게 조립해서 한 애플리케이션으로 만들 수 있느냐는 것입니다. 이것이 새로운 경량 컨테이너들이 해결하려고 하는 주된 문제 영역 중 하나입니다. 그리고 보편적으로 제어 역전을 그 해법으로 사용합니다.

제어 역전


경량 컨테이너들을 소개할 때에 왜 그것들이 유용한지를 말하면서 이유가 "제어 역전"을 구현했기 때문이라고 할 때면 전 무척 당황스럽습니다. "제어 역전"은 프레임워크들이 갖는 공통적인 특성입니다. 그렇기에 경량 컨테이너들이 자기들은 제어 역전을 쓰기 때문에 특별하다고 말하는 건 마치 내 차에는 바퀴가 있어서 특별하다고 하는 것과 같습니다.

질문해야 할 것은 어떤 제어를 역전하려고 하느냐는 것입니다. 제가 처음 접했던 제어 역전은 유저 인터페이스의 중심 제어에서였습니다. 초기 유저 인터페이스들은 애플리케이션 프로그램에서 제어를 했습니다. 흔히 "이름을 입력하시오", "주소를 입력하시오" 같은 일련의 명령 문구를 가지고 있고 프로그램이 이것들을 하나씩 물어보고는 답을 입력 받곤 했습니다. 그래픽(몇몇 스크린 기반도) 유저 인터페이스에서는 유저 인터페이스 프레임워크가 제어권을 가지고 있고 프로그램은 화면의 각 요소에 대한 이벤트 처리자만 마련하고 있으면 됩니다.  프로그램의 주 제어권은 역전된 것입니다. 애플리케이션에서 프레임워크로 옮겨진 것이지요.

이 새로운 컨테이너들에서의 역전은 플러그인 구현물을 찾을 방법과 관련되어 있습니다. 우리의 순진한 예제에서 목록 객체는 찾기 구현물의 인스턴스를 직접 생성해서 씁니다. 이렇게 하면 찾기 객체를 플러그인이라고 할 수 없습니다. 경량 컨테이너들이 사용하는 접근법은 플러그인을 사용하는 누구라도 어떤 관례를 따르기만 하면 독립된 조립 모듈이 구현물을 목록 객체에 주입하도록 보장합니다.

결국 이런 패턴을 지칭하는 보다 지협적인 이름이 필요한 것 같습니다. 제어 역전은 너무 일반적인 용어라서 사람들이 혼란스러워 합니다. 다양한 IoC 지지자들과 많은 토론을 한 결과 의존성 주입이라고 부르기로 합의하였습니다.

이제 여러가지 형태의 의존성 주입에 대해서 말하려고 하지만 여기서 확실해 해두고 싶은 것은 이것이 플러그인 구현물에서 애플리케이션 클래스의 의존성을 제거하는 유일한 방법이 아니라는 것입니다. 의존성 제거에 쓸 수 있는 다른 패턴으로 서비스 위치탐색기가 있습니다. 이에 대해서는 의존성 주입을 설명하고 나서 다루겠습니다.

[번역] 제어 역전 컨테이너와 의존성 주입 패턴 [1][2][3][4]

---------

워낙 유명한 글이지만 막상 이 글을 읽는 사람도 IOC나 DI의 의미를 이해하고 쓰는 사람이 많지 않은 것 같습니다. 스프링을 쓰는 몇몇 프로젝트에 참여해서 일해본 적이 있지만 매번 스프링을 이런식으로 쓰는 게 무슨 의미인가? 하는 회의만 들었습니다. 그래서 출퇴근 시간에 조금씩 번역해서 올려보려고 합니다. 대충 보니 네 부분으로 나눌 수 있을 것 같습니다. 

by 박성철 | 2009/09/28 00:37 | 프로그래밍 이야기 | 트랙백 | 덧글(0)

하이버네이트의 hbm2ddl.auto에 update가 좋을까? validate가 좋을까?

최근에 참여하는 스터디에서 하이버네이트를 사용해서 작은 프로젝트를 진행했습니다. 저는 지금 몸담고 있는 프로젝트 때문에 미안하게도 구경만 했었는데 진행 중에 hbm2ddl.auto 설정 관련 작은 토론이 있었습니다. 

그동안 너무 블로그를 내버려둬 이번 기회에 한번 제 생각을 정리해보려고 합니다.

hbm2ddl.auto란?

먼저 hbm2ddl.auto 설정이 뭔지 간단히 살펴보겠습니다. 

hbm2ddl은 하이버네이트에 포함된 모듈로 맵핑 설정을 기반으로 DB 스키마 생성 스크립트를 만듭니다. 하이버네이트는 시작할 때마다 맵핑 설정을 DB 스키마에 반영하는 작업을 할 수 있는데 hbm2ddl.auto 속성값에 따라 다음과 같이 다르게 동작 합니다.

create

Session factory가 실행될 때에 스키마를 지우고 다시 생성합니다. 스키마를 생성한 다음에 classpath에서 import.sql 파일이 있는지 찾아 이 파일에 등록된 쿼리문을 실행합니다.

create-drop

create와 같지만 session factory가 내려갈 때 DB의 스키마를 삭제합니다.

update

시작하면서 도메인 객체 구성과 DB의 스키마를 비교해 필요한 테이블이나 칼럼이 없을 때 도메인 객체에 맞춰 DB 스키마를 변경합니다. 데이터나 스키마를 지우지는 않습니다.

validate

처음에 도메인 객체 구성과 DB 스키마가 같은지 확인만 할 뿐 DB 스키마에 전혀 손대지 않습니다. SessionFactory 시작 시 확인을 해서 문제가 있으면 예외를 토해내고 죽습니다.

제 생각에는...

create와 create-drop이 특별하다는 건 뻔한 것 같습니다. 애플리케이션을 시작할 때마다 DB가 초기화되어야 한다면 이 설정이 적합하겠지요. 프로그램이 실행되는 동안에만 DB에 데이터가 들어가 있으면 되는 상황 말입니다. 가장 먼저 생각나는 게 테스트네요. 임시로 대용량 데이터를 처리해야 데스크탑 애플리케이션에서도 쓸 수 있을 것 같고요.

update는 참 멋집니다. 도메인에 맞춰서 자동으로 스키마를 바꿔주니까요. 그런데 전 애석하게 (특별한 경우 아니라면) 이 멋진 기능을 쓰는 것보다 좀 번거롭게 작업하는 게 좋겠다고 결정했습니다. 아마도 이쪽으로 흐르기 시작한 건 제 작업 스타일 때문이겠지만 이유도 없이 오직 스타일 때문에 고집을 부려서는 안 되겠죠. 그래서 몇 가지 이유를 정리해 보려고 합니다.

일반화된 하이버네이트의 스키마 표현

하이버네이트는 DDL 생성시 사용할 스키마 정보를 비교적 상세하게 도메인 객체 설정에 넣을 수 있도록 준비해 두었습니다. 하지만 어느 정도 추상적일 수 밖에 없습니다. 하이버네이트가 DB 추상화 도구이니까요.

DB에는 같은 가변 문자열 타입 중에도 (처리 방식, 제한 길이, 지원 문자셋 같은 이유로) 여러 종류가 마련되어 있을 수 있고 날짜 저장용 타입도 비슷하지만 조금씩 것들이 여러가지 있는 경우가 많습니다. 하아버네이트의 dialect가 객체 설정에 지정된 하이버네이트 타입이나 자바 타입에 최적화된 DB 칼럼 타입을 알아서 선정해주기는 하지만 이 결과가 언제나 마음에 드는 건 아닙니다.

하이버네이트는 이럴 때 쓰는 설정 옵션이 있어 정확한 칼럼 속성을 명기 할 수 있습니다. 하지만 이 기능을 사용하면 DB 종속성이 생겨 어쩔 수 없는 경우 아니라면 피하는 것이 좋을 듯 합니다.

dialect가 어떤 타입을 선정할지 정확히 예측 하기도 어렵습니다. 하이버네이트가 상식적인 선택을 하기는 하지만 예측하기 힘든 경우도 있습니다. 예를 들어 postgresql은 아주 긴 문장을 저장하는 text 타입이 있는데 이 타입을 선택하도록 하려면 해당 칼럼을 clob으로 지정해야 합니다. 그런데 postgresql에는 text 칼럼 타입이 생기기 전부터 clob이라는 칼럼 타입이 이미 있었습니다. 물론 dialect가 clob 칼럼 타입 대신 text를 선택하는 것이 옳은 선택이기는 하지만 문서화가 되어 있지 않기 때문에 하이버네이트에서 clob이라 지정하면 DB에서도 clob이 선택될 것이라고 생각하기 쉽습니다.

또 DBMS는 하이버네이트가 자동으로 만들어주는 스키마로 만족하기에는 훨씬 더 넒은 선택의 폭과 기능을 가지고 있고 이것들을 사용하지 않는 건 그리 현명한 일이 아닙니다. 좀 격하게 말하면 hbm2ddl이 자동으로 만드는 DDL로는 그냥 겨우 돌아간다는 것 이상을 기대하기 어렵습니다. DBMS 세상에는 도메인 모델 세상이 이해할 수 없는 희로애락이 있습니다.

상호 독립, mapper

하이버네이트는 RDBMS를 상당히 존중합니다. 지금은 객체가 지배하는 세상이니 DB도 객체지향으로 완전히 바뀌어야 한다거나 객체와 어울리려면 RDB에도 최소한 상속이나 커스텀 타입 같은 객체지향 속성 정도는 추가되어야 한다거나 하는 주장을 하지 않습니다. 오히려 RDB가 지금까지 이룩한 업적과 지금 누리는 지위를 인정합니다. 심지어 다른 기술로 돌아가던 legacy 시스템에 손을 대지 않고 하이버네이트를 도입해 사용할 수 있도록 배려합니다.

하이버네이트는 도메인 모델에도 특별한 제약을 주지 않습니다. 어떤 추상 객체를 상속하거나 인터페이스를 구현할 필요도 없습니다. 일반 POJO 객체를 그대로 사용할 수 있습니다. 이 객체는 하이버네이트가 없는 곳에서도 마음대로 쓸 수 있습니다. 또 비즈니스 로직은 이 객체가 영속화되었는지 아닌지 전혀 신경 쓸 필요가 없습니다. 

하이버네이트는 이렇게 두 영역이 서로에 대해 의미론적 독립성을 최대한 유지하고 두 영역 모두 중간에 뭔가 있다는 사실을 신경 쓰지 않아도 상관없도록 해줍니다. 그러면서도 이 두 영역을 깔끔하게 이어줍니다. 이런 하이버네이트를 쓴다면 도메인 모델링과 DB 설계 중 어느 하나를 더 중요한 것으로 보고 나머지는 자동화 처리한다는 건 좀 어울리지 않습니다.

단순한 ORM 설정

hbm2ddl을 쓰지 않는다면 ORM 설정이 무척 단순해집니다. XML로 설정할 때도 그렇지만 애노테이션을 써서 맵핑 설정을 할 때에 설정이 복잡해지면 도메인 객체 코드가 무척 지저분해져 도메인 객체 자체의 유지 보수도 힘들어지고 맵핑 설정도 실수가 생깁니다. 대부분 설정 실수는 컴파일이나 테스트에서 잡아내기 어렵고 말이죠.

DDL을 따로 만들면 도메인 설정이 아주 단순해집니다. 정해진 관례를 따른다면 더욱 단순해지지요. @Entity 하나만 쓰면 될 수도 있습니다.

무덤덤한 하이버네이트

하이버네이트는 설정 속성에서 많은 부분을 선택사항으로 열어두고 있습니다. 그래서 꼭 지정하지 않으면 적당한 기본값으로 군말 없이 작동합니다. 그런데 이런 편리한 부분이 때로는 문제가 되기도 합니다. 

도메인 설정에서 테이터 저장 공간의 길이를 잊고 지정하지 않을 수도 있고 Not Null 등의 제약 조건을 잊을 수도 있습니다. 수작업으로 DDL을 만들 때에도 같은 오류가 생길 수 있다고 할 수 있겠지만 제가 보기에 하이버네이트의 설정은 SQL DDL 문보다 복잡해서 실수하기도 더 쉽고 오류를 찾기도 어렵습니다. 

또 하이버네이트는 도메인의 필드와 칼럼의 타입이 달라도 변환 가능하다면 오류를 내지 않고 동작합니다. 예를 들어 DB의 칼럼은 varchar인데 도메인 객체의 필드는 int일 경우 별문제 없이 작동합니다.

동적 타입을 지원하는 언어로 프로그래밍할 때에 종종 타입을 신경 쓰지 않고 쓰다가 나중에 찾기 어려운 오류를 만들 수가 있는데 하이버네이트를 쓰면서 스키마 설계를 신경 쓰지 않으면 그런 일이 생길 수 있습니다. 위의 예처럼 칼럼이 varchar인데 숫자인 줄 알고 쓰다가 해당 칼럼을 기준으로 소트를 수행한다거나 검색 조건에 넣었다면 예상하지 못한 결과가 나올 수 있습니다.

Design by contract

Design by contract는 Eiffel 언어의 대표적인 특징입니다. 사업이 계약에 따라 움직이듯 OOP에서 객체들을 설계할 때에 강력한 계약 조건(호출 전 조건, 호출 처리 후 조건, 불변 조건 등)을 만들어 그에 따라 객체들이 상호 간에 협력하게 하자는 생각이 기본입니다. 타입 시스템을 더 강화하는 것이라고 보면 될 듯합니다. 

하이버네이트에 design by contract를 언급하는 게 어울리지 않을 것 같기도 하고 반대로 무슨 말을 하려는지 뻔하기도 한데요. 제 생각에 손으로 작성한 DDL 스크립트는 일종의 계약서 역할을 할 수 있지 않나 싶습니다. 하이버네이트 맵핑 설정만으로 DDL을 자동 생성해서 스키마를 만들면 테스트는 쉽게 통과했지만 스키마가 원하는 모습이 아닐 수 있습니다. 의도하지 않았는데 필요없는 link table을 만들기도 하고 FK 칼럼이 이상한 이름이 되기도 하고 칼럼 타입이 적당하지 않을 수도 있습니다.

별 생각 없이 (또는 실수로) 도메인 객체 구성을 바꾸었는데 이것이 DB에 원하지 않는 영향을 줄 수도 있습니다. 임시로 만든 객체 필드 때문에 쓰지 않는 칼럼이 슬그머니 만들어지는 경우같이 말입니다. 물론 신중하게 맵핑 설정을 하지 않은 것이 문제겠지만, 사람이란 늘 실수하기 마련이고 이런 실수를 빨리 알아차리고 일이 커지기 전에 수정할 수 있으면 좋을 것입니다. 

update가 칼럼을 마구 지운다거나 무리하게 칼럼 타입을 바꾸어버린다거나 하는 만행을 저지르지 않습니다. 그냥 테이블이나 칼럼이 없으면 만드는 정도이지요. 어찌 보면 기능이 없어 보이지만 이렇게 얌전하게 구는 게 지 잘난 맛에 나서다가 일 저지르는 것보다는 난 것 같습니다.

사실 제가 자동 생성한 DDL보다 수작업으로 만든 DDL이 DbC 관점에서 좋지 않겠느냐고 생각한 건 Spring-WS라는 비슷한(?) 사례가 있기 때문입니다. 지금까지 제가 SAOP로 리모팅을 할 때에는 WSDL을 자동 생성해서 썼었습니다. 그런데 이렇게 자동생성을 하면 실제 java service는 바뀐 것이 없더라도 알지 못하는 사이에 WSDL이 바뀔 수 있어 호환성에 문제가 될 수 있습니다. 그래서 Spring-WS는 WSDL을 사람이 수작업으로 만들고 이 WSDL에 맞춰 웹 서비스를 구성하도록 합니다.

참고로 Java용 DbC 솔루션 중 제가 아는 것이 두 가지 있습니다. iContract (http://www.icontract2.org/)는 상당히 오래된 프로젝트로 Java용 DbC 지원 도구 중 대표적인 프로젝트입니다. xdoclet을 사용합니다. 또 하나 Contract4J (http://www.contract4j.org/contract4j)는 annotation 기반의 DbC 도구입니다. 

결론

앞에서 create와 create-drop은 테스트에 쓰면 좋을 것 같다고 했습니다. 그럼 update와 validate는 어떤 상황에서 쓰면 좋을까요?

제가 보기에 update는 개발 초기나 프로토타입 제작에 사용하면 좋을 것 같습니다. 아직 객체-관계 맵핑 작업을 수행하기 전 개발 초기나 일단 작동되기만 하면 되는 프로토타입 제작 시 빨리 결과를 얻을 수 있으니까요. 어느 정도 초기 상태가 지나면 dbm2ddl의 DDL 생성 기능을 사용해 기본적인 SQL DDL 문장을 얻은 다음 독립적으로 발전시켜나가면 될 것 같습니다.

프로덕션 환경에서 update를 사용한다면 아마도 DB가 별로 중요하지 않은 상황이겠죠. 어떤 예가 있을지 얼른 떠오르지 않습니다만... ^^

by 박성철 | 2009/09/07 22:57 | 프로그래밍 이야기 | 트랙백 | 덧글(2)

자기 전에 잠깐 살펴본 Spring Roo

초인 블로거 Toby님의 친절한 설명 시리즈(1,2,3,4)와 기선님의 설치예제 실행법에 힘입어 저도 자기 전에 (사실 아직 잠을 안 잤으니 자기 전일지 아애 잠을 안 잘지는 잘 모르겠네요) 잠깐 Roo를 맛 봤습니다.

사실 Roo라는 것도 한 달 전 쯤 Toby님에게 우연히 듣게 되었고 몇가지 블로그 글 읽으면서 코끼리 다리 만지듯 이런 저런 상상만 하고 있었는데 이렇게 발표가 되니 반갑기도 하고 당황스럽기도 합니다.

일단 열어 본 첫 인상은 이 Roo가 그 Roo 맞나 싶습니다. 얘기 듣던 것과 구조가 많이 달라서요. 알렉스 아저씨가 지난 3년 동안 발전시킨 생각을 반영해서 원래 Roo와 다른 공개용 Roo를 새로 만든 건 아닌지 모르겠네요. (제가 좀 넘겨 짚기 대왕입니다. -_-);

자세한 것은 Toby님 글을 보면 될 것이고... Toby님 글을 보고 궁금했던 것만 따로 좀 살펴 보았습니다.

우선 Domain에 Repository의 로직을 통합했는데 이건 정확히 Active Record Pattern으로 보입니다. 사실 RoR의 ActiveRecord가 진짜 ActiveRecord는 아닌 것 같다고 생각하고 있었는데... (물론 큰 의미는 없는 구분입니다만) Roo에서 사전적인 구현을 보게 되었네요.

제가 Toby님 글에 답글로 너무 RoR과 비슷하게 만들려고 한 것 아니냐고 쓴 것에 대해 Toby님께서 Roo는 DDD를 구현했다는 점에서 다르다고 하셨는데 DDD가 가능하다는 것은 알겠지만 예제는 자동 생성된 CRUD와 Finder 몇개 외의 DDD의 맛을 볼 수 있는 부분은 아직 없는 듯 합니다. 앞으로 예제가 보완되기를 기대합니다.

다음은 Controller 부분을 봤습니다.

자동 생성된 컨트롤러는 아무런 로직도 없습니다.

@RooWebScaffold(automaticallyMaintainView = true, formBackingObject = PetType.class)
@RequestMapping("/pettype/**")
@Controller
public class PetTypeController {
}

그래서 믹스인되는 AspectJ 파일을 보니 여러가지가 메소드가 정의되어 있네요.
  • create() : RequestMapping(value = "pettype", method = RequestMethod.POST) 
  • createForm() : RequestMapping(value = "pettype/form", method = RequestMethod.GET)
  • show() : RequestMapping(value = "pettype/{id}", method = RequestMethod.GET)   
  • list() : RequestMapping(value = "pettype/{id}", method = RequestMethod.GET)   
  • update() : RequestMapping(method = RequestMethod.PUT)   
  • updateForm() : RequestMapping(value = "pettype/{id}/form", method = RequestMethod.GET)   
  • delete() : RequestMapping(value = "pettype/{id}", method = RequestMethod.DELETE)   
REST 방식입니다. Parameter가 아닌 PathInfo에서 id를 받고 있는 것도 그렇고 create에 POST, update에 PUT, delete에 DELETE HTTP Method를 사용하는 것도 그렇고 말이죠.
그러니 당연히 spring 3를 쓰고 있다는 거죠.

발표가 안 될 것 같았던 Roo가 Spring 3의 마일스톤 버전이 공개되는 이 시점에 갑자기 발표되는 것이 어떤 의미가 있는 듯 합니다. 마치 개발하기 시작한지 얼마 안 된 것 같은 모습으로 말이죠.

그리고 아직 Presentation 단에는 별다른 기술이 적용되지 않았습니다. 그냥 담백하게 jstl로 갈지, 아니면 다른 생산성 높은 기술이 적용될지 궁금합니다. 저는 웹 어플리케이션의 생산성 향상의 키는 Presentation 단에 있다고 생각합니다. 재사용성을 높이고 변경에 빨리 대응할 수 있는 기술이 적용되기를 기대해봅니다.

가장 감동 받은 부분은 domain에 static method로 믹스인 되는 finder 메소드입니다.
사실 제가 작년에 spring jdbc를 가지고 active record를 구현하려고 시도했었습니다. 한참 진행하다 바쁘고 만들어봤자 쓸 사람도 없고 (회사가 망했습니다. -_-) 해서 그냥 포기했는데요. 그 때 finder를 domain의 static method로 넣고 싶었지만 generic으로는 방법이 없더라고요.

그냥 범용 finder를 만들고 domain class를 parameter로 넘기거나

MyDomain domain = finder.findById(MyDomain.class, 100);


Generic을 사용해서 finder를 domain과 분리하여 따로 만드는 수 밖에 없는 것 같더군요.

MyDomain domain = new Finder<MyDomain>().findById(100);

제가 원하던 방식은 이런 건데 말이죠.

MyDomain domain = MyDomain.findById(100);

그런데 Roo는 AOP로 간단히(?) 구현했네요. 감동이 쓰나미 처럼 몰려옵니다. (다만 Roo의 XXX_Finder.aj에 있는 Finder 메소드들은 Domain 객체를 찾아주지는 않고 JPA Query 객체를 반환합니다. 유연성 때문인지... 반면에 XXX_Entity.aj의 FindXXX 메소드들은 도메인 객체를 직접 읽어서 반환합니다. )

솔직히 지금까지 Mixin이 복잡도를 너무 높이는 것 아니냐는 생각을 가지고 있었습니다. 그런데, 이미 객체지향기술도 한물갔다는 소리가 나오고있는 상황이고... 그동안 트랜젝션 처리나 로깅 정도로만 생각했던 AOP가 어떻게 변화를 줄 수 있는지 실감이 나네요.
 
우짰든 살펴보니 아직 개발 초기단계인 듯 하고 앞으로 계속 주목해야 할 것 같습니다. 완성되면 어떨지 몰라도 아직은 광고처럼 대단해 보이지는 않네요. Toby님 말씀처럼 DDD 대응이라는 점과 Spring을 그대로 사용할 수 있다는 부분이 경쟁 기술에 비해서 나은 점으로 보입니다.

살펴본 것은 30분도 안 되는데 글 쓰느라 한 시간도 넘게 지나갔네요. 글만 쓰면 왜 이렇게 머리 속이 하얘지는 지... 자겠습니다. -_-

by 박성철 | 2009/05/09 04:36 | 프로그래밍 이야기 | 트랙백 | 덧글(3)

◀ 이전 페이지다음 페이지 ▶