Go 1.23 제네릭을 활용한 공통 응답 래퍼 구현
문제 상황
회사 프로젝트에서 Go로 REST API 서버를 개발하던 중, 모든 엔드포인트가 유사한 응답 구조를 반환하고 있었다. 각 핸들러마다 interface{}로 데이터를 감싸는 코드가 반복되고, 타입 안정성도 떨어졌다.
func GetUser(w http.ResponseWriter, r *http.Request) {
user := fetchUser()
response := map[string]interface{}{
"success": true,
"data": user,
}
json.NewEncoder(w).Encode(response)
}
제네릭 도입
Go 1.18부터 지원되는 제네릭을 활용해 공통 응답 래퍼를 만들었다.
type APIResponse[T any] struct {
Success bool `json:"success"`
Data T `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func WriteJSON[T any](w http.ResponseWriter, status int, data T) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
response := APIResponse[T]{
Success: status < 400,
Data: data,
}
return json.NewEncoder(w).Encode(response)
}
핸들러 코드가 간결해지고 타입 체크도 컴파일 타임에 가능해졌다.
func GetUser(w http.ResponseWriter, r *http.Request) {
user := fetchUser()
WriteJSON(w, http.StatusOK, user)
}
에러 처리 확장
에러 응답도 동일한 구조로 통일했다.
func WriteError(w http.ResponseWriter, status int, message string) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
response := APIResponse[any]{
Success: false,
Error: message,
}
return json.NewEncoder(w).Encode(response)
}
결과
- 핸들러 코드 30% 감소
- 타입 안정성 확보로 런타임 에러 사전 방지
- 응답 구조 일관성 유지
제네릭이 Go에 늦게 도입되긴 했지만, 실무에서 충분히 유용하게 활용할 수 있다는 걸 확인했다.