backend

Go 프로젝트 구조와 패키지 설계: 실무에서 흔히 쓰는 기준 정리

mirabo01 2026. 1. 19. 22:08

동시성까지 한 번 훑었다면, 이제는
**“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와 의존성 관리 방식을 다루면 흐름이 좋다.