Backend/spring cloud (MSA)

OpenFeign: MSA ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ 

dddzr 2025. 4. 10. 21:32

๐Ÿ“ŒMSA์—์„œ์˜ HTTP ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ

MSA ํ™˜๊ฒฝ์—์„œ๋Š” ๊ฐ ์„œ๋น„์Šค๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ์„œ๋กœ์˜ API๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ ๋‹ค์–‘ํ•œ HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„ฑ๋Šฅ๊ณผ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ๊ณ ๋ คํ•ด ์ ์ ˆํ•œ ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ•œ๋‹ค.

 

โœ… HTTP ํด๋ผ์ด์–ธํŠธ๋Š” MSA์—์„œ๋งŒ ์‚ฌ์šฉํ• ๊นŒ?

โžก๏ธ ์•„๋‹ˆ๋‹ค! ์ผ๋ฐ˜์ ์ธ ๋ชจ๋†€๋ฆฌ์‹ ์•„ํ‚คํ…์ฒ˜์—์„œ๋„ ๋‹ค๋ฅธ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ์œ„ํ•ด HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
โžก๏ธ ํ•˜์ง€๋งŒ MSA์—์„œ๋Š” ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, OpenFeign ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํŠนํžˆ ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค.

 

โœ… ์ผ๋ฐ˜์ ์ธ API ํ˜ธ์ถœ๊ณผ์˜ ์ฐจ์ด์ ์€?

โžก๏ธ ๋ณดํ†ต API ํ˜ธ์ถœ์€ ๋‹จ์ˆœํ•œ HTTP ์š”์ฒญ์ด์ง€๋งŒ, MSA์—์„œ๋Š” ๋” ์„ธ๋ฐ€ํ•œ HTTP ํด๋ผ์ด์–ธํŠธ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค.

  • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ(Eureka) ๋ฅผ ์ด์šฉํ•œ ๋™์  URL ๋งคํ•‘
  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ (Ribbon, Spring Cloud LoadBalancer)
  • ๋ณด์•ˆ ๋ฐ ์ธ์ฆ ์ฒ˜๋ฆฌ (JWT, OAuth ๋“ฑ)

๐Ÿ”ฅ HTTP ํด๋ผ์ด์–ธํŠธ ๋น„๊ต

๋ฐฉ์‹ ๋™๊ธฐ/๋น„๋™๊ธฐ ์ฃผ์š” ํŠน์ง• ์žฅ์  ๋‹จ์ 
RestTemplate (Deprecated) ๋™๊ธฐ Spring ๊ธฐ๋ณธ HTTP ํด๋ผ์ด์–ธํŠธ (Spring 5 ์ดํ›„ ๋น„์ถ”์ฒœ) ์‚ฌ์šฉ๋ฒ•์ด ๊ฐ„๋‹จ Blocking ๋ฐฉ์‹, ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€
WebClient ๋น„๋™๊ธฐ Spring WebFlux ๊ธฐ๋ฐ˜, Reactive ์ง€์› ์„ฑ๋Šฅ ์šฐ์ˆ˜, ํ™•์žฅ์„ฑ ์ข‹์Œ ์ฝ”๋“œ ๋ณต์žก
OkHttp / Apache HttpClient ๋™๊ธฐ/๋น„๋™๊ธฐ ๊ฐ€๋Šฅ ์ €์ˆ˜์ค€ HTTP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ธ๋ฐ€ํ•œ ์ œ์–ด ๊ฐ€๋Šฅ ์ง์ ‘ ๊ตฌํ˜„ ํ•„์š”
OpenFeign ๋™๊ธฐ (๊ธฐ๋ณธ) ์„ ์–ธํ˜• API ํด๋ผ์ด์–ธํŠธ, Spring Cloud ๊ณต์‹ ์ง€์› ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐ, ์œ ์ง€๋ณด์ˆ˜ ์‰ฌ์›€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™๊ธฐ ์ฒ˜๋ฆฌ (๋น„๋™๊ธฐ๋Š” ์„ค์ • ํ•„์š”)



๐Ÿ“Œ ์˜ˆ์ œ ์ฝ”๋“œ

โœ… 1. RestTemplate (Deprecated)

์ฃผ๋ฌธ ์„œ๋น„์Šค → ์ƒํ’ˆ ์„œ๋น„์Šค API ํ˜ธ์ถœ ์˜ˆ์ œ

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class ProductServiceClient {
    private final RestTemplate restTemplate = new RestTemplate();

    public ProductDetailDTO getProductDetail(int productId) {
        String url = "http://product-service/api/query/products/" + productId;
        return restTemplate.getForObject(url, ProductDetailDTO.class);
    }
}

โœ”๏ธ ์žฅ์ : ๊ฐ„๋‹จํ•˜๊ฒŒ HTTP ์š”์ฒญ ๊ฐ€๋Šฅ
โŒ ๋‹จ์ : ๋™๊ธฐ ๋ฐฉ์‹(Blocking)์ด๋ผ ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๊ณ , Spring 5๋ถ€ํ„ฐ ๋น„์ถ”์ฒœ๋จ

 

โœ… 2. WebClient ์‚ฌ์šฉ (๋น„๋™๊ธฐ ๋ฐฉ์‹, ์ถ”์ฒœ!)

Spring WebFlux์˜ WebClient๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ!

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class ProductServiceClient {
    private final WebClient webClient;

    public ProductServiceClient(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://product-service").build();
    }

    public ProductDetailDTO getProductDetail(int productId) {
        return webClient.get()
                .uri("/api/query/products/{id}", productId)
                .retrieve()
                .bodyToMono(ProductDetailDTO.class)
                .block(); // ๋™๊ธฐ ์ฒ˜๋ฆฌ (๋น„๋™๊ธฐ๋กœ ํ•˜๋ ค๋ฉด .subscribe())
    }
}

โœ”๏ธ ์žฅ์ : ๋น„๋™๊ธฐ(Non-blocking) ๋ฐฉ์‹, ์„ฑ๋Šฅ ์šฐ์ˆ˜, WebFlux์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋†’์€ ํ™•์žฅ์„ฑ ์ œ๊ณต

โŒ ๋‹จ์ : ์ฝ”๋“œ๊ฐ€ ๋‹ค์†Œ ๋ณต์žก

 

โœ… 3. OkHttp ๋˜๋Š” Apache HttpClient ์ง์ ‘ ์‚ฌ์šฉ

Low-Level HTTP Client๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹
๋ณดํ†ต OkHttp๋‚˜ Apache HttpClient ๊ฐ™์€ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง์ ‘ ์จ์•ผ ํ•จ.
์ด ๋ฐฉ์‹์€ ๊ฑฐ์˜ ์•ˆ ์”€!

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class ProductServiceClient {
    private final OkHttpClient client = new OkHttpClient();
    private final ObjectMapper objectMapper = new ObjectMapper();

    public ProductDetailDTO getProductDetail(int productId) throws Exception {
        Request request = new Request.Builder()
                .url("http://product-service/api/query/products/" + productId)
                .get()
                .build();

        try (Response response = client.newCall(request).execute()) {
            return objectMapper.readValue(response.body().string(), ProductDetailDTO.class);
        }
    }
}

โœ”๏ธ ์žฅ์ : ์„ธ๋ฐ€ํ•œ HTTP ์š”์ฒญ ์ œ์–ด ๊ฐ€๋Šฅ
โŒ ๋‹จ์ : ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฒƒ์ด ๋งŽ์Œ (์ง์ ‘ ์š”์ฒญ ์ƒ์„ฑ, ์‘๋‹ต ์ฒ˜๋ฆฌ)

 

โœ… 4. OpenFeign (์ถ”์ฒœ!)

@FeignClient(name = "์„œ๋น„์Šค๋ช…")๋งŒ ํ•˜๋ฉด Eureka๋กœ ์„œ๋น„์Šค ๊ฐ„ ํ˜ธ์ถœ ๊ฐ€๋Šฅ!

@FeignClient(name = "product-service", url = "http://product-service")
public interface ProductClient {
    @GetMapping("/api/query/products/{id}")
    ProductDetailDTO getProductDetail(@PathVariable("id") int id);
}

โœ”๏ธ ์žฅ์ : ์„ ์–ธํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋งŒ์œผ๋กœ API ํ˜ธ์ถœ ๊ฐ€๋Šฅ, ์œ ์ง€๋ณด์ˆ˜ ํŽธ๋ฆฌ, Spring Cloud์—์„œ ๊ณต์‹ ์ง€์›

โŒ ๋‹จ์ : ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™๊ธฐ ๋ฐฉ์‹ (๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์ถ”๊ฐ€ ์„ค์ • ํ•„์š”)



๐Ÿš€๊ฒฐ๋ก : ์–ด๋–ค ๋ฐฉ์‹์„ ์„ ํƒํ• ๊นŒ?

โžก๏ธ ๋‹จ์ˆœํ•œ API ํ˜ธ์ถœ: RestTemplate (ํ•˜์ง€๋งŒ ๋น„์ถ”์ฒœ)
โžก๏ธ ๋น„๋™๊ธฐ + ์„ฑ๋Šฅ ์ค‘์š”: WebClient
โžก๏ธ ์„ธ๋ฐ€ํ•œ HTTP ์ œ์–ด ํ•„์š”: OkHttp / Apache HttpClient
โžก๏ธ MSA ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ํšจ์œจ์ : OpenFeign ( Spring Cloud ๊ณต์‹ ์ง€์›!)

 

OpenFeign์„ ์“ฐ๋ฉด ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ(Eureka)์™€ ์—ฐ๊ณ„ํ•˜์—ฌ ๋™์  API ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ,

MSA ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ํšจ์œจ์ ์ด๋‹ค! ๐Ÿ˜„