API 요청이 실패했을 때 아무런 안내 없이 사용자에게 500 에러를 던지는 건 친절하지 않다.
실제 요청한 사용자의 이메일로 실패 이유를 직접 안내하는 구조를 아래와 같이 구현했다.
@GetMapping("/brave/search")
public ResponseEntity<ApiResponse<BraveSearchResponseDto>> searchBrave(
@RequestParam String query,
@AuthenticationPrincipal CustomUserPrincipal customUserPrincipal) {
String email = customUserPrincipal.getEmail();
BraveSearchResponseDto result = braveSearchService.search(query, email);
return ResponseEntity.ok(ApiResponse.ok(result));
}
@AuthenticationPrincipal을 통해 로그인한 사용자의 이메일을 추출하고,
그 이메일을 BraveSearchService.search(query, email)에 전달한다.
public BraveSearchResponseDto search(String query, String email) {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");
headers.set("X-Subscription-Token", braveSearchProperties.getNextKey());
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
String url = braveSearchProperties.getUrl() + "?q=" + query + "&count=5";
try {
ResponseEntity<BraveSearchResponseDto> response = restTemplate.exchange(
url, HttpMethod.GET, requestEntity, BraveSearchResponseDto.class);
return response.getBody();
} catch (Exception e) {
notifyCallback(email, "Brave Search 요청 실패: " + e.getMessage());
throw e;
}
}
private void notifyCallback(String email, String reason) {
Map<String, String> body = Map.of("email", email, "reason", reason);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers);
try {
restTemplate.postForEntity("http://localhost:8081/internal/failure-callback", request, Void.class);
} catch (Exception ex) {
System.err.println("콜백 전송 실패: " + ex.getMessage());
}
}
@RestController
@RequestMapping("/internal")
public class CallbackController {
private final CallbackMailService callbackMailService;
@PostMapping("/failure-callback")
public ResponseEntity<Void> receiveFailureCallback(@RequestBody FailureCallbackRequest request) {
callbackMailService.sendFailureEmail(request.email(), request.reason());
return ResponseEntity.ok().build();
}
}
public record FailureCallbackRequest(String email, String reason) {}
@Service
@RequiredArgsConstructor
public class CallbackMailService {
private final JavaMailSender mailSender;
public void sendFailureEmail(String to, String reason) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject("[DevMountain] 요청 실패 안내");
message.setText(reason);
mailSender.send(message);
}
}
spring:
mail:
host: smtp.gmail.com
port: 587
username: your-email@gmail.com
password: your-app-password
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
| 항목 | 내용 |
|---|---|
| 실패 감지 위치 | BraveSearchService에서 직접 감지 |
| 알림 대상 | 요청을 보낸 인증 사용자 |
| 메일 전송 시점 | API 호출 실패 시 즉시 콜백 |
| 포맷 | 사용자 이메일 + 실패 사유 전송 |
| 장점 | 사용자 입장에서 무응답보다 훨씬 낫고, 시스템 신뢰도 향상 |
/brave/search?q=Spring 강의 요청이렇게 구성하면 서비스 운영 신뢰도가 확실히 높아진다. 피드백 내용에 맞춰 실패할 경우에 예외 처리 사항으로 구현하면 될 듯 하다. 지금까지 구현을 중심으로만 생각한 자신에게 좀 더 생각해 볼 여지를 만들 수 있었다. 기본적인 기능이지만 사용자 중심 대응으로 매우 유의미한 개선이다.