ํ’€์Šคํƒ ์›น๐ŸŒ ๊ฐœ๋ฐœ์ž ์ง€๋ง์ƒ ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ป
โž• ์ธ๊ณต์ง€๋Šฅ ๊ด€์‹ฌ ๐Ÿค–


Categories


Recent views

  • 1
  • 2
  • 3
  • 4
  • 5

Spring5-HATEOAS

  1. HATEOAS๋ž€?
    • ์ง์ ‘ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ
      • ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ๋ฅผ ํ†ตํ•œ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€

      Spring5-HATEOAS

      ๐Ÿ—ฃ๏ธ ์ถœ์ฒ˜

      _ ์ดˆ๋ณด ์›น ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์Šคํ”„๋ง 5 ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ž…๋ฌธ _์™€ ์Šคํ”„๋ง ์ธ ์•ก์…˜ ์˜ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

      HATEOAS๋ž€?

      ๋งŒ์•ฝ ๋ฐฑ์—”๋“œ API์˜ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๊ฒŒ ๋œ๋‹ค๋ฉด, ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๊ฐ€ ๊ณ ์žฅ์ด ๋‚  ๊ฒƒ์ด๋‹ค.

      HATEOAS(Hypermedia As The Engine Of Application)๋Š” API์—์„œ ์‘๋‹ต ์‹œ, ๋ฐ˜ํ™˜๋˜๋Š” ๋ฆฌ์†Œ์Šค์™€ ๊ด€๋ จ๋œ ํ•˜์ดํผ๋งํฌ๋ฅผ ์ •๋ณด์— ํฌํ•จํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•œ๋‹ค.

      ๐Ÿงพ๏ธ ๋ณดํ†ต์˜ REST API VS HATEOAS API

      HATEOAS๊ฐ€ JSON ์‘๋‹ต์— ํ•˜์ดํผ๋งํฌ๋ฅผ ํฌํ•จ์‹œํ‚ค๋Š” ํ˜•์‹์„ HAL(Hypertext Application Language)์ด๋ผ๊ณ  ํ•œ๋‹ค.

      // REST API
      [
      	{
      		"id": 4,
      		"name": "Veg-Out",
      		"createdAt": "2018-01-31T20:15:53.219+0000",
      		"ingredients": [
      			{"id": "FLTO", "name": "Flour Tortilla", "type": "WRAP"},
      			{"id": "TMTO", "name": "Diced Tomatoes", "type": "VEGGIES"},
      			...
      		]
      	},
      ]
      // HATEOAS API
      {
      	"_embedded": {
      		"tacoResurceList": [
      			{
      				"name": "Veg-Out",
      				"createdAt": "2018-01-31T20:15:53.219+0000",
      				"ingredients": [
      					{
      						"name": "Flour Tortilla", "type": "WRAP",
      						"_links": {
      							"self": {"href": "http://localhost:8080/ingredieints/FLTO" }
      						}
      					},
      					{
      						"name": "Diced Tomatoes", "type": "VEGGIES",
      						"_links": {
      							"self": {"href": "http://localhost:8080/ingredieints/TMTO" }
      						}
      					},
      				],
      				"_links": {
      				"self": {"href": "http://localhost:8080/design/4"}
      				}
      			}
      		]
      	},
      	"_links": {
      		"recents": {
      			"href": "http://localhost:8080/design/recent"
      		}
      	}
      }
      
      

      ๋‚ด๋ถ€์— "_links" ์†์„ฑ์„ ํฌํ•จํ•˜๋ฉฐ, ๊ด€๋ จ API๋ช…๊ณผ URL์„ ํฌํ•จํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

      ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ HATEOAS๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋˜๋Š” ๋ชจ๋“ˆ๊ณผ ํด๋ž˜์Šค, ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

      • org.springframework.boot.spring-boot-starter-hateoas ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

      ์ง์ ‘ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

      ์Šคํ”„๋ง HATEOAS๋Š” ํ•˜์ดํผ๋งํฌ๋ฅผ ๋‹จ์ผ ๋งํฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” Resource ํƒ€์ž…๊ณผ ๋ฆฌ์†Œ์Šค ์ปฌ๋ ‰์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” Resources ํƒ€์ž…์œผ๋กœ ๋‹ค๋ฃฌ๋‹ค.

      ์ด๋“ค์„ ํ†ตํ•ด ์‘๋‹ต์— ํ•˜์ดํผ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๐Ÿงพ๏ธ ๋ฆฌ์†Œ์Šค์— ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

      ๋‹ค์ˆ˜์˜ ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•ด์•ผํ•  ๋•Œ๋Š” Resources ํƒ€์ž…์„ ๊ฐ์‹ผ Resource ํƒ€์ž…์„ ๋ฆฌํ„ดํ•ด์•ผ ํ•œ๋‹ค.

      add() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด recentTacos()์˜ ๋งคํ•‘ URL์„ ์ง‘์–ด๋„ฃ๋Š” ์ฝ”๋“œ์ด๋‹ค.

      import static org.springframework.hateoas.mvc.ControllerLinkBuilder
      
      import org.springframework.hateoas.Resources;
      import org.springframework.hateoas.Resource;
      
      @GetMapping(path="/recent", produces="application/hal+json")
      public Resources<Resource<Taco>> recentTacos() {
      	PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
      
      	List<Taco> tacos = tacoRepo.findAll(page).getContent();
      	// ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์†Œ์Šค ํƒ€์ž…์œผ๋กœ ๊ฐ์‹ธ๊ธฐ
      	Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
      	
      	recentResources.add(
      	//	new Link("http://localhost:8080/design/recent", "recents") // ์˜๋ฏธ์—†๋Š” ํ•˜๋“œ์ฝ”๋”ฉ ๋ฒ„์ „
      	ControllerLinkBuilder.linkTo(DesignTacoController.class)
      		.slash("recent")
      		.withRel("recents"));
      		
      	return recentResources;
      }
      
      • ControllerLinkBuilder๋ฅผ ํ†ตํ•ด URL์„ ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ  ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ์•Œ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ด€๋ จ ๋งํฌ์˜ ๋นŒ๋“œ๋ฅผ ๋„์™€์ฃผ๋Š” ํŽธ๋ฆฌํ•œ API๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
      • linkTo() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •(๊ธฐ๋ณธ ๊ฒฝ๋กœ ์ถ”๊ฐ€)
      • slash(๋‚ด์šฉ)๋ฉ”์„œ๋“œ๋Š” /๋‚ด์šฉ ๋ฌธ์ž์—ด์„ URL์— ์ถ”๊ฐ€ํ•œ๋‹ค.(๋งคํ•‘ ๊ฒฝ๋กœ ์ถ”๊ฐ€)
      • withRel(๋งํฌ๋ช…)์„ ํ†ตํ•ด ํ•ด๋‹น ํ•˜์ดํผ๋งํฌ์˜ API๋ช…์„ ์ง€์ •ํ•œ๋‹ค.

      ํ•˜์ง€๋งŒ ์•„๋ž˜์ฒ˜๋Ÿผ ๋” ์งง๊ณ  ๋งคํ•‘ ๊ฒฝ๋กœ๊นŒ์ง€ ์ง์ ‘ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ข‹๋‹ค.

      ๐Ÿงพ๏ธ ๋” ๋‚˜์€ ๋ฒ„์ „์˜ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€ ์ฝ”๋“œ
      import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*
      
      Resources<Resource<Taco>> recentResources = Resources.wrap(tacos); recentResources.add(
      	linkTo(methodOn(DesignTacoController.class).recentTacos())
      	.withRel("recents"));
      
      • methodOn(์ปจํŠธ๋กค๋Ÿฌ)์€ ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

      ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ๋ฅผ ํ†ตํ•œ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€

      ์œ„ ๋ฐฉ๋ฒ•์€ ์ง๊ด€์ ์ด์ง€๋งŒ, ์—ฌ๋Ÿฌ ๊ฐ์ฒด๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผํ•  ๋•Œ ๋งค๋ฒˆ ๋ฃจํ”„๋ฅผ ์‹คํ–‰ํ•˜๋ฏ€๋กœ ๋ฒˆ๊ฑฐ๋กญ๋‹ค.

      ๊ฐ ๊ฐ์ฒด์˜ ํ•˜์ดํผ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์ผ์ผ์ด Resources.wrap()์„ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹ , ResourceSupport ๊ฐ์ฒด๋ฅผ ์ƒ์†๋ฐ›๋Š” ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ์ž๋™์œผ๋กœ ํ•˜์ดํผ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด ์ƒ์„ฑ

      ResourceSupoort ํด๋ž˜์Šค๋Š” ๋ฆฌ์†Œ์Šค์™€ ๊ด€๋ จ๋œ Link ๊ฐ์ฒด๋“ค๊ณผ ์ด๊ฒƒ๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

      ๐Ÿงพ๏ธ ๋„๋ฉ”์ธ ๋ฐ์ดํ„ฐ์™€ ํ•˜์ดํผ ๋งํฌ๋ฅผ ๊ฐ–๋Š” ํƒ€์ฝ” ๋ฆฌ์†Œ์Šค

      ์ด๋•Œ, ๊ธฐ์กด์˜ ๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ์—ญํ• ๋„ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๊ฐ€ ๋™์‹œ์— ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ, ์„œ๋กœ ์“ธ๋ชจ์—†๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์ด ์ข‹์„ ์ˆ˜ ์žˆ๋‹ค.

      • ๋„๋ฉ”์ธ ๊ฐ์ฒด๋Š” ๋„๋ฉ”์ธ์˜ id ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ๋งํฌ ๊ฐ์ฒด๋Š” ํ•„์š”์—†๋‹ค.
      • ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋Š” ๋งํฌ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ self ๋งํฌ๊ฐ€ ์‹๋ณ„์ž ์—ญํ• ์„ ํ•˜๋ฏ€๋กœ id ๊ฐ’์€ ํ•„์š”์—†๋‹ค.
      package tacos.web.api;
      
      import java.util.Date;
      import java.util.List;
      import org.springframework.hateoas.ResourceSupport;
      import lombok.Getter;
      import tacos.Taco; // ๋„๋ฉ”์ธ ๊ฐ์ฒด
      
      public class TacoResource extends ResourceSupport {
      
      	// ๋„๋ฉ”์ธ ๊ฐ์ฒด์™€ ๋‹ฌ๋ฆฌ id๊ฐ’์€ ํ•„์š”์—†๋‹ค.
      	@Getter
      	private final String name;
      
      	@Getter
      	private final Date createdAt;
      
      	@Getter
      	private final List<IngredientResource> ingredients;
      
      	public TacoResource(Taco taco) { // Taco ๊ฐ์ฒด์—์„œ ์†์„ฑ๊ฐ’ ๋ณต์‚ฌ
      	    this.name = taco.getName();
      	    this.createdAt = taco.getCreatedAt();
      	    this.ingredients = taco.getIngredients();
      	}
      }
      

      ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ผ๋ฐ˜ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋กœ ๋ฐ”๊พธ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

      ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ ๊ตฌ์„ฑ

      ResourceAssemblerSupport๋ฅผ ์ƒ์†๋ฐ›์€ ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ๋Š” ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ํ•˜์ดํผ๋งํฌ๋ฅผ ๋งŒ๋“ค์–ด๋‚ธ๋‹ค.

      ๐Ÿงพ๏ธ ํƒ€์ฝ” ๋ฆฌ์†Œ์Šค๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ
      package tacos.web.api;
      import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
      
      import tacos.Taco;
      
      public class TacoResourceAssembler extends ResourceAssemblerSupport<Taco, TacoResource> {
      	// ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ํ•˜์ดํผ๋งํฌ ์ƒ์„ฑ
      	public TacoResourceAssembler() {
      		super(DesignTacoController.class, TacoResource.class);
      	}
      
      	// ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ๋ฉ”์†Œ๋“œ
      	@Override
      	protected TacoResource instantiateResource(Taco taco) {
      		return new TacoResource(taco); // ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๊ฐ€ ์˜ค๋ฒ„๋ผ์ด๋“œํ•œ ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€์งˆ ๊ฒฝ์šฐ ์ง์ ‘ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•ด์ค˜์•ผํ•จ
      	}
      
      	// ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ + ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€ ํ•˜๋Š” ๋ฉ”์„œ๋“œ
      	// ์œ„์˜ instantiateResource ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•œ๋‹ค.
      	@Override
      	public TacoResource toResource(Taco taco) {
      		//์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ api๋ฅผ ๋‚˜๋ˆ„๋Š” ์‹๋ณ„์ž๋ฅผ ์ค˜์•ผํ•จ.
      		return createResourceWithId(taco.getId(), taco);
      	}
      }
      

      ์ด๋ฅผ ์ด์šฉํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ”์„œ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์ž.

      ๐Ÿงพ๏ธ ๋ฆฌ์†Œ์Šค ๋ฆฌ์ŠคํŠธ ํ•˜์ดํผ๋งํฌ ์ถ”๊ฐ€
      @GetMapping(path="/recent", produces="application/hal+json")
      public Resources<TacoResource> recentTacos() {
      	PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
      
      	List<Taco> tacos = tacoRepo.findAll(page).getContent();
      
      	List<TacoResource> tacoResources = new TacoResourceAssembler().toResources(tacos);
      
      	Resources<TacoResource> recentResources = new Resources<TacoResource>(tacoResources);
      
      	recentResources.add(
      		linkTo(methodOn(DesignTacoController.class)
      		.recentTacos())
      		.withRel("recents"));
      	return recentResources;
      }
      

      ๋ฆฌํ„ด ํƒ€์ž…์ด Resources<Resource<Taco>> ๋Œ€์‹  Resources<TacoResource>๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค.

      ์•ž์„  ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ๋ฅผ ํ†ตํ•ด ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“  ๋’ค ์ด๋ฅผ ํ†ตํ•ด Resources ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์ถ”๊ฐ€ํ–ˆ๋‹ค.

      ์ค‘์ฒฉ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด ๊ตฌํ˜„

      ์œ„์˜ ๊ฐ ํƒ€์ฝ” ๊ฐ์ฒด์—๋Š” ์‹์ž์žฌ(ingredient)๋ผ๋Š” ์ค‘์ฒฉ๋œ ๋„๋ฉ”์ธ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค.

      ์ด๋“ค์„ ์œ„ํ•ด ์ถ”๊ฐ€๋กœ ํ•˜์ดํผ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ๋”ฐ๋ฅด๋ฉด ๋œ๋‹ค.

      1. ์‹์ž์žฌ๋ฅผ ์œ„ํ•œ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด, ๋ฆฌ์†Œ์Šค ์–ด์…ˆ๋ธ”๋Ÿฌ ์ƒ์„ฑ
      ๐Ÿงพ๏ธ IngredientResource.java
      package tacos.web.api;
      import org.springframework.hateoas.ResourceSupport;
      import lombok.Getter;
      import tacos.Ingredient;
      import tacos.Ingredient.Type;
      
      public class IngredientResource extends ResourceSupport {
      
      	@Getter
      	private String name;
      	
      	@Getter
      	private Type type;
      	
      	public IngredientResource(Ingredient ingredient) {
      		this.name = ingredient.getName();
      		this.type = ingredient.getType();
      	}
      }
      
      ๐Ÿงพ๏ธ IngredientResourceAssembler.java
      package tacos.web.api;
      import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
      import tacos.Ingredient;
      
      class IngredientResourceAssembler extends ResourceAssemblerSupport<Ingredient, IngredientResource> {
      	public IngredientResourceAssembler() {
      		super(IngredientController.class, IngredientResource.class);
      	}
      	
      	@Override
      	public IngredientResource toResource(Ingredient ingredient) {
      		return createResourceWithId(ingredient.getId(), ingredient);
      	}
      
      	@Override
      	protected IngredientResource instantiateResource(Ingredient ingredient) {
      		return new IngredientResource(ingredient);
      	}
      }
      

      ์•ž์„œ ์ƒ์„ฑํ–ˆ๋˜ ๋ฐฉ์‹œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์ด๋‹ค.

      1. ํ•ด๋‹น ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ์ƒ์œ„ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด ๋ณ€๊ฒฝ
      ๐Ÿงพ๏ธ TacoResource.java
      package tacos.web.api;
      
      import java.util.Date;
      import java.util.List;
      import org.springframework.hateoas.ResourceSupport;
      import lombok.Getter;
      import tacos.Taco; 
      
      public class TacoResource extends ResourceSupport {
      
      	private static final IngredientResourceAssembler ingredientAssembler = new IngredientResourceAssembler();
      
      	@Getter
      	private final String name;
      
      	@Getter
      	private final Date createdAt;
      
      	@Getter
      	private final List<IngredientResource> ingredients;
      
      	public TacoResource(Taco taco) {
      	    this.name = taco.getName();
      	    this.createdAt = taco.getCreatedAt();
      	    this.ingredients = ingredientAssembler.toResources(taco.getIngredients());
      	}
      }
      

      ๊ฐ ๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ์‹์žฌ๋ฃŒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‹์žฌ๋ฃŒ ์–ด์…ˆ๋ธ”๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ƒ์„ฑํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค.

      embedded ๊ด€๊ณ„ ์ด๋ฆ„ ์ง“๊ธฐ

      ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  API๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž๋™์œผ๋กœ tacoRsourceList๋ผ๋Š” ์†์„ฑ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค.

      ๐Ÿงพ๏ธ ์ •ํ•ด์ง„ Json ํ•„๋“œ๋ช…
      {
      	"_embedded": {
      		"tacoResourceList": [ // ๋ฆฌ์†Œ์Šค๊ฐ์ฒด๋ช… + List๋กœ ์ •ํ•ด์ง„๋‹ค. (๊ธฐ๋ณธ๊ฐ’)
      			{
      				"name": "Veg-Out",
      				"createdAt": "2018-01-31T20:15:53.219+0000",
      				"ingredients": [
      				...
      

      ๋ฌธ์ œ๋Š” ์ด๊ฒƒ์ด ํด๋ผ์ด์–ธํŠธ ์ธก์— ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด ํด๋ผ์ด์–ธํŠธ์— ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ๋‹ค.

      ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด @Relation ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์†์„ฑ๋ช…์„ ๋ฐ”๊ฟ”์ค„ ์ˆ˜ ์žˆ๋‹ค.

      ๐Ÿงพ๏ธ ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด
      @Relation(value="taco", collectionRelation="tacos")
      public class TacoResource extends ResourceSupport {
      	//...
      }
      

      ์ด์ œ tacoResourceList ์†์„ฑ๋ช…์€ tacos๋กœ ๋ฐ”๋€๋‹ค.