지금은 너도 나도 ’아키텍트’라고 불리기를 좋아한다. 명함에도 Architect라고 쓰고 다닌다. 개발자나 프로그래머라는 단어는 이제 흔하고 값이 싸게 들리기 때문인지 아니면 자기의 실력을 과시하고 싶어서인지 아니면 진짜 아키텍처를 만들고 책임지는 아키텍트라서 그런지는 알 수 없다.

당연히 아키텍트라면 아키텍처를 책임질 수 있어야 한다. 소프트웨어 아키텍트라면 개발하는 또는 아키텍팅하는 시스템의 아키텍처를 ‘견고하고 건강하게’만들 줄 알아야한다.

아키텍처 설계와 구조화에 관한 기술은 여러가지가 있다. 그중에서 코드의 구성을 어떻게 할 것인가도 매우 중요하다. 코드를 쉽게 읽고 네비게이션 할 수 있고 손쉽게 이해하려면 구조적으로 잘 설계된 패키징이 필요하다. 자바는 코드를 계층화된 package라는 논리적인 단위로 구분할 수 있도록 해서 이를 돕고 있다.

대부분의 시스템의 코드는 초기구조를 그대로 유지하는 것이 아니라 대부분 지속적으로 추가되고 발전된다는 특징이 있다. 끊임없는 새로운 기능과 요구사항이 추가될 때마다 초기의 구조를 그대로 유지시킬 수 있을까? XP나 Agile기법에서는 끊이없는 리팩토링과 리스트럭처링을 통해서 코드를 발전, 개선 시킨다. 하지만 Margin Fowler가 리팩토링 책에서도 얘기했듯이 published interface는 리패토링하기가 매우 힘들다. 그것을 사용하는 client의 코드에 영향을 주기 때문이다. 즉 이전버전과의 호환성에 장애가 될 수 있다.

프레임워크나 코어엔진 또는 서비스의 API/SPI들이나 서로 의존관계에 있는 애플리케이션은 리팩토링에 많은 제한을 받는다. 물론 많은 오픈소스 프레임워크들은 버전을 바꾸면서 코드를 첨부터 새로 짜거나(어떤 개발자들은 그걸 자랑이라고 한다 -_-+) 구조를 왕창 바꿔버려서 기본 버전을 이용하던 개발자들을 당황하게 만든다. 구버전을 이용해서 개발한 애플리케이션을 호환성 문제때문에 새버전으로 업그레이드 못하는 경우를 주위에서도 많이 접하게 된다. 그래서 공개된 API의 변화가 없고 backward compatibility가 보장되는 필요들이 절실하게 요구되어진다.

또 문제는 세분화된 패키징이 필요할 경우다. 하나의 개발된 프레임워크가 다양한 구조의 모듈이나 레이어로 래패키징되는 필요는 자주 등장을 한다. 구지 CBD의 개념이 아니더라도 컴포넌트 레벨 또는 모듈, 번들(OSGi)같은 구조로 재구성되어질 경우 모듈간의 의존성은 큰 문제가 되기도 한다.

의존성문제는 사실 매우 심각한 지경에 이르렀다. 과거 MS기술에는 DLL Hell이라고 불리는 문제가 있었다. 버전별, 애플리케이션별 의존성이 매우 복잡하게 얽히고 꼬여있어서 어떤 경우는 시스템을 마비시키거나 애플리케이션이 동작하지 않을 정도의 문제까지 일으킨다. 사실 .NET(Assembly Hell)이나 자바(Jar Hell)도 그 정도가 좀 덜할 뿐이지 만만치 않다.

개발시 사용하는 라이브러리나 프레임워크가 수십개에 달하는 요즘 개발방식을 볼 때 코드의 외부 라이브러리의 버전에 관한 의존관계는 매우 복잡해지고 있다. 수시로 새 버전이 나오는 오픈소스 프레임워크의 경우 그 문제가 한층 더 심각하다.

건전한 아키텍처를 가진 코드베이스만이 그러한 문제를 해결해 줄 수 있다. 어떻게 코드 아키텍처를 잘 구성해야지만 지속적인 발전 속에서도 안정적인 호환성을 유지하며 스파게티 의존관계문제를 일으키지 않을 수 있을까?

거기에 대한 딱 뿌러지는 정답은 쉽게 내놓을 수 있는 것은 아니다.  하지만 몇가지 주의를 기울여야 할 문제가 있음을 알게되었고 그에 대한 본격적인 고민을 시작하게 되었다.

가장 먼저 생각할 것은 내가 만들고 있는 시스템 또는 프레임워크의 내부의존관계에 대한 정립이다. 또 아키텍처를 가진 설계와 접근, 검증과정의 도입이다.

자바의 초보과정에서 반드시 배우는 것에 package가 포함되어있다. Package는 단순히 클래스명의 충돌을 막기 위한 naming space만은 아니다. Package는 코드 organization의 기반과 경계가 된다. 하지만 자바를 배우는 개발자들은 기껏해야 com.companyname.systemname…. 따위의 패키지네임 컨벤션만을 배웠을 뿐이지 그 안에서 어떻게 패키지를 구분하고 설계할지에 대해서는 제대로 배운적이 없다. 기껏해야 간단한 패키징 정도를 정해서 사용하고 있을 뿐이다. 

코드의 발전에 package구분이 미치는 영향은 매우 크다. 사실 복잡한 시스템 설계나 코드설계에서 제일 주의해야 할 것은 의존관계의 복잡성이다. Package구조에서도 의존관계의 복잡성을 가지게 되면 여러가지 문제를 일으킨다. 지나치게 복잡한 의존관계로 서로 얽혀 있는 시스템이라면 무엇인가 설계에 결함이 있다고 생각할 수 있다. Package간에서도 마찬가지이다.

그래서 Package레벨의 의존관계(사실은 그 안에 속한 Class간의 의존관계)에 대한 분석을 통해서 시스템 아키텍처를 검토하려는 많은 시도들이 있다. 

JDepend같은 툴은 그런면에서 많은 도움이 될 수 있는 분석도구이다.  그 사용법에 관한 좋은 아티클(Managing Your Dependencies with JDepend)도 여럿 찾아볼 수 있다.

의존관계 분석에서 중요한 것 중의 하나는 순환의존관계(Cyclic Dependency)이다. Cyclic Dependency는 많은 문제를 일으키는 안좋은 설계의 대표적인 예이다. Cyclic Dependency가 일으킬 수 있는 문제점은 매우 다양하다. 위에서 얘기한 OnJava의 아티클이나 Krkk의 블로그에 그 이유들이 잘 설명되어있다. 상식적으로도 컴포넌트나 패키지, 모듈이 순환적인 의존관계를 가지면 안된다는 것은 쉽게 이해할 수 있다.

Layered Architecture를 설계할 때에 주의 할 것이 바로 한 방향으만 의존관계를 가져야한다는 점이다. Isolation이니 Decoupling이니 하는 것들은 귀가 따갑도록 들어왔다. Service layer가 UI(MVC) layer에 의존적이거나 DAO layer가 Service Layer와 상호의존적이라는 것은 잘못된 Layered Architecture의 대표적인 사례이다. Spring이나 Ioc/DI를 지지하는 많은 사람들이 추구했던 것이 결국 잘못된 의존관계로 인해서 매우 지저분하게 coupling된 시스템을 피하자는 것이었다.

순환의존구조는 단지 A<->B사이에만 일어나는 것은 아니다. 상호의존적인 것은 가장 큰 문제겠지만 사실 의존관계를 따라가다 보면 결국에는 다시 자신에게 돌아오는 순환적인 구조또한 문제이다. A->B->C->D->A 같은 구조도 동일한 문제를 더 교묘하게 일으킬 수 있다.

순환의존관계가 Class레벨에서는 필요한 경우가 있다. 같은 목적을 가지는 매우 긴밀한 관계의 클래스들 사이에서의 순환구조는 필요하다. 하지만 그 범위가 일정 수준을 넘어가는 것은 설계상의 치명적인 결함을 가져오게 된다. 그 경계가 되는 가장 좋은 범위는 자바의 package라고 볼 수 있다. 보통 한 Package에 들어갈 수 있는 적절한 클래수의 수는 20-30개 미만이라고 한다. 그렇다면 그 이상의 클래스들이 패키지구조 속에 섞여있을 때 각 패키지 안에 있는 클래스들간의 의존관계를 패키지의 의존관계로 해석해볼 수 있다. 그랬을 경우 각 패키지간의 의존관계를 설정할 수 있는데 이 때 패키지 레벨에서도 순환의존관계가 발생하는 것도 또한 피해야 할 일이다.

물론 그 순환의존관계의 범위를 어디까지로 볼 것이냐는 논쟁의 소지가 있다. Package계층구조를 통채로 순환의존관계의 경계로 잡는다거나 나중에 모듈화(jar)되는 범위나 레이어 단위로 구분할 수도 있다. 하지만 package범위를 넘어서는 순환의존관계는 결국 코드자체를 복잡하게 만드는 원인이 된다. 그래서 한쪽의 변화가 다른 곳에 순환적인 영향을 주게 되고 코드를 지속적으로 발전시키는데 장애가 된다.

모듈은 물론이고 패키지간의 순환구조를 피하는 것이 일단 첫번째 목표로 삼아야 할 것이라고 생각된다.

JDepend말고 좋은 아키텍처-코드구조 분석툴로 SonarJ라는 것이 있다. 상용제품이지만 비상업적인 용도나 오픈소스팀에는 라이센스가 무료로 제공된다.

SonarJ로 지금 개발하고 있는 시스템을 분석해봤다. 검증할 대상은 두가지 였는데 하나는 Package레벨의 Cyclic Dependecy가 존재하는가의 여부와 두번째는 Relative average component dependency(RACD)이다. 첫번째는 단 한개라도 존재하지 않아야할 것이고 두번째는 15미만을 유지해야 적절한 의존관계를 가진 건전한 아키텍처라/패키지구조를 가졌다고 할 수 있다.

지금 개발하는 시스템은 현재 총 302개의 패키지에 1312개의 타입을 가지고다. RACD는 2.99. 아무래도 프레임워크라기 보다는 독립된 비즈니스 애플리케이션 컴포넌트 구조이니 의존정도는 매우 적당하다.  하지만 cyclic dependency에 걸린 패키지가 무려 45개나 된다. 전체 패키지의 1/6정도가 cyclic dependency를 가지고 있다.

예를 들어

cycle1.jpg

위의 경우는 9개의 패키지를 통해서 처음 패키지로 돌아온다. 복잡한 순환구조이다. 코드를 잘 분석해보면 support에 해당하는 패키지가 다시 dao나 service layer등에 의존하고 있는 문제가 있다는 것을 알 수 있다.

분석중에 OSAF코드도 같이 살펴봤는데 허걱 4개 패키지에 순환의존관계가 있다.

osafcycle.jpg

음음. 당장 코드를 분석해봤다. 나름대로 패키지 구조를 잘 설계했다고 생각했는데 이런 문제가 있었다니. 살펴본 결과 특정 인터페이스 하나가 항상 순환구조에 끼어있음을 알게 되었다. 결국 한개의 종속적인 인터페이스를 루트패키지에 둔 것이 문제였다. 간단히 그 인터페이스를 적절한 패키지로 이동하고 나서 분석을 다시 했을 때 OSAF내의 모든 순환의존관계가 사라졌다. :)

순환의존관계는 초기에는 잘 드러나지 않지만 코드베이스가 규모가 커지고 복잡해지만 문제를 가져오게 마련이다. SonarJ는 의도적인 아키텍처구조를 Layer/Verical Slice/Subsystem이라는 관점으로 세팅해 놓고 그것을 통해서 아키텍처에 위배된 코드나 문제점을 찾아준다. 매우 유용하다.

하지만 지금 개발하는 전체시스템의 순환의존문제는 간단히 해결할 수 있는 것이 아니다. 좀 더 고민을 해보고 아키텍처를 다시 설계를 해서 정리해야 할 것이다. 연말에 바쁜데 아주 대박이다. :( 순환의존구조는 코드질을 떨어뜨린다. 그런면에서 대표적인 code smell의 하나로 인식해야 한다. 리팩토링의 대상일 뿐더러 초기부터 주의해서 설계해야하는 대상이 된다. 주의 할 것은 cyclic dependency를 피하기 위해서 code duplication을 허용해서는 안된다는 것이다.

시스템이 복잡하면 package cyclic dependency는 피할 수 없는 것이 아닌가라고 생각해봤다. 특히 util, core, support나 여러 서브시스템이 자주 사용하는 핵심부분에서는 어쩔 수 없이 발생할 수도 있지 않은가라고 변명할 수도 있을 것이다. 하지만 그건 “나는 충분히 smart하지 않아서 적당히 타협하겠다”라는 발언일 뿐이다. 귀찮은데 if문으로 떡칠을 하건 한 메소드에 1000라인을 짜건 copy&paste를 하건 class를 다 루트패키지에 두건 일단 돌아가기만 하면 되지않냐라는 것과 다를 바 없는 주장일 것이다. 그런 자세를 가진 사람이라면  아키텍트라는 타이틀은 떼버려야 할 것이다.

그런 것이 가능하다는 이상적인 증거는 Spring이다.

Spring2.01의 코드를 SonarJ로 분석해봤다. Spring의 모든 모듈을 다 분석했다. 프레임워크의 핵심코드만 무려 1967개의 type과 190개의 패키지 그리고 91,456라인의 코드(LOC)로 구성되어있다. Spring의 내부 아키텍처를 조금이라도 아는 사람이라면 Bean/AOP/Persistent/Util/Transaction/Resource/Remote등등이 얼마나 복잡하게 서로를 이용하고 있는지 잘 알것이다. 하지만 Spring2.0에는 패키지레벨의 cyclic dependency가 단 한개도 없다!! 단 한개도. 모든 의존구조가 one-way로만 되어있을 뿐이다. 물론 당연히 module간 레이어간의 순환의존관계로 없다. RACD는 3.35에 불과하다. 10만라인의 매우 복잡한 코드를 CD단 한개도 없이 190개의 패키지에 효율적으로 구성해놓은 Spring의 아키텍처가 정말이지 놀랍다. 코드 구조를 관리해왔다는 Juergen Hoeller가 존경스러워진다. 나이는 나랑 비슷하던데. ㅜㅜ

그 덕분에 Spring은 1.0부터 지금까지 완벽한 backward compatibility를 지켜왔다. 3년간 수많은 기능이 추가되고 업그레이드 되고 메이저 업데이트가 되면서도 완벽한 호환성을 지켜왔다는 것은 보기드문 일이다. Spring1.x으로 개발된 시스템을 Spring2.0으로 업그레이드 하는 방법에 대해서 Adrian Coyler가 설명을 해줬다. 방법은 spring.jar를 라이브러리 폴더에 복사하면 끝. 멋지다.

Spring의 정교하게 설계된 패키지구조와 코드베이스의 아키텍처 그리고 뛰어난 확장성을 가진 구조는 수많은 기능이 추가되고 변화가 있음에도 core class의 변화가 거의 없고 완벽한 호환성을 보장하고 있다. 이것은 앞으로도 계속 지켜질 것으로 기대된다.

그럼 Spring은 처음부터 이런 완벽한 구조를 가지고 있었을까? 아니다. Spring1.0의 소스를 가지고 CD를 체크해보면 CD문제가 분명 있다. Juergen Hoeller는 이부분에 대해서 초기에 고민했던 얘기를 들려줬다. bean<->aop나 aop<->util등의 상호관계에서 분명 CD문제가 있었다. 이런 것으로 인해서 향후 확장에 생길 수 있는 문제들을 고민했고 매우 많은 노력을 통해서 보다 완벽한 아키텍처 구조로 리팩토링을 해서 그러한 문제를 해결했다고 했다. 그 결과가 Spring2.0까지 이어지는 Spring의 코드 아키텍처이다.

궁금해지기 시작했다. 다른 오픈소스들은 과연?

나는 한때는 오픈소스의 코드는 완벽한 줄 알았다. 적어도 코드가 공개될 정도니 뭐 꽤 잘만든 코드나 공개를 할 거고 또 공개된 후에 많은 사람들의 노력으로 코드가 완벽하게 다듬어져나갈 것이라고 생각했다. 하지만!  허접한 코드의 오픈소스도 많다. 쓰레기 코드나 스파게티 코드의 오픈소스가 태반이다. 사실 그다지 사람들은 오픈소스제품의 코드에 관심이 없다. 아무튼 개중에는 매우 뛰어난 코드베이스를 가진 것도 있지만 그렇지 않은 것도 많다는 사실을 Liferay를 분석하면서 정말 절실하게 느꼈다. 그 후에도 Tomcat의 코드의 문제를 지적한 글등을 보거나 내가 소스코드를 분석해보면서 느꼈던 점은 거의 대부분의 오픈소스 코드는 그다지 sound하지 못하다는 것이다.

어쨌든 내가 소스를 다운 받아놓았던 유명 오픈소스 프로젝트를 검사해보기 시작했다.

먼저 ant1.6

567개 타입이 25개 패키지로 구성되어있다. 그중 CD에 걸리는 패키지는 무려 17개. RACD는 무려 32. 좋지 않다. 역시 흔히 볼 수 있는 util패키지와의 순환구조가 많은 CD의 케이스에 해당된다. 특히 CD케이스가 많이 보인다. 이렇게 크지 않은 코드가 이렇게 복잡한 CD문제를 가지고 있다는 것은 아키텍처 설계에 상당히 결함이 있다는 증거이다.

다음은 axis1.4

762개의 타입이 56개 패키지로 구성되어있다. 그중 43개 패키지가 CD문제를 가지고 있다. RACD는 37.6. 역시 불합격.

다음은 혹시나 기대를 가져본 junit4.1

93개 타입 11개의 패키지에 8개 패키지가 CD. 거의 모든 패키지가 순환적인 고리로 복잡하게 연결되어있다.

다음은 hibernate3.2

거의 Spring에 육박하는 코드베이스를 가지고 있다. 9만6천라인의 LOC. 1294개의 타입. 76개의 패키지. 그중 무려 70개의 패키지가 CD문제를 가지고 있다. RACD도 69.3이나 된다. 심각하게 복잡한 구조로 얽혀있는 의존관계를 가지고 있음을 알 수 있다. 사실 Hibernate는 사용할 때와 달리 코드를 분석하다보면 많이 실망하게 된다. Spring코드 때문에 눈이 높아져서 그럴 수도 있긴하다.

다음은 cglib2.1

256개의 타입에 10개의 패키지. 패키지가 적은게 은근히 불안한데. 앗. 그러나 단 한개의 Cyclic Dependency가 없다. 빙고. RACD도 14.5. 안정선 안쪽이다. 역시 잘 설계된 오픈소스가 없을리가 없다. 일단 cglib팀에게 박수. cglib도 꽤 오랬동안 발전해왔음을 기억한다면 그런 것을 고려해서 아키텍처와 패키지 구조를 잘 설계하고 관리해왔다는 것이 분명하다.

다음은 아키텍처 구조의 개발을 강조하는 maven2.0

Maven은 서브 패키징을 통한 모듈화를 강조하는 듯 14개의 jar파일로 매우 fine-grained된 모듈 패키징 구조를 가지고 있다. 그러나! 전체 패키지 53개중 28개의 패키지가 cyclic dependency를 가지고 있다. 더 심각한 것은 이 CD가 jar파일의 범위를 넘어선다는 것이다. 즉 module간의 cyclic dependency도 가지고 있다는 뜻이다.

maven.jpg

위의 그림을 보면 top level package들이 cyclic dependency로 얽혀있는 것을 알 수 있다. 이런 cyclic dependency가 수백개에 이른다. 이럴 바엔 그렇게 모듈화 해놓은게 무슨 의미가 있고 앞으로 계속 기능이 확장되고 업그레이드 되면서 깔끔한 아키텍처의 관리를 어떻게 할 수 있을 것인지 의문이다.

다음에 분석해본 것은 mina-1.0.1

역시 6개정도의 모듈로 구분이 되어있다. 17,782라인에 255개 타입. 그리고 25개 패키지로 구성되어있다. 이중 12개의 패키지가 cyclic dependency를 가지고 있다. CD가 제일 많이 발생하는 곳은 util과 nio. 각각 15, 12개의 cycle을 가지고 있다. Jar모듈간에 CD가 있는지는 까지는 확인해보지 못했다. 있다면 대박일거고.

이젠 거의 지쳐버렸다. 유명 프로젝트들 조차 대부분 기대했던 이하였다.

마지막으로 Spring의 서브프로젝트인 Spring-Web-Flow 1.0

12,229LOC의 273개의 타입이 31개의 패키지로 구분되어있다. Cyclic dependency는 0. 역시 Spring패밀리 답다. RACD는 9.98.

이외에도 더 많은 오픈소스 라이브러리와 프레임워크를 분석해봤는데 Spring과 SWF를 제외하면 cglib를 비롯한 극소수의 프로젝트만이 dependency문제가 없었다.

한편으로는 나만 잘 못하고 있었던 것이 아니엇다는 안도감도 든다. 하지만 Spring을 보고 난 충격덕분에 여기서 만족할 수는 없을 것 같다. SonarJ를 이용해서 더 구조적인 아키텍처와 의존성관리를 해나가면서 나도 10만라인이상 발전하는 코드를 멋지게 organize할 수 있도록 노력해봐야겠다.

무늬만 말고 진짜 architect가 될 길은 참 멀다.

Related posts:

  1. Dependency Injection 표준화?
  2. DI의 본질 – 다이나믹 (타입) 언어는 Dependency Injection이 필요없는가?
  3. 마이크로 DI(dependency injection)
  4. Dependency Injection의 Dependency란 무엇인가?
  5. JSR-330 Dependency Injection for Java 최종 승인
  6. EJB3의 Dependency Injection
  7. Spring 3.0 (7) Spring 3.0 Dependency Matrix
  8. Spring 3.0 (42) Spring Dependency Matrix 업데이트
  9. Spring 3.0 (53) Spring Dependency Matrix 업데이트

Facebook comments:

to “Code Organization & Cyclic Dependency Problem”

  1. mbt shoe stores Code Organization & Cyclic Dependency Problem » Toby’s Epril

Leave a Reply to uggs cyber monday Cancel reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha