Skip to content

Latest commit

ย 

History

History
223 lines (193 loc) ยท 7 KB

File metadata and controls

223 lines (193 loc) ยท 7 KB

HATEOAS

HATEOAS References

๊ธฐ๋ณธ์ ์œผ๋กœ 2๊ฐ€์ง€์˜ ํ˜•ํƒœ๋กœ ์ œ๊ณต

WebLink(RFC5988)

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 ๋ฐ ์ •์˜์— ๋”ฐ๋ผ ์‚ฌ์šฉ

HAL (Hypermedia API Language)

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
  1. itemPaymentInformationForHalJson๋กœ application/hal+json ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด
  2. application/json์š”์ฒญ์ด ๋“ค์–ด์˜ค๋Š” itemPaymentInformation ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ 
  3. ์•ˆ์— body๋ฅผ ๊บผ๋‚ด์„œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.
  • entity : Object ๋‚ด๋ถ€์˜ field๋ฅผ ์‘๋‹ต root์— ์ •์˜ํ•œ๋‹ค.
  • embed : Collection ํ˜•ํƒœ๋ฅผ _embedded ๋‚ด๋ถ€์— ์ •์˜ํ•ด์ค€๋‹ค.
  • link : _links์— ๋‚ด์šฉ์„ ์ฑ„์›Œ๋„ฃ์–ด์ค€๋‹ค.
    • withRel : link์— ํ‘œ์‹œ๋  key ์ด๋ฆ„
    • withHref : link์— ํ‘œ์‹œ๋  url ๊ฐ’

Assembler๋ฅผ ์ด์šฉํ•œ ๋ณ€ํ™˜

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);
    }
}
 

HAL ๊ทœ์น™

https://leedo1982.github.io/wiki/HAL/

Affordances

๋” ๋งŽ์€ ๋‚ด์šฉ์„ ๋‹ด๊ธฐ ์œ„ํ•œ .. https://github.com/spring-projects/spring-hateoas-examples/tree/main/affordances

Get link์— Parameter ์ •๋ณด ๋„ฃ๊ธฐ

Request๋ฅผ Object๋กœ ๋ฐ›์•˜์„๋•Œ``` java

// ํ˜ธ์ถœ @GetMapping(value = "/v1.0/payment") public ResponseEntity test(@Valid RequestDto request) {}


``` json
{
    "_links": {
        "self": {
            "href": "http://localhost:8080/"
        }
    }
}

@RequestParam์œผ๋กœ ๋ฐ›์•˜์„๋•Œ

// ํ˜ธ์ถœ 
@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"
        }
    }
}

@RequestParam Annotation ์ œ๊ฑฐํ–ˆ์„ ๊ฒฝ์šฐ

// ํ˜ธ์ถœ 
@GetMapping(value = "/v1.0/payment")
public ResponseEntity<Object> test(String email,
                                   String name) {}
{
    "_links": {
        "self": {
            "href": "http://localhost:8080/"
        }
    }
}

X-Forwarded-* ํ—ค๋”๋“ค ์œ„์ž„ํ•˜๊ธฐ

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;
	}