Go를 제대로 공부해보고 싶었다. 이름은 오래전부터 알고 있었는데, 주변에서 쓰는 곳이 점점 늘어나니 더 이상 미루기 어렵다는 생각이 들었다. learning-golang이라는 이름으로 저장소를 만들고 공식 문서와 예제를 따라가면서 사흘 동안 기본기를 익혔다.
프로젝트 레이아웃
처음부터 Standard Go Project Layout을 기준으로 잡았다. 학습용이지만 실제 구조에 익숙해지는 게 낫겠다는 판단이었다.
learning-golang/
├── cmd/
│ ├── hello/ # 기본 패키지 호출 예제
│ ├── rest/ # Echo 기반 REST 서버
│ └── rest-failed/ # CockroachDB 연동 시도 (미완성)
├── pkg/
│ ├── greetings/ # Hello 함수 + 테스트
│ └── music/ # 맵 활용 예제
├── internal/
│ └── dict.go # 내부 유틸리티 (Keys, Values)
├── Dockerfile
└── docker-compose.ymlcmd에 진입점을 두고, pkg에는 외부에서 쓸 수 있는 패키지를, internal에는 이 모듈 안에서만 쓸 유틸리티를 넣었다. Go의 internal 패키지는 상위 모듈 밖에서는 임포트가 안 된다. 직접 확인해보니 컴파일 에러로 막힌다.
패키지 구조
pkg/greetings는 공식 Go 튜토리얼(go.dev/doc)에서 처음 나오는 예제다. 이름을 받아서 인사 문자열을 반환하는 함수인데, 에러 처리를 함께 보여준다는 점이 인상적이다. Java나 Python에서는 예외를 던지는 게 보통인데, Go는 에러를 반환값으로 명시적으로 돌려주는 방식이라 코드 흐름이 훨씬 눈에 잘 들어온다.
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("empty name")
}
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}pkg/music은 map을 써보기 위해 만든 예제다. 가수와 노래 제목을 매핑해두고 조회하는 구조다. internal.Keys와 internal.Values를 호출해서 internal 패키지 연결이 잘 되는지도 확인했다.
cmd/hello에서는 greetings, music, 그리고 외부 패키지인 rsc.io/quote를 모두 불러와서 한꺼번에 출력한다.
테스트
Go 테스트는 별도 프레임워크 없이 표준 라이브러리 testing만으로 작성한다. greetings_test.go에 두 가지 케이스를 넣었다. 정상 이름을 넣었을 때 응답에 그 이름이 포함되는지 정규식으로 검증하고, 빈 문자열을 넣었을 때 에러가 반환되는지 확인한다.
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b` + name + `\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}t.Fatalf로 실패 메시지를 포맷팅해서 출력한다. 테스트 실패 시 어떤 값이 들어왔고 무엇을 기대했는지 한눈에 보이게 쓰는 게 Go 테스트의 관행인 것 같다.
Docker 연동
Dockerfile은 golang:1.20-alpine 기반으로 빌드하고, ARG module로 어떤 cmd를 빌드할지 선택할 수 있게 했다.
ARG module=rest
RUN go build -o main ./cmd/${module}빌드 시 --build-arg module=hello처럼 넘기면 원하는 커맨드를 이미지로 만들 수 있다. 기본값은 rest다.
docker-compose.yml에는 Echo 서버와 CockroachDB를 함께 띄우는 구성을 넣었다. 환경 변수로 DB 접속 정보를 주입하고, 서버가 DB보다 먼저 뜨는 상황을 backoff로 재시도하는 방식이다.
CockroachDB 연동 시도
cmd/rest-failed가 이름 그대로다. Echo + CockroachDB + cockroach-go 드라이버를 연결해서 메시지를 저장하고 조회하는 REST API를 만들려 했는데, 로컬 환경에서 CockroachDB 접속이 제대로 안 된다. 디버그 로그만 잔뜩 남긴 채 멈춰있다.
err = backoff.Retry(openDB, backoff.NewExponentialBackOff())재시도 로직은 cenkalti/backoff를 써서 지수 백오프로 구현했다. 연결은 아직 못 잡았지만 트랜잭션 처리를 crdb.ExecuteTx로 감싸는 패턴은 눈에 익었다.
마치며
사흘 동안 Go의 핵심 개념 몇 가지를 잡았다. 에러를 반환값으로 다루는 방식, internal 패키지로 접근 범위를 제어하는 방법, 표준 라이브러리만으로 충분한 테스트 환경, 그리고 멀티 스테이지 없이도 간결한 Dockerfile. 일단 여기서 마무리하고 다음 단계로 넘어가려 한다.