본문으로 건너뛰기
Advertisement

3.5 글로벌 공통 응답 구조 (Common Response) 설계

1. 왜 공통 응답 구조가 필요한가?

API를 여러 개 만들다 보면 각 컨트롤러마다 응답 형태가 제각각이 될 수 있습니다.

// A 컨트롤러 응답 형태
{ "id": 1, "name": "홍길동" }

// B 컨트롤러 응답 형태 (오류 시)
{ "message": "사용자를 찾을 수 없습니다." }

// C 컨트롤러 응답 형태 (목록 조회)
[{ "id": 1 }, { "id": 2 }]

이렇게 응답 형태가 들쭉날쭉하면 프론트엔드 개발자 가 매번 API마다 다른 응답 구조를 파악해야 하고, 오류 처리 로직도 제각각으로 짜야 해서 협업이 매우 힘들어집니다.

공통 응답 구조(Common Response Wrapper) 를 도입하면, 모든 API 응답을 일관된 봉투(Envelope) 에 담아 내보낼 수 있습니다.


2. 공통 응답 클래스 설계

public class ApiResponse<T> {

private boolean success; // 성공 여부
private String message; // 사람이 읽을 수 있는 결과 메시지
private T data; // 실제 응답 데이터 (제네릭)

// 생성자를 private으로 막고, 정적 팩토리 메서드로만 생성
private ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}

// ✅ 성공 응답 생성 메서드
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "요청이 성공적으로 처리되었습니다.", data);
}

public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(true, message, data);
}

// ✅ 실패 응답 생성 메서드
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}

// Getter 생략 (Lombok @Getter 또는 직접 작성)
}

3. 컨트롤러에 적용하기

@RestController
@RequestMapping("/api/users")
public class UserController {

@GetMapping("/{id}")
public ResponseEntity<ApiResponse<UserResponseDto>> getUser(@PathVariable Long id) {
UserResponseDto user = userService.findById(id);

if (user == null) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.fail("해당 ID의 사용자를 찾을 수 없습니다."));
}

return ResponseEntity.ok(ApiResponse.success(user));
}

@PostMapping
public ResponseEntity<ApiResponse<UserResponseDto>> createUser(
@RequestBody UserCreateRequestDto request) {
UserResponseDto created = userService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success("사용자가 등록되었습니다.", created));
}
}

4. 실제 JSON 응답 예시

성공 시:

{
"success": true,
"message": "요청이 성공적으로 처리되었습니다.",
"data": {
"id": 1,
"name": "홍길동",
"totalPoints": 1000
}
}

실패 시:

{
"success": false,
"message": "해당 ID의 사용자를 찾을 수 없습니다.",
"data": null
}

5. Lombok을 활용한 실무 버전

실무에서는 @Getter, @Builder, @AllArgsConstructor 등 Lombok 애너테이션으로 코드를 훨씬 간결하게 작성합니다.

import lombok.Getter;

@Getter
public class ApiResponse<T> {

private final boolean success;
private final String message;
private final T data;

private ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}

public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "OK", data);
}

public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}
}

핵심 정리: 공통 응답 클래스를 도입하면 팀 전체가 일관된 API 응답 규격 아래에서 개발할 수 있습니다. 프론트엔드는 항상 success, message, data 세 필드만 확인하면 되므로, 협업 효율이 크게 올라갑니다. 이 패턴은 모든 실무 스프링 프로젝트의 기본 뼈대입니다.

Advertisement