[코드가 환경을 모르는 구조 1/7] 코드는 무엇을, 환경은 어디서 - 다시 더 깊이

기술 블로그
페이스북링크드인트위터

본편 5화 이후 받은 질문들

"그 원리가 어떻게 배포, 인프라, 디버깅, 테스트까지 전부 관통하느냐."
본편 5화 "코드가 환경을 모르는 구조"를 공개한 뒤 블로그 댓글과 사내 채널, 외부 엔지니어의 DM으로 쏟아진 질문은 이 한 주제로 모였습니다.

질문을 정리하면 대체로 다섯 갈래였습니다.

첫째, Helm과 ArgoCD로 배포가 환경을 모르게 만드는 구조는 이해가 가는데, 값이 세 층으로 쌓이는 과정에서 어떤 재앙을 예방하는지 구체적인 예가 궁금하다.
둘째, "IaC에 헥사고날 아키텍처가 관통한다"는 문장이 인상적이었는데, Kotlin과 Pulumi로 그걸 어떻게 구현하는지 보고 싶다.
셋째, 타임머신과 Rewrite Host가 어떻게 "하나의 축"만 교체하는지 설계 수준에서 보여 달라.
넷째, Testcontainers 이야기가 너무 축약되어 있었는데, 컨테이너가 왜 폭발하며 어떤 기법으로 한 컨테이너에 담아내는지 알고 싶다.
마지막으로, 이 모든 것이 결국 하나의 원리라고 주장한다면 그 원리를 짧은 말로 다시 정의해 달라.

다섯 갈래는 한 질문으로 수렴합니다. "경계를 어디에 긋고, 그 경계 위에서 무엇을 교체 가능하게 만드는가." 이 한 문장이 서브시리즈의 출발점입니다.


한 문장으로 말하면

본편 5화에서 이 원리를 이렇게 요약했습니다.

코드는 "무엇을" 정의하고, "어디서"는 외부에서 주입한다.

문장은 간단하지만, 무엇이 "무엇을"이고 무엇이 "어디서"인지는 맥락마다 달라집니다. 레이어별로 한 줄씩 붙여 보면 이렇습니다.

  • 애플리케이션 코드 — 급여 계산 로직이 "무엇을", 그 로직이 꽂히는 DB 주소와 바라보는 시계가 "어디서".
  • 배포 파이프라인 — 배포 절차가 "무엇을", 어느 클러스터·네임스페이스에 내려가는지가 "어디서".
  • 인프라 코드 — VPC를 만들어 서브넷을 붙이는 코드가 "무엇을", 어느 클라우드·리전에 만드는지가 "어디서".
  • 디버깅 도구 — 비즈니스 로직이 "무엇을", 그 로직이 바라보는 시계와 대상 서비스 주소가 "어디서".
  • 테스트 — 테스트 시나리오가 "무엇을", 어느 컨테이너의 어느 스키마에 접속하는지가 "어디서".

레이어는 달라도 골격은 같습니다. 애플리케이션 코드에서 헥사고날 아키텍처가 Port와 Adapter로 경계를 긋듯, 배포·인프라·디버깅에서도 같은 모양의 경계가 반복됩니다.
"무엇을"은 계약과 의도로 남기고, "어디서"는 외부에서 꽂히는 값으로 남긴다 — 경계의 위치만 제대로 잡으면, 교체 가능성은 따라옵니다.


다섯 개의 축, 하나의 원리

이 원리가 겨냥하는 다섯 갈래는 "어떤 축을 교체할 수 있게 만들었는가"로 다시 쓸 수 있습니다. 각 축은 현장에서 겪는 고통 하나씩에 대응합니다.

1. 배포 축 — dev 스크립트와 prod 스크립트가 어느새 달라져, 장애가 나고서야 차이를 발견하는 고통. Helm values의 3단 오버라이드와 ArgoCD의 App-of-Apps 패턴이 같은 템플릿 위에 환경별 값만 층층이 쌓도록 강제합니다. 교체되는 것은 "이 앱이 어느 클러스터·네임스페이스에 올라가는가"입니다.

2. 클라우드 축 — 멀티 클라우드를 지원한다고 말하지만 코드가 사실상 AWS 전용이어서, NCP 고객이 들어오면 6개월이 사라지는 고통. flex-terraform이 VirtualNetwork·KubernetesCluster·WorkloadIdentity 같은 Port를 두고 aws/ncp/azure 모듈을 Adapter로 꽂으면, 클라우드 자체가 갈아 끼워집니다. 교체되는 것은 "이 리소스가 어느 클라우드에서 만들어지는가"입니다.

3. 시간 축 — "내년 1월 1일에 급여 규정이 바뀌는데 미리 검증해 달라"는 요청이 들어오면, 서버 시계를 돌리거나 테스트 DB를 뜯는 수밖에 없는 고통. 비즈니스 로직이 시스템 시계 대신 Clock 인터페이스에서 시간을 얻는 규율만 지키면, 디버그 헤더 하나로 "지금"을 과거·미래로 밀 수 있습니다. 교체되는 것은 "지금이 몇 시인가"입니다.

4. 공간 축 — dev 환경의 수십 개 서비스를 로컬에 못 띄우니, 내가 고친 한 서비스만 꽂아 보려면 전체를 포크해야 하는 고통. Gateway가 디버그 헤더를 보고 특정 호스트 하나만 로컬 주소로 치환하면, 나머지는 그대로 둔 채 한 서비스만 교체됩니다. 교체되는 것은 "이 요청이 어느 주소로 가는가"입니다.

5. 테스트 축 — Spring 통합 테스트가 모듈마다 Testcontainer를 새로 띄워 CI가 30분씩 걸리는 고통. 컨테이너를 빌드 수준 싱글턴으로 올려두고 모듈별로 논리 스키마만 새로 만들면, 실제 DB 신뢰도를 유지하면서도 시간이 돌아옵니다. 교체되는 것은 "이 모듈이 어느 스키마를 쓰는가"입니다.

다섯 축이 놓인 레이어는 제각각입니다. 쿠버네티스 매니페스트, Pulumi 스택, Servlet 필터, Gradle 플러그인 — 도구가 전혀 다른데도, 설계의 모양은 놀랍도록 같습니다. 계약이 가운데에 있고, 주변에 갈아 끼울 수 있는 구현이 있고, 어느 구현을 쓸지는 호출자가 결정합니다.
이 시리즈는 그 다섯 축을 한 편씩 꺼내, 왜 그런 경계가 필요했는지·어떻게 풀었는지·무엇이 달라졌는지를 따라갑니다.


경계를 깎는다는 것

경계가 "있다"고 선언하는 것깎는 것은 다릅니다. 선언은 문서와 구두 약속으로 충분합니다. 깎는다는 건 컴파일러가 잡아주고, 빌드 파이프라인이 강제하고, 타입 시스템이 거부할 수 있는 수준으로 경계를 구조에 새겨 넣는다는 뜻입니다.

차이는 현장에서 이렇게 드러납니다.

  • 경계가 선언으로만 있을 때 — dev 파이프라인이 prod와 슬그머니 달라져, 리뷰어가 두 파일을 눈으로 비교해야 차이를 잡아냅니다. 누군가 깜빡하면 그대로 새어 나갑니다.
  • 경계가 구조에 새겨졌을 때 — 같은 차이가 diff 한 줄, 혹은 빌드 실패 한 번으로 드러납니다. 사람의 주의력에 기대지 않습니다.

이 시리즈에서 반복해 등장할 설계 양식이 Port/Adapter 단면입니다. 가운데에 인터페이스(Port)가 있고, 한쪽에는 그것을 호출하는 도메인이, 다른 한쪽에는 그것을 구현하는 Adapter들이 놓입니다. 도메인은 Adapter를 모르고, Adapter는 도메인을 끌어당기지 않습니다. 둘은 오직 Port를 거쳐 만납니다.

이 그림 자체는 많은 엔지니어에게 익숙할 겁니다. flex에서 다른 점은, 같은 그림이 애플리케이션 코드 안쪽에만 머무르지 않고 배포·인프라·디버깅·테스트 인프라까지 다섯 레이어에 걸쳐 반복된다는 점입니다. Helm values 구조, Pulumi Kotlin 코드, Gateway 필터, Gradle BuildService — 전혀 다른 도구들이 같은 판단 기준을 따릅니다. 그 판단 기준을 다섯 번 성립시키는 일이 "깎는다"는 말의 실제 내용입니다.

결과는 두 가지로 돌아옵니다. 경계가 분명하니 한쪽을 수정해도 다른 쪽이 깨지지 않고(안정성), 경계가 교체 가능한 형태로 열려 있으니 Adapter 하나만 바꿔 실험할 수 있습니다(속도). 본편에서 "이건 엔지니어의 이터레이션 속도에 직접 영향을 준다"고 맺은 이유가 여기에 있습니다.


다음 편부터, 다섯 개의 고통

이 서브시리즈가 겨냥하는 것은 다섯 개의 고통입니다. 각 고통은 현장에서 모두가 한 번쯤 겪었지만, 해결의 모양이 서로 닮았다는 사실은 놓치기 쉬운 것들입니다.

  • dev 스크립트와 prod 스크립트가 어느 순간 조용히 갈라져 있던 날.
  • "멀티 클라우드 지원"이라는 한 줄이 실제로는 6개월짜리 포팅으로 돌아오던 순간.
  • "내년 1월 1일 급여 규정을 미리 검증해 달라"는 한 마디 앞에서, 서버 시계를 돌릴지 테스트 DB를 뜯을지 고민하던 오후.
  • 수십 개 서비스가 엮인 dev 환경에서, 내가 고친 한 개만 꽂아 보려다 전체를 포크하게 되던 저녁.
  • 같은 Testcontainer가 모듈마다 새로 뜨느라 CI가 30분을 먹던 밤.

다음 편부터 이 다섯을 하나씩 꺼냅니다. 각 편에서 던질 질문은 같습니다. 경계를 어디에 그을 것인가. 그 경계 위에서 무엇을 "무엇을"에 남기고, 무엇을 "어디서"로 밀어낼 것인가. 같은 질문이 다섯 번 반복되는데 도구는 매번 다릅니다 — Helm values, Pulumi, Servlet 필터, Gradle 플러그인. 같은 질문에 도구마다 다른 대답이 나온다는 점, 그리고 그 대답들이 끝에 가서 하나의 모양으로 포개진다는 점이 이 시리즈의 뼈대입니다.

다음 화에서는,
첫 번째 고통. dev와 prod가 슬그머니 갈라져 있던 이야기로 내려갑니다. 같은 템플릿 위에 환경별 값만 층층이 쌓는 일이 왜 "선언"이 아니라 "강제"여야 하는지, 그 강제를 빌드 파이프라인에 새기는 일이 실제로 어떻게 생겼는지 — 거기서 시작합니다.

🚀플렉스팀 채용페이지 바로가기☕flex Private Talk 신청하기
글이 마음에 드셨나요?
공유하기
페이스북링크드인트위터
flex가 궁금하다면? 지금 무료로 체험해 보세요
flex가 궁금하다면? 지금 무료체험하기
  • [flex update] 실제 출퇴근 기록 저장·관리 기능 추가, 원온원 노트 구조 개선, Open API 기능 업데이트
    안녕하세요, 플렉스팀입니다. 4월 넷째 주 flex 제품 업데이트 소식을 안내해 드려요.
  • 아티클
    2020. 5. 25
    근태관리, 유연근무제, 그리고 코로나 시대
    코로나, 뉴노멀, 유연근무제, 그리고 근태관리