Camel DSL Best Practices

Follow the below best practices when writing Camel DSL routing scripts, connectors helper classes, or unit tests.

Build Camel DSL Routing Scripts

The Camel DSL is a language that allows you to configure the Camel Routing Engine behavior. When writing Camel DSL routing scripts, use the below best practices:

  1. Group all inbound routes under the rest directive, with each route directed to a mini-pipeline to handle each route request.

    Copy
    rest("/codat")
                    .consumes(MediaType.APPLICATION_JSON_VALUE)
                    .post("/createclient").to("direct:createClient")
                    .get("/checkdatastatus/{clientId}").to("direct:checkDataStatus")
                    .get("/getbalancesheet/{clientId}").to("direct:getBalanceSheet");
  2. To obtain the properties from the JSON request, get the desired property from the JSONPath and move it in exchangeProperties.

    To treat a required field as a bad request, 404, or some other custom error, the JSONPath can receive an extra bool parameter that stands for SupressJsonParseException. By default the SupressException is false and the parameter is optional. Without this parameter set to true, the parsing throws an exception and the execution of the DSL script exits with the HTTP status code 500.

    Copy
    .setProperty("personName").jsonpath("$.name", true)
                    .choice()
                        .when().simple("${exchangeProperty.name} == null")
                        .setBody(constant("name parameter is missing from request body"))
                        .to("direct:badRequest")
  3. Camel DSL does not differentiate between a route parameter or a URL query parameter when a request call is made. All parameters are found inside ${headers} and log(${headers}) displays all headers.

    NOTE  
    However, if you want to access a specific header, using ${headers.key} does not work. To access an individual header you need to reference it by using ${header.key}.
  4. Starting with the latest Apache Camel version, the ExchangeProperties can be accessed via ${exchangeProperties} as a collection. To access a specific key, the same mechanism from step 3 applies, you need to reference ${exchangeProperty.key}.

    Copy
    .toD("${exchangeProperty.baseUrl}/companies/${exchangeProperty.clientId}/data/financials/balanceSheet${exchangeProperty.queryParams}")
  5. To pass an inbound request body to the outbound request, use a Set Body component using the Simple language.

    Copy
    .setBody(simple("${body}"))
  6. An error regarding bridge requests is returned if the headers in a helper class are not cleared. If the headers are not cleared, than all the inbound request headers are passed to the outbound request headers ( authorization headers, API tokens headers, and API keys headers).

    HINT  
    Enabling bridge requests as an alternative is not recommended.
  7. To set a header, use the .setHeader(“key”, “value). Set Header component.

    Copy
    .setHeader(Exchange.HTTP_METHOD, constant(HttpMethods.GET))
  8. To execute methods from helper classes, use .bean(“class_name”, “method_name”). This invokes the method from the class in your Camel DSL Script.

    Copy
                    .bean("codatRequest", "mapClientIdUrlSegment")
                    .bean("codatRequest", "mapBalanceSheetQueryParams")                
                    .bean("codatRequest", "setupCodatBaseRequest")

Create Connectors Helper Classes

Helper classes are Java objects used to share knowledge such as basic error handling, helper functions, and so on. Below are some key points to take into account when writing Camel DSL helper classes for connectors.

  • Use @Service to make the class available for use in Camel DSL via the .bean directive.

    Copy
    @Service("codatRequest")
    public class CodatRequest {
    ....
    }
  • @Headers and @ExchangeProperties are shared variables by Camel DSL. If you want access to headers and exchangeProperties you need to add them as parameters. Then, they are injected automatically, allowing you manipulate them.

    Copy
    public void setupCodatBaseRequest(@Headers Map<String, String> headers, @ExchangeProperties Map<String, Object> properties) throws VaultConfigurationException {
            // get codat auth Token from Vault
            String authToken = getVaultProperty("AUTH_TOKEN");

            // set default headers & authorization for codat request
            headers.clear();
            headers.put(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
            headers.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            headers.put("Authorization", "Basic " + authToken);

            // set baseUrl for codat request
            properties.put("baseUrl", getVaultProperty("BASE_URL"));
        }
  • To handle different exceptions according to your business, develop your own Exception Classes for special handling of each cause of error.

    Copy
    public class VaultConfigurationException extends Exception {
        public VaultConfigurationException(String errorMessage) {
            super(errorMessage);
        }
    }
     
    Copy
    private String getVaultProperty(String property) throws VaultConfigurationException {
            String propertyKey = String.join("_", VAULT_PREFIX, property);
            var value =  cachingService.getStringValue(Constants.Cache.VAULT,  propertyKey);
            if (value.isPresent())
                return value.get();
            else 
                throw new VaultConfigurationException(property + " not configured in Vault");
        }

Write Unit Tests

Below are some procedures of writing unit tests for a simple route. The following Camel DSL script, receives an inbound request containing a JSON body, checks if the body contains a property called name and then performs an outbound request to an external service to retrieve some data. It then returns this data to the original caller.

Copy
from("direct:createClient")
                .routeId("createClient")
                .log("Codat Create Client Route started")
                .marshal().json()
                .log("Request Body: ${body}")
                .setProperty("name").jsonpath("$.name", true)
                .log("Received New Company Name: ${exchangeProperty.name}")
                .choice()
                    .when().simple("${exchangeProperty.name} == null")
                        .log("name parameter is missing from body")
                        .setBody(constant("name parameter is missing from body"))
                        .to("direct:badRequest")
                .otherwise()                
                .bean("codatRequest", "setupCodatBaseRequest")
                .setBody(simple("${body}"))
                .toD("${exchangeProperty.baseUrl}/companies?throwExceptionOnFailure=false").id("createClientEndpoint")
                .unmarshal().json()
                .log("Received Response: ${body}");
 

Add an id to the outbound request before writing unit tests for Camel connectors. For example:

Copy
.toD("${exchangeProperty.baseUrl}/companies?throwExceptionOnFailure=false").id("createClientEndpoint")
 

The id helps you mock the external request, so that your unit tests are contained within the solution. After the outbound request has been marked with an id. Then, add a MockEndpoint and a ProducerTemplate in your test class. For example:

Copy
@ActiveProfiles("local")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@CamelSpringBootTest
@EnableAutoConfiguration
class CodatRoutesTests {

    @Autowired
    private CamelContext camelContext;

    @Autowired
    private CachingService cachingService;

    @EndpointInject("mock:createClientEndpoint")
    private MockEndpoint mockCreateClientEndpoint;
    
    @Produce("direct:createClient")
    private ProducerTemplate createClientTemplate;

 

NOTE  
The MockEndpoint is a substitute for the outbound call tagged with an .id directive. The ProducerTemplate direct:createClient simulates the inbound request, hence why it bears the same name as the minipipeline from the Camel DSL.

A unit test should cover a route from inbound to outbound. When testing a happy path, you are expecting a response message. However, when testing a fail path, you are expecting no message (because the route DSL Script should fail before reaching outbound).