[Camel in Action] 5-3. Routing Slip, Dynamic Router, Load Balancer EIP

Routing Slip – 동적 라우팅 슬립

Routing Slip 패턴은 메시지가 통과해야 할 처리 단계 목록을 미리 메시지에 첨부하는 방식입니다. 메시지가 라우트를 따라 이동하면서 슬립에 적힌 순서대로 처리됩니다.

Routing Slip 구현

// 헤더에 라우팅 슬립 지정
from("direct:orderEntry")
    // 헤더에 처리 단계를 콤마로 구분해 저장
    .setHeader("routingSlip",
        simple("direct:validate,direct:inventory,direct:billing,direct:ship"))
    .routingSlip(header("routingSlip"))
    .log("모든 단계 처리 완료");

// 또는 Bean으로 동적 결정
from("direct:processOrder")
    .bean(OrderRouter.class, "buildRoutingSlip")
    .routingSlip(body());  // body에서 라우팅 슬립 읽기

@Component
public class OrderRouter {
    public String buildRoutingSlip(@Body Order order) {
        StringBuilder slip = new StringBuilder("direct:validate");
        if (order.isHighValue()) slip.append(",direct:fraudCheck");
        if (order.isInternational()) slip.append(",direct:customs");
        slip.append(",direct:fulfillment");
        return slip.toString();
    }
}

@RoutingSlip 어노테이션

@Component
public class AnnotatedRouter {
    @RoutingSlip(delimiter = ",")
    public String route(@Header("orderType") String type) {
        return switch (type) {
            case "DIGITAL" -> "direct:digital,direct:emailDelivery";
            case "PHYSICAL" -> "direct:physical,direct:warehouse,direct:shipping";
            default -> "direct:unknown";
        };
    }
}

from("direct:order")
    .bean(AnnotatedRouter.class);

Dynamic Router – 단계별 동적 결정

각 단계를 처리한 후 다음 목적지를 동적으로 결정하는 패턴입니다. null을 반환하면 라우팅이 종료됩니다.

@Component
public class DynamicOrderRouter {
    private final Map<String, String> stateMap = new ConcurrentHashMap<>();

    @DynamicRouter
    public String route(Exchange exchange, @ExchangeProperty("currentState") String state) {
        String orderId = exchange.getIn().getHeader("orderId", String.class);

        if (state == null) {
            exchange.setProperty("currentState", "VALIDATE");
            return "direct:validateOrder";
        }
        return switch (state) {
            case "VALIDATE" -> {
                exchange.setProperty("currentState", "INVENTORY");
                yield "direct:checkInventory";
            }
            case "INVENTORY" -> {
                exchange.setProperty("currentState", "PAYMENT");
                yield "direct:processPayment";
            }
            case "PAYMENT" -> null;  // 라우팅 종료
            default -> null;
        };
    }
}

from("direct:order")
    .dynamicRouter(method(DynamicOrderRouter.class, "route"));

Load Balancer EIP – 부하 분산

여러 처리 인스턴스 또는 서버에 부하를 분산합니다.

// 라운드 로빈 (균등 분배)
from("direct:orders")
    .loadBalance().roundRobin()
        .to("direct:server1", "direct:server2", "direct:server3")
    .end();

// 랜덤 분배
from("direct:orders")
    .loadBalance().random()
        .to("direct:worker1", "direct:worker2")
    .end();

// Sticky (같은 키는 같은 서버로)
from("direct:orders")
    .loadBalance()
        .sticky(header("customerId"))
        .to("direct:node1", "direct:node2")
    .end();

// 가중치 기반
from("direct:orders")
    .loadBalance()
        .weighted(true, "3,1,1")  // 60%, 20%, 20%
        .to("direct:heavy", "direct:medium", "direct:light")
    .end();

Failover Load Balancer

// 실패 시 다음 서버로 자동 전환
from("direct:orders")
    .loadBalance()
        .failover(3, false, true,  // 최대 3번, 라운드로빈, inheritErrorHandler
            IOException.class, TimeoutException.class)
        .to("http://primary-service/api", "http://secondary-service/api")
    .end();

Leave a Comment