충분히 테스트 해본 것은 아니지만 Maven에서 다중 리포지토리에 중복해서 등장하는 동일 artifact의 사용은 주의해야 할 것이라는 나름 근거있는 결론을 내렸다.

3.0의 최신 빌드버전을 위해서 별도 서버에 3.0용 리포지토리를 등록했다. 기존의 springsource의 번들 리포지토리가 있지만 2.5.x버전까지 밖에 배포가 안되어 있으니 사설 리포지토리를 적용할 수 밖에 없었다.

Spring 3.0의 최신 빌드는 별도 리포지토리에 배포하지만 그 의존 라이브러리들은 springsource의 것을 그대로 이용한다.

즉 두개의 리포지토리가 최소한 필요하다는 것이다. 물론 Central은 제외하고.

 

이 경우 양쪽에 모두 동일 groupId, artifactId를 가지는 폴더들이 존재한다. org.springframework / org.springframework.context 같은 것들이다.

이때 한쪽에는 정식 버전으로 파싱가능한 2.5.6 같은 것이 등록되고 한쪽에는 버전은 무시되고 qualifier로 전체가 잡히는 3.0.0.BUILD-xxx 등이 등록이 된다.

문제는 이런 두개의 리포지토리를 사용해서 3.0 라이브러리에 접근하려고 하면 아티팩트를 못찾는다는 에러가 난다. 메시지를 보면 기존 2.5.x 버전이 등록된 쪽에서 다운로드를 시도한다. 아티팩트 이름을 정확히 찾은 것으로 봐서는 3.0이 배포된 서버도 체크한 것 같은데, 정작 다운로드는 다른 쪽에서 시도한다.

이 때문에 Spring 3.0을 이용해서 개발했던 한 시스템을 서버에 올려서 테스트하는 과정에서 많은 문제를 일으켰다. 결국 리포지토리 한쪽을 막은 채로 필요한 아티팩트를 다운하고, 나머지를 열고 모두 가져오는 방식으로 서버의 로컬 리포지토리에 다운을 받고 사용하는 것으로 일단 해결은 했다.

 

혹시 다중 리포지토리를 사용한 POM에서 리포지토리 별로 순서를 부여할 수 있는가 확인해봤으나 그런 기능은 없었다. 다만 Central이 가장 나중에 확인된다는 것만 알 수 있었다.

Maven의 버그일까, 아니면 다른 문제일까. 케이스를 만들어서 테스트 해보고 싶기도 하지만, 그동안 Maven 적용하느라 소모했던 시간들이 아까워서 포기. 소스를 뜯어보고 싶게 만드는 오픈소스 프로젝트에는 두가지 종류가 있다. 하나는 스프링 처럼 정말 잘 만들어진 제품이라 그 설계와 구현도 공부하고 싶게 만드는 것이고, 다른 하나는 Maven이나 예전의 JBoss 처럼 알 수 없는 오동작과 버그로 의심되는 기능들 때문에 소스를 뒤져서라도 문제의 원인을 확인하고 싶게 만드는 것이다.

 

문제가 있으면, 조심해서 피해서 써야지.

어쨌든 오늘 Spring 3.0 으로 개발한 한 애플리케이션을 운영 서버에 등록하고 정식으로 사용을 개시했다. 혹시나 문제가 있을지 몰라서 TDD로 조심스럽게 테스트를 만들어가면서 개발해왔다. Maven이 좀 꼬였던 것 말고는 별다른 문제 없이 테스트와 정상동작하는 것을 확인할 수 있었다. 서버의 상태를 모니터링하고 리포팅하는 그다지 복잡하지는 않은 시스템이긴 하지만, 3.0의 기능을 여러모로 테스트해볼 수 있는 기회가 되는 것 같아서 즐겁다.

사실 JavaConfig까지 적용해서 개발을 하다가 현재 M5-Build버전은 2.5.x하고만 호환이 되기 때문에 포기했다. 2.5.6에는 있던 org.springframework.asm 패키지는 도대체 어디로 간거지…??

Spring 3.0의 최신 소스를 빌드해서 사용하는 방법을 알아봤던 이전 글에서 지적했듯이 Maven의 버전관리 기능은 주의하지 않으면 큰 혼란을 일으킬 수 있다.

 

Maven Version

메이븐의 비공식 매뉴얼인 Maven: The Definitive Guide에 소개된 Maven의 버전포맷은 다음과 같다. (참고로 Maven 개발팀은 버전설정과 관련한 공식문서를 준비하자는 이슈를 등록하고.. 질질 끌고 있다)

<major version>.<minor version>.<incremental version>-<qualifier>

Major,minor,incremental 버전은 모두 0 또는 이상의 숫자여야 한다. Qualifer에는 다시 build no가 들어갈 수 있지만 현재(2.0.9)는 무조건 문자로 처리되어 비교되므로 build no 개념은 별 의미가 없다.

위의 포맷의 모든 요소는 모두 optional이다. 대신 major version과 qualifier 중의 하나는 반드시 존재해야 한다.

문제는 위의 포맷과 비슷해보이지만 정확히 맞지 않는 경우 Maven은 에러를 내는 것이 아니고 전체 버전을 임의의 스트링 값을 가질 수 있는 qualifier로 취급해버린다는 것이다.  바로 이점이 Maven의 버전을 사용할 때 위험한 부분이다.

예를 들어 1.0-M1은 major=1, minor=0, incremental=0, qualifier=M1으로 파싱되지만 1.0.M1은 major=0, minor=0, incremental=0, qualifier=1.0M1으로 파싱되다는 점이다. 대쉬(-) 앞에 숫자와 점(.)으로 이루어진 버전이 나오고 그 외에는 모두 qualifier라는 규칙 때문에 1.0.M1은 표준 포맷으로 파싱이 불가능하고 숫자버전은 모두 0이고 1.0.M1이 qualifier로 취급되어 버린다.

POM 설정에서 정확히 일치하는 버전을 단 한번만 사용한다면 별 문제가 문제가 없다.

하지만

  • 버전범위(version range)를 사용하는 경우
  • 같은 아티팩트의 다른 버전이 동시에 존재하는 경우 그것을 자동으로 해결의 경우
  • RELEASE, LATEST와 같이 최신버전을 자동으로 선택하는 경우

에는 문제가 될 수 있다. 3.0.0.BUILD-001 보다 2.5.6이 더 최신버전이라고 판다하는 식의 문제가 생기기 때문이다.

따라서 Maven을 사용할 경우에는 최소한 숫자버전이 정확히 들어가도록 표준 포맷을 따라서 버전을 붙여야 한다.

표준을 지키지 않는 포맷을 아무런 경고 메시지조차 없이 허용하고 상당한 혼란을 줄 수 있다는 면에서 Maven 개발자들의 무책임함이 느껴지는 부분이지만, 뭐 어쨌든 Maven이 정한 룰이니까 잘 따르면 될 것이라고 생각할 수 있다.

 

OSGi Version

문제는 OSGi 번들이다. OSGi 번들로 패키징된 라이브러리나 아티팩트의 사용이 점차로 늘고 있다. 당장에 Eclipse의 모든 번들이 그렇고, 스프링이 OSGi/DM에 올인하면서 스프링 모듈을 포함해서 대표적인 라이브러리를 OSGi번들호환으로 재포장해서 제공하고 있는 리포지토리의 사용이 그렇다.

OSGi는 Maven과 유사해보이지만 큰 차이가 있는 버전포맷을 가지고 있다. 다음은 OSGi 스펙에 나오는 버전포맷이다.

version ::= major( ‘.’ minor ( ‘.’ micro ( ‘.’ qualifier )? )? )?
major  ::= number                   // See 1.3.2
minor ::= number
micro ::= number
qualifier  ::= ( alphanum | ’_’ | ‘-’ )+

여기서 ()는 grouping, ?는 optional이라는 뜻이다.

OSGi도 major,minor,micro라는 세단계 숫자로된 버전을 가지고 있다. 문제는 그 다음 qualifier가 나오는 부분인데, 그 구분자가 점(.)으로 되어있다는 것이 대쉬(-)를 사용하는 Maven과 다르다.

또 OSGi의 qualifier는 micro버전까지 나온 뒤에만 적용할 수 있다. 즉 1.0.0.M1 이라고는 쓸 수 있지만 1.0.M1이라고는 사용할 수 없다는 뜻이다. 하지만 Maven은 구분자 자체를 별도로 해놨기 때문에 1.0-M1이라고 쓸 수 있다. 이 것이 각각의 qualifier를 위한 구분자를 다르게 사용한 이유인지도 모르겠다.

 

문제는 스프링소스가 제공하는 OSGi호환 번들의 버전은 OSGi 버전포맷을 따른다는 점이다. 이부분에서 Maven의 버전과 충돌이 일어난다. 물론 Maven의 나이브함 때문에 에러가 나지 않고 정상적으로 처리되는 듯 하나, 내부적으로 보자면 큰 차이가 있고 결국 버전처리에 혼란을 가져올 수 밖에 없다.

예를 들어 스프링의 번들을 기준으로 보자면

스프링소스의 리포지토리에서는 2.5.6과 2.5.6.A가 모두 제공된다. 여기서 qualifier A가 붙는 것은 패키징에 미묘한 수정이 있기 때문이다. 하지만 이 두개의 번들을 Maven에서는 완전히 다른 버전으로 인식한다.

2.5.6은 정상적으로 2,5,6의 세가지 버전을 가진 표준 포맷으로 보지만

2.5.6.A는 OSGi의 표준에 맞는 정확한 버전구조를 가졌지만 2.5.6 바로 뒤에 나올 수 있는 버전이 아니라 Maven에서 보기에는 0,0,0버전에 2.5.6.A라는 qualifier를 가진 버전으로 해석이 된다는 말이다. 현재 공식버전인 2.0.9를 포함해서 2.0.x의 최신 브랜치와 새로운 2.1.x의 브랜치의 Maven을 가져다 테스트해봐도 동일하다. 아직 alpha1인 3.0은 테스트 해보지 않았다.

결국 이부분이 이전에 지적한 Maven의 버전범위 비교방식으로 적용해보면 2.5.6.A가 2.5.6보다 훨신 옛날 버전이 되버린다.

 

버전 사용에 대한 주의

결론적으로 이러한 차이가 가져오는 문제점은 경우에 따라 심각한 결과 내지는 혼란을 초래할 수 있기 때문에 주의해야 한다. 특히 스프링이 제공하는 모듈과 OSGi 번들을 사용할 경우는 더욱 그렇다.

당연하게도 Maven 내부에서 이부분에 대한 문제제기와 어설픈 Maven의 버전체계를 정리하자는 요구가 지속적으로 나오고 있다. 당장에 OSGi와의 버전차이에 관한 문제나 빌드넘버 문제 등은 사용자들의 패치가 제공되는 등의 수정을 요구받고 있다. 하지만 적용되고 있지 않고 있다. 몰려가서 vote를 해야 할까.

이부분에 대한 근본적인 해결책은 Maven의 버전체계를 종합적으로 개선하는 것까지 가야 할 듯 하다. 이에 대해서 괴민을 많이 했던 한 개발자가 정리해놓은 Maven Versioning이라는 위키문서에 그런 내용이 잘 정리되어있다. Maven 사용자라면 주의 깊게 읽어볼만 하다.

Maven은 무려 3개의 브랜치(2.0.x, 2.1.x, 3.0.x)가 동시에 개발되고 있는 복잡한 프로젝트이다. 이런 저런 버그와 불편함으로 욕도 엄청나게 먹어왔다. 그래도 m2eclipse,nexus와 같은 쓸만한 툴의 개발을 지원하는 등을 보면 바른 방향으로 나아가고 있는듯 하다. 리포지토리 개념을 공유하는 Ant/Ivy와 Buildr같은 툴들도 등장했다.

이 Maven 버전문제 제대로 확인하겠다고 Maven을 수정해서 직접 빌드하고 테스트 하는등의 작업을 하면서 오전시간을 거의 날려버렸다. 부디 이 버전 문제만큼은 좀 빨리 해결되었으면 좋겠다. 마음 같아서는 위의 이슈에 올라온 패치를 적용해서 쓰고 싶기도하다. 다른 개발자들과 혼란이 생길테니 그럴 엄두는 못내겠지만.

 

이름은 참 좋은데…

ma·ven  n. 《미·속어》 숙달한 사람, 프로, 명수(expert) 

Maven의 버전범위(version range) 기능은 정확한 버전을 지정하지 않아도 일정한 범위에 해당하는 버전을 모두 체크해서 그 중 가장 최신 버전을 선택하게 해주는 유용한 기능이다. 예를 들어 Spring dependency의 버전을 [2.5,2.6) 이라고 넣으면 2.5부터 시작해서 2.6이 되기 전까지 지속적으로 업그레이드 되는 최신 마이너버전(2.5.1, 2.5.2...)을 버전의 수정없이 가장 상위버전으로 선택해서 사용하게 해준다.  []는 경계버전의 포함, ()는 제외이다.  상위나 하위 범위 중 하나를 생략하면 이전, 이후의 모든 버전을 다 지정하게 할 수 있다. [3.5,]이라고 넣으면 3.5 이상의 모든 버전에서 가장 상위 버전을 선택해준다.

문제는 며칠전에 쓴 Spring 3.0 개발버전에서 Maven 활용하기에 나오는 방법을 이용해서 스프링의 최신버전을 계속 빌드하고, 리포지토리에 올려 그것을 활용하려는 작업이 자꾸 실패한다는 것이다.

3.0의 최신 소스를 받아서 빌드하는데는 아무런 문제가 없다. 이전 글에서 제시한 방법으로 전용 리포지토리에 배포하는 것도 잘 된다.

문제는 거의 매일 새로 업데이트 되는 버전을 빌드해서 올리기 때문에 최신 버전을 사용해서 3.0을 이용하는 애플리케이션을 빌드하려면 자동으로 가장 최신의 모듈을 가져올 수 있게 해야 한다는 것이다. 당연히 이럴 때 유용한 version range를 적용했다.

스프링 빌드가 만들어주는 Maven의 아티팩트 버전은 좀 독특하다. 마치 SNAPSHOT 버전이 배포될 때 자동으로 날자-시간이 따라 붙는 것처럼, 스프링의 Ivy 빌드에서 생성하는 Maven모듈과 POM 정보에도 3.0.0.BUILD-200901020304와 같은 식으로 날자-시간이 붙는다. 이전에 얘기한 것처럼 버ㅈ번에 SNAPSHOT이 없기 때문에 스냅샷으로 취급되지 않고 Release버전으로 올라가야 한다. 따라서 SNAPSHOT의 dependency를 사용할 수 없다. 대신 버전의 범위를 주는 것이 가장 편리한 방법일 것이다.

처음에 테스트 했을 때는 별 문제가 없었던 것 같은데, 오늘 다시 작업을 하느라 새로 프로젝트를 만들고 메이븐 설정을 한 뒤 빌드를 해보니 스프링의 모듈을 가져오지 못해서 빌드가 실패했다.

리포지토리에 올라가 있는 것은 최신버전은 3.0.0.BUILD-20090120232117이다.

내가 <dependency> 항목에 설정한 버전은 [3.0.0,] 이었다.

빌드를 시도하면 리포지토리에서 발견되는 [..., 3.0.0.BUILD-20090120232117]에서 [3.0.0,]의 범위에 드는 것을 찾을 수 없다는 메시지가 나오면서 빌드가 실패한다. 지난번에는 잘 가져온 것 같은데 어찌 된 것일까 하고 한참을 테스트를 해봤지만 원하는 번들을 가져올 수 없었다.

심지어는 [2.0,] [1.0.0,5.0.0]과 같은 조건을 주어도 리포지토리 버전에 있는 3.0.0.BUILD-20090120232117이 범위에 들지 않는다는 경고만 뜰 뿐이었다. 뭔가 수상하다 싶어서 다른 모듈에 테스트를 해보았는데 JUnit이나 일반 라이브러리에는 정상적으로 범위가 잘 적용되었다.

 

오기가 나서 Maven 자료를 다시 뒤져보기 시작했다. 이슈트래커에 최신버전(2.0.10, 2.1-M2,3.0.0-alpha)에 등록된 이슈들을 뒤져보았지만 별 내용이 없다. 결국 지금 사용하는 Maven 2.0.9의 소스코드를 받아서 관련된 클래스의 동작방법을 분석해기 시작했다. -X 옵션을 주면 디버깅 정보가 뜨긴하지만, 소스를 보면 알겠지만 사실 제공하는 디버그 메시지에서는 얻을 수 있는 정보가 거의 없다.

Version range를 다루는 클래스는 이름 그대로 VersionRange이다. 그 안에 버전의 범위를 체크하는 코드가 들어있다. 상식적으로 알고 있는 버전 범위를 체크하는 기능들이 좀 거칠지만 나름 꼼꼼하게 들어있다. 테스트 코드도 충분하게 작성되어있었다.

소스를 분석하면서 발견한 흥미로운 정보는 ArtifactVersion이라는 클래스였다. Maven이 아티팩트의 버전을 어떻게 파싱하고 저장, 비교하는지를 알수 있는 가장 핵심적인 내용이다.

아티팩트의 1.0.0-M1-NIGHTLY-123과 같은 정보는 어떻게 저장될까?

Maven의 버전의 표준 포맷은

<major version>.<minor version>.<incremental version>-<qualifier>

이다. 위의 버전이라면 major = 1, minor = 0, incremental = 0 그리고 qualifier = M1-NIGHTLY-123 이라고 파싱되어서 저장된다. 여기서 qualifier는 다시 마지막에 나오는 123과 같은 숫자를 Build No로 따로 분리해서 관리한다.

앞의 세가지 버전은 숫자로 관리되므로 당연히 10.0.0이 8.0.0보다 크다. 하지만 뒤에 따라오는 qualifier 영역은 빌드넘버까지 보함해서 모두 스트링으로 취급된다. 즉 M1-10과 M1-8이 있다면 M1-8이 더 최신버전(큰 버전)으로 인식된다는 것이다. 따라서 3가지 버전넘버 뒤에 따르는 부분은 스트링으로 비교된다고 생각하고 자리수를 일치 시켜서 M1-08, M1-10과 같이 사용해서 실수가 없다. 사실상 공식 레퍼런스 문서인 Maven Definitive Guide에 나오는 설명이다.

 

내가 위의 범위 설정을 적용했지만 제대로 동작하지 않은 것은 두가지 이유가 있다.

첫째는 현재 스프링의 빌드스크립트가 만들어내는 아티팩트 버전의 네이밍 방법이 Maven표준을 따르지 않는다는 점이다. 3개의 버전은 나오지만 대쉬(-)가 빠져있다. 이럴 때 Maven은 전체를 모두 qualify로 보고 스트링으로 처리해버린다.

따라서 3.0.0.BUILD-xxxx 라고 되어있지만 [2.0,]에도 포함되지 않는 것이다. major,minor,incremental version을 해석하지 못하기 때문이다.

 

결국 스프링의 현재 버전넘버는 모두 스트링으로 해석되고 비교된다는 것인데, 그래도 의문은 왜

[3.0.0.BUILD,]라고 준 범위에 3.0.0.BUILD-20090120232117이 포함되지 않느냐 하는 것이다. 자바의 스트링의 compareTo를 이용해서 두 값을 비교하면 3.0.0.BUILD가 더 작은 값으로 나온다. 따라서 3.0.0.BUILD와 무한대 사이에 위의 버전이 포함되는 것이 상식이다.

결국 이 것은 DefaultArtifactVersion의 코드를 뜯어보다가 발견한 코드에서 답을 찾았다.

Maven의 버전비교 로직에 보이는 희한한 코드가 있는데, 그것은 버전 스트링의 앞부분이 같은 경우 한쪽이 더 길이가 길면 긴쪽을 무조건 낮은 버전으로 취급한다는 것이다. 앞부분이 같다면 전체 길이도 같을 때만 스트링의 compareTo를 적용한다. DefaultArtifactVersion의 compareTo() 메소드를 살펴보면 알 수 있다.

예를 들어서

M1-10과 M1-8을 비교하면 스트링의 일반적인 비교방법을 생각하면 10이 더 작은 버전으로 나온다. 이런 경우는 가이드 문서에 나온 대로 이해가 된다.

그런데 M1-10과 M1-1를 비교하면 M2-1가 더 작은 버전으로 나올 것 같은데, 그렇지 않고 M1-10이 더 작은 버전으로 해석된다. 앞부분이 같다면 길이가 긴 버전이 작은 버전으로 무조건 취급되기 때문이다.

따라서 3.0.0.BUILD와 3.0.0.BUILD-20090120232117는 항상 뒤의 것이 더 낮은 버전으로 해석된다.

결국 맞는 버전의 범위를 만들려면

  1. 표준버전포맷이 아니고 버전의 시작부분이 다르며 스트링 비교에서 작은 값으로 나오는 [2.0.0.BUILD,] 같은 것을 사용하는 것
  2. 앞부분이 같고 길이가 일치하면서 스트링 비교에서 작은 값으로 나오는 [3.0.0.BUILD-00000000000000,]을 이용하는 것

중의 한가지 방법을 써야 한다.

단, 리포지토리에 기존 2.5.x버전이 있는 것을 포함했다면 위의 범위를 적용했을 때 2.5버전을 가져온다. 2.5.6는 표준 버전이고 3.0.0.BUILD-xxx 버전은 표준이 아니므로 major버전이 0으로 잡힌다. 따라서 [3.0.0.BUILD-0000..,]을 적용하면 표준버전이 더 큰 값으로 인식이 되서 2.5.6을 가져온다. 리포지토리를 3.0버전으로 제한하면 모르겠지만, 기존 리포지토리도 포함하는 경우라면 비표준 버전에서 적당히 큰 값을 상위 범위로 잡아준다. 즉 [3.0.0.BUILD-00000000000000,9.9.9.BUILD] 같은 것을 사용하면 된다.

 

일관성 없는, 문서에는 정확하게 제시되지 않은 버전 비교방법을 적용한 Maven 덕분에 저녁 시간을 몽땅 날려버렸다. 사실 표준 버전체계를 따르지 않은 스프링의 책임이 더 크기는 하다. 어쨌든 덕분에 Maven 소스도 찬찬히 뜯어보게 되었고 (코드는 정말 맘에 안들었다) 애매하게 알고 있던 메이븐 아티팩트의 버전개념을 확실하게 정리 할 수 있는 시간이어서 흥미로웠다.

 

다만 이것은 2.0.9에 적용된 것이라는 점. 2.0.x가 계속 나오고, 2.1, 3.0 (무려 3개의 버전 브랜치가 독립적으로 개발중이다. 이 메이븐 팀은 도대체 무슨 생각인지 모르겠다)이 개발되면서 이 룰은 달라질 수 있다는 점을 주의 해야 한다.

Maven의 핵심은 선언적인 방법으로 프로젝트에 대한 정보를 담은 POM이다. POM의 정보는 상속되기도 하고, 의존관계로 임포트 되기도 하기 때문에 현재 프로젝트에 정의한 pom.xml만을 봐서는 정확하게 Maven이 어떻게 동작할지를 예측하기가 힘들다.

그래서 Maven을 사용하는 개발자들이 자주 확인해봐야 하는 것이 바로 effective POM이다. Effective POM은 Maven의 기본설정을 담은 Super POM에서부터 출발해서 parent pom, dependent pom, profile등을 모두 적용해서 최종적으로 현재 프로젝트에 적용될 실제 POM을 말한다.

커맨드라인이라면 mvn help:effective-pom 이라는 명령을 통해서 effective POM을 확인할 수 있다.

이클립스에서 Maven 설정을 손쉽게 다루게 해주는 M2Eclipse 플러그인을 매우 만족스럽게 사용하고 있다. 그 중에서도 가장 맘에 드는 것은 바로 effective POM을 보여주는 기능이다. 의외로 M2E 플러그인을 사용하면서 이 기능을 모르는 개발자들이 많은 듯 하다.

Effective POM을 보는 방법은 간단하다. M2Eclipse의 POM에디터 화면에서 effective bom 버튼을 누르면 원본 POM과는 다른 [effective pom]이 적용된 화면이 나타난다.

effective-pom1

프로젝트 또는 서브모듈의 POM과 Effective POM을 비교해가면서 작업하면 골치 아픈 Maven 설정작업이 한결 수월해 질 것이다.

effective-pom2

Maven의 핵심개발자들이 만든 회사인 Sonatype의 m2eclipse에 이은 두번째 역작 Nexus 1.0이 릴리즈 됐다.

Maven을 성공적으로 사용하기 위해서 개인 또는 기업에게 꼭 필요한 것 두가지를 말하라면 자체적인 Parent/Abstract/Base POM을 만들어서 사용하라는 것과 기업 또는 팀에서 사용할 전용 Maven 리포지토리를 이용하는 것이라고 대답하겠다.

이 중에서 내부 Maven 리포지토리의 필요성에 대해서는 박재성님도 강조한 바 있고, Atlassian의 개발자 블로그에 실린 Maven Infrastructure에 관한 글에도 잘 나타나 있다.

 

Maven의 최대 약점은 부실한 Central Repository 관리에 있다. Maven2.0 초기부터 열심히 적용해서 AppFuse를 개발해온 Matt Raible이 제발 clean up좀 해줬으면 좋겠다고 블로그에서 여러번 호소한 적이 있을 정도로 Central의 악명은 높다. 관리체계나 검증방법, 등록과정이 어떻게 되는지는 잘 모르겠지만 최신 라이브러리가 Central에 등록되는데는 상당한 시간이 걸린다. 짜증날 정도로 허접하게 설정된 POM과 아직도 잘 지켜지지 않는 package naming rule도 마찬가지고. 그래서 많은 오픈소스 프로젝트나 기업들이 Central과는 별도로 자체적인 Maven 리포지토리를 만들어서 공개 또는 내부적으로 활용하고 있다.

잘 알려진 Central/Codehaus 이외의 공개 리포지토리로는 Atlassian, JBoss, SpringSource에서 공개운영하는 것들이 있다. 이런 리포지토지는 많이 사용되는 제품의 경우 빠르게 업데이트가 되고, 유용한 자체 개발 플러그인들이 등록되어있기 때문에 본격적으로 오픈소스 제품을 사용하는 프로젝트에 Maven을 적용할 경우 이용하지 않을 수 없다.

하지만 개발자 각자 또는 프로젝트 마다 이런 저런 리포지토리들의 설정을 복잡하게 해두고 관리하는 것은 불편한 일이다. 내 자리에서 잘 되는 빌드가 CI서버로 간다거나 다른 개발자 자리에서는 안되는 경우 리포지토리의 설정이 서로 다르기 때문일 가능성이 높다.

그래서 기업이나 프로젝트 팀의 전용 서버를 외부 리포지토리의 Proxy 개념으로 활용하면 매우 편리하다. Nexus와 같은 리포지토리툴은 여러개의 외부 리포지토리를 Proxy개념으로 연결해서 내부 개발자들에게는 Virtual한 하나의 release, snapshot URL 형태로 제공한다. 따라서 개발자들은 내부 리포지토리 설정 하나만으로도 충분하다. 필요한 추가 외부 리포지토리가 있으면 proxy 등록만 해주면 된다.

Proxy의 적용이 주는 또 한가지 장점은 다운로드 속도가 빠르다는 것이다. Maven의 transitive dependency는 깔끔하게 정의되지 않은 의존관계를 가진 POM을 만나게 되면 정체를 알 수 없는 엄청난 다운로드(그것도 같은 라이브러리의 여러개의 버전이 한꺼번에)가 일어난다. Central 서버가 그다지 빠르지 않을 뿐더러 Maven의 다운로딩 처리도 매우 느린 것으로 소문나 있다. 2.1 또는 3.0에서 개선될 것이라고 하지만 언제 나올지 기약이 없다. 그런 면에서 로컬의 proxy는 프록시가 원래 그렇듯이 아주 빠르게 라이브러리를 다운로딩 해준다. 한가지 문제는 snapshot의 업데이트인데 그건 적절한 서버 설정으로 해결봐야 할 듯.

 

내부 리포지토리의 또 한가지 필요성은 공개된 리포지토리에는 올라갈 수 없는 또는 올라가지 않은 라이브러리를 이용하고 싶은 경우이다. 라이센스 문제로 공개사이트에서 배포가 불가능한 SUN에서 제공하는 자바스펙 구현들(물론 별도 구현한 대체파일을 찾을 수는 있지만 번거롭다)을 포함해서 상용으로 구매한 라이브러리나 또는 내부적으로 개발해서 외부에 공개할 수 없는 것들은 전용 리포지토리에 직접 업로드해서 내부 개발자들에게만 공개한 형태로 사용되어질 수 있다. 내부 POM도 마찬가지로 서버에 등록이 되어있어야 할테고.

 

아무튼 이런 전용 Maven 리포지토리를 손쉽게 만들어 사용할 수 있도록 지원하는 여러 제품들이 있다. 한동안 Artifactory를 사용해왔는데 큰 불편 없었지만 종종 드러나는 작은 버그들과 서버의 불안정성 때문에 다른 대안이 없나 찾다가 Nexus를 발견했다. Maven 개발팀에 대한 불신이 큰 탓에 그쪽 동네서 만든 제품은 손도 대지 말자라고 생각하고 있었는데, Sonatype이라는 회사를 만든 이후로는 여러가지 좋은 제품과 문서들을 내놓고 있어서 인식이 180도 바뀌었다. The Definite Guide라는 문서도 지금까지 공개된 어떤 Maven 관련 책과 문서보다 뛰어난 Maven 가이드북이다. Eclipse 플러그인인 m2eclipse 역시 기대했던 이상의 편리한 기능과 안정성을 가지고 발전하고 있어서 Maven 적용 프로젝트의 필수 툴로 새롭게 떠오르고 있다. 또 하나 주목할 만한 것이 바로 Nexus이다.

Nexus는 Artifactory랑 비교해서 보면 기본 기능과 개념은 거의 비슷하다. 하지만 기능이 매우 뛰어나고 안정적일 뿐더러 관리자 UI 또한 사용하기 편리하게 ExtJs를 이용해서 만들어져있다.

또 하나의 장점은 인덱스의 활용이다. 리모트 리포지토리의 인덱스를 이용해서 필요한 라이브러리를 빠르게 검색할 수 있다. M2eclipse가 이 인덱스를 활용하기 때문에 Nexus의 도움을 받아서 인덱스를 업데이트 해가면서 필요한 라이브러리를 프로젝트에 쉽게 추가하는데도 적용할 수가 있다.

 

그 밖에 서버관리를 위한 여러가지 편리한 기능을 제공한다. 자세한 활용법은 The Definitive Guide에 나온 Nexus 챕터를 잘 읽어보면 도움이 될 것이다.

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha