최근 회사에서 레거시 프로젝트 개선 작업이 한창이다. 특히 하나의 서버로 배포되는 코드들이 서로 다른 레포지토리에 존재한다는 이유 때문에 생산성이 크게 떨어지는 이슈가 있었다. 나의 사례를 공유하고 기록함으로써 개발자의 영원한 친구 레거시 프로젝트를 무찌르는데에 도움을 주고 한다.
회사에서는 네 개의 프로젝트가 기괴한 형태로 의존하고 있다. 이 기괴한 형태가 생산성을 갉아먹고 있었다. 이게 기존에는 네 개의 프로젝트가 서로 다른 서버로 운영되고 있었다고 한다. 그러니까 다른 팀에서 운영하던 서버를 우리 팀이 이관 받은 상황이고 과거에 어떠한 이유로 서버들을 합쳐서 서비스 하기로 의사결정이 되었고 그 과정에서 이렇게 기괴한 의존 형태가 탄생했다.
어쨋든 어떤 점이 기괴한지 차근차근 설명해보겠다. 일단 아래를 읽어보자.
- 네 개의 프로젝트들은 각자의 repository에 존재한다.
- 네 개의 프로젝트들을 빌드하기 위한 별도의 repository가 존재한다. (build repository로 부르겠다)
- build repository에서 네 개의 프로젝트들을 서브모듈로 참조하고 있다.
- A repository는 B, C, D repository를 import하고 있다.
여기까지를 그림으로 나타내면 아래와 같다
이런 형태에서는 작업 내용이 반영되려면 다음과 같은 과정이 필요하다.
- 각 repository에 작업 내용을 merge한다.
- build repository에 서브모듈을 최신 버전으로 반영하여 merge한다.
즉, 하나의 작업 내용을 반영하기 위해서 총 두번의 PR이 발생하고 네 명의 개발자에게 리뷰를 요청해야 한다. (우리 팀에서는 PR을 반영하기 위해서 2개의 승인이 필요하다)
여기까진 뭐 참을만 하다. 아직 끝나지 않았다. 위의 의존 관계에서 이런 의존 관계도 추가된다.
- 네 개의 프로젝트들은 팀 내에서 개발한
common_module.jar
를 의존한다.- common_module은 별도의 repository에서 개발되고 있다.
지금까지의 의존 관계를 그림으로 살펴보자.
common_module의 버전 업이 필요하다고 가정해보자. 그렇다면 다음과 같은 과정이 필요하다.
- common_moudle의 버전을 올려서 maven repository에 배포해야한다. PR이 한번 발생한다.
- 네 개의 프로젝트에 common_module의 최신 버전을 import 하도록 수정 한다. 총 네개의 PR이 발생하고 여기까지 PR이 5개 발생한다.
- 마지막으로 build repository에 네개의 서브모듈을 최신버전으로 올리면 PR까지 총 6개의 PR이 발생하며 12명의 개발자에게 리뷰를 받아야한다.
코드 한줄 추가하는데 12명의 리뷰어가 필요하다. 농담 아니다. 진짜 쒸리어쓰... 협업을 해본 개발자들은 알겠지만 리뷰를 받는다는 것은 참 힘든 일이다.
이런 과정이 너무 귀찮아서 팀 내부에서 common_module의 버전 업을 일부 레포지토리에만 반영하기도 했다.그러면 하나의 애플리케이션에는 서로 다른 버전의 common_module.jar가 존재하게 된다.
근데 필요하면 해야하는거 아닌가? 여러개의 프로젝트가 common_module 을 사용하는걸 뭐 어쩌란 말인가. 하지만 가장 중요한 사실이 있다.
이 네 개의 프로젝트는 결국 하나의 애플리케이션으로 빌드되어서 배포된다. 서로 다른 서버로 배포된다면 이 작업을 매번 해줘야하는게 맞겠지만 어차피 단일 애플리케이션이기 때문에 이렇게 할 필요가 전혀 없다고 판단했다.
그래서 하나의 repository로 합쳐야 한다는 상황을 팀원들에게 공유했고 작업을 시작했다.
repository를 합칠 때 각 repository들의 커밋 히스토리가 남아있길 원했다. 이런 요구사항을 만족하면서 repository를 합칠 수 있는 명령어가 존재했다.
바로 git subtree
명령어인데 서로 다른 repository를 하나로 통합할 수 있다. git hash 값들도 모조리 긁어온다. 심지어 명령어도 너무 간단하다. 명령어를 살펴보자
git subtree add --prefix {local directory being pulled into} \
{remote repo URL} \
{remote branch}
이게 끝이다. 실제 예제를 살펴보자
git subtree add --prefix ./src/main/java/com/vimrc \
https://github.com/newfivefour/vimrc.git \
master
이렇게 명령어만 입력하면 --prefix에 명시한 디렉토리에 우리가 명시한 repository의 명시한 branch 내용과 히스토리가 복사되어 들어온다.
이제 복사된 내용을 파일들을 이리저리 옮기고 수정하고 커밋 푸시하면 repository 통합 작업이 완료된다.
통합이 완료된 후의 의존 관계를 살펴보자
말 그대로 통합되었다. 이렇게 통합함으로써 얻은 이득은 다음과 같다.
물론 손해도 있다. 중복 코드가 많이 발생했다. 겹치는 클래스 이름이 많았는데 의미 없이 prefix를 붙여주는 등의 작업을 하며 또 다른 레거시를 생산해냈다. 중복 코드들을 common_module.jar 로의 이관 등의 리팩토링을 고려해야겠다.
단순히 파일과 커밋 히스토리 통합 자체는 너무 간단했다. 명령어 한줄이면 끝났기 때문이다. 통합 이후에 프로젝트를 실행 가능한 상태로 만들기 위해서 수 많은 에러와 중복 코드들을 마주했지만 ㅎㅎ 그것은 차근차근 디버깅해서 해결했다. 결론적으로 최악의 경우 6번 발생하던 PR을 한번으로 줄일 수 있었다. 너무 만족스럽다.
이번엔 subtree를 단순히 파일 복사와 커밋 히스토리 복사를 위해서 사용했다. subtree는 그 외에도 더 강력한 기능을 제공한다. 서브 모듈과 비슷한 기능을 제공하는데 subtree에 대한 자세한 내용은 별도의 포스팅에서 다루는 것으로 하자.
아무튼 최근에 레거시를 주로 다루다보니 레거시를 다루는 방법에 관심이 많아졌다. 여러 레거시를 다루다보면 다루기 쉬운 레거시도 있고 Git Blame 마려운 레거시도 있다. 레거시를 다루면서 느낀점은 레거시를 잘 다루는 법도 정말 중요하지만 다루기 쉬운 레거시를 만드는 것도 정말 중요한 것 같다.
잘 다루고 잘 만드는 개발자가 되고 싶다.