해당 포스트는 Spring Cloud에 속한 기술들에 대한 개념과 주요 기술에 대해 알아보고 실무에 적용했었던 내용을 정리하는 포스트입니다.
MSA와 관련된 패턴 중 어플리케이션 패턴으로 마이크로서비스 간 동기 통신 / 비동기 통신이 있는데요.
기능 / 관심사 / 도메인 별로 분리된 마이크로서비스 간 협력이나 트랜잭션을 수행해야 할 때 가장 쉽게 사용하는 것이 REST API를 활용한 동기 통신입니다.
이 때 서버 측에서는 다른 서버에 통신을 보내는 동기 통신 클라이언트가 있어야 하는데, 대표적으로 RestTemplate과 Spring Cloud OpenFeign이 있습니다.
Spring Cloud OpenFeign은 Spring Cloud 프로젝트에 포함된 동기 통신 클라이언트로, 선언적 REST 클라이언트로서 웹 서비스 클라이언트 작성을 보다 쉽게 할 수 있습니다.
직접 RestTemplate을 호출해서 대상 서버에게 통신을 요청하는 기존 방식과는 달리 인터페이스로 선언만 해두면 자동으로 구현체가 생성되는 형식입니다.
// 인터페이스를 선언
@FeignClient(name = "example-service", url = "${example-service.url}")
public interface ExampleFeignClient {
@GetMapping("/api/resource")
String getResource();
}
RestTemplate과 Spring Cloud OpenFeign의 주요 차이점은 다음과 같습니다.
Spring Cloud OpenFeign의 기능을 활성화하는 방법은 간단합니다. 메인 클래스에 다음과 같은 주석을 추가합니다.
@EnableFeignClients // OpenFeign 활성화
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
기능을 활성화 한 후, 클라이언트에 대한 구성 클래스를 생성하면 됩니다.
다만 여기서 LoadBalancer가 존재하지 않거나 Ribbon을 쓰지 않는 로컬 환경에서의 구성 클래스는 다음과 같이 진행해야 하는데요.
@Configuration
public class FeignConfig {
// Ribbon 로드 밸런서를 사용하는 것이 기본값이라 이 부분을 null로 처리
// 처리하지 않을 경우 아래의 오류 메시지 출력
// No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
@Bean
public Client feignClient() {
return new Client.Default(null, null);
}
}
반면 LoadBalancer를 사용할 수 있는 환경이라면 별도의 Client 설정을 처리하지 않아도 됩니다.
그밖에 Client 설정에서 SSL 관련 설정 등 다양한 설정을 할 수 있습니다.
application.yml에서도 부가 설정을 처리할 수 있습니다.
spring:
cloud:
openfeign:
httpclient:
enabled: true
connection-timeout: 5000 # 통신 요청 후 서버 연결 시간이 5초 경과 시 connection-time out 처리
ok-http:
read-timeout: 5000 # 응답 데이터를 읽는 시간이 5초 경과 시 read-time out 처리
인터페이스와 선언적 API로 클라이언트를 간단하게 만들 수 있다고 했는데 예시를 들면 다음과 같습니다.
// Client 선언부, name 또는 url 사용 가능
// name은 Spring Cloud Discovery Client
// 또는 Spring Cloud Kubernetes Discovery Client로 서비스 이름을 직접 등록할 수 있음
@FeignClient(url = "http://localhost:8081")
public interface SampleClient {
@GetMapping("/api/v1/internal/sample/cases")
ResponseEntity<List<CaseDTO>> getCaseList(@RequestHeader("Internal-Auth-Token") String token);
@GetMapping("/api/v1/internal/sample/cases/{id}")
ResponseEntity<CaseDTO> getCase(@RequestHeader("Internal-Auth-Token") String token,
@PathVariable("id") Long id);
}
위의 코드는 test라는 마이크로서비스에서 sample(url은 http://localhost:8081)이라는 마이크로서비스의 특정 엔드포인트로 통신을 요청하는 인터페이스를 설정한 것입니다.
클라이언트 어노테이션에 요청을 받을 대상의 URL이나 서비스 이름을 선언할 수 있습니다. (name에 서비스 이름 대신 URL을 넣어도 작동합니다)
그리고 클라이언트가 sample이라는 마이크로서비스에 보낼 요청에 대한 메서드를 작성할 때 HTTP 메서드와 URI, 리턴값, 요청 헤더와 PathVariable을 모두 선언할 수 있습니다.
반대로 sample이라는 마이크로서비스에서는 test가 요청을 보낼 수 있도록 위의 인터페이스에서 선언한 메서드를 그대로 구현하면 됩니다.
// sample에서 동기 통신을 받을 API와 관련된 Controller 내부
@GetMapping("/api/v1/internal/sample/cases")
public ResponseEntity<List<CaseDTO>> getCaseList(@RequestHeader("Internal-Auth-Token") String token) {
return ResponseEntity.ok().body(...);
}
@GetMapping("/api/v1/internal/sample/cases/{id}")
public ResponseEntity<CaseDTO> getCase(@RequestHeader("Internal-Auth-Token") String token,
@PathVariable("id") Long id) {
return ResponseEntity.ok().body(...);
}
마지막으로 test에서는 위의 클라이언트를 활용할 수 있습니다.
@RequiredArgsConstructor
@RestController
public class TestController {
// Client 인터페이스 사용
private final SampleClient client;
@GetMapping("/api/v1/cases")
public void handleSampleClient() {
// 클라이언트를 활용해 sample 서비스와 동기 통신을 하여 응답을 가져옴
ResponseEntity<List<CaseDTO>> result = client.getCaseList();
// ...
}
https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html