๊ธฐ๋ณธ์ ์ผ๋ก 2๊ฐ์ง์ ํํ๋ก ์ ๊ณต
https://tools.ietf.org/html/rfc5988
- Target URI:
- ์ ์ด, ์์ฒญ์ ํ ์ ์๋ link.
href๋ผ๊ณ ํํ
- Link Relation Type:
- target resource์ URI์ ํ์ฌ URI๊ฐ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋์ด์๋์ง ๊ด๊ณ์ ๋ํด ์ ์
rel๋ผ๊ณ ํํ
- Attribute for target URI:
- target link๋ฅผ ๋ถ๊ฐ ์ค๋ช ํ ์ ์๋ ์์
- title, media, type ๋ฐ ์ ์์ ๋ฐ๋ผ ์ฌ์ฉ
https://tools.ietf.org/html/draft-kelly-json-hal-03
- HAL์ json๋๋ xml์ content์ link๋ฅผ ๋ด๋ ๋ฐฉ์
์๋ 2๊ฐ์ contentType ์ฌ์ฉํ์ฌ ํํ
application/hal+xml,application/hal+json
{
"_links": {
"self": {
"href": "/boards"
}
},
"_embeded": {
"boards": [ {
"idx": 1,
"title": "1 title",
"contents": "1 contents"
"_links": {
"self": {
"href": "/boards/1"
}
}
},{
"idx": 2,
"title": "2 title",
"contents": "2 contents"
"_links": {
"self": {
"href": "/boards/2"
}
}
} ]
}
}_link: ํธ์ถํ resource์ ์ฐ๊ด์๋ link๋ค์ collection์ ํ์์ผ๋ก ํํ_embedded: ํด๋น resource๊ฐ ํฌํจํ๊ณ ์๋ ์์๋ฅผ ํํself: ๊ฐ Resource์ ๋ํด์ ๋ณธ์ธ์ Resource์ ๋ํ link๋ ๋ฐ๋์ ํฌํจ
https://docs.spring.io/spring-hateoas/docs/current/reference/html
// build.gradle
dependencies {
/** Hateoas */
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
}// PaymentController.java
public class PaymentController {
private final PaymentService paymentService;
@GetMapping(value = "/payment", produces = {MediaType.ALL_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Response> itemPaymentInformation(Request request) {
PaymentInfo paymentInfo = paymentService.retrievePayment(request.getCode());
Response response = Response.of(paymentInfo);
return ResponseEntity.ok(response);
}
@GetMapping(value = "/v1.0/payment", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Object> itemPaymentInformationForHalJson(Request request) {
ResponseEntity<Response> response = itemPaymentInformation(request);
ItemPaymentInfoDto.Response body = response.getBody();
return ResponseEntity.ok(
HalModelBuilder.emptyHalModel()
.entity(ResponseForHal.of(body.getHancoin()))
.embed(body.getAnyList(), LinkRelation.of("list"))
.embed(body.getAnotherList(), LinkRelation.of("anotherList"))
.link(linkTo(methodOn(getClass()).itemPaymentInformation(null)).withSelfRel())
.link(linkTo(getClass()).withRel("profile").withHref("http://domain/v1.0/docs/index.html"))
.build());
}
@Getter
@AllArgsConstructor(staticName = "of")
public static class ItemPaymentInformationForHal {
private long hancoin;
}
}application/json, application/hal+json ๋ ์์ฒญ์ ๋ค ๋ฐ๊ธฐ ์ํด์ ์๋์ ๊ฐ์ด ๋๊ฐ์ ๋ฉ์๋๋ฅผ ์ค์ ํ์ฌ์ค๋ค .
itemPaymentInformation:produces = {MediaType.ALL_VALUE, MediaType.APPLICATION_JSON_VALUE}itemPaymentInformationForHalJson:produces = MediaTypes.HAL_JSON_VALUE
itemPaymentInformationForHalJson๋กapplication/hal+json์์ฒญ์ด ๋ค์ด์ค๋ฉดapplication/json์์ฒญ์ด ๋ค์ด์ค๋itemPaymentInformation๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ- ์์ body๋ฅผ ๊บผ๋ด์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ํํ์ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด์ค๋ค.
- entity : Object ๋ด๋ถ์ field๋ฅผ ์๋ต
root์ ์ ์ํ๋ค. - embed : Collection ํํ๋ฅผ
_embedded๋ด๋ถ์ ์ ์ํด์ค๋ค. - link :
_links์ ๋ด์ฉ์ ์ฑ์๋ฃ์ด์ค๋ค.- withRel : link์ ํ์๋ key ์ด๋ฆ
- withHref : link์ ํ์๋ url ๊ฐ
RepresentationModelAssembler๋ฅผ ์์๋ฐ์ Object์ธ ์ํฉ๊ณผ Collection ํํ์ ์ํฉ์ ๊ฐ๊ฐ ์ ์ํ์ฌ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
@Component
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EmployeeDTO> {
@Override
public EmployeeDTO toModel(Employee employee) {
ModelMapper modelMapper = new ModelMapper();
EmployeeDTO employeeDto = modelMapper.map(employee, EmployeeDTO.class);
Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
employeeDto.add(selfLink);
return employeeDto;
}
@Override
public CollectionModel<EmployeeDTO> toCollectionModel(Iterable<? extends Employee> employeesList) {
ModelMapper modelMapper = new ModelMapper();
List<EmployeeDTO> employeeDTOS = new ArrayList<>();
for (Employee employee : employeesList){
EmployeeDTO employeeDto = modelMapper.map(employee, EmployeeDTO.class);
employeeDto.add(linkTo(methodOn(EmployeeController.class)
.getEmployeeById(employeeDto.getId())).withSelfRel());
employeeDTOS.add(employeeDto);
}
return new CollectionModel<>(employeeDTOS);
}
}
https://leedo1982.github.io/wiki/HAL/
๋ ๋ง์ ๋ด์ฉ์ ๋ด๊ธฐ ์ํ .. https://github.com/spring-projects/spring-hateoas-examples/tree/main/affordances
// ํธ์ถ @GetMapping(value = "/v1.0/payment") public ResponseEntity test(@Valid RequestDto request) {}
``` json
{
"_links": {
"self": {
"href": "http://localhost:8080/"
}
}
}
// ํธ์ถ
@GetMapping(value = "/v1.0/payment")
public ResponseEntity<Object> test(@RequestParam String email,
@RequestParam String name) {}{
"_links": {
"self": {
"href": "http://localhost:8080?email=llsb156@gmail.com&name=Leesangbae"
}
}
}// ํธ์ถ
@GetMapping(value = "/v1.0/payment")
public ResponseEntity<Object> test(String email,
String name) {}{
"_links": {
"self": {
"href": "http://localhost:8080/"
}
}
}Spring-Boot 2.1 / Spring 5.1๋ถํฐ X-Forwarded-*๋ฅผ ์ฒ๋ฆฌํ๋ ์ฑ ์์ Spring HATEOAS ์์ Spring MVC๋ก ์ด์ ํ์ฌ ๋์์ ์ํ์
spring-projects/spring-framework#21209
ํด๋น Bean ์์ฑํ์ฌ ํด๊ฒฐ
@Bean
FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new ForwardedHeaderFilter());
return bean;
}