동시성까지 한 번 훑었다면, 이제는
**“Go 프로젝트를 어떻게 구성하는 게 좋은가”**라는 질문으로 넘어오게 된다.
Go는 문법뿐만 아니라
프로젝트 구조와 패키지 설계에서도 강한 의견을 가진 언어다.
처음에는 “너무 자유로운 것 아닌가?”라는 느낌을 받을 수 있지만,
몇 가지 기준만 잡아두면 오히려 유지보수가 쉬워진다.
이 글에서는
- Go 프로젝트의 기본 구조
- 패키지를 나누는 기준
- 실제로 많이 사용하는 디렉터리 패턴
을 중심으로 정리한다.
Go 프로젝트 구조에는 정답이 없다
먼저 전제부터 정리할 필요가 있다.
Go에는 공식적으로 강제되는 프로젝트 구조가 없다
Spring, Django처럼
“이 디렉터리는 반드시 이 역할” 같은 규칙이 존재하지 않는다.
대신 Go는 다음 원칙을 강하게 밀고 있다.
- 패키지는 작게
- 의존성은 한 방향으로
- 불필요한 계층은 만들지 않는다
그래서 구조를 고민할 때도
프레임워크 기준이 아니라 코드 의존성 기준으로 접근하는 게 좋다.
가장 단순한 Go 프로젝트 구조
아주 간단한 예제 프로젝트는 다음 정도로 시작한다.
myapp/
├── go.mod
├── main.go
└── handler/
└── handler.go
- main.go: 프로그램 진입점
- handler: 역할별 로직 분리
처음에는 이 정도 구조로도 충분하다.
불필요하게 디렉터리를 나누는 게 오히려 독이 된다.
[이미지: Go 기본 프로젝트 구조 예시]
main 패키지의 역할
Go 프로그램은 main 패키지에서 시작된다.
package main
func main() {
// entry point
}
실무에서는 main 패키지를 이렇게 사용하는 경우가 많다.
- 설정 로드
- 의존성 초기화
- 서버 실행
즉, 비즈니스 로직은 거의 두지 않는다.
main은 조립 역할만 담당하는 게 이상적이다.
패키지를 나누는 기준
Go에서 패키지를 나눌 때 가장 중요한 기준은 이것이다.
“이 코드가 어디에서 어떻게 사용되는가”
예를 들어 다음과 같은 분리는 비교적 자연스럽다.
user/
├── model.go
├── service.go
└── repository.go
하지만 이런 구조는 조심할 필요가 있다.
service/
repository/
controller/
이 방식은
- 계층은 나뉘지만
- 실제 도메인 맥락은 드러나지 않는다
Go에서는 보통
기술 기준보다 도메인 기준 패키지 분리가 더 선호된다.
패키지 이름은 역할보다 “개념”
Go 패키지 이름은 짧고 명확해야 한다.
import "myapp/user"
- userservice ❌
- user_service ❌
- user ⭕️
패키지를 import하는 코드가
자연스러운 문장처럼 읽히는지가 하나의 기준이 된다.
실제로 Go 표준 라이브러리도
이 원칙을 매우 일관되게 따른다.
internal 디렉터리 활용
Go에는 internal이라는 특별한 디렉터리 규칙이 있다.
internal/
└── auth/
- internal 아래 패키지는
- 외부 모듈에서 import 불가
즉,
“이 코드는 프로젝트 내부에서만 사용된다”
라는 의도를 컴파일 단계에서 강제할 수 있다.
실무에서는 다음 용도로 자주 사용된다.
- 핵심 비즈니스 로직
- 외부에 노출되면 안 되는 구현체
[이미지: Go internal 패키지 접근 제한 구조]
cmd 디렉터리는 언제 필요한가
다음 구조도 종종 보게 된다.
cmd/
└── myapp/
└── main.go
- 실행 파일이 여러 개인 경우
- CLI, 워커, 서버가 분리된 경우
단일 실행 파일 프로젝트라면
굳이 cmd 디렉터리를 만들 필요는 없다.
“필요해질 때 추가한다” 정도의 인식이 적당하다.
패키지 순환 참조는 반드시 피한다
Go는 패키지 순환 참조를 허용하지 않는다.
A → B
B → A ❌
이 제약은 처음엔 불편해 보이지만,
설계를 되돌아보게 만드는 장치이기도 하다.
순환이 발생한다면 보통은
- 책임이 잘못 나뉘었거나
- 공통 개념을 분리해야 할 시점
인 경우가 많다.
실무에서 자주 사용하는 구조 예시
myapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── user/
│ └── auth/
├── pkg/
│ └── logger/
└── go.mod
- cmd: 실행 진입점
- internal: 내부 비즈니스 로직
- pkg: 외부에서도 재사용 가능한 코드
모든 프로젝트가 이 구조를 따라야 할 필요는 없지만,
중·대형 프로젝트에서는 참고할 만한 형태다.
[이미지: Go 실무 프로젝트 구조 예시]
정리
Go 프로젝트 구조는
단순함과 의존성 방향을 가장 중요하게 본다.
- 패키지는 작고 명확하게
- main은 조립 역할만 담당
- 도메인 기준으로 패키지 분리
- internal로 접근 범위 제한
초반에는 구조보다
코드가 자연스럽게 읽히는지에 집중하는 게 좋다.
구조는 프로젝트가 커지면서 자연스럽게 다듬어도 늦지 않다.
다음 글에서는 프로젝트 구조와 바로 이어지는 주제로
go mod와 의존성 관리 방식을 다루면 흐름이 좋다.
'backend' 카테고리의 다른 글
| Go 로깅과 설정 관리 정리: 운영 환경을 고려한 기본 기준 (1) | 2026.01.21 |
|---|---|
| Go 모듈(go mod)과 의존성 관리: 실무에서 헷갈리지 않는 기준 정리 (0) | 2026.01.20 |
| Go 언어 select 문 정리: 여러 channel을 동시에 다루는 방법 (0) | 2026.01.18 |
| Go 언어 channel 기초: goroutine 간 통신과 동기화 방법 (1) | 2026.01.17 |
| Go 언어 goroutine과 동시성 기초: 병렬 처리의 기본 단위 이해하기 (0) | 2026.01.16 |