EP.2 - 디렉토리는 공간이다

Ep.2 - 디렉토리는 공간이다

by Lee

건축가가 도면을 펼쳐 건물을 설계하듯이, 프로그래머는 폴더와 파일로 이루어진 디렉토리 구조를 설계합니다. 코드의 디렉토리 하나하나는 마치 건물의 방과 같고, 여러 디렉토리가 모여 거대한 건축물을 이루기도 합니다. 잘 조직된 코드 구조는 건축물의 청사진처럼 프로젝트의 윤곽을 보여주며, 이는 곧 유지보수와 확장에 큰 영향을 미칩니다. 이번 장에서는 **“디렉토리는 공간”**이라는 화두를 따라, 소프트웨어 디렉토리 구조를 건축학적 은유로 풀어보겠습니다. 작은 방 하나짜리 프로젝트에서 시작해, 복잡한 도시와 같은 대규모 코드베이스에 이르기까지—공간으로 비유한 다양한 코드 구조의 세계를 여행해봅시다.


들어가며: 공간으로 사고하는 프로그래밍

프로그래밍을 처음 배울 때, 우리는 파일 몇 개로 시작하는 작은 프로젝트를 접합니다. 모든 코드가 한 폴더에 뒤섞여 있어도 문제를 느끼지 못할 만큼 규모가 작지요. 그러나 프로젝트가 자라고 팀원이 늘어나면, 코드는 급격히 복잡해지고 구조의 중요성이 대두됩니다. 어떤 파일이 어디에 있는지 한눈에 파악하기 어려워지면 마치 창고에 물건을 잔뜩 쌓아 둔 방처럼 혼란에 빠집니다. 이때 필요한 것이 디렉토리 구조를 통한 체계적인 정리입니다. 프로그래머는 코드를 배치할 공간을 나누고 이름 붙이며, 각 공간의 역할을 정의해야 합니다. 이는 건축가가 건물의 용도별로 공간을 설계하는 작업과 닮아 있습니다.


잘 짜인 디렉토리 구조를 만들면 프로젝트 규모가 커져도 유지보수가 수월합니다. 새로운 기능을 추가하려 할 때, 관련된 코드를 어디에 두어야 할지 명확하고, 기존 기능을 수정할 때도 어느 파일을 건드려야 할지 금세 찾을 수 있지요. 구조가 잡혀 있다면 파일과 폴더가 아무리 많아져도 쉽게 찾고 수정할 수 있다는 경험담은 많은 개발자들이 공감하는 부분입니다. 결국 디렉토리 구조에 대한 감각은 코드를 공간적으로 바라보는 능력이며, 이는 곧 프로그래머의 사고를 한 단계 성장시켜 줍니다.


이제, 하나의 프로그램을 건축 공간에 비유하며 그 구조를 살펴보겠습니다. 예시로 들 여러 가지 폴더 구조들은 글 마지막에 부록으로 정리해 두었으니, 흐름을 따라가며 필요한 경우 참고하시기 바랍니다.


장면: 작은 방 - 단일 폴더 프로젝트

어느 조용한 저녁, 한 신입 개발자가 책상 앞에 앉아 처음 만드는 프로그램에 열중하고 있습니다. 그의 컴퓨터 화면 속 프로젝트는 작은 방 하나처럼 보입니다. 방 안에는 하나의 폴더가 있고, 그 안에 몇 개의 파일이 나란히 들어있습니다. 컴퓨터 안에 지어진 이 자그마한 방은 이 신입 개발자의 아이디어로 채워진 첫 작품이지요. 방 한구석에는 main이라는 이름의 파일이 책상처럼 놓여 있고, 옆에는 helpers라는 이름의 작은 서랍장이 보입니다. 방은 비록 협소하지만 필요한 것은 모두 손이 닿는 거리에 있습니다. 한눈에 모든 코드가 보이고, 머릿속 생각이 곧바로 이 방 안의 물건(파일)들로 대응됩니다.


나레이션: 1인용 방의 장단점

단일 폴더로 이루어진 프로젝트는 원룸과도 같습니다. 규모가 작을 때는 오히려 이 편이 효율적입니다. 파일 간 이동이 잦지 않고, 모든 로직이 한 곳에 모여 있으니 작은 프로젝트에서는 개발 속도가 빠릅니다. 예컨대 간단한 파이썬 스크립트나 학부 과제 수준의 프로그램이라면 굳이 여러 폴더로 나누지 않고

main.py, functions.py정도로만 관리해도 충분합니다. 이 1인용 공간은 당장 개발이 쉬운 소규모 프로젝트에 용이하다는 장점이 있습니다.


하지만 규모가 커지기 시작하면 단점이 드러납니다. 방 한 칸에 살림살이가 너무 많아지면 금세 어질러지는 것처럼, 폴더 하나에 코드가 비대해지면 유지보수가 어려워집니다. 작은 수정 하나를 위해 이리저리 흩어진 코드를 모두 살펴야 하고, 서로 관련 없는 기능들까지 엉켜 있을 수 있습니다. 실제로 모놀리식 아키텍처(Monolithic Architecture)라고 불리는 옛날 방식의 거대한 단일코드베이스에서는, 일부 오류만 발생해도 전체 서비스가 중단될 위험이 있고, 작은 변경에도 전체를 다시 빌드/배포해야 하는 문제가 있었지요. 즉, 응집도(cohesion)가 낮고 결합도(coupling)가 높은 구조가 될 위험이 있습니다. 프로그래머는 곧 깨닫습니다. 더 큰 프로젝트를 위해서는 방을 나누거나, 새로운 방을 만들어나가야 한다는 것을요.

[예시 1] 디렉토리 및 파일 구조 출처: https://fomerain.tistory.com/406 [모두 이루지리라 얍!:티스토리]


장면: 방이 여러 개인 집 - 컴포넌트 기반 프론트엔드

며칠이 지나, 우리의 개발자는 새로운 웹 애플리케이션을 만들기로 합니다. 이번에는 규모가 좀 더 커져서, 화면도 여러 개이고 재사용할 UI 요소들도 있습니다. 그는 마치 여러 방을 가진 집을 설계하듯 프로젝트 구조를 구상합니다. 현관에 해당하는 index.js(진입 파일)를 통해 앱에 들어서면, 거실 격인 App.js가 전체 구조를 잡고 있습니다. 복도를 따라가면 거실, 주방, 서재처럼 용도에 따라 구분된 방들이 나타납니다. 실제 폴더로 비유하자면 components/ 폴더에는 곳곳에서 쓰이는 버튼이나 입력창 같은 공용 가구들이 정리되어 있고, pages/ 폴더에는 Home, About 등 각 방마다의 역할을 담당하는 페이지 컴포넌트들이 놓여 있습니다. 거실 벽에는 Header와 Footer라는 액자(레이아웃 컴포넌트)도 걸려 있네요. 이 집에는 또 다른 방들이 있습니다. styles/라는 옷장 방에는 CSS 파일들이, utils/ 창고에는 여러 잡다한 도구 함수들이 들어있죠.


나레이션: 프론트엔드 구조와 건축의 공통점

현대적인 프론트엔드 프로젝트, 특히 React와 같은 라이브러리를 사용할 때 권장되는 폴더 구조는 집의 평면도처럼 체계적입니다. 위 장면에서 묘사한 것처럼, 대개 assets(이미지나 폰트), components(재사용 UI), pages(페이지 단위 컴포넌트), services(API 연동 로직), store(전역 상태관리), styles(스타일시트), utils(유틸 함수) 등 폴더를 나누어 관리합니다. 이렇게 역할별로 방을 마련해두면 협업할 때도 편리합니다. 디자이너는 assets에서 이미지를 찾고, 프론트 개발자는 components와 pages에서 UI 코드를, 백엔드와 연동하는 부분은 services에서 확인하는 식이죠.


비유하자면, 이 구조는 방 하나짜리 원룸을 가족이 사는 집으로 확장한 모습입니다. 거실에 온 가족의 살림을 몰아넣을 수 없듯이, 프로젝트 규모가 커지면 역할별로 방을 나누는 것이 자연스럽습니다. 한 방(폴더) 안에는 해당 역할에 꼭 필요한 물건(파일)만 두고, 방과 방 사이에는 명확한 경계가 생깁니다. 이는 결국 관심사의 분리(Separation of Concerns)를 실천하는 방법이기도 합니다. 각 폴더가 자신의 관심사(예컨대 UI 컴포넌트, 라우팅, 스타일 등)만 책임지도록 함으로써, 코드는 더 읽기 좋고 관리하기 쉬워집니다.


이렇듯 컴포넌트 기반 프론트엔드 구조는 하나의 집 안에 여러 방을 꾸린 형태로 볼 수 있습니다. 이 집에서 개발자는 필요에 따라 알맞은 방으로 이동하며 일을 볼 수 있죠. 프로젝트가 커질수록 방의 수도 늘어날 수 있지만, 기본 원칙은 같습니다: 관련있는 것들끼리 같은 공간에 모아두기. 그렇게 하면 프로젝트가 복잡해져도 전체 구조를 한눈에 파악하기 쉽고, 새로운 개발자도 그 집의 지도를 보고 빠르게 적응할 수 있습니다.


장면: 부서별 사무실 - 도메인 중심 백엔드

이제 개발자는 더욱 큰 규모의 시스템에 도전하게 됩니다. 이번에는 사용자 관리, 주문 처리, 결제 등 여러 도메인이 얽힌 백엔드 프로젝트입니다. 그는 한 건물 안에 여러 부서가 입주한 큰 사무실을 떠올립니다. 1층에는 주문처리부서, 2층에는 회원관리부서, 3층에는 상품관리부서... 각 부서 사무실 문 앞에는 부서 이름이 써 붙어 있고, 내부에 들어가면 부서 역할에 맞는 자료와 도구들이 배치되어 있습니다. 예를 들어 주문처리부서 방에는 주문서를 다루는 서랍장(OrderRepository), 주문 받는 카운터(OrderController), 주문 규칙을 정하는 매뉴얼(OrderService) 등이 한데 모여 있습니다. 다른 층의 회원관리부서 방에 가면 회원 명부(UserEntity), 회원 가입 양식(UserController), 회원 정책 문서(UserService) 등이 그 공간을 채우고 있지요.건물 중앙에는 공용 로비도 있습니다. 각 층에서 모두 이용하는 복사기나 커피머신처럼, 프로젝트 전반에서 두루 쓰이는 코드들이 모인 common/또는 shared/폴더에 해당합니다. 이 로비에는 예를 들어 공통 설정파일, 전체 부서가 같이 쓰는 유틸리티 함수, 에러 처리 모듈 등이 놓여 있습니다. 각 부서는 자기 일에 집중하면서, 필요한 공용 자원은 로비에서 꺼내 쓸 수 있는 구조입니다.


나레이션: 도메인 주도 설계의 공간 구현

이러한 구조는 **도메인 주도 설계(DDD)**에서 권장하는 도메인 중심 패키지 구조와 맞닿아 있습니다. 기존의 계층형 구조가 기술 별로 (예: 컨트롤러 폴더, 서비스 폴더, 저장소 폴더) 코드를 모아두었다면, 도메인 중심 구조에서는 기능/도메인 별로 코드를 모읍니다. 예를 들어 사용자와 관련된 모든 코드가 users/폴더 아래 모여 있고, 주문관련 코드는 orders/ 아래 모여 있는 식입니다. 이렇게 하면 개발자가 특정 기능을 수정하거나 파악할 때 여러 폴더를 전전하지 않고, 한 폴더 안에서 관련 내용을 모두 살펴볼 수 있습니다. 실제로 기능별(도메인별) 패키지 구조로 전환한 후에 “원하는 클래스를 찾기 쉬워지고 코드의 응집도가 높아졌다”는 현업 개발자의 보고도 있습니다.

[그림2] 도메인형 디렉토리 아키텍처 (Java) [참고 자료] mson-it.tistory.com.

DDD의 관점에서 각 도메인 폴더는 **경계(bounded context)**로서 독립성을 가집니다. 우리의 은유에서 각 부서 사무실이 독립된 공간이지만 회사라는 건물 내에서 협력하듯, 도메인 모듈들도 명시적인 인터페이스로만 상호 작용합니다. 이렇게 경계를 명확히 하면, 한 도메인(부서)의 변경이 다른 도메인에 영향을 덜 미치게 되어 낮은 결합도를 달성하게 됩니다.


물론 도메인별로 코드를 묶어도 그 내부에서는 다시 계층별 구분이 존재할 수 있습니다. 예컨대 orders폴더 안에 controller, service, repository하위 폴더를 두는 식이죠. 이는 마치 부서 사무실 안에서도 접수 창구(Controller), 사무 업무실(Service), 문서 보관소(Repository) 등을 둬 역할을 나누는 모습입니다. 다만 외부에서 볼 때는 각 층(도메인)별로 구분이 확실하니 전체 구조 파악이 수월해집니다. 최근에는 이렇게 도메인형 구조를 채택하는 프로젝트가 많아지는 추세이기도 합니다. 우리 개발자는 이 도메인 중심 설계를 통해 복잡한 백엔드 시스템을 잘 정리해냈습니다. 이제 새로운 요구사항이 생겨도 “어느 도메인에 속하는 기능인가?”를 자문한 뒤 해당 폴더로 가서 작업하면 됩니다. 이는 곧잘 높은 응집력과 낮은 결합도를 가져와 변경과 확장에 용이한 설계를 얻는 DDD의 핵심 목표와도 일맥상통하지요.


장면: 도시와 도로 - 마이크로서비스 아키텍처

프로젝트가 더욱 커져서, 이제는 한 건물에 넣기조차 벅찰 만큼 다양한 서비스들로 분리되었다고 상상해봅시다. 우리의 개발자는 한 걸음 물러서서, 눈앞에 펼쳐진 작은 도시를 바라보고 있습니다. 이 도시는 여러 채의 건물이 모여 하나의 생태계를 이룹니다. 저쪽에는 인증 서비스 건물이, 이쪽에는 주문 서비스 건물이 따로 서 있고, 그 옆에는 결제 서비스 건물이 보입니다. 건물들은 각자 고유한 모양과 구조를 갖추고 독립적으로 세워져 있지만, 도로와 다리(API 통신망)로 연결되어 사람들이(데이터와 요청들이) 오갈 수 있게 되어 있습니다. 한 건물의 문을 닫아도(한 서비스 장애) 다른 건물들은 여전히 자신의 역할을 수행합니다. 각 건물 안에는 자체 전기와 수도(데이터베이스)가 따로 돌아가고 있어, 하나에 문제가 생겨도 전체 도시가 마비되지 않는 탄력성이 있습니다.


이 작은 도시는 바로 **마이크로서비스 아키텍처(MSA)**로 구현된 소프트웨어 세상입니다. 이전까지 한곳에 모여 있던 기능들이 각자 서비스라는 건물로 나뉘어 배치된 것이지요. 개발자는 이제 도시의 시청 역할을 하는 중심 조율자(예: API Gateway나 Service Mesh)를 통해 여러 건물의 협업을 관리합니다.


[그림 3] 모놀리식 아키텍처와 마이크로서비스식 아키텍처 (참고 자료) sons6488.tistory.com

나레이션: 분산된 서비스, 유연한 확장

마이크로서비스 구조에서는 애플리케이션이 여러 개의 독립된 서비스(=프로세스)로 분할됩니다. 각각이 하나의 작은 모놀리식처럼 자기 완결적인 아키텍처를 가지고 있다고도 볼 수 있습니다. 앞선 은유에서 건물마다 자체 인프라(데이터베이스 등)를 가진다고 묘사한 것처럼, 실제로도 마이크로서비스들은 자신만의 DB를 포함하는 경우가 많습니다. 이렇게 하면 서비스별로 배포와 확장을 독립적으로 수행할 수 있어 유연성이 극대화됩니다. 필요한 부분에만 새로운 층을 얹거나(스케일 아웃) 보수를 하면 되니, 전체를 건드릴 필요가 없는 것이지요. 대규모 시스템에서 MSA가 각광받는 이유도 바로 이 확장성과 신뢰성 때문입니다.


하지만, 도시를 운영하는 일은 한 채 건물 관리보다 복잡합니다. 코드와 데이터가 여러 곳에 분산되어 있어 모니터링이 어려워지고, 서비스 간 통신에서 새로운 유형의 오류가 발생할 수 있습니다. 마치 건물 간 도로망이 복잡해지면 교통 정리가 필요하듯, MSA 환경에서는 로깅, 트레이싱, 오류 대응 등의 추가적인 관리 도구와 운영 비용이 필요합니다. 또 한 서비스에서 다른 서비스로 기능을 이용하려면 그냥 메서드를 호출하는 대신 네트워크 통신(HTTP 요청 등)을 거쳐야 하므로 개발 난이도가 올라가는 면도 있습니다.


폴더 구조의 측면에서 볼 때, 마이크로서비스는 흔히 하나의 리포지토리에 서비스별 폴더를 갖거나, 아예 서비스별로 별도 리포지토리를 운영하는 형태를 취합니다. 전자의 경우 프로젝트 루트에 account-service/,

order-service/, payment-service/ 와 같이 폴더를 나누고 각 폴더 안에 각각의 서비스 코드를 담는 식입니다. 이러한 구성은 앞선 도메인별 구조와 겉보기는 비슷하지만 스케일은 더 큽니다. 왜냐하면 서비스들 사이에 코드 공유가 거의 없고(또는 아주 제한적이고), 배포 단위도 각각이기 때문이죠. 각각 하나의 독립된 건물이라고 했던 은유를 기억해봅시다.


우리 개발자는 이 마이크로서비스 도시를 구축하면서, 경계 명확한 분리가 주는 장점과 동시에 분산된 복잡성이란 과제도 함께 다루고 있습니다. 하지만 그는 알고 있습니다. 도시가 커질수록 표준화된 도시 계획과 행정 관리가 중요해지듯, 마이크로서비스 체계에서도 각 서비스들의 폴더 구조를 유사한 패턴으로 유지하고, 공통 규약을 만들어주는 것이 필요합니다. 이를 통해 새로운 서비스가 생겨나도 기존 도시와 이질감 없이 통합될 수 있습니다.


장면: 층으로 쌓은 빌딩 - 계층형 아키텍처

도시 곳곳을 둘러본 개발자는 다시 시선을 한 빌딩으로 돌립니다. 이번에 살펴볼 건축물은 층을 여러 겹으로 쌓은 고층 빌딩입니다. 빌딩의 맨 아래층에는 창고와 기계실이 있어 데이터와 하부 인프라가 자리하고 있습니다. 그 위층에는 사무실 공간이 있어 직원들이 업무를 처리하고 있고, 가장 윗층에는 접수처와 안내 데스크가 있어 손님을 응대합니다. 빌딩을 옆에서 보니 층층이 구분이 뚜렷하고, 아래층에서 위층으로 사람과 자료가 이동하며 건물이 기능하고 있군요.


이 다층 빌딩은 전통적인 계층형 소프트웨어 아키텍처의 은유입니다. 한 층은 **표현 계층(Presentation)**으로, 사용자와 직접 소통하는 UI나 컨트롤러가 존재합니다. 중간 층은 응용 계층(Application) 또는 **도메인 계층(Domain)**으로, 핵심 비즈니스 로직이 실행되고 규칙이 적용되는 곳입니다. 그리고 하위층은 **인프라 계층(Infrastructure)**으로서 데이터베이스나 외부 시스템과의 연결을 담당합니다. 이 구조에서는 윗층이 아랫층을 의존하지만, 아랫층은 윗층을 몰라도 되는 단방향 의존을 가지는 것이 일반적입니다. 가령 도메인 로직은 DB가 어떻게 구현되었는지 몰라도 되지만, DB 계층은 도메인 객체를 저장하기 위해 도메인을 알아야 하는 식입니다.


[그림4] 계층형 모놀리식 아키텍처 (Java)

나레이션: 견고한 층 구조의 역할 분담

*Layered Architecture(계층형 아키텍처)**는 말 그대로 책임을 층(layer)으로 분리한 설계 방식입니다. 이 접근은 각 층이 하나의 관심사에만 집중하도록 만드는 것이 목표입니다. 예를 들어, UI 처리 로직은 Presentation 층에 몰아두고 비즈니스 규칙은 Domain 층에 위치시키며, 데이터 접근 코드는 Infrastructure 층에 모읍니다. 이렇게 하면 각 층을 구현하는 기술이나 구현 상세가 바뀌어도 다른 층에 미치는 파급이 적어집니다. 데이터베이스를 교체하는 일은 인프라 층 안에서 해결되고, UI 프레임워크를 변경하는 일은 표현 층 안에서 이루어지므로, 전체 시스템을 바라보면 느슨한 결합을 유지할 수 있지요.


우리의 빌딩 은유로 돌아가 보면, 지하 창고를 MySQL에서 MongoDB로 바꾸더라도 건물 1층의 업무에는 영향이 덜할 것입니다. 엘리베이터(계층 간 인터페이스)만 적절히 마련되어 있다면 말이죠. 또, 최상위 층의 인테리어(UI)를 리모델링하더라도 그 아래층의 사무공간(비즈니스 로직)은 멀쩡히 기능할 수 있습니다. 각 층이 자신의 역할만 충실히 하고 상호작용은 정의된 경로를 통해서만 이루어지기에 가능한 일입니다.


계층형 아키텍처는 앞서 다룬 도메인별 구조나 마이크로서비스와 대비되기도 합니다. 도메인별 구조와 계층형 구조는 사실 상호배타적인 개념은 아닙니다. 하나의 도메인 폴더 안에 다시 계층 구조를 적용할 수도 있고, 반대로 계층별 폴더를 두되 각 계층 안에서 도메인별 하위폴더를 운영할 수도 있습니다. 다만 전통적으로 계층형 패키징은 일정 규모 이상의 옛날 모놀리식 프로젝트들에서 흔하게 볼 수 있었고, 점차 규모가 커지면서 도메인 중심 구조로 진화하는 경향이 있다는 것이 업계의 경험입니다. 계층형에서 도메인형으로 바꾸는 것은 마치 건물 내부를 리모델링하여 부서별로 층을 재배치하는 작업과 비슷합니다. 기존 시스템을 한꺼번에 뜯어고치긴 어렵지만, 서서히 영역을 구분하고 의존성을 정리해나가다 보면 궁극적으로 유연하고 이해하기 쉬운 구조에 도달할 수 있습니다.


장면: 복합 단지 - 모노레포와 패키지 구조

한편, 다시 도시 규모의 비유로 돌아가 보겠습니다. 이번에는 여러 건물이 모여 있지만 하나의 커다란 담장 안에 함께 존재하는 캠퍼스형 단지를 상상해봅시다. 이 단지 안에는 연구동, 행정동, 공용 도서관, 식당 등이 각각 건물로 서 있지만, 모두 하나의 울타리로 둘러싸여 있어 출입 관리가 통합되어 있고 자원도 공유됩니다. 방문자는 단지 입구에서 신분증을 한번만 보이면 각 건물을 자유롭게 오갈 수 있습니다. 관리 측면에서도 개별 건물마다 별도 경비를 두지 않고 단지 전체를 아우르는 관제가 이뤄집니다.


이것이 소프트웨어 개발에서의 **모노레포(monorepo)**에 해당하는 그림입니다. 모노레포는 말 그대로 하나의 저장소(repo)에 여러 프로젝트를 담는 방식을 말합니다. 예를 들어 프론트엔드, 백엔드, 공용 라이브러리 등이 각각 폴더로 구분되어 한 저장소에 있지만, 빌드나 버전 관리를 공동으로 하는 식입니다. Node.js 생태계에서 사용하는 Lerna나 Yarn Workspaces, 혹은 최근의 Turborepo 같은 도구들이 모노레포 운영을 돕는 대표적 예이지요. 마치 하나의 캠퍼스 안에서 여러 팀이 일하지만 공통 인프라를 함께 쓰는 것처럼, 모노레포에서는 코드의 재사용과 중복 방지가 수월해집니다. 여러 패키지가 같은 유틸리티나 설정을 공유하고, 변경 사항도 한 곳에서 관리되니까요.


나레이션: 하나의 저장소, 여러 프로젝트

모노레포 구조의 장점은 한 곳에서 모든 것을 관리할 수 있다는 것입니다. 여러 개의 작은 저장소에 분산되어 있었다면 겪을 법한 의존성 충돌이나 버전 불일치 문제가 줄어듭니다. 모든 패키지가 동일한 릴리스 주기와 이슈 트래커 속에서 움직이므로 협업 면에서도 투명성이 높아집니다. 앞서 비유한 단지 안의 건물들은 서로 긴밀히 소통하고, 공용 도서관이나 식당(=shared 폴더나 공용 모듈)을 통해 지식을 나눕니다. 예를 들어 백엔드와 프론트엔드가 동일한 utils 함수를 단지 내 도서관에서 같이 가져다 쓸 수도 있는 것이죠.


물론 모노레포에도 단점은 있습니다. 모든 것을 한곳에 담다 보니 레포지토리 자체의 크기가 커지고 복잡도가 상승합니다. 빌드 설정도 여러 프로젝트를 아우르도록 정교하게 조율해야 합니다. 잘 관리된 캠퍼스는 효율적이지만, 관리가 부실하면 오히려 난장판이 될 수 있다는 점에서 일맥상통합니다. 그래서 모노레포를 도입할 때는 일정 수준 이상의 자동화 도구와 팀 규율이 함께 따라야 합니다. 예컨대 린터와 테스트를 전체 레포에 걸쳐 돌리는 파이프라인을 만들거나, 변경된 부분만 빌드하는 스마트한 스크립트 등이 필요합니다.


폴더 구조로 본다면 전형적인 모노레포는 프로젝트 루트에 packages/ 폴더를 두고, 그 아래에 각 서브 프로젝트(패키지)들을 폴더로 배치하는 형태를 취합니다. packages/frontend, packages/backend, packages/shared 등으로 구성하는 식이지요. 각 패키지 폴더는 자체적으로는 하나의 프로젝트 구조(작은 집 또는 빌딩)이고, 루트에서는 이들을 묶어서 관리하는 구성입니다. 이러한 통합 구조 덕분에

코드의 공유와 의존 관리가 통일된 맥락에서 이뤄집니다.


사례: LLM 기반 AI 프로젝트의 디렉토리 설계

마지막으로, 필자인 제가 작업했던 LLM 기반 AI 프로젝트의 실제 폴더 구조를 간략히 소개하며 공간 비유 이야기를 맺고자 합니다. 이 프로젝트에서는 여러 개의 서비스를 개발했는데, 예를 들어 대화(chat) 서비스와 임베딩(embedding) 서비스 두 개로 나눠서 개발이 진행되었습니다. 처음에는 이를 완전히 분리된 두 프로젝트로 관리할 수도 있었겠지만, 저는 하나의 모노레포 안에 두 서비스를 모두 포함시키는 방향을 택했습니다. 그렇게 하면 서비스 간에 공통으로 사용되는 자원들을 쉽게 공유할 수 있기 때문입니다.


프로젝트의 최상위에는 chat-service/, embedding-service/ 폴더가 나란히 존재했습니다. chat-service 폴더에는 채팅 응답 생성에 필요한 모델 코드와 API 엔드포인트 구현이 들어 있었고, embedding-service

폴더에는 문장을 벡터로 변환하는 임베딩 모델 코드와 관련 API들이 들어 있지요. 이 둘은 서로 독립적으로 돌아가는 작은 마이크로서비스들이지만, 완전히 떨어져 있지는 않습니다. 두 서비스 모두에서 활용하는 공통 자원을 위해 shared/라는 별도의 공간을 만들었기 때문입니다. shared/폴더에는 다국어 지원을 위한 i18n 문자열 모음, 공용 설정 파일(config), 그리고 두 서비스에서 공통으로 사용하는 라이브러리 코드 등이 담겨 있습니다. 저는 이 shared폴더를 마치 두 건물 사이를 잇는 공용 창고나 지하 통로처럼 여겼습니다. 덕분에 chat-service와 embedding-service는 자신들의 핵심 기능에 집중하면서도, 필요한 공통 데이터나 기능은 shared를 통해 손쉽게 공유할 수 있었죠.이 구조를 통해 얻은 가장 큰 이점은 명확한 분리와 협업의 용이함이었습니다. 새로운 팀원이 들어와서 채팅 기능을 수정해야 한다면 chat-service라는 건물만 살펴보면 됩니다. 반대로 두 서비스 모두에 영향주는 설정을 바꾸려면 shared라는 한 곳만 확인하면 되지요. 폴더 이름만 봐도 각 부분이 무슨 역할을 하는지 드러나기 때문에 프로젝트 전반을 이해하기도 쉬워졌습니다. 실제로 많은 대규모 프로젝트에서 공통 모듈을 shared또는 common디렉토리로 분리하여 관리하는데, 이는 제가 몸소 실천해보니 확실히 효과적이었습니다.


또한 이 프로젝트는 AI 모델 중심의 서비스였기 때문에, 서로 다른 모델 서비스들을 이렇게 분리된 공간에 넣어둔 것이 개발 파이프라인에도 도움이 되었습니다. 모델별로 필요한 라이브러리나 환경 설정이 조금씩 달랐는데, chat-service와 embedding-service를 분리한 덕분에 각자의 의존성을 충돌 없이 관리할 수 있었던 것입니다. 모노레포 안에서 마이크로서비스 분리를 실천한 좋은 사례였다고 생각합니다.


맺음말: 건축적 사고로 짜는 코드 구조

소프트웨어 설계에서 코드 구조를 잡는 일은 단순히 폴더를 나누는 관리적 작업이 아닙니다. 이것은 건축에서 공간을 설계하는 창의적 작업과 많이 닮아 있습니다. 우리는 작은 프로그램을 만들 때 방 한 칸에서 출발하지만, 거기서 함수를 만들고 클래스를 늘려가며 자연스레 칸을 나누고 구획을 정의하게 됩니다. 규모가 커질수록 우리는 건물의 청사진을 그리듯 청사진으로서의 아키텍처를 구상하게 되죠. 어디에 어떤 역할의 코드를 두고, 이 부분과 저 부분은 어떻게 연결할 것인지, 어디서 경계를 지을 것인지 고민하는 과정 자체가 건축적 사고입니다.


이 장에서 살펴본 여러 은유들—방, 집, 사무실, 도시, 빌딩, 캠퍼스—은 각각 소프트웨어 구조의 한 양상을 비추는 거울입니다. 실무에서는 이 개념들이 혼합되어 쓰이기도 하고, 프로젝트의 성격과 팀의 철학에 따라 선호 구조가 다를 수 있습니다. 중요한 것은 왜 그런 구조를 취하는지 이해하고, 코드가 돌아가는 공간적 맥락을 인지하는 것입니다. 초심자 개발자라 할지라도 작은 프로젝트부터 이런 사고를 습관들이면, 훗날 거대한 시스템을 마주해도 당황하지 않고 공간을 조직화하는 안목을 발휘할 수 있을 것입니다.


프로그래밍을 배우다 보면 언젠가 복잡한 코드를 마주하게 됩니다. 그때 **“코드는 건축이다”**라는 말을 떠올려 보세요. 눈앞의 코드 덩어리를 건물에 비유해보고, 더 나은 설계를 위한 공간 배치를 상상해보는 겁니다. 이것이 곧 리팩터링의 첫걸음이자, 소프트웨어 아키텍트로 가는 길의 시작입니다. 방 하나짜리 코드에서 도시 규모의 시스템까지, 구조에 대한 통찰을 가지고 있다면 우리는 언제나 더 견고하고 아름다운 프로그램을 지을 수 있을 것입니다.



(참고한 자료) 개발에 있어 폴더 구조의 중요성과 다양한 구조적 패턴에 관해 참고한 자료들: React 프로젝트 예제 구조fomerain.tistory.comfomerain.tistory.com, 도메인 주도 패키지 설계 사례mson-it.tistory.com, 계층형 아키텍처 설명, 모놀리식 vs 마이크로서비스 비교sons6488.tistory.comsons6488.tistory.com, 모노레포의 정의 등이 있습니다. (각주에 명시된 번호를 통해 원문 내용을 확인하실 수 있습니다.)




(제품 개발하느라 .. 연재가 늦었습니다. 죄송합니다.)



월, 목, 토 연재
이전 02화EP.1 - AI 시대의 소프트웨어 건축 철학