[스프링 인 액션] 7.REST 서비스 사용하기

김하영·2021년 7월 28일
0
  • 이 장에서 배우는 내용
    RestTemplate을 사용해서 REST API 사용하기
    Traverson을 사용해서 하이퍼미디어 API 이동하기

7.1 RestTemplate으로 REST 엔드포인트 사용하기

RestTemplate은 REST 리소스를 사용하는 데 번잡한 일을 처리해준다.

REST 리소스와 상호 작용하기 위한 41개 메서드 제공한다.

고유한 작업을 수행하는 메서드는 12개이고, 나머지는 이 메서드들의 오버로딩 버전이다.

TRACE를 제외한 표준 HTTP 메서드 각각에 대해 최소한 하나의 메서드를 갖고 있다.
execute()와 exchange()는 모든 HTTP 메서드 요청을 전송하기 위한 저수준 범용 메서드를 제공한다.

위 표의 메서드는 세 가지 형태로 오버로딩되어 있다.

  • 가변 인자 리스트에 지정된 URL 매개변수에 URL 문자열(String 타입)을 인자로 받는다.
  • Map<String, String>에 지정된 URL 매개변수에 URL 문자열을 인자로 받는다.
  • java.net.URI를 URL에 대한 인자로 받으며, 매개변수화된 URL은 지원하지 않는다.

RestTemplate을 사용하려면 필요한 시점에 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()

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은 도움이 안 된다.

(더 상세한 리소스 데이터를 가져와서 그 안에 포함된 콘텐츠와 링크를 사용할 수 도 있지만, 간단하지는 않다)

?? 질문 : 사내에서는 왜 restTemplate을 사용하지 않는가?

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로 이동할 수 있다.
profile
Back-end Developer

0개의 댓글