..
Modern Beautiful API Response Design: Exception Translation Layer
TOC
- Overview
- 왜 번역 계층이 따로 필요한가
- 설계 목표
- 1. 번역 책임은 프레젠테이션 계층에 둔다
- 2. Advisor는 내부 계약을 외부 계약으로 바꾼다
- 3. 번역 계층이 하면 안 되는 일
- Closing
Overview
앞선 글에서 내부 예외 모델과 외부 에러 계약을 분리했다. 이제 남은 질문은 하나다. 이 둘을 어디서 연결할 것인가?
이 글에서는 ProblemDetail 같은 외부 응답 계약을 실제로 누가 만들고, 왜 그 책임을 프레젠테이션 계층에 두어야 하는지를 다룬다.
왜 번역 계층이 따로 필요한가
내부 예외 모델과 외부 응답 계약은 역할이 다르다.
- 내부는 아키텍처와 도메인 책임을 지키기 위한 모델이다.
- 외부는 클라이언트와의 안정적인 통신 계약이다.
이 둘을 같은 계층에서 처리하면 내부 설계가 외부 포맷에 끌려가고, 외부 포맷 변경이 내부 코드까지 흔들게 된다. 그래서 중간에 번역 계층이 필요하다.
설계 목표
- 내부 예외 모델은 순수하게 유지한다.
- 외부 응답 계약은 프레젠테이션 계층에서만 조립한다.
- 번역 계층은 비즈니스 판단 없이 변환만 수행한다.
1. 번역 책임은 프레젠테이션 계층에 둔다
헥사고날 아키텍처 기준으로 HTTP 응답은 프레젠테이션 어댑터의 책임이다. 따라서 예외를 ProblemDetail이나 공통 API 에러 포맷으로 바꾸는 일도 프레젠테이션 계층이 맡아야 한다.
즉:
- 도메인 계층은 예외를 던진다.
- 애플리케이션 계층은 흐름을 제어하다가 예외를 던진다.
- 프레젠테이션 계층은 그 예외를 HTTP 응답으로 번역한다.
그래서 @RestControllerAdvice는 단순 편의 기능이 아니라, 아키텍처적으로도 자연스러운 번역 지점이다.
2. Advisor는 내부 계약을 외부 계약으로 바꾼다
Advisor의 역할은 새 규칙을 만드는 것이 아니라, 이미 정의된 내부 계약을 외부 계약으로 번역하는 것이다.
@RestControllerAdvice
public class GlobalExceptionAdvisor {
@ExceptionHandler(ApplicationException.class)
public ProblemDetail handleApplicationException(ApplicationException e) {
HttpStatus status = determineHttpStatus(e.getErrorCode());
ProblemDetail problem = ProblemDetail.forStatus(status);
problem.setTitle("Application error");
problem.setDetail(e.getMessage());
problem.setType(URI.create("https://api.example.com/problems/" + e.getSpecificCode()));
problem.setProperty("code", e.getSpecificCode());
problem.setProperty("source", "application");
return problem;
}
}
핵심은 두 가지다.
- 상태 코드는 여전히 내부의
ErrorCode를 기준으로 결정한다. - 클라이언트 식별 코드는
ProblemSpec이 제공한 값을 응답에 싣는다.
즉 Advisor는 내부 계약을 외부 계약으로 바꿀 뿐, 새로운 비즈니스 판단을 하지 않는다.
3. 번역 계층이 하면 안 되는 일
번역 계층이 과도하게 똑똑해지면 다시 책임이 섞인다. Advisor는 아래 일을 하면 안 된다.
- 도메인 규칙을 새로 판단하는 일
- 예외를 보고 비즈니스 흐름을 다시 결정하는 일
- 원래 없던 식별 코드를 임의로 만드는 일
번역 계층은 이미 만들어진 내부 계약을 받아, 외부 포맷으로 안정적으로 바꾸는 데만 집중해야 한다.
Closing
시리즈를 정리하면 역할은 이렇게 나뉜다.
- Internal Exception Model: 서버 내부에서 실패를 어떻게 모델링할 것인가
- External Error Contract: 클라이언트에게 어떤 에러 계약을 보여줄 것인가
- Exception Translation Layer: 그 둘을 어디서, 어떻게 연결할 것인가
이렇게 나누면 내부 설계와 외부 계약이 서로를 덜 오염시키고, 각 계층의 책임도 훨씬 선명해진다.