ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 인 액션] Chapter 7 - REST 서비스 사용하기 :: RestTemplate으로 REST 엔드포인트 사용하기
    개발서적읽기/Spring in Action 제 5판 2020. 8. 25. 13:03



    이번 장에서는 REST API 클라이언트를 작성하고 사용하는 방법을 알아본다.


    그리고 REST API 클라이언트가 추가된 타코 클라우드 애플리케이션을 


    빌드하고 실행해 볼 것이다.


    스프링 애플리케이션은 다음과 같은 방법을 사용해서 REST API를 사용할 수 있다.


     RestTemplate  

     스프링에서 제공하는 간단하고 동기화된 REST 클라이언트 

     Traverson

     스프링 HATEOAS에서 제공하는 하이퍼링크를 인식하는 동기화 

    REST 클라이언트로, 같은 이름의 자바스크립트 라이브러리로부터 비롯됨

     WebClient 

     스프링 5에서 소개된 반응형 비동기 REST 클라이언트


    WebClient는 11장 스프링 반응형 웹 프레임워크에서 알아볼 것이다.


    지금은 나머지 두 개의 REST 클라이언트부터 알아볼 것이다.


    우선 RestTemplate부터 시작해 보자


    클라이언트 입장에서 REST 리소스와 상호작용하려면 해야 할 일이 많아서 코드가 장황해진다.


    즉, 저수준의 HTTP 라이브러리로 작업하면서 클라이언트는 클라이언트 인스턴스와 요청 객체를


    생성하고, 해당 요청을 실행하고, 응답을 분석하여 관련 도메인 객체와 연관시켜 처리해야 한다.


    또한 그 와중에 발생될 수 있는 예외도 처리해야 한다.


    그리고 어떤 HTTP 요청이 전송되더라도 이런 모든 진부한 작업이 반복된다.


    이처럼 장황한 코드를 피하기 위해 스프링은 RestTemplate을 제공한다.


    JDBC를 사용할 때 번거로운 작업을 JDBCTemplate이 처리하듯, RestTemplate은 REST 리소스를


    사용하는 데 번잡한 일을 처리해준다.


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


    그렇지만 고유한 작업을 수행하는 메서드는 12개이며, 나머지는 이 12개 메서드들이


    오버로딩된 메서드들이다.


    아래 표는 12개의 메서드들을 정리해놓은 표이다.


     메서드

     기능 설명 

     delete(...) 

     지정된 URL의 리소스에 HTTP DELETE 요청을 수행한다. 

     exchange(...) 

     지정된 HTTP 메서드를 URL에 대해 실행한다. 그리고 응답 몸체와 연결되는 객체를 포함하는 ResponseEntity를 반환한다. 

     execute(...)

     지정된 HTTP 메서드를 URL에 대해 실행한다. 그리고 응답 몸체와 연결되는 객체를 반환한다. 

     getForEntity(...)

     HTTP GET 요청을 전송한다. 그리고 응답 몸체와 연결되는 객체를 포함하는 ResponseEntity를 반환한다. 

     getForObject(...)

     HTTP GET 요청을 전송한다. 그리고 응답 몸체와 연결되는 객체를 반환한다. 

     headForHeaders(...)

     HTTP HEAD 요청을 전송한다. 그리고 지정된 리소스 URL의 HTTP 헤더를 반환한다. 

     optionsForAllow(...) 

     HTTP OPTIONS 요청을 전송한다. 그리고 지정된 URL의 Allow 헤더를 반환한다.

     patchForObject(...) 

     HTTP PATCH 요청을 전송한다. 그리고 응답 몸체와 연결되는 결과 객체를 반환한다.

     postForEntity(...) 

     URL에 데이터를 POST한다. 그리고 응답 몸체와 연결되는 객체를 포함하는 ResponseEntity를 반환한다. 

     postForLocation(...)

     URL에 데이터를 POST한다. 그리고 새로 생성된 리소스의 URL을 반환한다. 

     postForObject(...) 

     URL에 데이터를 POST한다. 그리고 응답 몸체와 연결되는 객체를 반환한다. 

     put(...) 

     리소스 데이터를 지정된 URL에 PUT한다.


    RestTemplate은 TRACE를 제외한 표준 HTTP 메서드 각각에 대해 최소한 하나의 메서드를 


    갖고 있다. 또한 execute()와 exchange()는 모든 HTTP 메서드의 요청을 전송하기 위한 


    저수준의 범용 메서드를 제공한다.


    위 표의 메서스들은 다음의 세 가지 형태로 오버로딩되어 있다.


    1. 가변 인자 리스트에 지정된 URL 매개변수에 URL 문자열(String 타입)을 인자로 받는다.


    2. Map<String, String>에 지정된 URL 매개변수에 URL 문자열을 인자로 받는다.


    3. java.net.URI를 URL에 대한 인자로 받으며, 매개변수화된 URL은 지원하지 않는다.


    RestTemplate에서 제공하는 12개의 메서드와 이 메서드들의 오버로딩된 버전이


    어떻게 작동하는지 이해하면 REST 리소스를 사용하는 클라이언트를 잘 작성할 수 있다.


    지금부터는 4개의 주요 HTTP 메서드인 GET, PUT, DELETE, POST를 지원하는 


    RestTemplate의 메서드를 알아보자. 우선 GET 메서드를 지원하는 getForObject()와 


    getForEntity()부터 시작하자.




    ■리소스 가져오기(GET)


    타코 클라우드 API로부터 식자재(ingredient)를 가져온다고 해보자. 만일 해당 API에


    HATEOAS가 활성화되지 않았다면 getForObject()를 사용해서 식자재를 가져올 수 있다.


    예를 들어, 다음 코드에서는 RestTemplate을 사용해서 특정 ID를 갖는 Ingredient 객체를


    가져온다.

    public Ingredient getIngredientById(String ingredientId) {
    return rest.getForObject("http://localhost:8080/ingredients/{id}",
    Ingredient.class, ingredientId);
    }

    여기서는 URL 변수의 가변 리스트와 URL 문자열을 인자로 받게 오버로딩된 


    getForObject()를 사용한다. getForObject()에 전달된 ingredientId 매개변수는


    지정된 URL의 {id} 플레이스홀더에 넣기 위해 사용된다. 이 예에는 하나의 변수만


    있지만, 변수 매개변수들은 주어진 순서대로 플레이스홀더에 저장된다는 것을 알아 두자.


    getForObject()의 두 번째 매개변수는 응답이 바인딩되는 타입이다. 여기서는 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);
    }

    여기서 ingredientId 값의 키는 "id"이며, 요청이 수행될 때 {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} 플레이스홀더는


    바로 앞의 getForObject() 오버로딩 버전과 동일하게 Map 항목 값으로 교체된다.


    getForObject() 메서드는 리소스로 도메인 객체만 가져와서 응답 결과로 반환한다.


    그러나 클라이언트가 이외에 추가로 필요한 것이 있다면 getForEntity()를 사용할 수 있다.


    getForEntity()는 getForObject()와 같은 방법으로 작동하지만, 응답 결과를 나타내는 도메인


    객체를 반환하는 대신 도메인 객체를 포함하는 ResponseEntity 객체를 반환한다.


    ResponseEntity에는 응답 헤더와 같은 더 상세한 응답 콘텐츠가 포함될 수 있다.


    예를 들어 도메인 객체인 식자재 데이터에 추가하여 응답의 Date 헤더를 확인하고 싶다고


    하자. 이때는 다음과 같이 getForEntity()를 사용하면 쉽다.

    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()를 호출할 수 있다.




    ■리소스 쓰기(PUT)


    HTTP PUT 요청을 전송하기 위해 RestTemplate은 put() 메서드를 제공한다. 이 메서드는


    3개의 오버로딩된 버전이 있으며, 직렬화된 후 지정된 URL로 전송되는 Object 타입을


    인자로 받는다. 이때 URL 자체는 URI 객체나 문자열로 지정될 수 있다. 


    그리고 getForObject()와 getForEntity()처럼 URL 변수들은 가변 인자 리스트나 Map으로


    제공될 수 있다. 


    특정 식자재 리소스를 새로운 Ingredient 객체의 데이터로 교체한다고 해보자.


    이때는 다음과 같은 메서드를 사용할 수 있다.

    public void updateIngredient(Ingredient ingredient) {
    rest.put("http://localhost:8080/ingredients/{id}",
    ingredient, ingredient.getId());
    }

    여기서 URL은 문자열로 지정되었고 인자로 전달된 Ingredient 객체의 id 속성 값으로


    교체되는 플레이스홀더를 갖는다. put()은 Ingredient 객체 자체를 전송하며, 반환 타입은


    void이므로 이 메서드의 반환값을 처리할 필요는 없다.




    ■리소스 삭제하기(DELETE)


    특정 식자재를 완전히 삭제하고 싶다고 해보자. 이때는 RestTemplate의 delete()를 


    호출하면 된다.

    public void deleteIngredient(Ingredient ingredient) {
    rest.delete("http://localhost:8080/ingredients/{id}",
    ingredient.getId());
    }

    여기서는 문자열로 지정된 URL과 URL 변수 값만 delete()의 인자로 전달한다.


    다른 RestTemplate 메서드와 마찬가지로, URL은 Map으로 된 URL 매개변수나 URI 객체로


    지정될 수 있다.




    ■리소스 데이터 추가하기(POST)


    새로운 식자재를 타코 클라우드 메뉴에 추가한다고 해보자. 이때는 요청 몸체에 식자재 


    데이터를 갖는 HTTP POST 요청을 ~/ingredients 엔드포인트에 하면 된다.


    RestTemplate은 POST 요청을 전송하는 오버로딩된 3개의 메서드를 갖고 있으며


    URL을 지정하는 형식은 모두 같다. POST 요청이 수행된 후 새로 생성된 Ingredient 


    리소스를 반환받고 싶다면 다음과 같이 postForObject()를 사용한다.

    public Ingredient createIngredient(Ingredient ingredient) {
    return rest.postForObject("http://localhost:8080/ingredients",
    ingredient, Ingredient.class);
    }

    postForObject()는 문자열 URL과 서버에 전송될 객체 및 이 객체의 타입을 인자로 받는다.


    또한 여기서는 필요 없지만, URL 변수값을 갖는 Map이나 URL을 대체할 가변 매개변수


    리스트를 네 번째 매개변수로 전달할 수 있다.


    만일 클라이언트에서 새로 생성된 리소스의 위치가 추가로 필요하다면 postForObject()


    대신 postForLocation()을 호출할 수 있다.

    public URI createIngredient(Ingredient ingredient) {
    return rest.postForLocation("http://localhost:8080/ingredients",
    ingredient, Ingredient.class);
    }

    postForLocation()은 postForObject()와 동일하게 작동한다. 하지만 리소스 객체 대신


    새로 생성된 리소스의 URI를 반환한다는 것이 다르다. 반환된 URI는 해당 응답의 Location


    헤더에서 얻는다. 만일 새로 생성된 리소스의 위치와 객체 모두가 필요하다면 


    postForEntity()를 호출하면 된다.

    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();
    }

    RestTemplate 메서드들의 용도는 다르지만 사용하는 방법은 매우 유사하다.


    호출해야할 API가 하이퍼링크를 담고 있다면 RestTemplate보다는 Traverson을 사용하는


    것이 낫다. 다음 포스트에서 Traverson을 공부해보자.

    댓글

Designed by Tistory.