From inside the code: Camel Routing Engine Part I
So I’ve recently re-kindled my interest in how Apache Camel works. Camel is such a powerful integration tool and is very widely used, but anytime I put it to work, I can’t help but think “well, how the hell does it do all that!!” … guess I just have knack for not just accepting that it’s wonderful.. I want to know why.
If you’ve followed some of my posts in the past, you’ll remember I do have a blog post that dives into how the Camel DSL API works. More specifically, how can you just magically type “from(..)” “choice(..)” “simple(..)” etc and the DSL is just smart enough to know what you mean and how to string everything together. If you’re interested in how that all works, please take a peak at that post. Be forewarned, there is a couple UML class diagrams in there that are quite verbose and long.
So if you recall from the previous post, the DSL (whether it’s the Java DSL, or XML, or Scala, or any others) has a very specific role. It helps the author of the integration route express very clearly his/her intent and then build that into an abstraction called a RouteDefinition. The RouteDefinition is kind of the “blueprint” of your route, and knows about all of your processors, EIPs, and components. You can consider any time you call from(..) to be the start of a new RouteDefinition. The rest of the route gives it its shape. So for every from(..) there is a one-to-one mapping to RouteDefinition.
So when the CamelContext starts up, it gathers up all of the RouteDefinition(s) and begins to build a route from them. Take a look at DefaultCamelContext#doStartCamel for the entry point to all of this. Among other important things, like putting together the registry, endpoints, starting basic services, management, etc, etc.. you’ll see a call to DefaultCamelContext#startRouteDefinitions. This tells the RouteDefinitions to build Route objects, which are the actual Consumers (input) + Pipeline of processors (output) that route exchanges.
The next block goes into a little more detail about how this all works. Just like in previous blog posts about the internal workings of Camel, this detail is mostly for me: i.e., in the future when I have forgotten half of this, I want a refresher. And some people who’d like to contribute to Camel may find this interesting. For all others.. Feel free to skip this block.
– Start detailed section –
The DefaultCamelContext will loop through each RouteDefinition and will try to build a list of RouteS and RouteContextS.
Quick Detour:
What is a RouteContext though? In simple terms, you can think of it as the brains of the Route, and a place where the route-specific configurations live (stream caching? tracing? handle faults? etc, etc). It knows about the “from” consumer, the rest of the pipeline, the intercept strategies, route policy, and is able to construct the Route that will operate on exchanges.
It can be kind of confusing because there are a chain of calls to methods named “addRoutes()” when really they are building RouteContexts and building Routes. But look that aside for a moment. So the call to RouteDefinition#addRoutes(..) will return a list of RouteContext objects. It will also populate a List of (initially empty) routes. The multiplicity here is basically n to n. Because you can have multiple inputs to a RouteDefinition (e.g., by stringing together multiple from(..).from(..)), a single 1-to-1 RouteContext to Route can be expected, with a single pair PER from(). So in the previous example, there would be two RouteContexts and two Routes in the List. In the call to addRoutes(..) it also tries to turn the output definitions into real Processors. The Processors are the meat of the Route object. Each processor is built based on its respective definition (e.g., ChoiceDefinition, LogDefinition), but ultimately gets wrapped in a Channel object and added to the RouteContext.
So to wrap this up, The RouteDefinition will create the Route, RouteContext, and will also turn the individual output definitions into Processors. After these are created, a RouteService is created from the newly minted Route + RouteContext pairs, and this is established with the CamelContext for later starting and controlling the lifecycle of the route.
– End detailed section –
So …once we have the RouteService initialized, we need to start the routes, depending on whether they are auto-start up or not and their order. Take a look at DefaultCamelContext#doStartCamel once more, and toward the bottom is the call to DefaultCamelContext#doStartOrResumeRoutes. This will loop through our RouteServiceS and identify the correct startup order for the routes and then start them.
Routes are started in two phases:
- WarmUp: In warm up phase, we look through all of the routes and start all of the “outputs” or processors/eips. Camel takes care to make sure
this happens first, because we do NOT want to have the “inputs” or consumers started before all of the “outputs” are available. - Start/Resume: Start the consumer! This is endpoint/component specific. For example, the JMS Consumer will begin listening to a destination.
Note, at all parts of these stages in the lifecycle, there are callbacks that can be invoked so that you can plug in to this lifecycle and add your own custom startup code to coordinate with your application. Take a look at the Camel API for the type of callbacks available LifecylceStrategy
And there you have it. This is how route definitions are converted to actual routes and then started.
In Part II, I hope to go into how the processors are chained together, including a little more advanced chaining with the AsyncProcessor.