Camel development series part 7

In this post I will go through how I found a usefully way of handling asynchronous error handling when working with Apache Camel.

The requirements are as follows.

  1. We have a bunch of routes chained together who exist in different routeBuilder classes.
  2. The client does not require a response back so there is no need for an synchronous connection.
  3. We want a generic error handling strategy as we don’t want to replicate the error handling code in each routeBuilder class.

Essentially we want a global exception handler applied to all routeBuilder classes in a uniform way. Some months ago I faced the above requirements and googled a bit and found this post on stackoverflow:

http://stackoverflow.com/questions/9283861/camel-exception-handling-doesnt-work-if-exception-clause-is-defined-in-a-separat

This helped a lot accomplishing what I wanted. So let’s break it down:

Standard routeBuilder class

 

public class MyRouteBuilder extends RouteBuilder {
  
  @Override
  public void configure() throws Exception {
	ExceptionBuilder.setup(this);
	from()...to();
	}
}

Notice, that we have added the line

 ExceptionBuilder.setup(this);

which sends this instance of the RouteBuilder class to our ExceptionBuilder.

Now off course we need to create our ExceptionBuilder class. This is how I have done:

public class ExceptionBuilder {

    public static void setup(RouteBuilder routeBuilder) {
    routeBuilder.onException(Exception.class).useOriginalMessage().handled(true).to("direct://ErrorHandler");
  }  
}

I have created a standard java class which has a setup method accepting a RouteBuilder object as a parameter.

It then configures the .onException handling and directs the exchange to the ErrorHandler route. The configuration I have used is based on my needs, you can configure it your way. I want to listen for all possible exceptions, and use the original message and handle the exception which is why it looks like that. Now all we have left is to create our ErrorHandler route.

public class ErrorHandler extends RouteBuilder {

  @Override
  public void configure() throws Exception {
    String hashSeparator = "##################################################################################################################";
    String lineBreak = System.lineSeparator();
    from("direct://ErrorHandler").startupOrder(1).routeId("ErrorHandler")
      .log(LoggingLevel.ERROR, "ErrorHandler", hashSeparator)
      .log(LoggingLevel.ERROR, "ErrorHandler", "An error occured in the integration integration" + lineBreak)
      .setHeader("CamelMessageId", simple("${id}"))
      .setHeader("CamelContextId", simple("${camelId}"))
      .setHeader("CamelCreatedTimestamp", simple("${property.CamelCreatedTimestamp}"))
      .setHeader("CamelExceptionCaught", simple("Exception Object: ${property.CamelExceptionCaught}"))
      .setHeader("CamelStacktrace", simple("${exception.stacktrace}"))
      .setHeader("ExceptionMessage", simple("${exception.message}"))
      .setHeader("CamelFailureEndpoint", simple("${property.CamelFailureEndpoint}"))
      .setHeader("CamelFailureRouteId", simple("${property.CamelFailureRouteId}"))
      .log(LoggingLevel.ERROR, "ErrorHandler", " Headers: ${headers}" + lineBreak + lineBreak)
      .log(LoggingLevel.ERROR, "ErrorHandler", " Body: ${body}" + lineBreak)
      .log(LoggingLevel.ERROR, "ErrorHandler", hashSeparator);
  }
}

The ErrorHandler route is a normal route but with an explicit startup order and then I log a bunch of specific headers and set other headers. You can add and remove stuff as you please and as you want your error log output to look.

Combining the three steps we have seen gives us a powerful way to do error handling in a generic manner applied to all our RouteBuilder classes. This is especially useful for integrations where the client doesn’t need a response and the error handling is the same for all routes.
 

Annonser

Camel development series part 6

It has been months since I wrote a blog on Camel. This has been mainly due to a lot of activity in my personal and professional life but I thought would get back on track with the blog.

In today post we will look at some basic beginner ”best practice”. Note, if someone reading disagree then please do comment as it is always interesting to receive constructive feedback.

Always name your CamelContext.

  • It does not matter if you are writing in pure java or a mix of blueprint and java. Always name your CamelContext.
  • It makes writing and reading logs easier as there is now a user friendly name to look out for.
  • It gives your reader an understanding of what this Context is supposed to do.
  • It helps to get into the spirit of naming your Camel objects such as routes.
  • Code example:

    CamelContext context = new DefaultCamelContext();
    context.setName("MyCamelContext");
    

    #Blueprint version

    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
     
        <camelContext xmlns="http://camel.apache.org/schema/blueprint" name="MyCamelContext">
            <route>
                <from uri="timer:test" />
                <to uri="log:test" />
            </route>
        </camelContext>
     
    </blueprint>
    &nbsp;
    

    Always give a routeId to your routes.

  • Give a meaningful id to your route. Amongst others, it helps to show which routes have started when the camel context starts. Otherwise you will simply see route1, route2, route3 without know which of these match your routes.
  • It makes it much easier to write log statements if you have a routeId. Each log statement that belongs to a route can use the routeId to put the statements in their category. This makes it much easier to know which route publish which log statement.
  • It gives you an enormous benefit during testing because you can intercept, replace and do other things based on your routeId. I do a lot of replacement during tests for example I replace my eventbus route with a hardcoded response. You need a routeId for this.
  • Always give an id to statements that are configurable.

  • For example, I may send my exchange to a bean method or to a processor or to another route via direct. Give meaningful id names to each .to() statement. It makes testing so much easier as you can intercept and replace .to() endpoints and that is a big bonus during test.
  • Code example:

    from("inputUri).routeId("MyRouteId").to("outputUri).id("MyId");
    

    Keep inline processors short

  • In camel you can create inline processors to write normal java code and manipulate whatever is on the exchange or the headers. This is good for quick and dirty operations but if you are doing complex business logic or operations that should be configurable or hidden then create a Processor class instead and invoke that.
  • Keeping shorter inline processor code makes reading the camel dsl code easier as well.
  • By putting complex resuable code in a Processor class you ensure that other routes can call it as well. This means you write code once and call it from other parts of the CamelContext. You don’t then need to write same inline code everywhere.
  • Code example:

    from("activemq:myQueue").process(new Processor() {
        public void process(Exchange exchange) throws Exception {
            String payload = exchange.getIn().getBody(String.class);
            // do operations on the payload that should only happen at this //point in the route and only in this route.
    // Add simple logic for e.g. header or body manipulation.
    //Complex logic goes in separate Processor class.
           exchange.getIn().setBody("Changed body");
       }
    }).to("activemq:myOtherQueue");
    
    #Processor class
    public class MyProcessor implements Processor {
      public void process(Exchange exchange) throws Exception {
        String body = exchange.getIn().getBody().toString();
        body="changed";
    exchange.getIn().setBody(body);
    //Create a class when you need to class the Processor class from several parts of your route or routes or has configurable parts or contains complex
    business logic.
    
      }
    }
    

    Generate your Camel endpoint uris

  • Your endpoint uri whether in a from() or to() should be generated or injected rather than hardcoded.
  • Avoid from(”file://test/?fileName=test.txt”). Instead do from(fileUri) where fileUri is created in a utility class, some bean method or injected via a property. The same goes for .to() endpoints
  • Generating uri makes testing much easier because you can generate other test endpoints and simply inject those instead and never touch your main uris. If you have hardcoded uri the same is possible but cumbersome.
  • In particular if you have different uri for different environment then you cannot hardcode them otherwise you are creating different code for different environments and that is a bad practise. Here you defintely need to inject them via property file determined by some environment variable that tells you which environment the context is running in.
  • Code example:

    @PropertyInject("{{fileUri}}")
    private String fileUri;
    @PropertyInject("{{toUri}}")
    private String toUri;
    ...
    ...
    from(fileUri).to(toUri);
    

    Don’t complicate your logs

  • Your log files should contain at least two levels. One at debug level intended for technical minded people who wanted in depth knowledge of what is going on. Then you have logs at info level which should only give a brief description of what has happened. The main steps in a route should be at info level. The details of the exchange should be at debug level.
  • Do not put exchange headers, body or properties at info level at least it is of buisiness importance. Info level logs should be of the form:
    Order 123 generated. Customer request received. Message published. No technical details present.
  • At debug level you should log all aspects of the exchange to provide maximum details for debugging and troubleshooting. That means log ${headers}, ${body} and ${properties} where applicable. Because debug levels are not on by default the log files will not get massive straight away.
  • Link the log statements with the routeId so you know which route generated that statement. This makes it easier to backtrack from the logs to your code.
  • Code example:

    #Don't do this. It contains unnecessary amount of information.
    from(fileUri).routeId("MyRouteId").log(LoggingLevel.INFO,"MyRouteId", "Order ${body} received").to(toUri);
    #Log like this.
    from(fileUri).routeId("MyRouteId").log(LoggingLevel.INFO,"MyRouteId", "Order from customer received").log(LoggingLevel.DEBUG, "MyRouteId", "Order from customer with body: ${body} and headers ${headers}").to(toUri);
    

    Split your logic into several routes

  • Yes, you can create a giantic 1000 line log Camel route just as you can with normal java class but it is not considered good practice.
  • Decide how your problem can be proken into individual parts, set each parts action to be done by a specific route and chain the routes together.
  • Chaining routes means you can replace routes during tests or due to requirements changes which is harder to do when all the code is in one giant route
  • Chaining routes makes it easier to understand the different components and you can programmatically or via external commands stop individual routes for troubleshooting. You cannot do this if all the code exist in one giant route.
  • Use common sense off course. You can always start with a big route in order to check your code and then split the code in smaller routes. It is more important to get the coding working first and then get it more coherent then start with the optimization.
  • Code example:

    #Don't do this. It is a massive route.
    from(fileUri).routeId("MyRouteId").log(LoggingLevel.INFO,"MyRouteId", "Order ${body} received").to(eventBusUri).convertBodyTo(String.class).removeHeaders("eventbusHeaders").to()....;
    #Do more like this as each step is broken down and the implementation is written in individual routes
    from(fileUri).routeId("MyRouteId").to(eventBusRoute).to(processOrderRoute).to(generateReceiptRoute).to(eventbusRoute).to(saveReceiptRoute);