Quantcast
Channel: Planet Apache
Viewing all articles
Browse latest Browse all 9364

Christian Schneider: Using Camel to do light weight messaging over any protocol

$
0
0

Blog post added by Christian Schneider

At least for some time the whole world seemed to only talk about ESB and webservices. These technologies have their place in integration but they are quite complex and starting with them means you have to invest a lot of time and or money. Recently around the release of Java EE 6 the idea of simplicity came back to the Enterprise Java world. In this mindset I will look into some ways to do really light weight messaging with Apache Camel.

So basically we have the problem of moving some data from Application A to Application B. So let´s assume we have java objects representing this data and want to transport it in a simple yet open format. So one obvious choice is to use xml and do the transformation using JAXB. In the following examples I will use code first but it will work the same if the JAXB annotated classes were created from an XSD using a code generator.

The Payload

As an example for our payload I will use the following java bean class:

@XmlRootElement
@XmlType
public class Customer {
    String name;
    int age;

    public Customer() {
    }

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}

Sending

So how can we send this class as XML to a JMS queue?

Customer customer = new Customer("Christian Schneider", 38);
context.createProducerTemplate().sendBody("jms://test", customer);

This almost looks a bit too easy. So what is camel doing behind the scenes. The ProducerTemplate allows us to send any java object to any camel endpoint. The endpoint uri will setup the endpoint on the fly. In our case the endpoint is a jms endpoint and will send the data to the queue "test". As the object needs to be serialized to be transfered using jms camel will use a TypeConverter to do this. In our case the camel-jaxb component is on the classpath and the Customer class has JAXB annotations. So a TypeConverter will kick in that serializes the object using JAXB.

So the message on the queue will look like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<age>38</age>
<name>Christian Schneider</name>
</customer>

Receiving

Now that we sent the message how can we receive it on the other side? For this we can use a little route and a simple java class:

from("jms://test").to("log:testlog").bean(new CustomerReceiver());
public class CustomerReciever {

    public void receive(Customer customer) {
        System.out.println("Received a customer named " + customer.getName());
    }
}

The route listens on the jms queue and will trigger for each message received. The to(log:testlog) is not strictly necessary but shows that we indeed transport xml. In the end the message is sent to the CustomerServiceReceiver using the camel bean component. The bean component uses a lot of convention over configuration to determine how to process the message. What happens in our case is the following:

  • We only have one public method so camel knows it has to use the receive method
  • The recieve method has only one parameter so camel will give the body of the message to this parameter
  • As we get a String or byte[] from jms and again the class Customer has jaxb annotations the data is automatically deserialized into a Customer object

Using another protocol

By simply changing from "jms://test" to e.g. "file://target/test" we can switch the transfer protocol to files. So when doing sendBody a file with the xml is placed in the directory target/test. The route for the receiver will detect that file and send the content into the route. The result is the same as for jms: We transport our data as nice xml and can work with it as a java object on both sides.

You can try the same for http using "http://localhost:8080/test" for the sender and "jetty://localhost:8080/test" for the receiver.

What about request / reply?

What we saw was nice asynchonous one way messaging. Camel also allows a simlar aproach for request / reply.

So for sending a Customer object to the other side and receiving a changed Customer object synchronously you can use:

Customer changedCustomer = producer.requestBody("jms://test", customer);

This would send customer to the queue "test" as xml and create a temp queue where it wait for a reply. When the reply is received the data is deserialized and returned as the object changedCustomer.

On the receiver side the route can be kept as is and the CustomerReceiver.recive method simply should look like:

Customer receive(Customer customer);

It is really just as easy as this.

So what is good and what is bad about this aproach

Pro:

  • very easy to use
  • very few lines of code
  • CustomerReceiver does not contain any camel specifics

Con:

  • The sending side with the ProducerTemplate is very camel technical. You typically will not want this in your business code

How can I keep my business code clean from camel stuff

A simple way to keep camel classes out of your business code on the sender side is to use an interface that just has business logic in it:

public interface CustomerSender {
	public void send(Customer customer);
}

And the implementation for sending using Camel:

public class CustomerSenderImpl implements CustomerSender {
	private ProducerTemplate producer;
	private String endpointUri;

	public CustomerSenderImpl(ProducerTemplate producer, String endpointUri) {
		this.producer = producer;
		this.endpointUri = endpointUri;
	}

	@Override
	public void send(Customer customer) {
		producer.sendBody(endpointUri, customer);
	}

}

So you can inject the CustomerSenderImpl into your business code which only needs to know about the CustomerSender interface. This way your business code is nicely separated from camel.

Can I have this even simpler?

Camel has some annotations which allow to do this with even less code. You still need the CustomerSender interface but you can get rid of the route and the CustomerServiceImpl.

On the sender side you use:

@Produce(uri="jms://test")
CustomerSender customerSender;

...

customerSender.send(customer);

Camel will inject a dynamic proxy for customerSender which sends the customer to the queue. The drawback is that it currently will always send a BeanInvocation. So while this works for remoting it is not the nice xml document we want to have on the route. Besides that the annotations only work in spring beans and in the camel test framework.

On the receiver side you only need the CustomerReceiver enhanced with the @Consume annotation.

public class CustomerReciever {

    @Consume(uri="jms://test")
    public void receive(Customer customer) {
        System.out.println("Received a customer named " + customer.getName());
    }
}

Like before the reciever works even a little nicer than the sender. It can receive a BeanInvocation but will also work with the xml from our first example.

In general I am not so sure about the annotation aproach in general as this way your business code still contains camel specific stuff like the annotation and the endpoint uri.

Some TODO for me

I plan to improve the annotation based pojo messaging in camel a bit by supporting sending plain objects instead of BeanInvocations on the client side. With this little enhancement you will be able to send and receive pojos using just annotations and still have nice xml on the wire.


Viewing all articles
Browse latest Browse all 9364

Trending Articles