chap 7 jihoonKim - JAVA-JIKIMI/SPRING-IN-ACTION-5 GitHub Wiki

7. REST ์„œ๋น„์Šค ์ด์šฉํ•˜๊ธฐ

7์žฅ์—์„œ ๋ฐฐ์šฐ๋Š” ๋‚ด์šฉ

  • RestTemplate์„ ์‚ฌ์šฉํ•ด์„œ REST API ์‚ฌ์šฉํ•˜๊ธฐ
  • Traverson์„ ์‚ฌ์šฉํ•ด์„œ ํ•˜์ดํผ๋ฏธ๋””์–ด API ์ด๋™ํ•˜๊ธฐ

7.1. RestTemplate์œผ๋กœ REST ์—”๋“œํฌ์ธํŠธ ์‚ฌ์šฉํ•˜๊ธฐ

  • RestTemplate์€ REST ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ๋ฒˆ์žกํ•œ ์ผ์„ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.

  • REST ๋ฆฌ์†Œ์Šค์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•œ 41๊ฐœ ๋ฉ”์„œ๋“œ ์ œ๊ณต

    ๋ฉ”์„œ๋“œ ๊ธฐ๋Šฅ ์„ค๋ช…
    delete(..) ์ง€์ •๋œ URL์˜ ๋ฆฌ์†Œ์Šค์— HTTP DELETE ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
    exchange(..) ์ง€์ •๋œ HTTP ๋ฉ”์„œ๋“œ๋ฅผ URL์— ๋Œ€ํ•ด ์‹คํ–‰ํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” responseEntity๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    execute(..) ์ง€์ •๋œ HTTP ๋ฉ”์„œ๋“œ๋ฅผ URL์— ๋Œ€ํ•ด ์‹คํ–‰ํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    getForEntity(..) HTTP GET ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ResponseEntity๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    getForObject(..) HTTP GET ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    headForHeaders(..) HTTP HEAD ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉฐ, ์ง€์ •๋œ ๋ฆฌ์†Œ์Šค URL์˜ HTTP ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    optionsForAllow(..) HTTP OPTIONS ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉฐ, ์ง€์ •๋œ URL์˜ Allow ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    patchForObject(..) HTTP PATCH ์š”์ฒญ์„ ์ „์†กํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    postForEntity(..) URL์— ๋ฐ์ดํ„ฐ๋ฅผ POSTํ•˜๋ฉฐ, Resonse body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ResponseEntity๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    postForLocation(..) URL์— ๋ฐ์ดํ„ฐ๋ฅผ POSTํ•˜๋ฉฐ, ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ URL๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    postForObject(..) URL์— ๋ฐ์ดํ„ฐ๋ฅผ POSTํ•˜๋ฉฐ, Response body์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    put(..) ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์ •๋œ URL์— PUTํ•œ๋‹ค.
    • ๊ณ ์œ ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋Š” 12๊ฐœ์ด๊ณ , ๋‚˜๋จธ์ง€๋Š” ์ด ๋ฉ”์„œ๋“œ๋“ค์˜ ์˜ค๋ฒ„๋กœ๋”ฉ ๋ฒ„์ „
    • TRACE๋ฅผ ์ œ์™ธํ•œ ํ‘œ์ค€ HTTP ๋ฉ”์„œ๋“œ ๊ฐ๊ฐ์— ๋Œ€ํ•ด ์ตœ์†Œํ•œ ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค.
    • execute()์™€ exchange()๋Š” ๋ชจ๋“  HTTP ๋ฉ”์„œ๋“œ ์š”์ฒญ์„ ์ „์†กํ•˜๊ธฐ ์œ„ํ•œ ์ €์ˆ˜์ค€ ๋ฒ”์šฉ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
    • ์œ„ ํ‘œ์˜ ๋ฉ”์„œ๋“œ๋Š” ์„ธ ๊ฐ€์ง€ ํ˜•ํƒœ๋กœ ์˜ค๋ฒ„๋กœ๋”ฉ๋˜์–ด ์žˆ๋‹ค.
      • ๊ฐ€๋ณ€ ์ธ์ž ๋ฆฌ์ŠคํŠธ์— ์ง€์ •๋œ URL ๋งค๊ฐœ๋ณ€์ˆ˜์— URL ๋ฌธ์ž์—ด(String ํƒ€์ž…)์„ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.
      • Map<String, String>์— ์ง€์ •๋œ URL ๋งค๊ฐœ๋ณ€์ˆ˜์— URL ๋ฌธ์ž์—ด์„ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.
      • java.net.URI๋ฅผ URL์— ๋Œ€ํ•œ ์ธ์ž๋กœ ๋ฐ›์œผ๋ฉฐ, ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋œ URL์€ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • RestTemplate์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š” ์‹œ์ ์— RestTemplate ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ๋นˆ์œผ๋กœ ์„ ์–ธํ•˜๊ณ  ํ•„์š” ์‹œ ์ฃผ์ž…ํ•œ๋‹ค.

    RestTemplate rest = new RestTemplate();
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

7.1.1. ๋ฆฌ์†Œ์Šค ๊ฐ€์ ธ์˜ค๊ธฐ(GET)

๋‹ค์Œ ์ฝ”๋“œ๋Š” RestTemplate์„ ์‚ฌ์šฉํ•ด์„œ ํŠน์ • ID๋ฅผ ๊ฐ–๋Š” Ingredient ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
HATEOAS๊ฐ€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด getForObject()๋กœ ์‹์ž์žฌ(ingredient)๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

public Ingredient getIngredientById(String ingredientId) {
    return rest.getForObject("http://localhost:8080/ingredients/{id}",
                             Ingredient.class, ingredientId);
}
  • getForObject()์— ์ „๋‹ฌ๋œ ingredientId ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ง€์ •๋œ URL์˜ {id} ํ”Œ๋ ˆ์ด์Šค ํ™€๋”์— ์‚ฌ์šฉ๋œ๋‹ค.
  • ์ด ์˜ˆ์—๋Š” ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜๋งŒ ์žˆ์ง€๋งŒ, ๋ณ€์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์€ ์ฃผ์–ด์ง„ ์ˆœ์„œ๋Œ€๋กœ ํ”Œ๋ ˆ์ด์Šคํ™€๋”์— ์ง€์ •๋œ๋‹ค.
  • getForObject()์˜ 2 ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์‘๋‹ต์ด ๋ฐ”์ธ๋”ฉ๋˜๋Š” ํƒ€์ž…์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” JSON ํ˜•์‹์ธ ์‘๋‹ต ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ์ฒด๋กœ ์—ญ์ง๋ ฌํ™”๋˜์–ด ๋ฐ˜ํ™˜๋œ๋‹ค.

Map์„ ์‚ฌ์šฉํ•ด์„œ URL ๋ณ€์ˆ˜๋“ค์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

public Ingredient getIngredientById(String ingredientId) {
    Map<String, String> urlVariables = new HashMap<>();
    urlVariables.put("id", ingredientId);
    return rest.getForObject("http://localhost:8080/ingredients/{id}",
                             Ingredient.class, urlVariables);
}
  • ์š”์ฒญ์ด ์ˆ˜ํ–‰๋  ๋•Œ {id} ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋Š” ํ‚ค๊ฐ€ id์ธ Map ํ•ญ๋ชฉ ๊ฐ’(ingredientId ๊ฐ’)์œผ๋กœ ๊ต์ฒด๋œ๋‹ค.

URI ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” URI ๊ฐ์ฒด๋ฅผ ๊ตฌ์„ฑํ•˜์—ฌ getForObject()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค.

public Ingredient getIngredientById(String ingredientId) {
    Map<String, String> urlVariables = new HashMap<>();
    urlVariables.put("id", ingredientId);
    URI url = UriComponentsBuilder
              .fromHttpUrl("http://localhost:8080/ingredients/{id}")
              .build(urlVariables);
    return rest.getForObject(url, Ingredient.class);
}
  • URI ๊ฐ์ฒด๋Š” URL ๋ฌธ์ž์—ด ๋ช…์„ธ๋กœ ์ƒ์„ฑ๋˜๋ฉฐ, ์ด ๋ฌธ์ž์—ด์˜ {id} ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋Š” Map ํ•ญ๋ชฉ ๊ฐ’์œผ๋กœ ๊ต์ฒด๋œ๋‹ค.
  • getForObject() ๋ฉ”์„œ๋“œ๋Š” ๋ฆฌ์†Œ์Šค๋กœ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋งŒ ๊ฐ€์ ธ์™€์„œ ์‘๋‹ต ๊ฒฐ๊ณผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด์™ธ์— ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด getForEntity๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

getForEntity()๋Š” getForObject()์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋™์ž‘ํ•˜์ง€๋งŒ, ์‘๋‹ต ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ResponseEntity ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
ResponseEntity์—๋Š” Response ํ—ค๋”์™€ ๊ฐ™์€ ๋” ์ƒ์„ธํ•œ ์‘๋‹ต ์ฝ˜ํ…์ธ ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค.

public Ingredient getIngredientById(String ingredientId) {
    ResponseEntity<Ingredient> responseEntity =
        rest.getForEntity("http://localhost:8080/ingredients/{id}",
                          Ingredient.class, ingredientId);
    log.info("Fetched time: " + 
             responseEntity.getHeaders().getDate());
    return responseEntity.getBody();
}
  • getForEntity() ๋ฉ”์„œ๋“œ๋Š” getForObject()์™€ ๋™์ผํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ–๋„๋ก ์˜ค๋ฒ„๋กœ๋”ฉ๋˜์–ด ์žˆ๋‹ค.
    ๋”ฐ๋ผ์„œ URL ๋ณ€์ˆ˜๋“ค์„ ๊ฐ€๋ณ€ ์ธ์ž ๋ฆฌ์ŠคํŠธ๋‚˜ URI ๊ฐ์ฒด๋กœ ์ „๋‹ฌํ•˜์—ฌ getForEntity()๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.

7.1.2. ๋ฆฌ์†Œ์Šค์— ์“ฐ๊ธฐ(PUT)

๋‹ค์Œ ์ฝ”๋“œ๋Š” RestTemplate์„ ์‚ฌ์šฉํ•ด์„œ ํŠน์ • ID๋ฅผ ๊ฐ–๋Š” Ingredient ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. put() ๋ฉ”์„œ๋“œ๋Š” 3๊ฐœ์˜ ์˜ค๋ฒ„๋กœ๋”ฉ๋œ ๋ฒ„์ „์ด ์žˆ์œผ๋ฉฐ, ์ง๋ ฌํ™”๋œ ํ›„ ์ง€์ •๋œ URL๋กœ ์ „์†ก๋˜๋Š” Object ํƒ€์ž…์„ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

// ํŠน์ • ์‹์ž์žฌ ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒˆ๋กœ์šด Ingredient ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ๋กœ ๊ต์ฒด
public void updateIngredientById(Ingredient ingredientId) {
    rest.put("http://localhost:8080/ingredients/{id}",
             ingredient,
             ingredient.getId());
}
  • ์—ฌ๊ธฐ์„œ URL์€ ๋ฌธ์ž์—ด๋กœ ์ง€์ •๋˜์—ˆ๊ณ  ์ธ์ž๋กœ ์ „๋‹ฌ๋˜๋Š” Ingredient ๊ฐ์ฒด์˜ id ์†์„ฑ ๊ฐ’์œผ๋กœ ๊ต์ฒด๋˜๋Š” ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋ฅผ ๊ฐ–๋Š”๋‹ค.
  • put() ๋ฉ”์„œ๋“œ๋Š” Ingredient ๊ฐ์ฒด ์ž์ฒด๋ฅผ ์ „ใ……๋†ํ•˜๋ฉฐ, ๋ฐ˜ํ™˜ ํƒ€์ž…์€ void๋ผ์„œ ๋ฐ˜ํ™˜๊ฐ’ ์ฒ˜๋ฆฌํ•  ํ•„์š”๋Š” ์—†๋‹ค.

7.1.3. ๋ฆฌ์†Œ์Šค ์‚ญ์ œํ•˜๊ธฐ(DELETE)

// ํŠน์ • ์‹์ž์žฌ ์‚ญ์ œ
public void deleteIngredient(Ingredient ingredientId) {
    rest.delete("http://localhost:8080/ingredients/{id}",
                ingredient.getId());
}
  • ๋ฌธ์ž์—ด๋กœ ์ง€์ •๋œ URL๊ณผ URL ๋ณ€์ˆ˜ ๊ฐ’๋งŒ delete()์˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  • ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, URL์€ Map์œผ๋กœ ๋œ URL ๋งค๊ฐœ๋ณ€์ˆ˜๋‚˜ URL ๊ฐ์ฒด๋กœ ์ง€์ •๋  ์ˆ˜ ์žˆ๋‹ค.

7.1.4. ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ํ•˜๊ธฐ(POST)

public Ingredient createIngredient(Ingredient ingredientId) {
    return rest.postForObject("http://localhost:8080/ingredients/{id}",
                              ingredient, Ingredient.class);
}
  • POST ์š”์ฒญ์ด ์ˆ˜ํ–‰๋œ ํ›„ ์ƒˆ๋กœ ์ƒ์„ฑ๋œ Ingredient ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฌธ์ž์—ด URL๊ณผ ์„œ๋ฒ„์— ์ „์†ก๋  ๊ฐ์ฒด ๋ฐ ์ด ๊ฐ์ฒด์˜ ํƒ€์ž…(๋ฆฌ์†Œ์Šค body์˜ ๋ฐ์ดํ„ฐ์™€ ์—ฐ๊ด€๋œ)์„ ์ด์ž๋กœ ๋ฐ›๋Š”๋‹ค.
  • URL ๋ณ€์ˆ˜๊ฐ’์„ ๊ฐ–๋Š” Map์ด๋‚˜ URL์„ ๋Œ€์ฒดํ•  ๊ฐ€๋ณ€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋„ค ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•˜๋‹ค.
public URI createIngredient(Ingredient ingredient) {
    return rest.postForLocation("http://localhost:8080/ingredients",
                                ingredient, Ingredient.class);
  • postForObject()์™€ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜์ง€๋งŒ, ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด ๋Œ€์‹  ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ URI๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›๋Š”๋‹ค.
  • ๋ฐ˜ํ™˜๋œ URI๋Š” ํ•ด๋‹น Response์˜ Location ํ—ค๋”์—์„œ ์–ป๋Š”๋‹ค.
public Ingredient createIngredient(Ingredient ingredient) {
    ResponseEntity<Ingredient> responseEntity =
        rest.postForEntity("http://localhost:8080/ingredients",
                           ingredient, Ingredient.class);
    log.info("New resource created at " +
             responseEntity.getHeaders().getLocation());
    return responseEntity.getBody();
}
  • ๋ฆฌ์†Œ์Šค ๊ฐ์ฒด์™€ ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ URI ๋ชจ๋‘ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

API์—์„œ ํ•˜์ดํผ๋งํฌ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค๋ฉด RestTempate์€ ๋„์›€์ด ์•ˆ ๋œ๋‹ค.
(๋” ์ƒ์„ธํ•œ ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ ๊ทธ ์•ˆ์— ํฌํ•จ๋œ ์ฝ˜ํ…์ธ ์™€ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ๋„ ์žˆ์ง€๋งŒ, ๊ฐ„๋‹จํ•˜์ง€๋Š” ์•Š๋‹ค)

7.2. Traverson์œผ๋กœ REST API ์‚ฌ์šฉํ•˜๊ธฐ

Traverson์€ ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ HATEOAS์— ๊ฐ™์ด ์ œ๊ณต๋˜๋ฉฐ, ์Šคํ”„๋ง ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•˜์ดํผ ๋ฏธ๋””์–ด API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์†”๋ฃจ์…˜์ด๋‹ค.

  • ์ž๋ฐ” ๊ธฐ๋ฐ˜์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • '๋Œ์•„๋‹ค๋‹Œ๋‹ค(Traverse on)'์˜ ์˜๋ฏธ๋กœ, ์—ฌ๊ธฐ์„œ๋Š” ๊ด€๊ณ„ ์ด๋ฆ„์œผ๋กœ ์›ํ•˜๋Š” API๋ฅผ (์ด๋™ํ•˜๋ฉฐ) ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

Traversion์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์šฐ์„  ํ•ด๋‹น API์˜ ๊ธฐ๋ณธ URI๋ฅผ ๊ฐ–๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค.

Traverson traverson = new Traverson(
    URI.create("http://localhost:8080/api"), MediaTypes.HAL_JSON);
  • Traverson์—๋Š” URL๋งŒ ์ง€์ •ํ•˜๋ฉด ๋˜๊ณ , ์ดํ›„๋ถ€ํ„ฐ๋Š” ๊ฐ ๋งํฌ์˜ ๊ด€๊ณ„ ์ด๋ฆ„์œผ๋กœ API๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • Traverson ์ƒ์„ฑ์ž์—๋Š” ํ•ด๋‹น API๊ฐ€ HAL ์Šคํƒ€์ผ์˜ ํ•˜์ดํผ๋งํฌ๋ฅผ ๊ฐ–๋Š” JSON ์‘๋‹ต์„ ์ƒ์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์ž๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ด ์ธ์ž๋ฅผ ์ง€์ •ํ•˜๋Š” ์ด์œ ๋Š” ์ˆ˜์‹ ๋˜๋Š” ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ Traverson์ด ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.
  • ์–ด๋””์„œ๋“  Traverson์ด ํ•„์š”ํ•  ๋•Œ Traverson ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ์ฃผ์ž…๋˜๋Š” ๋นˆ์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๋ชจ๋“  ์‹์ž์žฌ ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
public Iterable<Ingredient> getAllIngredientsWithTraverson() {
    ParameterizedTypeReference<Resources<Ingredient>> ingredientType =
        new ParameterizedTypeReference<Resources<Ingredient>>() {};

    Resources<Ingredient> ingredientRes = traverson
                                          .follow("ingredients")
                                          .toObject(ingredientType);
	
    Collection<Ingredient> ingredients = ingredientRes.getContent();
	
    return ingredients;
}
  • ๊ฐ ingredients ๋งํฌ๋“ค์€ ํ•ด๋‹น ์‹์ž์žฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๋งํฌํ•˜๋Š” href ์†์„ฑ์„ ๊ฐ€์ง€๋ฏ€๋กœ ๊ทธ ๋งํฌ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด ๋œ๋‹ค.
  • follow() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฆฌ์†Œ์Šค ๋งํฌ์˜ ๊ด€๊ณ„ ์ด๋ฆ„์ด ingredients์ธ ๋ฆฌ์†Œ์Šค๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด ์‹œ์ ์—์„œ ํด๋ผ์ด์–ธํŠธ๋Š” ingredients๋กœ ์ด๋™ํ–‡์œผ๋ฏ€๋กœ toObject()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น ๋ฆฌ์†Œ์Šค์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
  • toObject() ์ธ์ž์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ๋“ค์ด๋Š” ๊ฐ์ฒด์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.
    • Resources ํƒ€์ž…์˜ ๊ฐ์ฒด๋กœ ์ฝ์–ด ๋“ค์—ฌ์•ผ ํ•˜๋Š”๋ฐ, ์ž๋ฐ”์—์„œ๋Š” ๋Ÿฐํƒ€์ž„ ์‹œ์— ์ œ๋„ค๋ฆญ ํƒ€์ž…์˜ ํƒ€์ž… ์ •๋ณด()๊ฐ€ ์†Œ๊ฑฐ๋˜์–ด ๋ฆฌ์†Œ์Šค ํƒ€์ž…์„ ์ง€์ •ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
    • ParameterizedTypeReference๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋ฆฌ์†Œ์Šค ํƒ€์ž…์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ƒ์„ฑ๋œ ํƒ€์ฝ”๋“ค ๊ฐ€์ ธ์˜ค๊ธฐ
public Iterable<Taco> getRecentTacosWithTraverson() {
    ParameterizedTypeReference<Resources<Taco>> tacoType =
        new ParameterizedTypeReference<Resources<Taco>>() {};

    Resources<Taco> tacoRes = traverson
                              .follow("tacos")
                              .follow("recents")
                              .toObject(tacoType);

//  Alternatively, list the two paths in the same call to follow()
//  Resources<Taco> tacoRes = traverson
//                            .follow("tacos", "recents")
//                            .toObject(tacoType);

    return tacoRes.getContent();
}
  • tacos ๋งํฌ ๋‹ค์Œ recents ๋งํฌ๋ฅผ ๋”ฐ๋ผ๊ฐ„๋‹ค.

Traverson์„ ์‚ฌ์šฉํ•˜๋ฉด HATEOAS๊ฐ€ ํ™œ์„ฑํ™”๋œ API๋ฅผ ์ด๋™ํ•˜๋ฉด์„œ ํ•ด๋‹น API์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์‰ฝ๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
Traverson์€ API์— ๋ฆฌ์†Œ์Šค๋ฅผ ์“ฐ๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค(RestTemplate์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์“ฐ๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, API ์ด๋™์ด ์–ด๋ ต๋‹ค).
API์˜ ์ด๋™๊ณผ ๋ฆฌ์†Œ์Šค์˜ ๋ณ€๊ฒฝ ๋˜๋Š” ์‚ญ์ œ ๋ชจ๋‘๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค๋ฉด RestTemplate๊ณผ Traverson์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
Traverson์€ ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๊ฐ€ ์ƒ์„ฑ๋  ๋งํฌ๋กœ ์ด๋™ํ•  ๋•Œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋™ํ•œ ๋‹ค์Œ ํ•ด๋‹น ๋งํฌ๋ฅผ RestTemplate์— ์ง€์ •ํ•˜์—ฌ HTTP ์š”์ฒญ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.


// ์ƒˆ๋กœ์šด ์‹์ž์žฌ(Ingredient ๊ฐ์ฒด) ์ถ”๊ฐ€
public Ingredient addIngredient(Ingredient ingredient) {
    String ingredientsUrl = traverson
                            .follow("ingredients")
                            .asLink()
                            .getHref();
    return rest.postForObject(ingredientsUrl,
                              ingredient,
                              Ingredient.class);
}
  • ingredients ๋งํฌ๋ฅผ ๋”ฐ๋ผ๊ฐ„ ํ›„์— asLink()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ingredients ๋งํฌ ์ž์ฒด๋ฅผ ์š”์ฒญํ•œ๋‹ค.
  • getHref()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ด ๋งํฌ์˜ URL์„ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ๊ฐ€์ ธ์˜จ URL์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•˜์—ฌ RestTempate ์ธ์Šคํ„ด์Šค์˜ postForObject()๋ฅผ ํ˜ธ์ถœ ๋ฐ ์ƒˆ๋กœ์šด ์‹์ž์žฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์š”์•ฝ

  • ํด๋ผ์ด์–ธํŠธ๋Š” RestTemplate์„ ์‚ฌ์šฉํ•ด์„œ REST API์— ๋Œ€ํ•œ HTTP ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Traverson์„ ์‚ฌ์šฉํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‘๋‹ต์— ํฌํ•จ๋œ ํ•˜์ดํผ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์›ํ•˜๋Š” API๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.
โš ๏ธ **GitHub.com Fallback** โš ๏ธ