SpringBoot HATEOAS

Hypermedia-Driven RESTful Web Service

HATEOAS๋Š” maturity model(์„ฑ์ˆ™๋„)์— ๋Œ€ํ•ด์„œ lv3์— ํ•ด๋‹นํ•˜๊ณ , ๋Œ€๋ถ€๋ถ„์€ ๋‹ค lv2์— ํ•ด๋‹นํ•ด์„œ ์ง„ํ–‰๋˜๊ณ  ์žˆ๋‹ค.

๋ ˆ๋ฒจ2๋Š” ๋ฉ”์†Œ๋“œ์— ์–ด๋–ค ๊ฒƒ์„ ์ง„ํ–‰ํ•  ๊ฒƒ์ธ์ง€ ์„ค์ •ํ•ด์ฃผ๊ณ , url์—๋Š” ์ž์›์„ ์ง‘์–ด๋„ฃ์–ด์ฃผ๋ฉฐ, JSON๋ฐฉ์‹์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋ ˆ๋ฒจ3์€ ๋ ˆ๋ฒจ2์—๋‹ค๊ฐ€ ์ถ”๊ฐ€์ ์ธ ๋งํฌ๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค. ์‘๋‹ต์—๋‹ค๊ฐ€ JSON+HAL(๋งํฌ๋ฅผ ์ถ”๊ฐ€)

Hypermedia As The Engine Of Application State(HATEOAS)

hypermedia = data + links(ํ˜„์žฌ resource์—์„œ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์•ก์…˜)

์—ฐ๊ด€๋œ ์ •๋ณด๋“ค๋„ ๋งํฌ์— ๋‹ด์•„์„œ ์‘๋‹ตํ•˜๋Š” ๋ฐฉ์‹

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ API์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œ๋ฅผ ํ•ด์•ผํ•œ๋‹ค๋Š” ์›์น™์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. โ‡’ ์ด๊ฑด ๋‹ค์Œ์œผ๋กœ ์–ด๋– ํ•œ step์ด ๊ฐ€๋Šฅํ•œ์ง€๋ฅผ ํ‘œ์‹œํ•จ์œผ๋กœ์จ ๊ฐ€๋Šฅํ•œ๋‹ค

  • ์ตœ์ข…๋ชฉ์  : ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌ์‹œ์ผœ์„œ ๊ฐ๊ฐ ๋”ฐ๋กœ ์ง„ํ™”ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค

HAL(Hypertext Application Language) Model

JSON + Link(relation + href) + embedded resource

์žฅ์ 

  • URL์„ ๋งˆ์Œ๊ป ๋ฐ”๊ฟ”๋„ ๊ดœ์ฐฎ๋‹ค,

  • API์— ์ฒ˜์Œ ์ ‘๊ทผํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ ์ข‹์€ ์•ˆ๋‚ด์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

  • client - server๊ฐ„ ์˜์กด์„ฑ์„ ์ค„์ด๊ณ  ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ๋ฐ”๊ฟˆ

์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  pom.xml์—์„œ

spring-boot-starter-hateoas ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผํ•จ

WebMvcLinkBuilder

์›๋ž˜๋Š” Link link = new Linkt("http://~"); ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์•ผํ•˜์ง€๋งŒ

WebMvcLinkBuilder์„ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜๋ฉด ๋งํฌ๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•ด์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ƒ์„ฑํ•˜๋Š”๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Link link = WebMvcLinkBuilder
	.linkTo(methodOn(WebController.class).getAllAlbums())
	.withSelfRel()
	.withRel("albums");

RepresentationModel

EntityModel(single resource) โ†’ PagedModel โ†’ CollectionModel(multiple resource) โ†’ RepresentationModel

Entity๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœํ•ด์„œ representationalModel์„ ๋งŒ๋“ค๊ฒŒ๋˜๋Š”๋ฐ ๋˜‘๊ฐ™์„์ˆ˜๋„ ์žˆ๊ณ  ์ผ๋ถ€๋งŒ ๋“ค์–ด๊ฐ€์žˆ์„์ˆ˜๋„ ์žˆ๋‹ค.

Presentation Layer์—๋Š” DTO(data, getter, setter, ์ƒ์„ฑ์ž ์ •๋„๋งŒ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๊ณ , ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์€ ๋”ฐ๋กœ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š”๋‹ค)

Bussiness Layer์—๋Š” Domain(entity)

Representational model assembler

entity๋ฅผ representational model(DTO)๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์ด๋‹ค.

RepresentationModelAssembler๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด๋ฏธ ์ œ๊ณต์ค‘์— ์žˆ๋‹ค. ์ด๊ฒƒ์„ implements ํ•ด์„œ ๋งŒ๋“  RepresentationModelAssemblerSupport ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค!!!

์ƒ์†๋ฐ›๊ณ 

toModel(), toCollectionModel() ์„ overrideํ•ด์„œ ์‚ฌ์šฉ

๋‚ด๋ถ€ ๊ตฌํ˜„

instantiateModel()์„ ํ†ตํ•ด์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ 

set์œผ๋กœ entity์— ์žˆ๋Š” ์†์„ฑ๋“ค์„ ๋„ฃ์–ด์ฃผ๊ณ 

๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ add(linkTo(methodOn().getActorById)))์„ ๋„ฃ์–ด์คŒ

Optional<Type> : ๊ฐ’์ด ์žˆ์œผ๋ฉด ๊ฐ’์„ ์ฃผ๋Š”๋ฐ, ๋งŒ์•ฝ null๊ฐ’์ด๋ฉด exception์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์คŒ

.map(actorModelAssembler::toModel).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());

Last updated

Was this helpful?