지난 글에서 우리는 시리즈 전체에 걸쳐 사용할 예제 프로젝트에 관해 살펴봤다. 이번에는 J2EE 프로젝트 구조와 Maven이 어떻게 동작하는지에 대해 살펴보도록 한다.
우리는 프로젝트를 웹 플랫폼에 배치(deploy)할 것이다. 이러한 형태의 배치 가능한 웹 애플리케이션에 대해, 웹 아카이브라고 하는 Java 스펙이 있다. 배치를 하는 최종 결과물은 .war 확장자를 가지는 ZIP파일이므로, WAR파일이라고 부른다. WAR파일은 web.xml라는 하나의 배치 디스크립터 파일 안에 어떤 스펙을 가진 하나의 특정 구조를 가지고 있다.
[그림 1]은 웹 아카이브 구조에 대한 예이다. 아카이브의 루트는 웹 애플리케이션의 루트로 취급되기 때문에, 어떠한 .html이나 다른 자원들이라도 여기 위치할 수 있다 - 이 경우에는 index.html이다. 루트에는 WEB-INF도 있는데, 여기에는 사용자가 브라우저를 통해 접근할 수 없는 중요한 파일들을 담고 있다.WEB-INF는 classes 디렉토리를 포함하는데, 이것은 애플리케이션에서 사용하는 Java 클래스들의 클래스패스 루트이다. 또한 classes와 같이 있는 lib 디렉토리는 JAR들로부터 클래스 패스에 추가된 Java 라이브러리들을 담고 있다. web.xml은 WEB-INF 루트에 있으면서 컨테이너(서버)에게 애플리케이션을 어떻게 배치할 것인지 알려준다.
[그림 1] 웹 아카이브 예제
우리의 프로젝트는 퍼시스턴스 계층으로 EJB 3.0을 사용할 것이다. 프로젝트의 퍼시스턴스를 구성하는 리소스들은 공식 Java 아카이브인 JAR로 나뉘어 패키징될 것이다. 이론적으로, 퍼시스턴스 유닛은 같은 도메인에서의 데이터를 관리할 필요가 있는 몇개의 애플리케이션이라도 패키징할 수 있으며, 그렇기 때문에 고유한 결과물로 유지된다. 퍼시스턴스 유닛도 마찬가지로 지정된 구조와 persistence.xml이라는 디스크립터 파일을 가진다. ([그림 2] 참조)
[그림 2] 퍼시스턴스 유닛 예제
퍼시스턴스 유닛은 내부적으로 정말 단순하다. 패키지 구조는 META-INF를 가진 루트 내에 존재하며, 여기에는 디스크립터 파일인 persistence.xml이 있다.
우리는 코드의 데이터 퍼시스턴스 부분을 분리된 프로젝트로 다룰 것이고, 프로젝트의 웹쪽 부분으로부터 분리된 결과물을 가질 것이다. 다른 아카이브들을 담는 또 다른 형태의 압축 스펙이 있는데, 그것은 엔터프라이즈 아카이브라는 Java 엔터프라이즈 에디션(Java EE) 스펙이다. 이것은 또다른 형태의 .zip 파일이며, .ear 확장자를 가지는데 보통은 아카이브의 루트에 있는 다른 아카이브들 만을 담는다. 그러므로 배치를 위해 .ear에 .war와 .jar를 넣도록 하겠다. (우습게 들릴 것이란걸 안다. 이러한 스펙 위원회 중의 누군가는 틀림없이 이것을 유머로 사용해 왔을 것이다.) [그림 3]은 엔터프라이즈 아카이브에 대한 예이다.
[그림 3] 엔터프라이즈 아카이브 예제
엔터프라이즈 아카이브는 파일 수 측면에서 가장 단순한 아카이브이다. 일반적으로 루트는 웹 아카이브들을 담고 있다. 가끔 JAR들은 서버상 클래스패스에 포함된 루트로 들어간다. 원한다면 별도의 lib 디렉토리에 넣을 수도 있는데, 이것은 application.xml에서 설정할 수 있기 때문에 그리 중요한 것은 아니다. 한편, application.xml은 META-INF에 들어가며, 다른 인클루드된 라이브러리들을 어떻게 로드할 것인지를 설명한다.
압축된 파일들에 대해 이렇듯 어려운 스펙을 가지는 것의 요점은, 개발하는 동안 이러한 별도의 프로젝트들을 혼자서 유지할 수 있고, 빌드 툴로 각각의 결과물들을 만들어서, 개발 지시사항을 가진 모든 것을 완비한 하나의 엔터프라이즈 애플리케이션 파일로 합칠 수 있다는 것이다. 이러한 점은 실제로 제품 환경에 배치하려는 사람들에게 훨씬 더 편리하다. 분리된 프로젝트들은 서로 다른 목적으로 개발자들에게 개발을 더 쉽게 해주고, 하나의 결과물은 가능한한 배치를 쉽게 만든다.
기억해 둘 것이 하나 있다. WAR와 EAR 파일은 한개의 파일로 ZIP 압축되어 배치될 수도 있고 압축되지 않은 디렉토리 형태로도 배치될 수 있다. 압축되지 않은 상태로 배치될 경우 종종 익스플로드된(exploded) 채로 참조되곤 한다. 개발 환경에서는 이러한 압축 파일들은 보통 로컬서버에 익스플로드된 형태로 배치된다. 우리의 개발용 Ant 작업은 이것을 실행 해주며, 컴파일된 리소스들과 동기화도 시켜준다.
Maven 프로젝트
src 내부는 프로젝트의 주요 소스를 위한 디렉토리이다. 같은 레벨 상에 test라는, 테스트 코드를 위한 폴더가 있다. 코드들은 main과 test 디렉토리 내에서 실행되며, 언어 형식으로 구성되어 있다 - 이 경우에는, 데이터 프로젝트 디렉토리의 java 디렉토리와, 웹 프로젝트 디렉토리의 java와 flex 디렉토리에 Java 코드를 담고 있다. Maven은 빌드 자동화 도구와 마찬가지로 코드 프로젝트 관리 도구이다. Maven은 우리가 작업하려는 형태의 프로젝트가 어떻게 구성되어야 하는지에 대한 의도를 가진다. 비록 모든 기본 설정들이 각각 설정될 수 있지만, 새로운 프로젝트를 시작하고 있으므로 Maven의 컨벤션을 따랐고, 여러분은 그것들이 어떤 의미를 가지는지 알수 있을 것이다.
Ant가 빌드 제어에 대한 설명을 다루는 빌드 파일을 가지는 것처럼, Maven은 개발자가 각각의 프로젝트를 설명하고 설정할 수 있는 파일을 가지는데, 그것은 pom.xml이다. POM은 프로젝트 객체 모델(Project Object Model)을 의미한다. Maven은 모든 프로젝트를 일반화하는 빌드 과정 상의 위치, 변수, 단계에 대한 모음 개념을 가진다.
pom.xml 디스크립터는 Maven에게 몇몇 중요한 프로젝트 설정을 알려준다. 먼저, 이것은 프로젝트를 이름과 속성들로 식별한다. 그러면 Maven으로 작업 할 수 있는 특정 플러그인들—뭏?들면 컴파일러 플러그인, 테스트 플러그인 등—?설정하는 것과 같은 많은 것들을 할 수 있다.
또 Maven은 매우 강력한 종속성 관리 기능을 가지고 있다. Maven은 POM으로부터 해당 프로젝트가 의존하는 종속성이나 라이브러리 리스트를 읽고, 해당 라이브러리들을 로컬 저장소로부터 다운로드 받는다. 이 저장소는 보통 홈 디렉토리의 .m2라는 이름의 디렉토리에 있다.
만약 이곳에서 라이브러리를 발견하지 못하면, Maven은 웹 상의 알고있는 저장소들로부터 이 라이브러리들을 다운로드 받는다. 이러한 저장소 리스트는 .m2디렉토리의 settings.xml 디스크립트로부터 읽는다.
이것이 얼마나 강력한 기능인지 약간의 설명이 필요할 것이다. 만약 Java에서 "JAR 사냥"을 다닌적이 있다면, 필자가 무엇을 말하려는지 벌써 알 것이다. 프로젝트가 라이브러리 A의 클래스들을 필요로 한다고 가정해 보자. 이 라이브러리는 특정 버전의 라이브러리 B에 의존한다. 라이브러리 B는 라이브러리 C, D, E에 의존한다. 이것을 알 수 있는 표준적인 방법은 없으므로, 여러분은 라이브러리 A와 B가 문서화가 잘 되어 있어서, 의존하고 있는 다른 라이브러리에 대해 잘 설명되어 있길 기대해야만 한다. 설령 운좋게 그것이 잘 설명되어 있다쳐도, 이러한 프로젝트들을 찾아서 그 결과물들을 다운로드 받아야 여러분의 프로젝트를 컴파일 할 수 있다. 그렇지 않으면 Java가 불평을 하며 에러를 낼 것이다.
그러나 Maven을 사용하고 있다면, 프로젝트가 A에 의존하고 있고, B, C, D, E 모두 Maven 식의 결과물을 만들며 이것들을 표준 Maven 저장소에 붙였다고 알려주기만 하면 된다. 다행히 거기서 실행하는 대부분의 라이브러리들은 그렇게 할 것이다.
그렇지 않다면, 여러분의 로컬 저장소에 Maven 형식으로 라이브러리들을 위치시키고 다른 개발자들과 공유하거나, 팀을 위해 프록시 서버를 두고 거기서 로컬 Maven 저장소를 실행할 수 있다.
이번엔 Maven의 관점에서 프로젝트를 수행할 것이다. 한 프로젝트는 EJB 3.0 퍼시스턴스 유닛을 빌드할 것이고, 또 다른 프로젝트는 Flex와 LCDS를 합친 웹 아카이브를, 세번째 프로젝트는 이 두 결과물을 담고 있는 .ear 파일을 빌드하고, 네번째 프로젝트는 앞의 세 프로젝트에 대한 상위 프로젝트 역할을 할 것이다. 다음 코드는 상위 프로젝트의 POM이며, Maven에게 다른 것들의 위치를 알려주기 위해 약간 수정한 것이다.
4.0.0lcds.examples.bookiebookieProject1.0-SNAPSHOTpomBookie Example Parent Projectbookie-databookie-uibookie-earorg.apache.maven.pluginsmaven-compiler-plugin1.5
먼저, modelVersion은 이 문서가 작성된 POM XML의 버전을 알려준다. Maven의 POM 문법이나 설정의 일부가 후에 변경될 수도 있다.
그 다음 세 개의 속성들은 가장 중요하다. 사실 POM이 다른 Maven 프로젝트들로부터 이 프로젝트를 식별하기 위해 필요한 것은 이것들이 전부이다.
groupId는 프로젝트를 빌드하는 그룹에 대한 유일한 식별자이다. 이것은 보통 업체의 URL로 이루어져 있지만, 업체나 조직에 대해 유일한 다른 무엇인가가 될 수도 있다. 이 식별자는 이 그룹에 의해 빌드된 프로젝트들을 묶을 수 있다.
artifactId는 프로젝트에 대한 유일한 식별자이다. 이 경우에, 상위 프로젝트는 bookieProject이다.
다음은 version인데, 이것은 프로젝트의 버전이고, 무엇이든 될 수 있지만, 이 프로젝트의 빌드가 진행되고 있는 주 버전 혹은 개정번호가 되어야 한다. 만약 이 프로젝트가 생성하는 결과물의 버전이 4.1이었다면, 그것이 여기로 들어간다. 프로젝트가 릴리즈되지 않았고, 버전 1을 향해 작업이 진행되고 있을 때 Maven은 1.0-SNAPSHOT이라는 명명법을 사용하며, 이것은 아직 릴리즈된 버전이 없으며 많은 변경사항들이 생기고 있음을 의미한다. 즉, 너무 많아서 버전을 콕 집어내 정할 수 없다는 뜻이다.
Maven은 결과물을 빌드하기 위해 이 "중요한 세가지" 속성을 이용한다. 만약 프로젝트가 JAR 파일을 생성했다면, 그 JAR 파일의 이름은 <프로젝트명>.<버전 번호>.jar가 될 것이고, 이 파일은 Maven 저장소의 groupId, artifactId, version과 맞는 디렉토리 구조에 놓일 것이다. 예를 들면 [그림 4]는 MySQL 드라이버에 대한 저장소 구조를 보여준다.
[그림 4] MySQL 드라이버에 대한 저장소 구조
이것은 필자의 로컬 Maven 저장소를 그대로 보여준 것이지만, 또한 웹 상의 어떠한 Maven 저장소에서라도 발견될 수 있는 구조이다. MySQL 라이브러리에 대해 groupId는 mysql, artifactId는 mysql-connector-java이고, 추가로 3.1.12, 5.0.3, 5.0.5 이렇게 세가지 버전이 보여지고 있다. JAR가 앞서 언급했던 형식으로 이름지어졌고, 프로젝트의 POM은 이 파일들이 이상없이 다운로드 되었음을 확인하는 몇몇 해시 파일들과 함께 여기에 저장되어 있것도 확인한다.
POM 파일이 저장소에 저장되어 있기 때문에, 다른 파일들 사이에서 Maven은 이 라이브러리가 얻을 필요가 있는 어떤 종속성이 있는지를 체크할 수 있다. 엮여져 있는 이러한 모든 종속성들에 대한 자동화된 검사와 다운로드는 겨우 약간의 설정을 기반으로 하며, 가입 비용에 대한 가치가 있다.
상위 POM으로 다시 돌아가보면, packaging 설정은 Maven이 어떻게 프로젝트를 묶을 것인가에 대한 정보를 가진다. 이 경우 상위 프로젝트에는 작업물이 없으므로, packaging은 pom이고, 이것은 그 아래의 디렉토리에 있는 다른 프로젝트들을 가진다는 것을 예상할 수 있다. 여기서의 다른 프로젝트들은 name설정 아래에 있는 modules 부분에 나열되어 있다. 나중에 각각의 섹션 시작부분에서 data와 ui프로젝트의 POM과 ear프로젝트 POM을 일부 볼 수 있을 것이다.
modules 설정 다음은 build 부분이다. 이 설정은 Maven 프로젝트 생명주기의 빌드 부분이다. 기본적으로, Java를 컴파일 하는 컴파일러 플러그인을 설정하는데, 소스파일을 Java 버전 1.5로 보고, 1.5로 컴파일하라고 설정하고 있다. 이것을 상위 프로젝트에서 설정했기 때문에 Maven은 각각의 하위 프로젝트들도 같은 Java 컴파일 설정을 적용한다.
다음 연재에서는 계속해서 Maven 프로젝트가 어떻게 구성되어 있는지 살펴보도록 하겠다. 전체 (영문) 연재는 언제든지 여기에서 찾을 수 있다.