최근에 개발되는 서비스들은 대부분 MSA(Micro Service Architecture)를 채택하고 있다.
기능별로 개발하며, 자신이 가진 기능을 API로 외부에 노출시키고 다른 서버가 해당 API를 호출해 사용할 수 있게 구성한다.
즉, 각 서버가 다른 서버의 클라이언트로 사용될 수 있도록 구성하는 것이다.
다른 서버로 요청을 보내고 응답을 받을 수 있게 도와주는 RestTemplate과 WebClient에 대해서 살펴보도록 하겠다.
스프링에서 HTTP 통신 기능을 쉽게 사용하도록 도와주는 템플릿이다.
기본적으로 동기 방식으로 처리되며, 비동기 방식으로 사용하고 싶을 경우 AsyncRestTemplate을 사용하면 된다.
RestTemplate의 동작을 확인하기 위해 간단한 프로젝트를 진행해보겠다.

Controller
@RestController
@RequestMapping("/rest-template")
public class RestTemplateController {
private final RestTemplateService restTemplateService;
public RestTemplateController(RestTemplateService restTemplateService) {
this.restTemplateService = restTemplateService;
}
@GetMapping
public String getName() {
return restTemplateService.getName();
}
@GetMapping("/path-variable")
public String getNameWithPathVariable(){
return restTemplateService.getNameWithPathVariable();
}
@GetMapping("/parameter")
public String getNameWithParameter(){
return restTemplateService.getNameWithParameter();
}
@PostMapping
public ResponseEntity<MemberDto> postDto(){
return restTemplateService.postWithParamAndBody();
}
@PostMapping("/header")
public ResponseEntity<MemberDto> postWithHeader(){
return restTemplateService.postWithHeader();
}
}
DTO 객체
public class MemberDto {
private String name;
private String email;
private String organization;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getOrganization() {
return organization;
}
public void setOrganization(String organization) {
this.organization = organization;
}
@Override
public String toString() {
return "MemberDTO{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
", organization='" + organization + '\'' +
'}';
}
}
Service
@Service
public class RestTemplateService {
public String getName() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithPathVariable() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/{name}")
.encode()
.build()
.expand("Flature") // 복수의 값을 넣어야할 경우 , 를 추가하여 구분
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithParameter() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/param")
.queryParam("name", "Flature")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public ResponseEntity<MemberDto> postWithParamAndBody() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.queryParam("email", "flature@wikibooks.co.kr")
.queryParam("organization", "Wikibooks")
.encode()
.build()
.toUri();
MemberDto memberDto = new MemberDto();
memberDto.setName("flature!!");
memberDto.setEmail("flature@gmail.com");
memberDto.setOrganization("Around Hub Studio");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.postForEntity(uri, memberDto,
MemberDto.class);
return responseEntity;
}
public ResponseEntity<MemberDto> postWithHeader() {
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/add-header")
.encode()
.build()
.toUri();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature");
memberDTO.setEmail("flature@wikibooks.co.kr");
memberDTO.setOrganization("Around Hub Studio");
RequestEntity<MemberDto> requestEntity = RequestEntity
.post(uri)
.header("my-header", "Wikibooks API")
.body(memberDTO);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.exchange(requestEntity,
MemberDto.class);
return responseEntity;
}
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
HttpClient client = HttpClientBuilder.create()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(2000);
factory.setReadTimeout(5000);
RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}
}
RestTemplate은 서비스 또는 비즈니스 계층에 구현된다.
즉, 클라이언트로 요청을 받는 컨트롤러 -> RestTemplate를 활용해 다른 서버에 통신 요청을 하는 서비스 계층 과정으로 서버 간 통신이 진행된다.
최신버전의 스프링부트에서는 RestTemplate이 지원 중단된다는 말이 있어, WebClient에 대해서도 알아야할 것 같다.
RestTemplate과 다르게 비동기형식으로 사용할 수 있다고 한다.
예시 코드를 통해서 살펴보자.
@Service
public class WebClientService {
public String getName() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
return webClient.get()
.uri("/api/v1/crud-api")
.retrieve()
.bodyToMono(String.class)
.block();
}
public String getNameWithPathVariable() {
WebClient webClient = WebClient.create("http://localhost:9090");
ResponseEntity<String> responseEntity = webClient.get()
.uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/{name}")
.build("Flature"))
.retrieve().toEntity(String.class).block();
ResponseEntity<String> responseEntity1 = webClient.get()
.uri("/api/v1/crud-api/{name}", "Flature")
.retrieve()
.toEntity(String.class)
.block();
return responseEntity.getBody();
}
public String getNameWithParameter() {
WebClient webClient = WebClient.create("http://localhost:9090");
return webClient.get().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.build())
.exchangeToMono(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(String.class);
} else {
return clientResponse.createException().flatMap(Mono::error);
}
})
.block();
}
public ResponseEntity<MemberDto> postWithParamAndBody() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature!!");
memberDTO.setEmail("flature@gmail.com");
memberDTO.setOrganization("Around Hub Studio");
return webClient.post().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.queryParam("email", "flature@wikibooks.co.kr")
.queryParam("organization", "Wikibooks")
.build())
.bodyValue(memberDTO)
.retrieve()
.toEntity(MemberDto.class)
.block();
}
public ResponseEntity<MemberDto> postWithHeader() {
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9090")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
MemberDto memberDTO = new MemberDto();
memberDTO.setName("flature!!");
memberDTO.setEmail("flature@gmail.com");
memberDTO.setOrganization("Around Hub Studio");
return webClient
.post()
.uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/add-header")
.build())
.bodyValue(memberDTO)
.header("my-header", "Wikibooks API")
.retrieve()
.toEntity(MemberDto.class)
.block();
}
public void cloneWebClient() {
WebClient webClient = WebClient.create("http://localhost:9090");
WebClient clone = webClient.mutate().build();
}
}