OpenFeign 완벽 활용: Spring Cloud 마이크로서비스 통신 구현

마이크로서비스 아키텍처에서 서비스 간 통신은 핵심 과제입니다. OpenFeign은 Spring Cloud 기반 환경에서 선언적 방식으로 HTTP 클라이언트를 구성할 수 있게 해주는 강력한 도구입니다.

환경 구성 및 활성화

먼저 의존성을 추가하고 애플리케이션에서 Feign 기능을 활성화합니다.

<!-- build.gradle 예시 -->
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

// 또는 Maven
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.client")
public class PaymentApplication {
    public static void main(String[] args) {
        SpringApplication.run(PaymentApplication.class, args);
    }
}

클라이언트 인터페이스 정의

원격 서비스 호출을 메서드 선언으로 추상화합니다. @FeignClient의 value는 서비스 디스커버리에 등록된 이름입니다.

@FeignClient(
    value = "inventory-service",
    url = "${inventory.service.url:}", // 선택적 직접 URL 지정
    path = "/v1/stock"
)
public interface InventoryRemoteClient {

    @GetMapping("/items/{sku}")
    StockResponse checkAvailability(@PathVariable String sku);

    @PutMapping("/items/{sku}/deduct")
    DeductResult reserveStock(
        @PathVariable String sku,
        @RequestParam("quantity") Integer qty,
        @RequestHeader("X-Transaction-Id") String txId
    );

    @PostMapping("/batch/query")
    List<StockResponse> batchQuery(@RequestBody List<String> skuList);
}

실제 비즈니스 로직에서 주입하여 사용합니다.

@Component
public class PaymentProcessor {
    
    private final InventoryRemoteClient inventoryClient;
    
    public PaymentProcessor(InventoryRemoteClient inventoryClient) {
        this.inventoryClient = inventoryClient;
    }
    
    public PaymentResult process(PaymentRequest req) {
        // 원격 호출이 로컬 메서드 호출처럼 읽힘
        StockResponse stock = inventoryClient.checkAvailability(req.getSku());
        if (!stock.isAvailable()) {
            throw new OutOfStockException();
        }
        // 후속 처리...
    }
}

세밀한 통신 설정

서비스별로 다른 특성을 가질 수 있으므로, 개별 설정이 가능합니다.

feign:
  client:
    config:
      # 전역 기본값
      default:
        connect-timeout: 2000
        read-timeout: 8000
        follow-redirects: false
        default-request-headers:
          X-Source-System: payment-service
      
      # 특정 클라이언트 오버라이드
      inventory-service:
        connect-timeout: 1000
        read-timeout: 3000
        logger-level: headers
      
      # 느린 서비스용 관대한 설정
      legacy-pricing-service:
        connect-timeout: 5000
        read-timeout: 30000

요청/응답 흐름 가시화

운영 환경과 개발 환경에서 로깅 전략을 다르게 적용합니다.

public class FeignLoggingConfiguration {
    
    @Bean
    @Profile("dev")
    public Logger.Level verboseLogging() {
        return Logger.Level.FULL;
    }
    
    @Bean
    @Profile("prod")
    public Logger.Level essentialLogging() {
        return Logger.Level.BASIC;
    }
}

로그 레벨 상세:

  • NONE: 완전한 생략 (프로덕션 권장)
  • BASIC: 메서드, URL, 상태코드, 소요시간
  • HEADERS: BASIC + 헤더 정보
  • FULL: 전체 바디 포함 (민감정보 유출 주의)

컨텍스트 전파: 인증 정보 전달

마이크로서비스 간 인증 컨텍스트를 유지하는 패턴입니다.

@Component
public class SecurityContextPropagator implements RequestInterceptor {
    
    private static final String AUTH_HEADER = "Authorization";
    private static final String TENANT_HEADER = "X-Tenant-Id";

    @Override
    public void apply(RequestTemplate template) {
        // 현재 스레드의 보안 컨텍스트에서 추출
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getCredentials() instanceof String) {
            template.header(AUTH_HEADER, "Bearer " + auth.getCredentials());
        }
        
        // MDC에서 테넌트 정보 추출
        String tenantId = MDC.get("tenantId");
        if (tenantId != null) {
            template.header(TENANT_HEADER, tenantId);
        }
    }
}

장애 대응: Fallback 구현

네트워크 장애 시 대체 동작을 제공하여 시스템 안정성을 확보합니다.

@FeignClient(
    name = "shipping-service",
    fallbackFactory = ShippingClientFallback.Factory.class
)
public interface ShippingClient {
    
    @PostMapping("/shipments")
    ShipmentInfo createShipment(@RequestBody ShipmentRequest request);
    
    @GetMapping("/shipments/{trackingNo}")
    ShipmentStatus trackPackage(@PathVariable String trackingNo);
}
@Component
@Slf4j
public class ShippingClientFallback implements ShippingClient {
    
    private final Throwable failureCause;
    
    public ShippingClientFallback(Throwable cause) {
        this.failureCause = cause;
    }

    @Override
    public ShipmentInfo createShipment(ShipmentRequest request) {
        log.warn("배송 서비스 호출 실패, 대기열에 저장: {}", failureCause.getMessage());
        // 비동기 재처리를 위한 이벤트 발행
        return ShipmentInfo.pending(request.getOrderId());
    }

    @Override
    public ShipmentStatus trackPackage(String trackingNo) {
        // 캐시된 마지막 상태 반환 또는 기본값
        return ShipmentStatus.unknown(trackingNo);
    }
    
    @Component
    public static class Factory implements FallbackFactory<ShippingClient> {
        @Override
        public ShippingClient create(Throwable cause) {
            return new ShippingClientFallback(cause);
        }
    }
}

Fallback 활성화를 위한 설정:

feign:
  circuitbreaker:
    enabled: true
    
# Resilience4j 또는 Sentinel 연동 시
resilience4j:
  circuitbreaker:
    configs:
      default:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s

고급 패턴: 동적 URL 구성

런타임에 대상 서비스를 결정해야 하는 시나리오를 처리합니다.

@FeignClient(name = "dynamic-client", url = "PLACEHOLDER")
public interface DynamicTargetClient {
    // ...
}

// 수동 빌더로 동적 인스턴스 생성
public class FeignClientFactory {
    
    public <T> T createClient(Class<T> clazz, String actualUrl) {
        return Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .target(clazz, actualUrl);
    }
}

성능 최적화 팁

  • 연결 풀: Apache HttpClient 또는 OkHttp로 대체하여 연결 재사용
  • 압축: feign.compression.request.enabled 활성화
  • 응답 캐싱: 변하지 않는 데이터는 @Cacheable과 결합
  • 비동기 처리: CompletableFuture 반환 타입과 AsyncFeign 고려
// Apache HttpClient 연동 예시
feign:
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50
    time-to-live: 900

OpenFeign은 선언적 방식으로 복잡한 HTTP 통신을 단순화하며, Spring Cloud 생태계와의 긴한 통합으로 마이크로서비스 간 협업을 효율적으로 구현할 수 있게 합니다.

태그: OpenFeign Spring Cloud microservices HTTP Client Resilience4j

5월 24일 11:00에 게시됨