Spring HATEOAS

Hypermedia As The Engine Of Application State์˜ ์•ฝ์ž๋กœ ํ•˜์ดํผ ๋งํฌ๋ฅผ ํ†ตํ•ด ์ƒํƒœ ์ด๋™์ด ์ผ์–ด๋‚˜์•ผ ํ•œ๋‹ค๋Š” ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

response body์— ๋งํฌ๋ฅผ ์ œ๊ณตํ•˜์—ฌ client๊ฐ€ ์ฐธ๊ณ  ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ

์ฃผ์š” ๊ธฐ๋Šฅ

  • ๋งํฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ธฐ๋Šฅ

    • ๋ฌธ์ž์—ด ๊ฐ€์ง€๊ณ  ๋งŒ๋“ค๊ธฐ

    • ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ฉ”์„œ๋“œ๋กœ ๋งŒ๋“ค๊ธฐ

  • ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ธฐ๋Šฅ

    • ๋ฆฌ์†Œ์Šค : ๋ฐ์ดํ„ฐ + ๋งํฌ

  • ๋งํฌ ์ฐพ์•„์ฃผ๋Š” ๊ธฐ๋Šฅ

    • Traverson : HAL ์Šคํƒ€์ผ์˜ ๋งํฌ๋Š” ์—ฐ์‡„์ ์œผ๋กœ ๋งํ‚นํ•˜์—ฌ ์ž์›์„ ์ฐพ์„ ์ˆ˜ ์žˆ์ฐŒ๋งŒ ๋‹จ๊ณ„๊ฐ€ ๋งŽ์•„ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์—, ์ž๋™์œผ๋กœ ๋งํฌ๋”ฐ๋ผ ์ˆœํšŒํ•˜๋ฉฐ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค.

      • ์›๋ž˜๋Š” nodejs์™€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ž‘ํ•˜๋Š” js๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ด๋ฅผ ์˜๊ฐ๋ฐ›์•„ spring์—์„œ ์ œ์ž‘ํ•˜์—ฌ ์ง€์›

    • LinkDiscoverers : spring HATEOASํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค๋กœ ๋งํฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ ์ ˆํ•œ ๋งํฌ๊ฐ€ ์žˆ๋Š”์ง€ ์ฐพ๊ธฐ ์œ„ํ•ด ๋ฏธ๋””์–ด ์œ ํ˜•์ด ๋ถ€ํ•ฉํ•œ์ง€ ๋“ฑ ์ฒดํฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์ด ์กด์žฌํ•œ๋‹ค.

๋งํฌ ๊ตฌ์กฐ

  • href (์ฃผ์†Œ)

  • rel (๊ด€๊ณ„)

    • self

    • profile

    • ...(IANA์— ๋ฏธ๋ฆฌ ์ •์˜๋œ ๋งํฌ ๊ด€๊ณ„๊ฐ€ ์กด์žฌ,๋‹ค์–‘ํ•˜๋ฉฐ ๊ฐœ์ธ์  ์ •์˜ ๊ฐ€๋Šฅ => restfulํ•œ api๋Š” ํ‘œ์ค€์ด ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉ๊ฐ€๋Šฅ)

      • udpate-event

      • query-events

์‚ฌ์šฉ๋ฒ•

Spring ์ด๋ผ๋ฉด @EnableHypermediaSupport๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•˜์ง€๋งŒ, Spring boot๋ผ๋ฉด hateoas๋งŒ dependecny์ถ”๊ฐ€ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์„ค์ •์ด ๋œ๋‹ค.

Link link = Link.of("http://localhost:8080/somothing");
Link link = new Link("http://localhost:8080/somothing");
assertThat(link.getHref()).isEqualTo("http://localhost:8080/somothing");
assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF);

๋งํฌ๋Š” Link๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ href์™€ rel๋“ฑ์„ ์žฌ์„ค์ €์ด ๊ฐ€๋Šฅํ•˜๊ณ  ๋งํฌ ๊ด€๊ณ„(rel)์€ ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” self๋กœ ์ž๊ธฐ์ž์‹ ์„ ์ฐธ์กฐํ•œ๋‹ค.

๋ฉ”์„œ๋“œ

  • linkTo : ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์˜ ๋งคํ•‘ ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฒ€์‚ฌํ•˜์—ฌ ๋ฉ”์„œ๋“œ์— ๋งคํ•‘๋œ URI๋ฅผ ๋นŒ๋“œํ•˜๋Š” ํ•จ์ˆ˜

    @Controller
    @RequestMapping(value = "/api/events", produces = "application/hal+json; charset=UTF-8")
    public class EventController {
    
        @PostMapping
        public ResponseEntity createEvent(@RequestBody Event event){
            URI createdUri = linkTo(EventController.class).slash("{id}").toUri();
            event.setId(10);
            return ResponseEntity.created(createdUri).body(event);
        }
    }
    • methodOn : ํด๋ž˜์Šค๊ฐ€ ์•„๋‹Œ ๋ฉ”์„œ๋“œ์— ๋งคํ•‘๋œ URI๋ฅผ ๊ฒ€์‚ฌํ•˜์—ฌ ๋นŒ๋“œ (๋ฉ”์„œ๋“œ๋ฅผ ํด๋ž˜์Šค๋กœ wrap)

    @Controller
    public class EventController {
    
        @PostMapping(value = "/api/events", produces = "application/hal+json; charset=UTF-8")
        public ResponseEntity createEvent(@RequestBody Event event){
            URI createdUri = linkTo(methodOn(EventContoller.class).createEvent(null)).slash("{id}").toUri();
            event.setId(10);
            return ResponseEntity.created(createdUri).body(event);
        }
    }

    ์œ„์™€ ๊ฐ™์ด ํด๋ž˜์Šค์˜ ๋งคํ•‘๋œ URI๊ฐ€ ์•„๋‹Œ ํด๋ž˜์Šค ๋‚ด์˜ ๋ฉ”์„œ๋“œ์˜ URI์„ ๋งคํ•‘ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด methodOn()์•ˆ์— ํด๋ž˜์Šค๋ฅผ ์ฃผ๊ณ  ํ•ด๋‹น ํด๋ž˜์Šค ๋‚ด์˜ ๋ฉ”์„œ๋“œ์˜ ์–ด๋…ธํ…Œ์ด์…˜ ์ •๋ณด๋กœ linkTo๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  • slash : /, #๊ณผ ๊ฐ™์€ slash์™€ ๋’ค์— ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ uri์— ๋ถ™์—ฌ์ฃผ๋Š” ํ•จ์ˆ˜

Last updated