자바 프로젝트를 진행하면 프로그래머들이, 여타 직장인들처럼
"오늘 점심메뉴는 뭘 먹을까?" 하듯 고민이 하는 부분이 한가지 있다. 물론 프로그래밍을 하다보면 많은 부분에서 고민을 해야겠지만 그중에도 가장 쉬우면서 딱히 가이드 잡기가 어려운
패키지별로 클래스를 정리하는 과정이 그러하다. 프로그래머는 도대체 어떤 기준으로 클래스를 분류해야 체계적으로 자신의 프로젝트를 분류가 되었고, 더 나아가 팀으로 작업할 시에
"왜 이 클래스는 이 패키지에 넣은 거죠?" 라는 난해한 질문을 받지 않을 수 있을까?
먼저 패키지 분류가 복잡해지라도 하나의 원칙과 명확한 분류기준이 있다면 그때그때 프로젝트마다 뒤죽박죽으로 바뀌는 마구잡이식 분류법을 피할 수 있다. 마구잡이식 프로젝트는 코드의 가독성을 해치고 확장성을 망가뜨리는 나쁜 프로그래밍 방법이다. 게다가
구분과 명분이 확실해야 차후에 프로젝트를 진행할 때도 미리 명확한 기준에 맞춰 진행할 수 있고 현재 작성된 코드를 재활용하는데도 사용자 간에 특별한 교육없이도 막힘이 사용할 수 있게 도와준다. 여기에 조금 플러스하자면 잘 분류된 클래스는 2인 이상, 3인 이상 할 것없이 수백명의 프로그래머들과 함께 작업을 하더라도 서로 헷갈릴 이유가 없다.
더욱이 이런 작업 기준을 미리 정해놓는 중요한 이유는 자바의 패키지 분류작업은 작업이 완료된 시점에서 실행되는 것이 아니라 코딩을 해나가는 과정에서 함께 일어나기 때문이다. 모든 작업을 완료한 뒤에서야,
"아 이건 내가 여기로 분류 했어야지...."라고 생각해 생각없이 클래스들을 옮겨댔다간 해당 클래스에 의존하고 있는 다른 클래스의 import문이 엉켜 엉망이 될 수 있다. 물론 이클립스는 이런 클래스 파일의 이동을 안전하게 보장해주는 자동 클래스패스 수정 기능이 있지만 이것은 자바 파일에만 한정되있을 뿐 jsp나 다른 xml문서에 대해서는 자동수정을 보장해주지 않는다.
그러므로 이런 보이지 않는 약속같이 중요한 작업은 개인의 독단적인 기준에 의해 분류될 것이 아니라 명확한 분류 기준과 서로 공감대를 가질 수 있는 원칙이 필요하다. 현재 작성되고 있는 이 문서는 기본적인 패키지 분류법에 대해 기술할 것이며 주된 목적은 웹기반 서비스들의 패키지 분류법이다.
패키지 분류 법칙 1. 루트 패키지는 x.y.z 식으로 구성한다.
x.y.z 식이란 최초의 프로젝트 생성시 프로젝트의 패키지를 최소 3단계로 분류해야 한다는 뜻이다. (물론 x.y.z라는 말은 필자가 기억하기 쉬우라고 만든 용어이므로 오용해서는 안된다.) 대부분의 프레임워크나 상용서비스들을 보면 이런 법칙을 준수해서 만드는 것을 볼 수 있는데 먼저 기본 루트 패키지를 굳이 3단계씩이나 걸쳐서 분류해야 하는지, 그 까닭을 보자.
import org.apache.taglibs.*
import net.sf.cglib.*
import com.sun.accessibility.*;
먼저 위의 import문 보자. 예리한 눈썰미를 가진 자바 프로그래머라면 위 코드만 보더라도 기존의 오픈소스 그룹들이 분명 보이지 않는 패키지 정렬법을 갖고 있다는 사실을 알 수 있다. 사실 오픈소스 그룹들 뿐만이 아니다. 대부분의 업계 또한 위의 법칙을 준수한다.
일단 첫번째로 org와 net으로 시작하는 가장 높은 부모 패키지가 보일 것이다. 이 패키지는 프로젝트를 이끄는 그룹의 성격을 결정한다. org는 organization의 약자인데 조직, 기구 단체를 뜻한다. 즉 비영리 그룹이나 단체가 될 것이다. 지극히 소규모의자바 동호회라 할지라도 어쨌든 org의 범주에 속한다. net은 정확히는 알 수 없지만, 이 라이브러리는 net을 기반으로 하는 기술이라는 것이라고 추측해 볼 수 있다. 그리고 그 아래의 com은 company의 약자로 영리 목적의 회사에서 제작한 프레임워크라는 것을 뜻한다. 만약에 회사에서 프로젝트로 라이브러리나 프레임워크, 또는 기본적인 기능소스를 구현할 지라도 classpath에는 명확히 이 소스의 단체 성격을 밝혀주어야 한다. 당장에는 필요 없더라도 확장시에 필히 요구되는 사항이다.
두번째로 apache, sf, sun을 보면 바로 알 수도 있겠지만 이 패키지 명은 자사의 그룹 또는 사명을 정해주는 구간이다. 일반적으로 회사라면 사명이 들어가고 그룹이라면 그룹명이 들어가는게 일반적이다. 이처럼 빈 루트패키지에 또 다시 빈 하위패키지를 정의해주는 이유는 만약에 이 소스가 본인 뿐 아니라 회사 내, 또는 그룹 내에서 활발하게 쓰인다면 누구나 프로젝트의 출처를 알 수 있게 되므로 작성자가 소스를 사용함과 동시에 출처 알게 된다는 것에 큰 의의가 있다. 작성자가 출처 정확히 알고 있다면 기능이 업데이트 되었을 시에 업그레이드된 프레임워크를 사용할 수도 있고, 필요하다면 사용자 피드백을 공유할 수도 있을 것이다.
세번째로 들어가는 taglibs나 cglib, accessibility는 실제 이 프로젝트의 artifact 구조를 뜻한다. 위의 두단계로 프로젝트 그룹의 성격과 그룹명을 정해주었다면 이젠 실질적인 소스의 역할 또는 프로젝트명이 들어가야 할 것이다. 예를 들어 설명해보자. 만약에 내가 삼성의 갤럭시 폰의 자바 프로젝트를 맡게 되었다면 패키지 명은 아래와 같이 정할 수 있다.
먼저 com.samsung으로 프로젝트의 그룹을 규정 해주고 그 하위 패키지로 galaxy라는 현재 프로젝트의 성격을 규정 지어준다. 많은 회사와 단체에서 이런 식으로 몇가지 단계를 나누어 패키지를 분류하는 이유는 성격을 구분짓는다는 이유 말고도 또 있다. 예를 들어 삼성이란 회사에서 galaxy 폰만 판다면 필자눈 굳이 위와 같은 분류로 힘들게 분류하지는 않았을 것이다. 오히려 아예 패키지를 com.samsung.galaxy로 하지 않고 galaxy로 심플하게 통일했을 수도 있고 패키지를 만들지 않았을지도 모른다! 하지만 삼성이런 거대기업은 galaxy만 팔진 않는다. 호텔사업도 하고, 놀이동산도 하고, 갤럭시도 2, 3, 4, 5… 돈이 될 때까지 찍어낼 것이다. 우리가 단 하나의 프로젝트를 구현하면서도 이런 그룹부분과 구조부분을 나뉘어 주는 것도 다 이런 이유에서이다. 즉 기업의 확장에 유연하게 대처할 수 있는 분류법을 사용해야 한다는 것이다. 당신이 이런 기본적인 구조만 갖춘다면 갑작스럽게 닥친 프로젝트에도 유연하게 패키지를 분류하고 빠르게 개발에 착수할 수 있게 된다.
더욱이 자바프로그래머로서 확장을 고려하지 않는 사람은 자바란 객체지향언어의 알맹이는 쏙빼고 껍질만 긁어먹는 사람과 똑같다. 정상적인 회사나 그룹이라면은 단체가 확장하고 번창하길 바라지, 축소를 바라는 단체는 없을 것이다. 게다가 그룹이 커지다 보면 회사나 단체가 책임에 따라 둘로 나뉠 수 있고 처음엔 비영리로 시작한 단체가 안타깝게도 영리 단체로 변모할 수도 있다. 우리가 이런 상황에 유연하게 대처하려면 이런 x.y.z 식의 구분은 필수로 갖추어야 한다.
패키지 분류 법칙 2. 기능별로 분류하자
작성중인 프로젝트를 위의 x.y.z식으로 분류한 다음은 어떻게 해야 좋을까? 이제부터 자바파일을 막 집어넣어도 되는 것일까? 간단하게 영광스런 제작자의 이름이나 어머니 아버지의 이름을 섞어서 패키지를 만드는 것은 어떨까? 아니면 간단하게 숫자나 알파벳순서로 패키지를 작성해 나갈까? 물론 모두 가능한 선택 옵션이지만 올바른 선택은 아니다. 먼저 글을 작성하기에 앞서 우리는 웹기반 서비스를 기준으로 패키지 분류하겠다고 밝혔는데 당신이 웹기반 서비스를 제작한다면 기본적으로 DB, MVC같은 기술과 계층을 나누는 식의 제작방식을 도입하려 할 것이다.
답을 먼저 말하지만 artifact id 이후로 나오게 될 패키지 분류는 가급적 기능의 성격으로 하는 것이 옳다. 이 성격이란 구분 지어 질 수 있는 가장 큰 성격을 말하는데 구체적으로 보자면 DB의 테이블이라 할 수도 있다. 기본적으로 업무에서 웹 프로젝트를 작성할 때 주된 작업은 DB에 있는 자료를 입력, 수정, 삭제하는 식의 작업이라는 사실을 개발경험이 있는 사람이라면 알 수 있을 것이다. (얼마나 DB 작업이 심하면 실무에서는 입출력만 알면 장땡이라는 말까지 있을 정도다.) 그리고 대부분의 DB작업은 테이블을 우선하여 결정짓게 되는데 테이블은 하나의 기능에 대하여 필요한 정보를 컬럼으로 묶는 식의 설계가 될 것이며 테이블은 한가지 기능에 대한 세부적인 세팅 데이터를 갖게 될 것이다.
그렇다면 우리는 단순히 테이블을 기준으로 분류를 나누기만 하면 패키지 분류에 대한 명확한 기준과 기능별 분류라는 이점까지 얻을 수 있다는 결론을 얻게 된다. 더욱이 중요한 것은 쿼리문은 한줄에 하나의 테이블에 대한 연산만 할 수 있도록 설계 되어 있다는 것이다. 물론 join이나 group같은 것을 사용하여 여러 테이블을 묶을 수도 있겠지만 궁극적으로 쿼리문은 한 테이블을 기준으로 동작 가능하게 설계하는 것이 옳다. 왜냐하면 코드의 재사용성을 고려하더라도 차라리 트랜잭션을 활용해 원자성을 유지하며 간단한 쿼리문을 사용하는 것이 좋지, 괜히 성능을 향상시킨다고 과도하게 쿼리문에 의존하다간 코드가 뒤죽박죽이 되버릴게 뻔하기 때문이다.
부득이하게 유지보수를 위하여 하나의 테이블을 여러 테이블로 나누었더라도 이 모든 테이블이 하나의 성격으로 통일될 수 있다면 복수의 테이블을 하나의 기능이라 생각하고 분류를 나누는 것도 좋다. 만약에 'phonebook'이라는 테이블이 있는데 이 테이블을 'phone'과 'book'이라는 테이블로 나뉘어 설계하는게 좋다면 당신은 phonebook.phone과 phonebook.book과 같은 식으로 패키지를 나눌 수도 있을 것이다.
테이블별로 분류한 당신의 패키지는 DB와 관련된 작업을 편리하게 해주면서 동시에 이 소스를 사용해 개발하는 사람들의 이해도를 대폭 상승시켜 줄 것이다. 더욱이 다양한 테이블이 서로 연관관계를 갖는 서비스 계층을 만든다 하더라도 사용자는 본인이 원하는 메소드가 어디에 위치한지 바로 알 수 있을 것이다.
패키지 분류 법칙 3. 계층별로 분류하자
기능별로 패키지를 분류하였다면 이젠 계층별로 분류하자. 사실 계층별로 분류하는 것은 기능별로 분류하는 것보다 더욱 중요한데 혹자는 이런 계층별 분류를 티어(tier, tiered) 또는 레이어(Layer)라고 부르기도 한다.티어는 사용자가 어떤 디자인 모델을 차용하서 제작하느냐에 따라 갈리므로 작성자가 먼저 어떤 구조로 설계하는지 결정하는게 가장 중요하다. 기본적인 웹 계층이라면 MVC 구조로 많이 제작될 것이며 View는 JSP로 Controller는 서블릿 또는 스프링이나 스트럿츠 같은 프레임워크가 관리하는 형태로 제작 될 것이다. 결국 우리가 클래스로 묶어서 배포할 부분은 모델부분에 해당한다. 모델은 직접 DB에 접근하여 데이터를 가져오고 연산하여 컨트롤러에 넘겨주는 주된 역할을 담당한다.
그러면 모델계층을 한번 분할시켜보자. 모델은 DAO(Data Access Object, DB에 접속하여 데이터를 가져오는 클래스) 계층과 자바빈과 같이 테이블의 컬럼을 오브젝트로 변환시키는 domain계층, domain과 dao계층을 활용하여 직접적인 기능을 구현하는 service 계층 등으로 나뉠 수 있다. 이런 역할분담은 매우 기본적인 것이므로 이런 계층에 따라 패키지를 나누어주는 것이 좋다.
한번 예를 들어보자 필자가 삼성이란 기업의 갤럭시 폰 프로젝트에서 user란 테이블을 만들고 이에 대한 작업을 한다면 다음과 같은 패키지 구성을 할 수 있을 것이다.
각 패키지에는 해당 기술을 추상화 하는 인터페이스가 들어갔을 수도 있고 이것을 구체화 하는 Implement 클래스가 있을 수도 있다. 서비스 패키지는 서비스 내부에 또 다양한 패키지로 기능별 구분이 있을 수도 있고 단순히 한가지 서비스에 대한 클래스가 존재할 수도 있다.
물론 user에는 이보다 더 많은 컨트롤러의 역할을 해줄 수 있는 com.samsung.galaxy.user.web이나 해당 그룹을 지원해줄 수 있는 com.samsung.galaxy.user.support 같은 것들도 들어갈 수 있다. 위와같은 구성은 참고하여 활용할 수 있는 것이지 절대적인 것은 아니다. 중요한 것은 계층별로 패키지를 분류한다는 것이다!
패키지 분류 법칙 4. 패키지 분류의 이점
패키지를 분류하지 않고 하나의 기본 패키지에 몽땅 집어넣으면서 설계하려는 사람은 최소한 자바프로그래머로서는 존재하지 않을 것이다. 근데 이런 분류에 너무 시간이 뺏기고 기본적인 개념이 잡혀있지 않다면 개발하면서 점점 코드가 엉키게 되고 서로 기능과 역할의 경계가 모호해짐과 동시에 실패한 코드가 되고 만다.
만약에 당신이 프로젝트가 시작함과 동시에 척척 패키지를 프로젝트 성격에 맞게 분류하고 곧바로 작업에 착수한다면 알만한 사람들은 당신이 어떤 모델계층을 사용하려 하고 있고 어떤 방식으로 개발하려는지 충분히 이해하고 있다 판단하며 당신을 작업방식을 신뢰할 것이다. 믿지 못하겠지만 매우 간단한 패키지 분류에서조차 얼마나 프로그래밍 실력이 있느냐가 갈리는 것이다.
현재 작성되고 있는 문서는 웹개발에 초점을 맞추어 작성되었지만 다른 소프트웨어 개발 프로젝트에서도 충분히 응용이 가능한 패턴이다. 다른 글에서도 재차 말했지만 초기세팅에 시간을 허비하는 것만큼 낭비가 없다. 만약에 모델의 세부계층에 대해 이해하지 못했다면 검색을 통해 지식을 쌓도록 하자. 모델계층의 계층분할은 곧 역할 분할로 이어지고 효율적이고 체계적인 프로그래밍을 가능케 해준다.