일기 slecs

결제대행사 연동 프로젝트 첫 커밋을 가볍게 시작하는 법

목차

결제대행사 연동 솔루션 신규 프로젝트의 첫 삽을 떴음. 빈 디렉토리에 그래들 래퍼 뜯어 넣고, .gitignore 깔고, 빌드 스크립트 골격만 박아두는 작업. 겉보기엔 별 게 없는데 이 단계에서 매번 30분-1시간씩 까먹는다. 오늘도 딱 그랬음.

프로젝트 성격상 이번엔 더 신경이 갔다. 결제 플랫폼 내부에서 여러 결제대행사 연동을 표준화하는 솔루션이라 의존성이 빠르게 불어날 게 뻔한 구조임. 결제대행사 SDK, 암호화·서명 라이브러리, 공통 에러 핸들러, 감사 로그 모듈... 몇 주만 지나면 build.gradle이 길어지고 도메인 코드도 붙기 시작하는데, 그 전에 빌드 환경 자체를 잘 잡아놔야 나중에 "내 로컬에선 되는데"류 디버깅이 줄어든다.

래퍼부터 박는 이유

팀에 합류하는 사람마다 로컬 그래들 버전이 다르면 빌드 결과도 달라짐. 래퍼 jar/properties 같이 커밋하면 클론하자마자 동일한 버전이 자동으로 내려와서 환경 차이가 사라짐.

핵심은 gradle/wrapper/gradle-wrapper.properties 파일 한 줄이다.

distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

이 줄 하나가 팀 전체 그래들 버전을 고정시킨다. CI 서버도, 신입 맥북도, 윈도우 쓰는 팀원도 ./gradlew build 한 번이면 같은 버전 내려받음. 버전 올릴 때도 이 파일 수정 + PR 하나로 히스토리 추적까지 되니까 관리가 편함.

래퍼 없이 각자 로컬 그래들 쓰다가 생기는 문제는 대개 이런 패턴이다.

  • 그래들 버전 간 플러그인 호환성 차이로 빌드 실패
  • 8.x에서 deprecated된 문법이 7.x에선 그냥 통과돼서 CI에서 갑자기 터짐
  • 신규 팀원이 온보딩 첫날 "저는 빌드가 안 되는데요"로 시작하는 상황

세 번째가 가장 아프다. 본인 잘못도 아닌데 첫날부터 막히면 팀 분위기가 쓸데없이 가라앉음. 온보딩 경험이 좋아야 초반 속도가 나는데, 그 입구에서 삽질하면 동기부여도 같이 깎인다.

그래들 버전 선택 기준으로는 사용할 스프링부트 버전 호환 매트릭스를 먼저 보는 게 낫다. 스프링부트 3.x는 그래들 7.5 이상을 요구하고 최신 플러그인들은 8.x를 권장하는 경우가 많아서, 8.x 중 안정된 버전 잡는 게 요즘 관례가 됐음. 래퍼를 쓰면 이 선택 자체도 properties 파일 한 줄로 기록이 남는다는 게 부수 효과로 좋다.

.gitignore는 선제 방어

.gitignore 없이 시작하면 IDE 설정 파일이나 빌드 산출물이 git status에 슬쩍 끼어들기 시작함. 문제는 한 번 커밋된 파일은 히스토리에서 빼기 까다롭다는 거다. git rm --cached로 추적을 끊어도 히스토리엔 남고, 팀원 전체가 pull 한 번 더 해야 하는 상황이 생김. 이미 커밋된 쓰레기를 정리하는 커밋을 또 만들게 되는데, 그게 PR에 섞이면 diff 읽기가 지저분해진다.

스프링+그래들 조합에서 첫 커밋에 챙겨야 할 무시 대상은 이 정도다.

무시 대상 이유
.gradle/, build/ 재생성 가능, 용량 큼
.idea/, *.iml, .vscode/ 사람마다 IDE 설정 다름
*.env, application-local.yml 비밀값 유출 방지
*.log, logs/ 로컬 실행 흔적
out/ IntelliJ 별도 빌드 출력 디렉토리

gitignore.io 같은 생성 도구가 편하긴 한데, 생성 결과를 그대로 갖다 붙이면 프로젝트 성격에 안 맞는 패턴까지 딸려오는 경우가 있다. 전에 Node.js용 패턴이 섞여 들어온 걸 나중에야 발견한 적 있음. 도구 쓰더라도 한 번은 눈으로 훑는 게 맞다.

결제 도메인은 application-local.yml이나 .env류 파일 관리가 특히 민감하다. 로컬 테스트용 API 키, 샌드박스 시크릿, 웹훅 서명 키 같은 게 들어가는 파일인데, 이게 public 리포지토리에 단 한 번이라도 커밋되면 키 전체를 갱신(rotation)하는 게 답이다. 비용은 둘째치고 그 과정에서 여러 팀이 얽히게 됨. 처음부터 막아두는 게 훨씬 낫다.

빌드 스크립트는 뼈대만

의존성이 빠르게 불어날 게 뻔하기 때문에 첫 커밋에는 플러그인 정의와 의존성 블록 형태만 잡고, 실제 라이브러리는 일부러 비워뒀음.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}

dependencies {
    // 모듈별로 분리 예정. 여기는 공통 베이스만.
}

이렇게 하면 다음 커밋에서 라이브러리 하나 추가할 때 diff가 깔끔하게 의도만 드러난다. 처음부터 한 방에 다 때려박으면 나중에 누가 뭐 왜 추가했는지 추적이 안 됨. "initial commit"이라는 메시지 뒤에 300줄짜리 diff가 달려있는 리포지토리를 몇 번 경험하고 나면 절대 그렇게 하고 싶지 않아짐.

멀티모듈로 갈 가능성이 있으면 settings.gradlerootProject.name만 박고 서브모듈 추가는 다음 커밋으로 미루는 게 낫다. 도메인 설계 논의가 끝나지 않은 상태에서 모듈 구조까지 첫 커밋에 잡으려다 보면, 설계가 흔들릴 때 빌드 파일까지 같이 뒤집어야 한다. 구조 결정과 코드 작성을 같은 커밋에 묶지 않는 게 원칙이라 생각함.

플러그인 버전을 첫 커밋에 명시해두는 것도 의도적이다. 버전 없이 시작하면 나중에 "이 플러그인 버전 뭐야?"가 build.gradle 한 파일에서 답이 안 나와서 플러그인 메타데이터 뒤지게 됨. 선언적으로 버전까지 박아두면 그 파일이 프로젝트 환경의 단일 진실 출처가 된다.


초기 커밋은 가벼울수록 좋다는 걸 다시 확인하는 날이었음. 폴더 구조와 빌드 도구 버전 합의만 끝내고, 실제 코드는 다음 커밋부터. 첫 커밋이 뚱뚱하면 그 안에 숨은 결정들이 커밋 메시지 한 줄에 통째로 묻혀버린다. 며칠만 지나도 왜 그렇게 했는지 본인도 기억 못 함.

총괄 팀장 포지션에서 이 습관이 팀 커뮤니케이션 비용에 직결된다는 걸 더 실감하는 편인데, 리뷰어 입장에서 컨텍스트 없는 묵직한 첫 커밋을 받는 것만큼 답답한 게 없다. 10줄짜리 커밋 30개가 300줄짜리 커밋 하나보다 무조건 낫다. 오늘 작업이 짧고 심심해 보여도, 다음 커밋부터 붙을 코드들이 깔끔하게 분리될 수 있는 기반이 됐다고 생각함.

댓글 0

첫 댓글 달아줘.