SPA
- 스프링 MVC를 사용해서 전통적인 MPA(Multi-Page Application)로 개발했던 것들을 앵귤러 기반의 SPA로 대체해보자!
- SPA에서는 프레젠테이션 계층이 백엔드 처리와 거의 독립적이므로, 백엔드 기능은 같으면서 사용자 인터페이스만 다르게 개발할 수 있다.
- 또한, 이런 API를 사용할 수 있는 다른 애플리케이션과 통합할 수 있는 기회를 제공한다.
REST 컨트롤러
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/orders", produces = "application/json")
@CrossOrigin(origins = "*") // 서로 다른 도메인 간의 요청 허용
public class MemberController {
private final OrderRepository orderRepo;
}
@RestController
- @RestController 어노테이션은 스테레오 타입 어노테이션으로, 이 어노테이션이 지정된 클래스는 스프링의 컴포넌트 스캔으로 찾을 수 있다.
- 또한 컨트롤러의 모든 HTTP 요청 처리 메서드에서 HTTP 응답 바디에 담아서 보낼 값을 반환한다는 것을 스프링에게 알려주기 때문에, 반환값이 뷰를 통해 HTML로 변환되지 않고 직접 HTTP 응답으로 브라우저에 전달된다.
- 만약 @Controller 어노테이션을 사용할 경우에는 클래스의 모든 요청 처리 메서드에 @ResponseBody 어노테이션을 지정해야만 @RestController와 같은 효과를 얻을 수 있다.
@RequestMapping
- @RequestMapping 어노테이션의 produces 속성을 지정하면, 요청의 Accept 헤더에 지정한 형식이 포함된 요청만을 메서드에서 처리한다.
- 위의 경우 "application/json"이 포함된 요청만 처리
@CrossOrigin
- 클라이언트와 서버가 별도의 도메인에서 실행 중일 경우, 서버 응답에 CORS 헤더를 포함시켜야 한다.
- 스프링에서는 @CrossOrigin 어노테이션을 지정하면 CORS를 적용할 수 있다.
- 즉, @CrossOrigin은 다른 도메인의 클라이언트에서 해당 REST API를 사용할 수 있게 해주는 스프링 어노테이션
GET 요청 (데이터 가져오기)
@GetMapping
public Iterable<Order> findAll() {
PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
return orderRepo.findAll(page).getContent();
}
@GetMapping("/{id}")
public ResponseEntity<Order> findById(@PathVariable("id") Long id) {
Optional<Order> optOrder = orderRepo.findById(id);
return optOrder
.map(order -> new ResponseEntity<>(order, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity<>(null, HttpStatus.NOT_FOUND));
}
@PathVariable
- @PathVariable에 의해 {id} 플레이스 홀더와 대응되는 id 매개변수에 해당 요청의 실제 값이 지정된다.
@ResponseEntity
- findById()는 결과가 없을수도 있기 때문에 Optional<T>로 반환된다.
- 만약 결과가 있다면 HTTP 200 OK 상태코드를 갖는 ResponseEntity에 Order 객체를 담아서 반환하고, 없다면 HTTP 404 NOT FOUND 상태코드를 갖는 ResponseEntity에 null을 담아서 반환한다.
POST 요청 (데이터 전송하기)
// Content-type이 application/json과 일치하는 요청만 처리
@PostMapping(consumes = "application/json")
@ResponseStatus(HttpStatus.CREATED)
public Order postOrder(@RequestBody Order order) {
return orderRepo.save(order);
}
@RequestBody
- 요청 바디의 JSON 데이터가 객체로 변환되어 매개변수와 바인딩된다는 것을 나타낸다.
- 만약 @RequestBody을 지정하지 않으면 요청으로 들어오는 매개변수가 곧바로 객체와 바인딩되는 것으로 간주되기 때문에 꼭 지정해주어야 한다.
@ResponseStatus
- @ResponseStatus(HttpStatus.CREATED) 어노테이션은 해당 요청이 성공이면서 요청의 결과로 리소스가 생성되면 HTTP 201 CREATED 상태코드가 클라이언트에게 전달된다.
- @ResponseStatus를 사용하지 않았을 때 받게되는 HTTP 200 OK 상태코드보다 더 상세한 설명을 알려줄 수 있으므로, @ResponseStatus를 사용하여 클라이언트에게 정확한 HTTP 상태코드를 전달하는 것이 좋다.
PUT/PATCH 요청 (데이터 변경하기)
- 일반적으로 POST 요청은 데이터 생성에 사용되고, 변경 시에는 PUT이나 PATCH 요청을 사용한다.
- PUT은 데이터를 변경하는 데 사용되기는 하지만, 실제로는 GET과 반대의 의미
- 즉, GET 요청은 서버로부터 클라이언트로 데이터를 전송하는 것이고, PUT 요청은 클라이언트로부터 서버로 데이터를 전송하는 것
- 이런 관점에서 PUT은 데이터 전체를 교체하는 것이고, 반면에 PATCH는 데이터의 일부분을 변경하는 것
- @PutMapping과 @PatchMapping 모두 요청 경로는 변경되는 데이터를 참조한다.
@PutMapping
@PutMapping("/{orderId}")
public Order putOrder(@RequestBody Order order) {
return orderRepo.save(order);
}
- 특정 주문의 속성값 일부를 변경하고 싶을 때 PUT 요청으로 처리하면, 클라이언트에서 해당 주문과 관련된 데이터 전체를 PUT 요청으로 제출해야 한다.
- PUT은 해당 URL에 이 데이터를 쓰라는 의미이므로 이미 존재하는 해당 데이터 전체를 교체하고, 만약 해당 주문의 속성이 생략되면 이 속성의 값은 null로 변경된다.
@PatchMapping
@PatchMapping(path = "/{orderId}", consumes = "application/json")
public Order patchOrder(@PathVariable("orderId") Long orderId,
@RequestBody Order patch) {
Order order = orderRepo.findById(orderId).get();
if (patch.getDeliveryName() != null) {
order.setDeliveryName(patch.getDeliveryName());
}
// 생략
return orderRepo.save(order);
}
- 데이터 일부만 변경하고자 할 때는 PATCH 요청으로 처리하며, 클라이언트에서는 변경할 속성만 전송하면 되고 서버에서는 클라이언트에서 지정하지 않은 속성의 기존 데이터를 그대로 보관할 수 있다.
- 이때 스프링 MVC 어노테이션들은 어떤 종류의 요청을 메서드에서 처리하는지만 나타내고 해당 요청이 어떻게 처리되는지는 나타내지 않기 때문에, PATCH가 부분 변경의 의미를 내포하고 있더라도 실제로 변경을 수행하는 메서드 코드를 작성해야 한다.
- PUT 요청에서는 해당 주문 데이터를 전송된 객체로 완전히 교체했지만, PATCH 요청에서는 객체의 각 필드 값이 null이 아닌지 확인하고 기존 주문 데이터를 변경해야 한다.
더보기
PATCH 고려사항
- 만약 특정 필드의 데이터를 변경하지 않는다는 것을 나타내기 위해 null 값을 사용한다면, 해당 필드를 null로 변경하고 싶을 때 클라이언트에서 이를 나타낼 수 있는 방법이 필요하다.
- 컬렉션에 저장된 항목을 삭제 혹은 추가할 방법이 없기 때문에, 클라이언트가 컬렉션의 항목을 삭제 혹은 추가하려면 변경될 컬렉션 데이터 전체를 전송해야 한다.
클라이언트는 실제 도메인 데이터를 전송하는 대신 PATCH에 적용할 변경사항 명세를 전송할 수 있으며, 이때는 도메인 데이터 대신 PATCH 명세를 처리하도록 요청 처리 메서드가 작성되어야 한다.
DELETE 요청 (데이터 삭제하기)
@DeleteMapping("/{orderId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable("orderId") Long orderId) {
try {
orderRepo.deleteById(orderId);
} catch (EmptyResultDataAccessException e) {}
}
- 데이터를 삭제할 때는 클라이언트에서 DELETE 요청으로 삭제를 요청하고, URL 경로 변수로 제공된 주문 ID를 인자로 받아서 리포지토리에 전달한다.
- 메서드가 실행될 때 해당 주문이 존재하면 삭제되고, 없으면 EmptyDataAccessException이 발생한다.
- 데이터를 삭제하는 경우에는 클라이언트에게 데이터를 반환할 필요가 없으므로, 보통 DELETE 요청의 응답은 바디에 데이터를 갖지 않으며, 반환 데이터가 없다는 것을 클라이언트가 알 수 있도록 HTTP 204 NO CONTENT 상태코드를 사용한다.
'Spring > Rest API' 카테고리의 다른 글
REST 서비스 사용 (0) | 2022.01.30 |
---|---|
스프링 데이터 REST (0) | 2022.01.30 |
RestContoller 요청과 응답 방법 (0) | 2022.01.20 |
REST API 인증 기법 (0) | 2022.01.20 |
REST 아키텍처 패턴 (0) | 2022.01.20 |