Reusing routing logic through template routes

One of the key advantages of using Java for defining routes is the ability to define the same, or similar, routing logic multiple times in your integrations, while changing key elements.

Consider the case of a route that:

  • Consumes bulk order data from CSV files in an input directory
  • Splits it into individual orders
  • Extracts the date of each order, formatted specific to the country, and converts it to a universal one
  • Places an order confirmation into another directory

Now consider that you may have orders from dozens of different countries, with different order and confirmation directories, and different date formats.

You could write similar routes dozens of times, but that is going to create a maintenance problem. Alternatively, using Camel's Java DSL, you can write the common routing logic once, and then use dependency injection to vary the values that are different when you instantiate the route.

This recipe will show you a strategy for creating Camel routes that can be created with different values at runtime, parameterizing your common routing logic.

Getting ready

Define your route within a RouteBuilder as usual, only this time make the start and end URIs, as well as any beans involved in the processing, properties of the RouteBuilder class:

public class OrderProcessingRouteBuilder extends RouteBuilder {
  String inputUri;
  String outputUri;
  private OrderFileNameProcessor orderFileNameProcessor;

  @Override
  public void configure() throws Exception {
    from(inputUri)
      // split into individual lines
      .split(body(String.class).tokenize("
"))
        .process(orderFileNameProcessor)
        .log("Writing file: ${header.CamelFileName}")
        .to(outputUri)
      .end();
  }
}

Note

Note that the URI variables are defined as package scoped. This will help us to test the class later.

The Java code for this recipe is located in the org.camelcookbook.structuringroutes.templating package. The Spring XML files are located under src/main/resources/META-INF/spring and prefixed with templating.

How to do it...

Use property setters on your RouteBuilder implementation to instantiate multiple instances of the route with different property values injected.

  1. Add setters for the properties:
    public void setInputDirectory(String inputDirectory) {
      inputUri = "file://" + inputDirectory;
    }
    public void setOutputDirectory(String outputDirectory) {
      outputUri = "file://" + outputDirectory;
    }
    public void setOrderFileNameProcessor(
        OrderFileNameProcessor orderFileNameProcessor) {
      this.orderFileNameProcessor = orderFileNameProcessor;
    }

    Tip

    A useful trick is to construct the endpoint URIs within the setters. This way, when instantiating the RouteBuilder, you only have to worry about which directories to use, not about the various additional attributes that you want to repeat for the file component each time.

  2. Validate that the mandatory bean properties were set. The following code uses the org.apache.commons.lang.Validate class to check for nulls and empty Strings:
    @PostConstruct
    public void checkMandatoryProperties() {
      Validate.notEmpty(inputUri, "inputUri is empty");
      Validate.notEmpty(outputUri, "outputUri is empty");
      Validate.notNull(orderFileNameProcessor,
                       "orderFileNameProcessor is null");
    }

    Tip

    If you are using the RouteBuilder from Spring, add a @PostConstruct method to check that all of the properties have been set. This way, if all of the fields have not been initialized correctly, the application will refuse to start up.

  3. To complete the integration we need to add a Processor that parses dates from a line of CSV text, changes the date to a universal format, and sets a header with the output filename. We encapsulate this logic in a class whose instances vary by a date format that is injected. The source for this class is available in the example code under: org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor.
  4. In your Spring XML file, you can now create multiple instances of this class:
    <bean id="dateFirstOrderFileNameProcessor"
          class="org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor">
      <property name="countryDateFormat" value="dd-MM-yyyy"/>
    </bean>
    
    <bean id="monthFirstOrderFileNameProcessor"
          class="org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor">
      <property name="countryDateFormat" value="MM-dd-yyyy"/>
    </bean>
  5. We now have all of the pieces in place to perform the same integration for a number of countries that use different input and output directories, and date formats, within their order files. We can now go ahead and instantiate the RouteBuilders and inject them into the Camel context:
    <bean id="ukOrdersRouteBuilder"
          class="org.camelcookbook.structuringroutes.templating.OrderProcessingRouteBuilder">
      <property name="inputDirectory"
                value="/orders/in/UK"/>
      <property name="outputDirectory"
                value="/orders/out/UK"/>
      <property name="orderFileNameProcessor"
                ref="dateFirstOrderFileNameProcessor"/>
    </bean>
    
    <bean id="usOrdersRouteBuilder"
          class="org.camelcookbook.structuringroutes.templating.OrderProcessingRouteBuilder">
    <property name="inputDirectory"
              value="/orders/in/US"/>
      <property name="outputDirectory"
                value="/orders/out/US"/>
      <property name="orderFileNameProcessor"
                ref="monthFirstOrderFileNameProcessor"/>
    </bean>
    
    <camelContext 
        xmlns="http://camel.apache.org/schema/spring">
      <routeBuilder ref="ukOrdersRouteBuilder"/>
      <routeBuilder ref="usOrdersRouteBuilder"/>
    </camelContext>

How it works...

By treating our RouteBuilder implementation as just another bean to use in a Spring context, we were able to instantiate it multiple times, introducing varying behavior by changing the injected values. In the future, if we were to change the routing logic, perhaps by adding more logging, it would all be done in one place in the code.

When we defined our URI properties in the RouteBuilder, we set them as package scoped. This is a handy strategy that allows us to inject endpoint types from within the same package that are not file: endpoints, which are set when our public setter methods are used. Since test classes are typically co-located in the same package, this allows us to initialize our RouteBuilder with more easily testable endpoints:

OrderFileNameProcessor processor = new OrderFileNameProcessor();
processor.setCountryDateFormat("dd-MM-yyyy");

OrderProcessingRouteBuilder routeBuilder =
    new OrderProcessingRouteBuilder();
routeBuilder.inputUri = "direct:in";
routeBuilder.outputUri = "mock:out";
routeBuilder.setOrderFileNameProcessor(processor);

See Chapter 9, Testing, for more details on testing.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194