RestApi ๋ง๋ค๊ธฐ - HATEOAS (5)
- ๊ฐ๋ฐ/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๋์๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋ฐฉ์์ ์๊ฐํด ๋ด์ผ ๋ ๋ฏ์ถ๋ค.
'๊ฐ๋ฐ > 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 |