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