TIL - 0606
REST API, 그리고 스프링에서 REST API 디자인해보기
- REST한 API(REST API)
- 간단하게 말하자면 API 디자인 가이드라인(원칙)
- API란? 원격으로 다른 시스템의 메소드 콜을 할 수 있도록 하는 것
- 어떤 클라이언트라도 일관된 방법으로 콜 할 수 있게, 서버에 변동이 생겨도 클라이언트는 아무런 영향 받지 않도록 - 하위 호환성, 일관성을 띄는 API 디자인 가이드라인(원칙)
- REST 왜 만들었을까?
- 현재의 웹 환경을 파괴하지않고도 HTTP를 발전시킬 수는 없을까 라는 생각 아래에 만들어짐
- REST한 API가 되려면 지켜야하는 디자인 원칙 : 각각의 디자인 원칙은 하나하나가 디자인 제약조건의 집합임(여러 제약조건이 모여 만들어진 집합)
- client - server
- stateless : 무상태성 유지(클라이언트 컨텍스트 저장하면 안됨)
- cache : 응답 캐싱
- code on demand : 서버가 보낸 코드를 클라이언트에서 실행시킬 수 있어야함(js)
- layered system : 서버의 아키텍쳐 디자인(여러 대의 서버 로드밸런싱, 프록시 서버 등)에 대해서는 클라이언트가 알 필요가 없음 - 요청값(URI)만 있으면 됨
- uniform interface : 일관성 있는 인터페이스(디자인)
- 가장 지키지않고 지키기 번거로운 uniform interface : 어떤 제약조건들이 있길래? 나머지 제약조건들은 HTTP를 지키면서 API 디자인을 하면 지켜짐
- 자원에는 식별자(@Id)가 있어야함 : URI를 통해 자원을 식별할 수 있어야함
- 메세지에는 자원에 대한 어떤 처리(GET/POST/PUT/DELETE)를 할 것인지 명시되어있어야함
- 메세지는 스스로를 설명할 수 있어야함 : 자원이 의미하는 바는 무엇인지 어디(명세)를 참조해야하는지, 호스트가 누구인지, 다음 가야할 링크가 뭔지, 데이터 타입이 무엇인지 등 어떤 클라이언트가 요청을 하더라도 메세지만 응답받으면 처리할 수 있도록 응답 메세지를 작성해야함
- 모든 어플리케이션 상태 전이는 하이퍼링크를 통해서 이뤄짐(HATEOAS) : 클라이언트에 링크가 제시되어야함, 응답 결과 어디로 가야하는지(location)
-
알아본 바로는 클라이언트가 무엇이라도 일관성(클라이언트가 무엇이라도, 서버에 변경이 생겨도) 있게 통신할 수 있게하는 것 그러려면 메세지에 필요한 정보를 다 넣어서 클라이언트가 결과 처리를 할 수 있도록 하는 것이 REST한 API 디자인이 아닐까 함 : 등장 배경이 그러하듯..!
- @RestController : Spring에서 제공하는 REST API용 컨트롤러 어노테이션
- @Controller + @ResponseBody를 합친 것 : 이전에는 두개를 써야했는데 하나로 퉁침 - 각 메소드(컨트롤러 액션)마다 @ResponseBody를 써야했으므로 매우 비효율적
- @RestController를 적용하면? 기존 Controller flow(컨트롤러 처리 -> 뷰 리졸버 -> 뷰 응답)이 아닌 컨트롤러 처리 -> 메세지 컨버터 -> 응답 메세지 작성 후 응답
- 메세지 컨버터 : 디폴트로 Jackson 라이브러리를 사용함 - 스프링부트는 web 스타터 모듈 하위에 json 모듈에 Jackson 디펜던시가 포함되어있음
- 컨트롤러 리턴에서 객체를 리턴하면 메세지 컨버터가 요청 타입(xml, json)에 따라 변환해서 응답 바디에 변환 결과를 부과함
@RestController
@RequestMapping("/questions")
public class QusetionController {
@Resource
private QnaService qnaService;
@PostMapping
public ResponseEntity<Void> create(@Valid @RequestBody QuestionDto questionDto) {
Qusetion addedQuestion = qnaService.add(questionDto);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create("/questions/" + addedQuestion.getId()));
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
}
ResponseEntity<T>
: T(타입파라미터)를 json으로 변환함(메세지 컨버팅), 헤더에 추가적인 내용을 부과할 때 ResponseEntity를 리턴타입으로 사용함- 다음 가야할 곳을 클라이언트에 알리기위해 헤더에 location을 부과함(HATEOAS)
- 그냥 오브젝트를 리턴타입으로 사용하면 헤더 변경없이 바디에 해당 오브젝트를 컨버팅해서 응답메세지 완성
스프링 API(또는 J2EE) 커스텀 파라미터(HandlerMethodArgumentResolver)
- HandlerMethodArgumentResolver : 메소드의 파라미터를 커스텀 하는 방법
- 예외처리를 할 수 있어서 좋음
@Valid
: 엔티티 validation을 위한 커스텀 파라미터, 어노테이션이 설정된 엔티티의 조건에 부합하지않을 때 MethodArgumentNotValidException를 발생시킴@RequestBody
: 요청 메세지의 바디를 특정 타입(클래스)오브젝트와 바인딩함 - REST API를 구현할 때 사용(요청 바디에 json 데이터 - 메세지 컨버터에 의해 변환)- 두 커스텀 파라미터 적용해보기(+ 예외처리 ControllerAdvice)
- 예외가 발생했을 때에도 클라이언트에 응답을 json(오브젝트 리턴하면 메세지 컨버터가 알아서 컨버팅해줌)으로
/* controller */
@PostMapping
public ResponseEntity<Void> create(@Valid @RequestBody UserDto user) {
User savedUser = userService.add(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create("/api/users/" + savedUser.getId()));
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
/* ControllerAdvice */
@RestControllerAdvice
public class ValidationExceptionControllerAdvice {
private static final Logger log = LoggerFactory.getLogger(ValidationExceptionControllerAdvice.class);
@Resource(name = "messageSourceAccessor")
private MessageSourceAccessor msa;
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorsResponse handleValidationException(MethodArgumentNotValidException exception) {
List<ObjectError> errors = exception.getBindingResult().getAllErrors();
ValidationErrorsResponse response = new ValidationErrorsResponse();
for (ObjectError objectError : errors) {
log.debug("object error : {}", objectError);
FieldError fieldError = (FieldError) objectError;
response.addValidationError(new ValidationError(fieldError.getField(), getErrorMessage(fieldError)));
}
return response;
}
}
해야할, 알아봐야할 것들
- HTTP 인증프로토콜(2) 다이제스트 인증에 대해 알아보기
-
- 스프링부트 스타터 디펜던시 알아보기
- 레이어 구조 정리하기
- JPA Persistence Context 한번 더 보기