[Apache Camel (ver 4.x)] Dynamic DSL Load

식빵·2025년 7월 6일

apache-camel

목록 보기
4/5
post-thumbnail

이 글에서는 Apache Camel 4.12.0, jdk 21 버전을 사용합니다.
또한 쉬운 시작을 위해서 Spring boot 프로젝트 기반으로 작성했습니다만,
실제 Spring Boot 기능을 사용하지는 않는다는 점 참고하시기 바랍니다.


📌 프로젝트 Dependency 추가

camel 에서 대표적인 2개의 DSL 인 xml, yaml dsl 적용을 위해서
아래와 같이 spring boot 프로젝트 생성시 기본으로 생성되는 pom.xml 에다가
2개의 dependency 를 추가했습니다.

  • camel-yaml-dsl
  • camel-xml-io-dsl
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  
    <groupId>coding.toast</groupId>
    <artifactId>camel-micro-service-a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-little-camel</name>
    
    <properties>
        <java.version>21</java.version>
        <camel.version>4.12.0</camel.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
            <version>${camel.version}</version>
        </dependency>

      	<!-- yaml dsl 사용을 위한 dependency -->
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-yaml-dsl</artifactId>
            <version>${camel.version}</version>
        </dependency>
      
      	<!-- xml dsl 사용을 위한 dependency -->
        <dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-xml-io-dsl</artifactId>
			<version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>



🖥️ Yaml DSL Router 동적 로딩

테스트 코드

package coding.toast.mylittlecamel;

import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.engine.DefaultResourceResolvers;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.ResourceHelper;


public class MainTestClass {
    public static void main(String[] args) {
        try (CamelContext camelContext = new DefaultCamelContext()) {
			
            
            // camelContext 를 미리 Start 합니다. 
            // Router 를 모두 적용한 이후에 해도 상관없습니다.
            camelContext.start();

			// DSL 정보를 담는 Resource 를 읽어옵니다.
            // 아래 Resource 는 classpath 에 파일(src/main/resources/my-route.yaml) 을 정보입니다.
            Resource resource 
            	= ResourceHelper.resolveResource(camelContext, "classpath:/my-route.yaml");

			// routeLoader 를 참조합니다.
            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(camelContext);

			// 참조하는 routeLoader 에 Router 정보를 담고 있는 DSL Resource 를 Load 합니다.
			loader.loadRoutes(resource);

			// 기본적으로 카멜은 별개의 쓰레드 위에서 비동기적으로 동작함으로, 
            // 강제로 main 쓰레드를 대기 시킵니다.
            Thread.sleep(10000);
            camelContext.stop();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}


// 참고: classpath 뿐만아니라, file 스키마도 지원합니다!
// Resource resource 
//	= ResourceHelper.resolveResource(camelContext, "file:C:\\my-route.yaml");

// 참고2: ResourceLoader 는 다음과 같은 방법으로 참조할 수도 있습니다.
// RoutesLoader loader 
//   = camelContext.getCamelContextExtension().getContextPlugin(RoutesLoader.class);



예시 Yaml DSL 작성법

src/resources/my-route.yaml 파일을 아래처럼 작성합니다.

- from:
    uri: "timer:yamlTimer?period=1000"
    steps:
      - setBody:
          simple: "Hello from YAML at ${date:now}"
      - to: "log:yamlRoute"


참고로 하나의 yaml 파일에 여러개의 from 절(=consumer)을 사용할 수 있습니다.

- from:
    uri: "timer:yamlTimer?period=1000"
    steps:
      - setBody:
          simple: "Hello from YAML at ${date:now}"
      - to: "direct:aaa"

- from:
    uri: "direct:aaa"
    steps:
      - to: "log:finish-logger"

이러면 "log:finish-logger" 에 의하여 로그가 찍히게 됩니다.

router id 도 부여하고 싶다면 아래처럼 하면 됩니다.

- route:
    id: foo1
    from:
      uri: "timer:yamlTimer?period=1000"
      steps:
        - setBody:
            simple: "Hello from YAML at ${date:now}"
        - to: "direct:aaa"

- route:
    id: foo2
    from:
      uri: "direct:aaa"
      steps:
        - to: "log:finish-logger"




🖥️ XML DSL Router 동적 로딩

예시 코드

package coding.toast.mylittlecamel;

import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.ResourceHelper;

public class MainTestClass {
    public static void main(String[] args) {

        try (CamelContext camelContext = new DefaultCamelContext()) {
            camelContext.start();

            // my-route.xml -> my-route.xml 로 바뀐 거 빼고는 다 똑같습니다.
            Resource resource 
             = ResourceHelper.resolveResource(camelContext, "classpath:/my-route.xml");

            RoutesLoader loader = PluginHelper.getRoutesLoader(camelContext);
            loader.loadRoutes(resource);
            Thread.sleep(10000);
            camelContext.stop();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

예시 xml dsl 작성

<routes>
    <route id="xmlRoute">
        <from uri="timer:xmlTimer?period=1000"/>
        <setBody>
            <simple>Hello from XML at ${date:now}</simple>
        </setBody>
        <to uri="direct:second"/>
    </route>
    <route id="xmlRoute2">
        <from uri="direct:second"/>
        <to uri="log:xml-log-finisher"/>
    </route>
</routes>

실행결과:



🖥️ 문자열 형태의 DSL 로딩

때로는 문자열 형태의 DSL 이 필요할 수 있습니다.
이때는 MemResolver 를 사용해보세요!

package coding.toast.mylittlecamel;

import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.engine.DefaultResourceResolvers;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.ResourceHelper;

class CamelTestMain {
    public static void main(String[] args) {
        try (CamelContext camelContext = new DefaultCamelContext()) {
            camelContext.start();

            String yaml = """
            - from:
                uri: "timer:inMemory?period=3000"
                steps:
                  - setBody:
                      simple: "Hi from MemResolver at ${date:now}"
                  - to: "log:memResolver"
            """;


			// 방법(1): Resolver 사용 
            try (DefaultResourceResolvers.MemResolver resolver 
            	 = new DefaultResourceResolvers.MemResolver()) {
            	
                // createResource 의 첫번째 인자를 넣을 때, 
                // 문자열 형태가 
                // - yaml 이면 "mem:??.yaml",
                // - xml 이면 "mem:???.xml"  
                // 처럼 끝에 확장자를 정확히 명시적으로 표기해야 합니다!
                Resource resource = resolver.createResource("mem:my-route.yaml", yaml);
                // Resource resource = resolver.createResource("mem:my-route.xml", xml);

                
                RoutesLoader loader = PluginHelper.getRoutesLoader(camelContext);
                loader.loadRoutes(resource);
            }
            
			// 방법(2): ResourceHelper 사용
			// 또 다른 방법으로는 ResourceHelper 를 사용하는 겁니다.
            // Resource resource 
            //	= ResourceHelper.fromBytes("dynamic-route.yaml", yaml.getBytes());
            // RoutesLoader loader = PluginHelper.getRoutesLoader(camelContext);
            // loader.loadRoutes(resource);

            Thread.sleep(10000);
            camelContext.stop();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}



✨ 참고: route 중지/제거

Thread.sleep(5000);
// stopRoute, removeRoute 모두 routeId 를 문자열로 넘겨줘야 합니다.

// 먼서 Stop 을 해야 합니다. Stop 없이 remove 하려면 무효화됩니다.
camelContext.getRouteController().stopRoute("2025-01-01-UUID1", 5000, TimeUnit.SECONDS);
camelContext.getRouteController().stopRoute("2025-01-01-UUID2", 5000, TimeUnit.SECONDS);

// Stop 이후 remove 를 하면 정상적으로 Load 했던 router 가 삭제됩니다.
camelContext.removeRoute("2025-01-01-UUID1");
camelContext.removeRoute("2025-01-01-UUID2");

Thread.sleep(5000); // keep app running for a while
camelContext.stop();
  • 참고로 어떤 Component/Endpoint 를 쓰냐에 따라서 곧바로 중지할 수도 있고,
    Graceful 하게 느릿하게 정지될 수도 있습니다.

  • 하지만 Graceful 하게 종료시키려고 함에도 timeout 날 수도 있습니다.
    이는 Camel 의 자연스러운 동작 방식입니다. Camel 은 어떻게든 마지막까지 데이터를
    다 받거나 주는 것이 안전한 것으로 판단하기 때문이죠.



✨ 추천: Jbang Camel

참고: https://camel.apache.org/manual/camel-jbang.html

Jbang + camel 을 활용하면 cli 환경에서 곧바로 camel route 를 실행할 수 있고,
심지어는 여러 camel 실행 내역을 관리하는 것도 가능합니다.

camel 실행예시


camel 관리예시

camel 을 가볍게 시작하시고 싶으시거나, 빠르게 ui 개발만 하고 실제 route 처리는
jbang camel 에게 위임하는 방식의 개발을 하고 싶으신 분들에게 매우 유용합니다.

더 다양한 예시는 https://github.com/apache/camel-jbang-examples 참고!




✨ 참고한 링크

profile
백엔드 개발자로 일하고 있는 식빵(🍞)입니다.

0개의 댓글