RestApi 만들기 - HATEOAS (5)
- SPRING START!/restAPI😢
- 2021. 2. 16. 22:39
HATEOAS란 무엇일까?
Hypermedia as the Engine of Application State의
약자로 요청에 대한 응답값에 사용자가 사이트를 네비게이션 할 수 있는 링크들을 만들어 포함시킬 수 있도록 해주는 라이브러리라고 한다.
이러한 기능을 스프링에서 제공하고 있다. 그래서 스프링 헤이티오스
그럼 헤이티오스는 어떤 것을 해줄까?
크게 2가지 부분으로 나눌 수 있다.
첫 번째는 링크를 만들 수 있는 기능을 제공한다.
링크 만드는 방법 :
linkTo(DeliveryController.class).slash(newDelivery.getId()).toUri();
링크를 만드는 방법은 이것말고도 많은데, 대표적으로 linkTo로 만드는 방법이 존재한다.
lonkTo로 uri를 만들 수 있게 되는데,
이 uri는 Controller위에 작성된
@RequestMapping(value = "/api/delivery", produces = MediaTypes.HAL_JSON_VALUE)
과 같은 말이다.
이렇게 만든 link는 ResponseEntity에 넣어서 보낼 수도 있다.
두 번째는 리소스를 만드는 기능이다.
리소스라하면 자원을 뜻하는데,
이것을 이용해서 로그인했을때 결과와 로그인을 안 했을때의 결과가 서로 다르게 나올 수 있다.
스프링 몇 부터인가 리소스를 만드는 방법이 달라졌다.
이유는 모르겠다.
-
ResourceSupport => RepresentationModel
-
Resource => EntityModel
-
Resources => CollectionModel
-
PagedResources => PagedModel
리소스를 만들기전에 어떤 것을 만들지 미리 테스트코드를 작성해보자.
@Test
void create_Delivery() throws Exception {
DeliveryDto delivery = DeliveryDto.builder()
.item("book")
.user("klom")
.deliveryTime(LocalDateTime.now())
.deliveryEndTime(LocalDateTime.now().plusDays(10))
.build();
mockMvc.perform(post("/api/delivery/")
.accept(MediaTypes.HAL_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(delivery))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("id").value(Matchers.not(10)))
.andExpect(jsonPath("status").value(DeliveryStatus.READY.name()))
.andExpect(jsonPath("_links.query-events").exists())
.andExpect(jsonPath("_links.update-events").exists())
.andExpect(jsonPath("_links.self").exists());
}
query-events : 목록으로 가능 링크
update-events : 업데이트 권한이 있는 사람이 갈 수 있는 링크
self : 보는거?
당연한 이야기지만, 이 코드는 실패하게 된다.
java.lang.AssertionError: No value at JSON path "_links.query-events"
왜냐하면 나는 리소스를 만들지 않았기 때문이다.
리소스를 만들어보자. 근데 이제 리소스라는 단어보다 모델이라는 단어가 더 잘 어울리나?..
트렌드에 맞춰 모델이라고 이름을 지어줬다.
public class DeliveryModel extends RepresentationModel {
private Delivery delivery;
public DeliveryModel(Delivery delivery) {
this.delivery = delivery;
}
public Delivery getDelivery() {
return delivery;
}
}
그럼 이제 이것을 사용해 보자.
기존에는 생성하는 부분은 다음과 같았다.
return ResponseEntity.created(createUri).body(deliver);
원래 delivery를 new DeliveryModel로 감싸주면 된다.
그러면 이제
return ResponseEntity.created(createUri).body(new DeliveryModel(deliver));
이렇게 바뀌었다.
이것을 실행하면....,
{"delivery":{"id":1,"item":"book","user":"klom","deliveryTime":"2021-02-16T21:53:46.112417","deliveryEndTime":"2021-02-26T21:53:46.112448","status":"READY","itemPrice":null,"deliveryCost":5000}}
원래 delivery라는것에 감싸는 형태는 아니였지만,
이것을 추가하고 보니 이렇게 바뀌었다.ㅜㅜ
해결 하는 방법은 3가지가 존재한다.
1. 무식한 방법
엄청 무식한 방법이다.
package restapiset;
import java.time.LocalDateTime;
import org.springframework.hateoas.RepresentationModel;
public class DeliveryModel extends RepresentationModel {
private int id;
private String item;
private String user;
private LocalDateTime deliveryTime;
private LocalDateTime deliveryEndTime;
private DeliveryStatus status;
private Integer itemPrice;
private Integer deliveryCost;
public DeliveryModel(Delivery delivery) {
this.id = delivery.getId();
this.item = delivery.getItem();
this.user = delivery.getUser();
this.deliveryTime = delivery.getDeliveryTime();
this.deliveryEndTime = delivery.getDeliveryEndTime();
this.status= delivery.getStatus();
this.itemPrice = delivery.getItemPrice();
this.deliveryCost = delivery.getDeliveryCost();
}
public int getId() {
return id;
}
public String getItem() {
return item;
}
public String getUser() {
return user;
}
public LocalDateTime getDeliveryTime() {
return deliveryTime;
}
public LocalDateTime getDeliveryEndTime() {
return deliveryEndTime;
}
public DeliveryStatus getStatus() {
return status;
}
public Integer getItemPrice() {
return itemPrice;
}
public Integer getDeliveryCost() {
return deliveryCost;
}
}
Delivery대신에 이렇게 넣어주면 된다.
get을 넣는 이유는 이것으로 값을 찾기 때문이다.
이것을 실행해보면,
{"id":1,"item":"book","user":"klom","deliveryTime":"2021-02-16T22:01:40.563615","deliveryEndTime":"2021-02-26T22:01:40.563635","status":"READY","itemPrice":null,"deliveryCost":5000}
아까 처럼 delivery가 사라졌다.
하지만 이 방법은 코드양이 너무 많아지기 때문에 별로라고 생각이 든다.
두 번째 방법 : get위에 @JsonUnwrapped을 넣어주면 된다.
public class DeliveryModel extends RepresentationModel {
private Delivery delivery;
public DeliveryModel(Delivery delivery) {
this.delivery = delivery;
}
@JsonUnwrapped
public Delivery getDelivery() {
return delivery;
}
}
이렇게 해도.
{"id":1,"item":"book","user":"klom","deliveryTime":"2021-02-16T22:05:20.685635","deliveryEndTime":"2021-02-26T22:05:20.685659","status":"READY","itemPrice":null,"deliveryCost":5000}
delivery가 나오지 않았다.
세 번째방법 : 이 방법은 백기선님이 알려준 방법으로 약간의 꼼수?다.
RepresentationModel의 하위 클래스로 EntityModel이 존재하는데
@Nullable
@JsonUnwrapped
@JsonSerialize(using = MapSuppressingUnwrappingSerializer.class)
public T getContent() {
return content;
}
@JsonUnwrapped가 존재한다는 것을 알 수 있다.
그러면 이것을 어떻게 사용하냐 하면,
public class DeliveryModel extends EntityModel<Delivery> {
public DeliveryModel(Delivery delivery, Link... links) {
super(delivery, links);
}
}
이렇게 사용하고 실행해보자.
{"id":1,"item":"book","user":"klom","deliveryTime":"2021-02-16T22:14:26.890015","deliveryEndTime":"2021-02-26T22:14:26.890037","status":"READY","itemPrice":null,"deliveryCost":5000}
역시 없어졌다.
근데 이거 사용해도 되나 @Deprecated사용하지 말라는데...
잘 모르겠으니 일단 사용하자.
이제 리소스에 연결되는 링크들을 만들어보자. 내가 만들어될 링크는 총 3개
query-events : 목록으로 가능 링크
update-events : 업데이트 권한이 있는 사람이 갈 수 있는 링크
self : 보는거?
model.add(linkTo(DeliveryController.class).withRel("query-events"));
model.add(linkTo(DeliveryController.class).slash(newDelivery.getId()).withRel("update-events"));
model.add(selfRelationBuilder.withSelfRel());
이렇게 수정해줬다.
원하는 그림이다.
그런데 위 링크를 다른곳에 넣을 수 는 없을까?
public DeliveryModel(Delivery delivery, Link... links) {
super(delivery, links);
add(linkTo(DeliveryController.class).withRel("query-events"));
add(linkTo(DeliveryController.class).slash(delivery.getId()).withRel("update-events"));
add(linkTo(DeliveryController.class).slash(delivery.getId()).withSelfRel());
}
이렇게 해결했다.!
super(delivey,links)는 @Deprecated되었기 때문에 다른 방안을 생각해 봐야 될듯싶다.
'SPRING START! > restAPI😢' 카테고리의 다른 글
RestApi 만들기 - 각종 문서 조각 생성하기(6) (0) | 2021.02.24 |
---|---|
RestApi 만들기 - spring-rest-docs (5) (0) | 2021.02.19 |
RestApi 만들기 - badRequest (4) (0) | 2021.02.13 |
RestApi 만들기 - 입력값 제한하기 + 입력값 이외의 에러 발생 (3) (0) | 2021.02.09 |
RestApi 만들기 - Repository추가하기 (2) (0) | 2021.02.06 |