flex가 사내 라이브러리 버전을 "되도록 최신"으로 유지하기 위해 만든 version family 규칙과, 그 단순한 규칙이 대부분의 조직에서는 왜 서지 못하는지를 다룬다. 개별 레포가 버전을 고르는 대신 group name에 소속되면 그 family의 검증된 최신 버전이 기본값으로 따라오고, 같은 family는 함께 움직이며 아티팩트는 위에서 아래로만 흐른다. 글의 무게는 구현이 아니라 이 규칙이 서기 위해 필요한 땅, 그리고 그 땅을 세울 때 부딪히는 세 가지 문제에 있다.
핵심 포인트- 진짜 고통은 "어느 버전 써요?"가 반복되는 것이 아니라, 지금 써도 되는 버전인지 답해줄 권위 있는 출처가 어디에도 없다는 것이다.
- 봇은 PR을 만들어 주지만 그 조직의 검증 상태까지는 알지 못한다. 판단 근거가 레포마다, 태그마다, 슬랙 스레드마다 흩어져 있다.
- 문제 1은 닭과 달걀이다. 버전 규칙을 강제하려면 모든 레포가 같은 빌드 전략을 공유해야 하는데, 그 동형성 자체가 강제 장치 없이는 유지되지 않는다.
- 문제 2는 비가역성이다. 예측 가능성과 비가역성은 같은 동전의 양면이라, 한 점에 응집시켰다는 것은 그 한 점의 실수가 곧 전 조직의 동시 장애라는 뜻이다.
- 문제 3은 인식이다. 성공의 증거가 고통의 부재라는 형태로만 남고, 없어진 고통은 아무도 기억하지 않는다.
상세 정리- 규칙의 모양: 개별 레포가 어느 버전을 박아둘지 고르는 대신 group name에 소속되면 그 family의 검증된 최신 버전이 기본값으로 따라온다. 고를 선택지 자체가 없어지면서 버전 질문이 대부분 사라진다.
- 구현은 사내 Gradle 플러그인이지만, 저자는 이 규칙이 자기 조직에서 어렵지 않은 이유가 플러그인의 영리함이 아니라 그 아래 필요한 조건이 이미 갖춰져 있어서라고 못박는다.
- 문제 1의 구조: group name 하나로 버전·릴리스·태깅·게시 규칙을 전부 유도하려면 규칙을 적용받는 모든 레포가 같은 빌드 시스템 위에 있어야 한다. 어떤 레포는 Gradle, 어떤 레포는 Maven, 어떤 레포는 자체 스크립트면 정책을 해석하고 강제할 단일 지점이 없다.
- 이질성의 출처: 대개 잘못이 아니라 성장의 흔적이다. 빠르게 크던 시기에 각 팀에 편한 방식으로 빠르게 가라며 부여한 자율성이 팀마다 다른 빌드 방식과 버전 관례, 게시 규칙으로 굳었다. 속도를 위해 옳았던 결정이 규모가 커진 뒤 부채로 청구된다.
- 대부분의 시도가 죽는 자리도 여기다. 빌드가 제각각인 곳에 family를 얹으면 강제할 지점이 없어 일부 레포에만 걸치고 반쪽짜리 규칙은 흐지부지된다. 순환의 어느 고리를 먼저 끊을 것인가가 플러그인을 짜는 일보다 훨씬 어려운 진짜 문제라는 지적이다.
- 문제 2의 전제: 내부 아티팩트의 흐름이 단방향으로 정렬돼 있어야 한다. 공통 라이브러리가 게시되면 서비스가 소비하고 그 반대는 일어나지 않으며 전체 의존이 순환 없는 하나의 DAG를 이룬다. 이건 플러그인이 만들어 주는 성질이 아니라 의존성을 설계할 때부터 지켜 온 제약이다.
- 그래서 "버전 전파를 자동화할 수 있는가"는 사실 "의존이 단방향인가"에 달려 있다. 흐름이 한 방향이면 배포 순서를 기계가 계산할 수 있지만, A를 올리려면 B가 먼저이고 B를 올리려면 A가 먼저인 교착이 섞이면 계산 자체가 성립하지 않는다.
- 응집의 대가: 각 레포가 자기 버전 규칙을 갖는 자유를 내려놓은 대신 조직 전체가 하나의 규칙 위에서 움직이게 됐고, 이 대가는 늦게 청구된다. 수백 개 아티팩트가 이미 그 규칙 위에서 돌기 시작하면 group name policy의 층위를 다시 긋거나 family 경계를 재설계하거나 게시 규칙을 손보는 일이 전 레포에 동시에 영향을 준다.
- 이 문제는 도입할 때가 아니라 성공하고 한참 뒤 무언가를 바꿔야 할 때 모습을 드러낸다고 짚는다.
- 문제 3의 구조: 새 기능은 데모할 수 있고 성능 개선은 그래프로 보여줄 수 있지만 "원래 있었어야 할 마찰이 처음부터 없는 상태"는 보여줄 대상이 없다. 설득하는 사람은 아직 아무도 절실하게 느끼지 않는 미래의 고통을 근거로 현재의 투자를 요구해야 하고, 일이 잘 끝나면 무엇을 막았는지조차 증명하기 어렵다.
- 저자는 조직들이 이 문제를 미뤄둔 것이 판단력 부족이 아니라 난이도를 정확히 알기 때문에 내린 합리적 유보였을 것이라고 보되, 그 유보의 비용은 지금도 조용히 청구되고 있다고 덧붙인다.
- family가 필요해진 구조: 한 레포 안에 여러 모듈을 두고 각각을 별도 아티팩트로 배포한다. 헥사고날을 따르니 도메인 하나에서 배포 대상이 여럿 나오고, family가 없으면 같은 레포 안 모듈끼리도 버전이 어긋나 소비자가 "이 모듈 3.1과 저 모듈 2.7이 같이 써도 되는 조합인가"를 매번 확인해야 한다.
- 더 정확한 구조는 레포 : 도메인 : 모듈이 1 : N : M이다. 한 백엔드 레포가 열 개가 넘는 독립 도메인을 담고 각 도메인이 다시 모델·유스케이스·어댑터·저장소 같은 헥사고날 계층을 별도 모듈로 갖는다.
- 헥사고날 경계를 패키지 네이밍이나 컨벤션이 아니라 각각의 Gradle 모듈로 쪼개 물리 경계로 강제했다. 의존 방향을 사람의 규율이 아니라 빌드가 막게 한 선택이다.
- 폴리냐 모노냐의 구분보다 중요한 것: 레포 하나에 서비스 하나인 폴리레포도 아니고 전 조직이 한 레포에 든 모노레포도 아니다. 핵심은 버전 관리의 단위를 물리적 레포 경계에서 떼어냈다는 점이다.
- family를 묶는 기준이 레포가 아니라 group name이라, 한 레포 안에 서로 다른 family가 공존할 수도 있고 여러 레포에 걸친 아티팩트가 한 family로 묶일 수도 있다. "레포=버전 단위"라는 암묵적 등식을 끊어둔 덕에 레포가 어떻게 쪼개지든 버전 체계가 흔들리지 않는다.
- 폴리냐 모노냐의 논의가 레포 배치에서 멈추면 정작 버전 단위가 무엇이어야 하는지는 설계 밖에 남기 쉽다는 지적으로 마무리한다.
왜 읽나여러 레포에 흩어진 사내 라이브러리 버전을 통제하려다 매번 절반에서 멈춘 플랫폼 팀이, 도구를 만들기 전에 무엇이 갖춰져 있어야 하는지와 응집의 대가를 미리 가늠할 수 있다.