JVM/SpringCloud

마이크로서비스간 동기 통신

kyoulho 2023. 12. 30. 18:39

 

동기식 HTTP 통신


 클라이언트가 요청을 보내고, 서버는 해당 요청에 대한 응답을 즉시 반환하는 방식으로 요청에 대한 응답이 필요한 경우 사용된다.
간단하며 직관적인 구조로 에러 처리가 상대적으로 쉬운 장점이 있지만 클라이언트는 응답을 기다려야 하므로, 블로킹이 발생할 수 있으며 서비스의 의존성이 높아질 가능성이 있다.

 Spring Boot의 RestTemplate을 이용하는 방식과 Spring Cloud의 Feign Client를 이용한 방식이 있다.

 

RestTemplate


RestTemplate 빈 등록

Spring Boot를 사용한다면, RestTemplate은 기본적으로 빈으로 등록되어 있다. 따라서 별도의 설정 없이 주입하여 사용할 수 있다. 그러나 Spring Boot를 사용하지 않는다면, RestTemplate을 빈으로 등록해야 한다.

@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

 

기본 사용법

@Autowired
private RestTemplate restTemplate;

public String getExample() {
    String url = "https://api.example.com/data";
    return restTemplate.getForObject(url, String.class);
}

puglic String getExample2() {
    URI uri = UriComponentsBuilder.fromUriString(url)
        .queryParam("param1", "value1")
        .queryParam("param2", "value2")
        .build()
        .toUri();

    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer token");

    HttpEntity<String> entity = new HttpEntity<>(headers);

    ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
    return response.getBody();
}

 

@LoadBalanced

@LoadBalanced가 붙은 RestTemplate은 Eureka에 등록된 서비스들 간의 로드 밸런싱을 수행할 수 있다.

@Bean
@LoadBalanced
fun restTemplate(): RestTemplate {
	return RestTemplate()
}

fun getOrders(userId: Long): List<OrderResponse> {
    return restTemplate.getForObject("http://ORDER-SERVICE/${userId}/orders", List::class.java) as List<ResponseUser>
}

 

Feign Client


REST Call을 추상화 한 Spring Cloud Netflix 라이브러리이다. LoadBalanced를 지원한다.
 

라이브러리

implementation("org.springframework.cloud:spring-cloud-starter-openfeign")

 

@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
class UserServiceApplication

fun main(args: Array<String>) {
    runApplication<UserServiceApplication>(*args)
}

 

기본사용법

@FeignClient(name = "ORDER-SERVICE") // Eureka에 등록된 서비스를 찾는다
interface OrderClient {
    @GetMapping("/{userId}/orders")
    fun getOrders(@PathVariable userId: String) : List<ResponseOrder>
}
fun getUserByUserId(userId: String): ResponseUser {
    return userRepository.findByUserIdOrThrow(userId)
        .let { ResponseUser.from(it) }
        .apply { orders = orderClient.getOrders(userId) }
}

 

로깅

생성된 각각의 Feign 클라이언트에 대해서 logger가 생성된다. 기본적으로 logger의 이름은 Feign 클라이언트를 생성하는데 사용되는 interface의 전체 클래스 이름이며 Feign 로깅은 오직 DEBUG 레벨에서만 동작하게 된다.

클라이언트별로 설정할 수 있는 Logger.Level 객체는 아래와 같다.

  • NONE : 기본이다
  • BASIC : Request Method와 URL 그리고 Reponse 상태 코드와 실행 시간을 로깅
  • HEADERS : Request, Response Header 정보와 함께 BASIC 정보를 로깅
  • FULL : Request와 Response의 Header, Body 그리고 메타데이터를 로깅

로깅은 하는 방법은 application.yml을 이용하거나 Configuratin 설정을 이용해서 설정할 수 있다.

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            logger-level: FULL

loggin:
  level:
    com.example.userservice.client: DEBUG

 

예외처리

ErrorDecoder는 Netflix Feign 라이브러리에서 제공하는 인터페이스로  클라이언트가 발생시키는 오류를 사용자가 정의한 방식으로 처리할 수 있게 해준다.

@Component
class FeignErrorDecoder : ErrorDecoder {
    override fun decode(methodKey: String, response: Response): Exception {
        return when (response.status()) {
            400 -> {
                if (methodKey.contains("getOrder")) ResponseStatusException(HttpStatusCode.valueOf(response.status()), "User's Order is empty")
                else Exception()
            }
            else -> Exception()
        }
    }
}