Go 1.24 generics 활용: API 응답 래퍼 타입 통일하기

문제 상황

회사 내부 마이크로서비스들이 각자 다른 응답 구조를 사용하고 있었다. 일부는 {data, error}, 일부는 {result, message, code} 형태였고, 프론트엔드 팀에서 통일 요청이 들어왔다.

기존에는 각 서비스마다 아래와 같은 구조체를 반복 정의했다.

type UserResponse struct {
    Data  *User  `json:"data"`
    Error string `json:"error,omitempty"`
}

type OrderResponse struct {
    Data  *Order `json:"data"`
    Error string `json:"error,omitempty"`
}

Generics 도입

Go 1.18부터 사용 가능한 generics를 활용해 공통 라이브러리를 만들었다.

type APIResponse[T any] struct {
    Data      *T     `json:"data"`
    Error     string `json:"error,omitempty"`
    Timestamp int64  `json:"timestamp"`
}

func Success[T any](data T) APIResponse[T] {
    return APIResponse[T]{
        Data:      &data,
        Timestamp: time.Now().Unix(),
    }
}

func Fail[T any](err error) APIResponse[T] {
    return APIResponse[T]{
        Error:     err.Error(),
        Timestamp: time.Now().Unix(),
    }
}

핸들러 코드가 훨씬 간결해졌다.

func GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := userService.FindByID(r.Context(), userID)
    if err != nil {
        json.NewEncoder(w).Encode(api.Fail[User](err))
        return
    }
    json.NewEncoder(w).Encode(api.Success(user))
}

결과

  • 5개 서비스에서 중복 코드 약 300줄 제거
  • 타입 추론으로 컴파일 타임 안정성 확보
  • 프론트엔드에서 일관된 에러 핸들링 가능

다만 generics 문법에 익숙하지 않은 팀원들을 위해 내부 문서를 별도로 작성했다. 러닝 커브는 있지만 장기적으로 유지보수성이 크게 개선되었다.