Go 1.19 제네릭 실전 적용기 - API 응답 래퍼 리팩토링
배경
사내 마이크로서비스 중 하나를 Go로 마이그레이션하면서 API 응답 처리 로직이 반복되는 문제가 있었다. 각 엔드포인트마다 비슷한 구조의 래퍼 함수를 작성하고 있었는데, Go 1.18부터 제네릭이 정식 지원되면서 이를 개선할 기회가 생겼다.
기존 코드
type UserResponse struct {
Success bool `json:"success"`
Data *User `json:"data"`
Error string `json:"error,omitempty"`
}
type ProductResponse struct {
Success bool `json:"success"`
Data *Product `json:"data"`
Error string `json:"error,omitempty"`
}
타입마다 동일한 구조의 응답 래퍼를 만들어야 했고, 응답 생성 함수도 각각 작성해야 했다.
제네릭 적용
type APIResponse[T any] struct {
Success bool `json:"success"`
Data T `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func SuccessResponse[T any](data T) APIResponse[T] {
return APIResponse[T]{
Success: true,
Data: data,
}
}
func ErrorResponse[T any](err error) APIResponse[T] {
return APIResponse[T]{
Success: false,
Error: err.Error(),
}
}
사용하는 쪽에서는 타입 추론이 잘 작동해서 명시적으로 타입을 지정하지 않아도 됐다.
func GetUser(c *gin.Context) {
user, err := userService.FindByID(id)
if err != nil {
c.JSON(500, ErrorResponse[*User](err))
return
}
c.JSON(200, SuccessResponse(user))
}
페이지네이션 응답
제네릭의 진가는 페이지네이션 응답에서 더 명확하게 드러났다.
type PaginatedResponse[T any] struct {
Success bool `json:"success"`
Data []T `json:"data"`
Total int64 `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
}
성능 확인
제네릭이 런타임 성능에 영향을 주는지 간단한 벤치마크를 돌려봤다. 인터페이스 기반 구현과 비교했을 때 오버헤드는 거의 없었고, 컴파일 타임이 약간 늘어나는 정도였다.
한계점
메서드에는 타입 파라미터를 추가할 수 없어서 구조체 레벨에서만 제네릭을 활용할 수 있었다. 그래도 반복 코드를 크게 줄이고 타입 안정성을 확보한 것만으로도 충분히 가치가 있었다.
프로덕션에 배포 후 별다른 이슈는 없었고, 새로운 엔드포인트 추가 시 보일러플레이트 코드가 눈에 띄게 줄어들었다.