Popular EIP Frameworks comparison

Paweł Weselak

Introduction

Enterprise Integration Patterns solves problems of enterprise application integration and message-oriented middleware. I recommend the book by Gregor Hohpe and Bobby Woolf where authors catalogued the patterns.

The aim of this article is to show the two EIP frameworks: Apache Camel and Spring Integration from the perspective of a developer who knows EIP patterns quite well but do not have very long experience with using these frameworks. For the purpose of this article I’m going to take the most recent versions of both frameworks (I will use new Java DSL from Spring Integration and Apache Camel fluent API) to write simple application for sending order notifications via email. I want to see which framework will meet my needs better, how long will it take to write a complete app, what is the learning curve, what problems I can have and what help is available in the Internet to solve them out.

Flow of sample application

The application does the one simple task – confirms to a client the order he placed in some online store. It can be considered as a part of bigger application built upon microservices architecture.

1.      HTTP inbound adapter receives XML order sent with POST method

2.      Received order is validated against XSD schema

a.      If validation fails message is rejected and stored in filesystem directory

3.      Message content is parsed using XPath to extract mail headers like client email address

4.      XML order is transformed to HTML email message using XSLT transformation

5.      The email is sent to the client

Order message contains information about the client who placed the order and the ordered items:

1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<order xmlns="http://www.j-labs.pl/blog/order" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.j-labs.pl/blog/order order.xsd" identifier="375153">
3.	  <date>2016-10-28</date>
4.	  <client identifier="44">
5.	    <name>Paweł</name>
6.	    <surname>Weselak</surname>
7.	    <address>Szlak 77, Kraków</address>
8.	    <email>pawel.weselak@j-labs.pl</email>
9.	    <phone>512262144</phone>
10.	  </client>
11.	  <items>
12.	    <item identifier="1">
13.	      <name>Sony Bravia</name>
14.	      <description>TV set</description>
15.	      <count>1</count>
16.	    </item>
17.	    <item identifier="2">
18.	      <name>HTC 10</name>
19.	      <description>smartphone</description>
20.	      <count>1</count>
21.	    </item>
22.	  </items>
23.	</order>

XSD schema describing order is defined like this:

1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.j-labs.pl/blog/order" targetNamespace="http://www.j-labs.pl/blog/order" elementFormDefault="qualified" attributeFormDefault="unqualified">
3.	  <xs:element name="order" type="tns:orderType"/>
4.	  <xs:complexType name="orderType">
5.	    <xs:sequence>
6.	      <xs:element name="date" type="xs:date" minOccurs="1" maxOccurs="1"/>
7.	      <xs:element name="client" type="tns:clientType" minOccurs="1" maxOccurs="1"/>
8.	      <xs:element name="items">
9.	        <xs:complexType>
10.	          <xs:sequence>
11.	            <xs:element name="item" type="tns:itemType" minOccurs="1" maxOccurs="3"/>            
12.	          </xs:sequence>
13.	        </xs:complexType>
14.	      </xs:element>
15.	    </xs:sequence>
16.	    <xs:attribute name="identifier" type="xs:int" use="required"/>
17.	  </xs:complexType>
18.	  <xs:complexType name="clientType">
19.	    <xs:sequence>
20.	      <xs:element name="name" type="xs:string"/>
21.	      <xs:element name="surname" type="xs:string"/>
22.	      <xs:element name="address" type="xs:string"/>
23.	      <xs:element name="email" type="xs:string"/>
24.	      <xs:element name="phone" type="xs:int"/>
25.	    </xs:sequence>
26.	    <xs:attribute name="identifier" type="xs:int" use="required"/>
27.	  </xs:complexType>
28.	  <xs:complexType name="itemType">
29.	    <xs:sequence>
30.	      <xs:element name="name" type="xs:string"/>
31.	      <xs:element name="description" type="xs:string"/>
32.	      <xs:element name="count" type="xs:int"/>
33.	    </xs:sequence>
34.	    <xs:attribute name="identifier" type="xs:int" use="required"/>
35.	  </xs:complexType>
36.	</xs:schema>

To convert XML order to HTML message XSLT transformation is used:

1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:order="http://www.j-labs.pl/blog/order" xmlns="http://www.w3.org/1999/xhtml">
3.	  <xsl:output method="xml" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" doctype-public="-//W3C//DTD XHTML 1.1//EN" indent="yes"/>
4.	  <xsl:template match="/">
5.	    <html>
6.	      <head>
7.	        <title>Order confirmation</title>
8.	      </head>
9.	      <body>
10.	        <p>Dear <xsl:value-of select="order:order/order:client/order:name"/><xsl:text> </xsl:text><xsl:value-of select="order:order/order:client/order:surname"/><xsl:text>,</xsl:text></p>
11.	        <p>Thank you for shopping in our store! Your order has been completed and it is waiting for send.</p>
12.	        <p>Order details:</p>
13.	        <p>Delivery address:</p>
14.	        <p><xsl:value-of select="order:order/order:client/order:name"/><xsl:text> </xsl:text><xsl:value-of select="order:order/order:client/order:surname"/><br/>
15.	           <xsl:value-of select="order:order/order:client/order:address"/></p>
16.	        <p>Ordered items:</p>
17.	        <table>
18.	          <thead>
19.	            <tr>
20.	              <th>Item</th>
21.	              <th>Description</th>
22.	              <th>Count</th>
23.	            </tr>
24.	          </thead>
25.	          <tbody>
26.	            <xsl:for-each select="order:order/order:items/order:item">
27.	            <tr>
28.	              <td><xsl:apply-templates select="order:name"/></td>
29.	              <td><xsl:apply-templates select="order:description"/></td>
30.	              <td><xsl:apply-templates select="order:count"/></td>
31.	            </tr>
32.	            </xsl:for-each>
33.	          </tbody>
34.	        </table>
35.	      </body>
36.	    </html>
37.	  </xsl:template>
38.	</xsl:stylesheet>

And the output HTML confirmation message should look like this:

1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3.	<html xmlns="http://www.w3.org/1999/xhtml" xmlns:order="http://www.j-labs.pl/blog/order">
4.	  <head>
5.	    <title>Order confirmation</title>
6.	  </head>
7.	  <body>
8.	    <p>Dear Paweł Weselak,</p>
9.	    <p>Thank you for shopping in our store! Your order has been completed and it is waiting for send.</p>
10.	    <p>Order details:</p>
11.	    <p>Delivery address:</p>
12.	    <p>Paweł Weselak<br />
13.	       Szlak 77, Kraków</p>
14.	    <p>Ordered items:</p>
15.	    <table>
16.	      <thead>
17.	        <tr>
18.	          <th>Item</th>
19.	          <th>Description</th>
20.	          <th>Count</th>
21.	        </tr>
22.	      </thead>
23.	      <tbody>
24.	        <tr>
25.	          <td>Sony Bravia</td>
26.	          <td>TV set</td>
27.	          <td>1</td>
28.	        </tr>
29.	        <tr>
30.	          <td>HTC 10</td>
31.	          <td>smartphone</td>
32.	          <td>1</td>
33.	        </tr>
34.	      </tbody>
35.	    </table>
36.	  </body>
37.	</html>

Spring Integration

Starting from version 4.0 Spring Integration provides Java DSL. It is basically a façade over Spring Integration components and it enables us to configure the flow using java code without any single line of XML configuration. Furthermore, If you love Java 8 lambda expressions you will be delighted.

How it looks in the practice? Let’s see the flow defined for the sample app:

package pl.jlabs.blog.eip.spring.integration.configuration;

import ...;
import pl.jlabs.blog.eip.spring.integration.component.CustomXPathHeaderValueMessageProcessor;

@Configuration
@EnableIntegration
public class OrderConfirmationFlowConfiguration {

    @Value("classpath:/order.xsd")
    private Resource orderXsd;
    
    @Value("classpath:/order.xslt")
    private Resource orderXslt;
    
    @Value("#{{ns:'http://www.j-labs.pl/blog/order'}}")
    private Map<String, String> namespaces;
    
    @Bean
    public IntegrationFlow sendConfirmationMailFlow() throws IOException {
        return IntegrationFlows.from(Http.inboundChannelAdapter("/order/confirmation")
                                .requestMapping(r -> r.methods(HttpMethod.POST)))
                .filter(xmlValidatingMessageSelector(), e -> e.discardFlow(
                                f -> f.handle(Files.outboundAdapter(new File("rejected")))))
                .enrichHeaders(h -> h.header(MailHeaders.SUBJECT, subjectMailHeaderValueMessageProcessor())
                                .header(MailHeaders.TO, fromMailHeaderValueMessageProcessor())
                                .header(MailHeaders.FROM, "Store admin <admin@store.com>")
                                .header(MailHeaders.CONTENT_TYPE, "text/html; charset=UTF-8"))
                .transform(Transformers.xslt(orderXslt))
                .handle(Mail.outboundAdapter("localhost")
                                .port(587)
                                .credentials("user", "pass")
                                .protocol("smtp")
                                .javaMailProperties(p -> p.put("mail.debug", "true")),
                        e -> e.id("sendMailEndpoint"))
                .get();
    }
    
    @Bean
    public MessageSelector xmlValidatingMessageSelector() throws IOException {
        return new XmlValidatingMessageSelector(orderXsd, SchemaType.XML_SCHEMA);
    }
    
    @Bean
    public HeaderValueMessageProcessor<String> fromMailHeaderValueMessageProcessor() {
        return new CustomXPathHeaderValueMessageProcessor("%s %s <%s>", namespaces, 
                "/ns:order/ns:client/ns:name", 
                "/ns:order/ns:client/ns:surname", 
                "/ns:order/ns:client/ns:email");
    }
    
    @Bean
    public HeaderValueMessageProcessor<String> subjectMailHeaderValueMessageProcessor() {
        return new CustomXPathHeaderValueMessageProcessor("Order confirmation %s", namespaces, 
                "/ns:order/@identifier");
    }
}
package pl.jlabs.blog.eip.spring.integration.component;

import ...;

public class CustomXPathHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor<String> {

    private static final XmlPayloadConverter converter = new DefaultXmlPayloadConverter();
    
    private String format;
    private Map<String, String> namespaces;
    private String[] xPathExpressions;

    public CustomXPathHeaderValueMessageProcessor(String format, String... xPathExpressions) {
        this(format, Collections.emptyMap(), xPathExpressions);
    }
    
    public CustomXPathHeaderValueMessageProcessor(String format, Map<String, String> namespaces,
            String... xPathExpressions) {
        this.format = format;
        this.namespaces = namespaces;
        this.xPathExpressions = xPathExpressions;
    }
    
    @Override
    public String processMessage(Message<?> message) {
        Node node = converter.convertToNode(message.getPayload());
        Object[] results = Arrays.asList(xPathExpressions)
                .stream()
                .map(expr -> XPathExpressionFactory.createXPathExpression(expr, namespaces))
                .map(expr -> expr.evaluateAsString(node))
                .toArray(size -> new String[size]);
        return String.format(format, results);
    }

}

I think Java DSL is consistent and thanks to that it is easy to use. The DSL has methods like filter, transform or handle corresponding to the SI components. Additional configuration for the parameter can be passed as a second parameter to the method, often it is a lambda expression. The defined flow is quite contracted but not sure if the code is easy to read for everyone. Sometimes number of lambdas can be overwhelming.

Was it difficult to learn and use new Java DSL? Spring has very good documentation in general which helps a lot, it is well structured and each Spring Integration component is described in detail. Java DSL documentation is available on github. It is also easy to read but little bit outdated, for example Http is not listed among supported protocols (namespace factories) although it is present in the current version of the API.

Sometimes we need to dive into the Spring code for searching the class name of the component we want to use because it is not always mentioned in the documentation. XmlValidatingMessageSelector is a good example. Moreover, when I finally found the class I wasn’t sure how to use it with Java DSL but luckily enough it turn out pretty simple.

What annoys me in Spring Integration is the lack of support for XML namespaces in several XML components. XPath SpEL expression is not namespace aware, you cannot pass a namespace to XPathHeaderEnricher also. Thankfully XPathExpressionFactory has factory method where one can pass namespaces map and I usedg that in my CustomXPathHeaderValueMessageProcessor.

To sum up, using Spring Integration Java DSL was interesting and enjoyable experience. Quite complicated app can be built up quickly without writing a lot of boiler plate code. Let’s check how another popular EIP framework will work out in the same task.

Apache Camel

For defining flows Java DSL is also available in another popular integration framework, Apache Camel. Java DSL is not the only option, Camel gives us plenty other DSLs we can choose from. Additionally, using Camel we can benefit from Spring, Guice and CDI (Camel Dependency Injection) frameworks.

Let me present the Camel Java DSL code I’ve written to create sample application flow:

package pl.jlabs.blog.eip.apache.camel.route;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.Namespaces;

public class OrderConfirmationRouteBuilder extends RouteBuilder {

    private static Namespaces namespaces = new Namespaces("ns", "http://www.j-labs.pl/blog/order");
    
    @Override
    public void configure() throws Exception {
        from("netty4-http:http://0.0.0.0:8080/eip-frameworks-apache-camel/order/confirmation?httpMethodRestrict=POST")
            .convertBodyTo(String.class)
            .doTry()
                .to("validator:order.xsd")
                .to("direct:setProperties")
                .setHeader("Subject").simple("Order confirmation ${exchangeProperty[orderIdentifier]}")
                .setHeader("To").simple("${exchangeProperty[clientName]} ${exchangeProperty[clientSurname]} <${exchangeProperty[clientEmail]}>")
                .setHeader("From").constant("Store admin <admin@store.com>")
                .setHeader("Content-type").constant("text/html; charset=UTF-8")
                .to("xslt:order.xslt")
                .to("smtp://localhost:587?username=admin&password=secret&debugMode=true")
            .doCatch(org.apache.camel.ValidationException.class)
                .to("file:rejected")
            .end();
        
        from("direct:setProperties")
            .setProperty("orderIdentifier").xpath("/ns:order/@identifier", namespaces)
            .setProperty("clientName").xpath("/ns:order/ns:client/ns:name/text()", namespaces)
            .setProperty("clientSurname").xpath("/ns:order/ns:client/ns:surname/text()", namespaces)
            .setProperty("clientEmail").xpath("/ns:order/ns:client/ns:email/text()", namespaces)
            .end();
    }
}

To run the application CamelContext must be created. For simplicity I’ve decided to create CamelContext programmatically and start it in the main method:

package pl.jlabs.blog.eip.apache.camel.main;

import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;

import pl.jlabs.blog.eip.apache.camel.route.OrderConfirmationRouteBuilder;

public class Main {
    
    public static void main(String[] args) throws Exception {
        CamelContext context = new DefaultCamelContext();
        context.addRoutes(new OrderConfirmationRouteBuilder());
        context.start();
    }
}

As you can see the code is self-explanatory and incredibly compact and very easy to read when well formatted. The conception of components URIs is surprisingly nifty. The worth to mention is fact that Camel has good XML namespace support.

Camel has comprehensive documentation, the components are very well documented and each component has a few examples of usage. When you want to perform certain task, you just open the components page, find the component you need, open the component description and you have ready to use example or at least detailed description. Nevertheless, documentation pages for me are not very intuitive to navigate, I use google from time to time to find documentation page I am interested.

The framework is really flexible, it allows to do one thing in many different ways. Although I think that sometimes it is not good idea to give too much freedom to developers, it can lead to bugs, confusions and a mishmash code. Often it is hard to decide which option will meet our needs the best.

Summarize

I share the opinion of Björn Beskow presented on his blog. In addition to that I’ve noticed that Camel DSL is less verbose than Spring DSL. Also writing application in Spring Integration I spent twice as many time as doing it in Camel. Spring surprised me with more exceptions which elongated the process, writing the flow in Camel was far way more intuitive.

To conclude, both frameworks are lightweight, mature, well supported and have good documentation. Each of us has different preferences so I recommend you to try out different available libraries. Maybe Mule I haven’t discussed in the article will be your favorite?

Complete source kod of the app is available on my GitHub.

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami