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:
-
Group all inbound routes under the rest directive, with each route directed to a mini-pipeline to handle each route request.
Copyrest("/codat")
.consumes(MediaType.APPLICATION_JSON_VALUE)
.post("/createclient").to("direct:createClient")
.get("/checkdatastatus/{clientId}").to("direct:checkDataStatus")
.get("/getbalancesheet/{clientId}").to("direct:getBalanceSheet"); -
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 theSupressException
isfalse
and the parameter isoptional
. Without this parameter set totrue
, 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") -
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}
andlog(${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}
. -
Starting with the latest Apache Camel version, the
ExchangePropertie
s 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}")
-
To pass an inbound request body to the outbound request, use a Set Body component using the Simple language.
Copy.setBody(simple("${body}"))
-
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. -
To set a header, use the
.setHeader(“key”, “value).
Set Header component.Copy.setHeader(Exchange.HTTP_METHOD, constant(HttpMethods.GET))
-
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 andexchangeProperties
you need to add them as parameters. Then, they are injected automatically, allowing you manipulate them.Copypublic 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.
Copypublic class VaultConfigurationException extends Exception {
public VaultConfigurationException(String errorMessage) {
super(errorMessage);
}
}Copyprivate 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.
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:
.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:
@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;
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).