반응형
6. REST 서비스 생성하기
- 최근에는 모바일 장치, 태블릿, 스마트 워치, 음성 기반 장치들이 흔히 사용된다.
- 또한 웹 브라우저 기반의 애플리케이션조차도 서버 위주로 실행되기보다는 프로세서가 있는 클라이언트에서 자바스크립트 애플리케이션으로 많이 실행된다.
- 이처럼 클라이언트 측에서 다양한 선택을 할 수 있으므로, 많은 애플리케이션이 클라이언트에 더 다가갈 수 있는 사용자 인터페이스 설계를 적용하고 있음.
- 또한 모든 종류의 클라이언트가 백엔드 기능과 상호작용할 수 있게 서버는 클라이언트가 필요로 하는 API를 제공.
REST 컨트롤러 작성
@RestController
@RequestMapping(path = "/design", produces = "application/json")
@CrossOrigin(origins = "*")
public class DesignTacoController {
private final TacoRepository tacoRepository;
public DesignTacoController(TacoRepository tacoRepository) {
this.tacoRepository = tacoRepository;
}
@GetMapping("/recent")
public Iterable<Taco> recentTacos() {
PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
return tacoRepository.findAll(page).getContent();
}
}
@RestController
- @Controller, @Service와 같이 스테레오타입 애노테이션으로, 이 애노테이션이 지정된 클래스를 스프링의 컴포넌트 검색으로 찾을 수 있다.
- @Controller(뷰로 보여줄 값을 반환)와 다르게 컨트롤러의 모든 HTTP 요청 처리 메소드에서 HTTP body에 직접 쓰는 값을 반환한다는 것을 스프링에게 알려줌.
@CrossOrigin
- 클라이언트는 API와 별도의 도메인(호스트와 포트 중 하나라도 다른)에서 실행 중이므로 클라이언트에서 API를 사용하지 못하도록 브라우저가 막는다.
- 이러 제약은 서버 응답에 CORS 헤더를 포함시켜 극복할 수 있음.
- @CrossOrigin은 다른 도메인의 클라이언트에서 해당 REST API를 사용할 수 있게 해주는 어노테이션.
@RestController
@RequestMapping(path = "/design", produces = "application/json")
@CrossOrigin(origins = "*")
public class DesignTacoController {
private final TacoRepository tacoRepository;
public DesignTacoController(TacoRepository tacoRepository) {
this.tacoRepository = tacoRepository;
}
...
@GetMapping("/{id}") // GET: http://localhost:8080/design/4
public ResponseEntity<Taco> findTacoById(@PathVariable("id") Long id) {
Optional<Taco> optionalTaco = tacoRepository.findById(id);
if (optionalTaco.isPresent()) {
return new ResponseEntity<>(optionalTaco.get(), HttpStatus.OK);
}
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
@PostMapping(consumes = "application/json") // Content-Type: application/json와 일치하는 요청만 처리.
@ResponseStatus(HttpStatus.CREATED)
public Taco postTaco(@RequestBody Taco taco) {
return tacoRepository.save(taco);
}
}
PUT vs PATCH
- PUT
- 데이터를 변경하는 데 사용되기는 하지만, 실제로는 GET과 반대의 의미를 갖는다.
- 즉, GET 요청은 서버로 부터 클라이언트로 데이터를 전송하는 반면, PUT 요청은 클라이언트로부터 서버로 데이터를 전송한다.
- 이런 관점에서 PUT은 데이터 전체를 교체하는 것.
- PATCH
- 데이터의 일부분을 변경하는 것.
@RestController
@RequestMapping(path = "/orders", produces = "application/json")
@CrossOrigin(origins = "*")
public class OrderApiController {
...
@PutMapping(path = "/{orderId}", consumes = "application/json")
public Order putOrder(@RequestBody Order order) {
return repo.save(order);
}
@PatchMapping(path = "/{orderId}", consumes = "application/json")
public Order patchOrder(@PathVariable("orderId") Long orderId, @RequestBody Order patch) {
Order order = repo.findById(orderId).get();
if (patch.getDeliveryName() != null) {
order.setDeliveryName(patch.getDeliveryName());
}
if (patch.getDeliveryStreet() != null) {
order.setDeliveryStreet(patch.getDeliveryStreet());
}
...
return repo.save(order);
}
}
PATCH 를 하는 방법은 여러 가지가 있다.
- patchOrder() 메소드에 적용하는 방법은 두가지 제약을 갖는다.
- 만일 특정 필드의 데이터를 변경하지 않는다는 것을 나타내기 위해 null 값이 사용된다면 해당 필드를 null로 변경하고 싶을 때 클라이언트에서 이를 나타낼 수 있는 방법이 필요하다.
- 컬렉션에 저장된 항목을 삭제 혹은 추가할 방법이 없다. 따라서 클라이언트가 컬렉션의 항목을 삭제 혹은 추가하려면 변경될 컬렉션 데이터 저체를 전송해야 한다.
- PATCH 요청을 처리하는 방법이나 수신 데이터의 형식에 관해 반드시 지켜야 할 규칙은 없다. 따라서 클라이언트는 실제 도메인 데이터를 전송하는 대신 PATCH에 적용할 변경사항 명세를 전송할 수 있다.
- 이때 도메인 데이터 대신 PATCH 명세를 처리하도록 요청 처리 메소드가 작성되어야 한다.
하이퍼 미디어 사용
HATEOAS
- 기본적인 API에서는 해당 API를 사용하는 클라이언트가 API의 URL Scheme을 알아야 한다.
- API 클라이언트 코드에서는 하드코딩된 URL 패턴을 사용하고 문자욜로 처리한다. 그러나 API의 URL 스킴이 변경되면 어떻게 될까?
- REST API를 구현하는 다른 방법은 HATEOAS(HyperMedia As the Engine Of Application State)가 있다.
- API로 부터 반환되는 리소스에 해당 리소스와 관련된 하이퍼링크들이 포함된다. 따라서 클라이언트가 최소한의 API URL만 알면 반환되는 리소스와 관련하여 처리 가능한 다른 API URL들을 알아내어 사용할 수
있다.
{
"content": "Hello, World!",
"_links": {
"self": {
"href": "http://localhost:8080/greeting?name=World"
// JSON 응답에 하이퍼링크를 포함
}
}
}
- 이런 형태의 HATEOAS를 HAL이라고 한다. 이것은 JSON응답에 하이퍼링크를 포함시킬 때 주로 사용되는 형식.
- 각 요소는 _links라는 속성을 포함하는데, 이 속성은 클라이언트가 관련 API를 수행할 수 있는 하이퍼링크를 포함한다.
Spring Data REST
- 스프링 데이터에는 애플리케이션의 API를 정의하는 데 도움을 줄 수 있는 기능도 있다.
- 스프링 데이터 REST는 스프링 데이터의 또 다른 모듈이며, 스프링 데이터가 생성하는 Repository의 REST API를 자동 생성한다.
- 따라서 스프링 데이터 REST를 우리 빌드에 추가하면 우리가 정의한 각 Repository 인터페이스를 사용하는 API를 얻을 수 있다.
정리
- REST 엔드포인트는 스프링 MVC, 그리고 브라우저 지향의 컨트롤러와 동일한 프로그래밍 모델을 따르는 컨트롤러로 생성할 수 있다.
- 모델과 뷰를 거치지 않고 요청 응답 몸체에 직접 데이터를 쓰기 위해 컨트롤러의 핸들러 메소드에는 @ResponseBody 어노테이션을 지정할 수 있으며, ResponseEntity 객체를 반환할 수 있다.
- @RestController 어노테이션을 컨트롤러에 지정하면 해당 컨트롤러의 각 핸들러 메소드에 @ResponseBody를 지정하지 않아도 되므로 컨트롤러를 단순화 해준다.
- 스프링 HATEOAS는 스프링 MVC에서 반환되는 리소스의 하이퍼링크를 추가할 수 있게 한다.
- 스프링 데이터 Repository는 스프링 데이터 REST를 사용하는 REST API로 자동 노출될 수 있다.
7. REST 서비스 사용하기
- 스프링 애플리케이션에서 API를 제공하면서 다른 애플리케이션의 API를 요청.
- 마이크로서비스에서는 REST API를 많이 사용.
- RestTemplate
- 스프링 프레임워크에서 제공하는 간단하고 동기화된 REST 클라이언트.
- Traverson
- 스프링 HATEOAS에서 제공하는 하이퍼링크를 인식하는 동기화 REST 클라이언트로 같은 이름의 자바스크립트 라이브러리로부터 비롯.
- WebClient
- 스프링 5에서 소개된 반응형 비동기 REST 클라이언트
RestTemplate으로 REST 엔드포인트 사용.
- 클라이언트 입장에서 REST 리소스와 상호작용하려면 해야 할 일이 많아서 코드가 장황해진다.
- 저수준의 HTTP 라이브러리로 작업하면서 클라이언트는 클라이언트 인스턴스와 요청 객체를 생성하고, 해당 요청을 실행하고, 응답을 분석해 관련 되메인 객체와 연관시켜 처리해야 함.
- 또한 발생될 수 있는 예외도 처리해야 함.
- 이러한 장황한 코드를 피하기 위해 스프링은 RestTemplate을 제공.
- RestTemplate은 REST 리소스를 사용하는 데 번잡한 일을 처리해 준다.
GET
@Slf4j
@Service
public class TacoCloudClient {
private final RestTemplate restTemplate;
public TacoCloudClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* getForObject()의 두번째 매개변수는 응답이 바인딩 되는 타입이다.
* 여기서는 JSON 형식인 응답 데이터가 객체로 역직렬화되시ㅓ 반환된다.
*/
public Ingredient getIngredientById(String ingredientId) {
return restTemplate.getForObject("http://localhost:8080/ingredients/{id}", Ingredient.class, ingredientId);
}
/**
* Map을 사용해서 URL 변수들을 지정할 수 있다.
*/
public Ingredient getIngredientById2(String ingredientId) {
Map<String, String> urlVariables = new HashMap<>();
urlVariables.put("id", ingredientId);
return restTemplate.getForObject("http://localhost:8080/ingredients/{id}", Ingredient.class, urlVariables);
}
/**
* URI 매개변수를 사용할 때는 URI 객체를 구성하여 getForObject()를 호출해야한다.
*/
public Ingredient getIngredientById3(String ingredientId) {
Map<String, String> urlVar = new HashMap<>();
urlVar.put("id", ingredientId);
URI uri = UriComponentsBuilder
.fromHttpUrl("http://localhost:8080/ingredients/{id}")
.build(urlVar);
return restTemplate.getForObject(uri, Ingredient.class);
}
/**
* 응답의 Date헤더를 확인하고 싶은 경우
*/
public Ingredient getIngredientById4(String ingredientId) {
ResponseEntity<Ingredient> responseEntity = restTemplate.getForEntity("http://localhost:8080/ingredients/{id}", Ingredient.class, ingredientId);
log.info("Fetched Time: " + responseEntity.getHeaders().getDate());
return responseEntity.getBody();
}
}
PUT, POST, DELETE
@Slf4j
@Service
public class TacoCloudClient {
private final RestTemplate restTemplate;
public TacoCloudClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public void updateIngredient(Ingredient ingredient) {
restTemplate.put("http://localhost:8080/ingredients/{id}", ingredient, ingredient.getId());
}
public void deleteIngredient(Ingredient ingredient) {
restTemplate.delete("http://localhost:8080/ingredients/{id}", ingredient.getId());
}
public Ingredient createIngredient(Ingredient ingredient) {
return restTemplate.postForObject("http://localhost:8080/ingredients", ingredient, Ingredient.class);
}
}
반응형
'Application > Spring Framework' 카테고리의 다른 글
[스프링 인 액션 정리] 10장. 리액터 개요 (0) | 2021.01.09 |
---|---|
[스프링 인 액션 정리] 8장. 비동기 메시지 전송하기 (0) | 2021.01.08 |
[스프링 인 액션 정리] 5장. 구성 속성 사용하기 (0) | 2021.01.07 |
[스프링 인 액션 정리] 4장. 스프링 시큐리티 (0) | 2021.01.06 |
[스프링 인 액션 정리] 3장. 데이터로 작업하기 (0) | 2021.01.05 |