모든 글

한 프로젝트 안에서 실행 역할별 배포 경계 만들기

공개 API, 어드민, 배치, 운영 작업을 실행 앱으로 분리하되 코드가 충분히 성숙하기 전부터 별도 프로젝트로 쪼개지 않는 방법을 설명합니다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

프로젝트는 코드의 경계이고 실행 앱은 실행의 경계입니다. 둘을 같은 것으로 취급하면 코드가 성장하기 어려워지고, 성격이 다른 작업을 아무 이유 없이 같은 서버에서 실행하게 됩니다.

싱글 모듈 프로젝트에서는 실행 앱 하나가 나오는 경우가 많습니다. 그래서 프로젝트당 실행 앱도 하나라고 느끼기 쉽습니다. 하지만 그것은 여러 구성 중 하나일 뿐입니다. 한 프로젝트 안에 진입점과 운영 역할이 다른 실행 앱을 여러 개 둘 수 있습니다. 프로젝트에 실행 앱이 몇 개여야 하는지를 묻기보다 코드와 작업 부하가 응집도 있게 묶였는지를 봐야 합니다.

코드는 함께 두고 실행은 분리하기

코어 API, 어드민 API, 어드민 배치를 운영하는 서비스를 생각해 봅시다. 셋이 성숙한 하나의 비즈니스 맥락을 공유하고 함께 변경된다면 같은 코드베이스에 속할 수 있습니다. 그렇다고 하나의 프로세스로 실행해야 하는 것은 아닙니다.

프로젝트 안에 각 역할을 실행 가능한 모듈로 둘 수 있습니다.

  • 코어 API는 제품 트래픽을 처리합니다.
  • 어드민 API는 관리 요청을 처리합니다.
  • 배치 앱은 예약되거나 필요할 때 수행하는 작업을 처리합니다.

배포 시스템은 각 실행 대상을 따로 빌드하고 시작합니다. 배치는 필요할 때만 실행할 수 있고, 두 API는 별도 서버로 계속 운영할 수 있습니다. 저장소의 응집은 유지하면서 모든 작업 부하를 한 런타임에 몰아넣지 않는 구조입니다.

어떤 영역을 별도 프로젝트로 떼어 내기 전 단계로도 쓸 만합니다. 서버가 하나 더 필요할 때마다 새 저장소나 최상위 프로젝트를 만들면 코드가 독립할 만큼 성숙하기 전에 강한 구조적 경계를 먼저 만듭니다. 실행 모듈은 지금 필요한 실행 경계를 제공하면서 더 큰 소스 경계는 되돌릴 수 있게 남겨 둡니다.

물론 실행 앱이 너무 많으면 그 자체가 운영 대상이 됩니다. 개수를 늘리는 것이 목적은 아닙니다. 역할과 부하, 실패 양상이 독립된 런타임을 정당화할 때 나누어야 합니다.

여기서 실행 앱을 여러 개 둔다는 말은 모든 앱을 한꺼번에 띄운다는 뜻도 아닙니다. 코어 API와 어드민 API는 각각 서버로 계속 실행하고, 어드민 배치는 필요한 시점에만 구동할 수 있습니다. 같은 프로젝트에서 빌드되더라도 실행 시점과 배포 대상은 서로 다릅니다. 이 차이를 분명히 해야 소스가 한곳에 있다는 이유로 운영 생명주기까지 하나로 묶는 실수를 피할 수 있습니다.

배포 방식이 소스 구조를 결정할 필요는 없다

배포 시스템이 프로젝트 하나에 앱 하나를 전제한다고 생각해서 그 형태를 유지하는 팀도 있습니다. 하지만 둘을 강하게 묶을 필요는 없습니다. 배포 작업에는 어느 소스 셋을 빌드하고 어떤 산출물을 실행할지만 알려 주면 됩니다.

나중에 어드민 API와 어드민 배치가 충분히 성장해 독립 프로젝트가 됐다고 가정해 봅시다. 배포에서의 정체성은 그대로 둘 수 있습니다. 빌드 대상만 기존 프로젝트의 모듈에서 새 프로젝트의 소스 셋으로 바꾸면 됩니다. 운영 경계가 이미 있었기 때문에 배포 설계를 처음부터 다시 만들 필요가 없습니다.

이 방식은 강한 분리를 서두르지 않고 코드가 성숙할 시간을 줍니다. 처음에는 관련 기능을 한 프로젝트와 내부 모듈에 둡니다. 소유권, 의존성, 변경 양상이 분명해지면 모듈을 바깥으로 옮깁니다. 배포 경계는 두 구조에서 모두 유지됩니다.

이동할 때 배포 설정 전체를 갈아엎을 필요도 없습니다. 배포 이름과 운영 역할은 유지하고 빌드가 바라보는 소스 위치만 바꾸면 됩니다.

따라서 프로젝트 수로 실행 앱 수를 정해서는 안 됩니다. 프로젝트와 모듈은 소스를 구성하고, 실행 앱은 실행을 구성합니다. 서로 영향을 주지만 하나가 다른 하나를 기계적으로 결정하지는 않습니다.

위험한 내부 작업을 격리하기

실행 앱을 나눠야 하는 운영상의 이유는 내부 API에서 더 선명하게 보입니다. 대량 갱신이나 대량 업로드를 위한 엔드포인트를 추가한다고 해봅시다. 이런 작업은 CPU를 많이 쓸 수 있습니다. 공개 코어 API에 함께 넣으면 내부 사용자가 요청을 잘못 보낸 것만으로 자원을 소진해 고객 트래픽까지 멈출 수 있습니다.

코어 API에 /operations 같은 경로를 두면 URL은 나뉘지만 실패는 나뉘지 않습니다. 두 경로가 같은 프로세스와 서버 자원을 사용하기 때문입니다. 별도의 운영 앱을 만들면 실제 런타임 경계가 생깁니다. 다른 서버에 배포하고, 다른 자원을 할당하며, 코어 API에 손대지 않고 중지하거나 확장할 수 있습니다.

어드민과 배치 작업에도 같은 판단을 적용할 수 있습니다. 공개 API와 사용자, 트래픽 모양, 실패 비용이 다릅니다. 비즈니스 코드는 가까이 두는 편이 응집에 도움이 될 수 있지만 실행을 나누면 한 역할의 문제가 다른 역할을 해치지 않습니다.

분리한 뒤에는 배포 단위별로 무엇을 관찰하고 복구할지도 정해야 합니다. 운영 API가 자원을 많이 썼을 때 코어 API와 무관하게 중단할 수 있는지, 배치 실패가 공개 요청 처리에 영향을 주지 않는지 확인합니다. 실행 경계는 디렉터리 이름이 아니라 장애가 났을 때 독립적으로 대응할 수 있을 때 의미가 있습니다.

지금의 문제를 푸는 경계를 그리기

지금까지 본 프로젝트마다 실행 앱이 하나였다면 한 프로젝트 안의 여러 실행 앱이 어색할 수 있습니다. 익숙함은 아키텍처 제약이 아닙니다.

책임과 운영 위험에서 시작해야 합니다. 같은 맥락에서 함께 진화하는 코드는 가까이 둡니다. 독립된 생명주기, 자원 예산, 실패 경계가 필요한 작업은 별도 실행 앱으로 만듭니다. 소스 자체가 독립 단위로 성숙했을 때만 새 프로젝트로 분리합니다.

이렇게 하면 모든 역할을 한 프로세스에 몰아넣는 극단과 서버 하나가 필요할 때마다 프로젝트를 늘리는 극단을 모두 피할 수 있습니다. 소스 구조는 응집도를 따르고, 배포 구조는 실행의 현실을 따르게 됩니다.