A composite Rest service using Apache Camel

The following example illustrates how you can implement a composite service using Apache Camel. The service is exposed as a REST resource, and it uses two other resources to collect the data required.

The composite service will enable a client to get info on a customer and it’s orders. To get this data the composite service first needs to query a customer service. Using a relation id retrieved from the customer service, the composite then needs to query an orders services. The steps are as follows:

  • Client calls the composite, eg., GET http://localhost/customerOrders?custNo=123
  • The composite gets the customer resource, but this resource expects the customer id to be named differently: GET http://localhost/customer?customer=123. Here’s an xml representation of the resource returned:

    <customer 
      custNo='123' 
      relationId='443' 
      firstName='Peter' 
      lastName='Semper'
    />
    
  • Next the composite needs to use the relationId in the customer resource to get it’s orders, e.g., GET http://localhost/orders?relId=443. This will result in the following xml document:

    <orders relationId='443'>
      <order id='1' product='Car'/>
      <order id='2' product='Bicycle'/>
    </orders>
    
  • The composite service will use the data returned by the customer resource and the order resource to create a new xml document containing both the customer info and the order info:

    <customer>
      <id>123</id>
      <orders>
        <order><id>1</id><product name="Car"/></order>
        <order><id>2</id><product name="Bicycle"/></order>
      </orders>
    </customer>
    

Here’s the sequence diagram illustrating the steps outlined above.

Sequence diagram composite rest service with Camel

You can achieve this with the following route:

from("jetty:http://localhost:9012/customerOrders")
    .removeHeaders("CamelHttp*")
    .setHeader("customer",header("custNo"))
    .setHeader(Exchange.HTTP_METHOD,constant("GET"))
    .to("http://localhost:9012/customer")
    .removeHeaders("CamelHttp*")
    .setHeader("relId", xpath("/customer/@relationId",String.class))
    .setHeader(Exchange.HTTP_METHOD,constant("GET"))
    .enrich("http://localhost:9012/orders",aggregate)
    .to("xquery:merge-customer-orders.xquery")
    .setHeader(Exchange.CONTENT_TYPE,constant("text/xml"));

Line by line explanation:

  1. Define an endpoint where the composite service is reachable. I’m using a jetty component to listen for requests.
  2. Remove the http headers from the message received, so they won’t be used for calls from the composite to the other services.
  3. Set the customer parameter for the http call to /customer.
  4. Specify that we’re GETting the customer resource.
  5. Get the customer resource.
  6. Again, remove any http headers, so we’re not messing up the next http call.
  7. Use an xpath expression to determine the relation id of the customer, set the query parameter used to GET the orders for this relation.
  8. Specify that we’re GETting the orders resource.
  9. GET the orders resource, and then use the aggregate object to combine customer and orders. In this example the aggregate object just concatenates both xml documents, so we can easily use it in the next step.
  10. Use an xquery to create a new xml document from the combined customer and orders document.
  11. Set the Http header for Content-type to text/xml, so the client knows it’s receiving an xml representation of the resource.

The aggregate object takes the message (exchange) received from the customer resource and the message received from the orders resource and concatenes them into one xml document:

AggregationStrategy aggregate = new AggregationStrategy(){
  @Override
  public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
    newExchange.getOut().setBody("<all>" 
      + oldExchange.getIn().getBody(String.class) 
      + newExchange.getIn().getBody(String.class) 
      + "</all>");
    return newExchange;
  }
};

The combined xml document can easily be used in an xquery to build a new xml document:

<customer>
  <id>{data(/all/customer/@custNo)}</id>
  <orders>
    { for $order
      in  /all/orders/order
      return <order><id>{data($order/@id)}</id><product name='{data($order/@product)}'/></order>
    }
  </orders>
</customer>

That’s most of the code you need. You’ll have to put the route configuration into a route builder and start a camel context to run the route:

    public static void main(String args[]) throws Exception {
        CamelContext ctx = new DefaultCamelContext();
        ctx.addRoutes(new CustomerOrdersResourceRouteBuilder());
        ctx.start();
    }

The nice thing about camel is that’s easy to test and debug. You can simply start the main class in your IDE and give the route a try. You can use the same approach to unit test the route. No need to do a complete deployment to some ESB server.

Another advantage of using Camel is that all your endpoints are normal strings in some java code that can easily be made configurable, for example using a property file

blog comments powered by Disqus