Christian Posta bio photo

Christian Posta

Field CTO at solo.io, author Istio in Action and Microservices for Java Developers, open-source enthusiast, cloud application development, committer @ Apache, Serverless, Cloud, Integration, Kubernetes, Docker, Istio, Envoy #blogger

Twitter Google+ LinkedIn Github Stackoverflow

Recently got back from Camel One and now it's time to get back to business. One topic of discussion I wanted to go over quickly is the Camel route builders and the DSL around them. Ever wonder how and why certain Java DSL methods are available in the route builder? For example, to begin a route, you must specify a consumer endpoint which is either push-driven or poll-driven and introduces an Exchange to your camel route. To do this, you use the from() DSL method. Which makes sense within the rest of the route, because you're getting messages/exchanges from an endpoint and then starting your route. But for some of you experienced Camel developers, you'll wonder what exactly you can do after you've created your consumer with the from() method. Well to fully understand that, you must understand a little about how the RouteBuilder API works.

When you define a route with the Java DSL, you start by creating a new class that extends from the org.apache.camel.builder.RouteBuilder abstract class and then override the configure() method and implement a route with the DSL. You start the route with the from() method mentioned above, but where does that method come from? Consider the following UML diagram:

class diagram of route builder

When you extend the RouteBuilder class, you inherit all of its methods as well as the BuilderSupport methods. The RouteBuilder class is where the from() method lives as well as a couple other methods that are "global" to the route such as onException(), errorHandler(...), intercept() etc. The methods in this class are intended to begin a route or configure its exception and error handling. RouteBuilder's parent class, BuilderSupport provides a bunch of methods for building expressions with the ValueBuilder (for body or headers), XPathBuilder (for creating xpath-compliant expressions), SimpleBuilder (camel's built-in expression language), ErrorHandlerBuilder, DeadLetterChannelBuilder and a couple others. These methods can be used throughout the route wherever the appropriate expression is expected. This is why, for example, the simple() method or the xpath() method is available within other DSL methods. See the full list of methods for each class below. Note the methods for each class are cohesive to the class -- meaning they all belong to their respective class for a reason. Those that belong to BuilderSupport are specifically to add commonly used expressions anywhere within the route (thus supporting the building of a route). The methods belonging to RouteBuilder are specific to beginning a route and configuring the global exception/error handling for that route.

class diagram of route builder with methods

When you call the from() DSL method, a RouteDefinition is created and added to the RouteBuilder's route group (so the builder is aware of the route and knows where it is), then returned. That means when you call from() it returns a RouteDefinition object upon which you can call whatever other DSL methods that make up the route. So this RouteDefinition class must be pretty important, right?

route definition class hierarchy

Yes, it is important. RouteDefinition is a concrete implementation that represents the beginning of a route definition and is where you begin to add the next steps of your route. In camel, every step of the camel route is represented as a "processor." Out of the box the DSL provides many implementations of endpoints and EIPs to string together your route. Take a look at the class hierarchy. The OptionalIdentifiedDefinition abstract class is around mostly for adding an identifier and description to your route. Not very interesting, but good to know. The ProcessorDefinition class, however, is very interesting as it's the superclass for a RouteDefinition as well as almost every other specialized route definition. This base class is the reason why you have access to the EIP DSL once you've created your consumer endpoint with from(). Take a look at its methods (note, kinda long list):

processor def class hierarchy with methods

As you can see, just about all of the DSL methods are on this class. This class also happens to be the base class for other route definitions so these methods are always available in your route. What do I mean by other route definitions? Well, consider route:

[java]
from("direct:input").choice()
[/java]

This route starts the route by creating a consumer with the "direct:input" component. Once the from method is called, it returns a RouteDefinition which is a subclass of ProcessorDefinition If you look above at the methods for ProcessorDefinition you'll see that among the other EIP methods, choice() EIP method is available which represents a content-based router. When you call the choice() method, a new definition is returned: a ChoiceDefinition. A ChoiceDefinition is a more specific type of definition that supports the semantics around the content-based router (such as the when and otherwise DSL methods). But since the ChoiceDefinition also inherits from the ProcessorDefinition you can continue your route by calling other EIPs or processor methods:

choice def class hierarchy with methods

What happens when you call when() on the ChoiceDefinition without any parameters (the when method expects an expression)? Note that it returns an expression object which allows you to build your expression inline with the DSL, but when you're done with the expression it will return the ChoiceDefinition object again to allow you to continue your DSL. To make this clear, here is the signature of the when method from the ChoiceDefinition class:

[java]
public ExpressionClause<ChoiceDefinition> when()
[/java]

What this means is that when you get the ExpressionClause object returned from your call to when you can call expression methods (see the definition of ExpressionClause in the API Docs) to build the predicate for your "when" condition of the content based router. Once you've called your expression, you will be returned a ChoiceDefinition (which is the generic type passed to the ExpressionClause class so it knows what to return) and continue your content based router or start a different EIP or processor.

I realize some of these details might be too low level for some people. It is not meant to be a step-by-step guide for using Camel, but more of an overview of the API to help conceptualize why some of the DSL methods are available (or not available) so you can become more comfortable with the DSL.

To recap: The RouteBuilder builds a RouteDefinition which inherits from a ProcessorDefinition. With the ProcessorDefinition you can string together many EIPs and processors some of which return their own specific definitions classes. But since the definition classes inherit from the ProcessorDefinition, you can continue building the route even after using the specific definition classes. When expressions are mixed in the with route, you can expect the expression methods to return whatever definition class that was used before the expression.

If someone has anything to add, or comments/questions, I am eager to hear.