Wednesday, December 30, 2009

Event Notification with Apache Camel

I ever tried using Apache Camel to solve the event notification problem.

Given a Sensor (event source) and a Display (listener), here's how I would implement it:

public class Sensor {

    @EndpointInject(uri="seda:sensor.events")
    private ProducerTemplate sensorProducer;
    
    private void fireSensor(SensorEvent sensorEvent) {
        sensorProducer.sendBody(sensorEvent);
    }

}

public class Display {

    @Consume(uri="seda:sensor.events")
    public void sensorUpdated(SensorEvent sensorEvent) {
        // ... code to update ...  
    }

}

Advantages


As you can see, the code is pretty concise and elegant. Even Listener interfaces are not needed here. Simply agree on an event object, and the endpoint (channel) URI, and the event is wired up.

POJO is somewhat preserved, if not for ProducerTemplate.

It's possible to receive response from listeners (asynchronously), but I think it should be thought as a feature of the routing engine, not the event notification framework.

Issues


The above isn't a true publish-subscribe though, since Camel's seda component won't support multiple consumers (multicasting) until Camel 2.2.0 is released.

It also uses hard coded endpoint URIs, which is quick and convenient for singleton event sources but falls apart when there are several event sources of the same type.

A routing engine introduces one more concept besides event sources, listeners, and event objects: endpoints (channels a.k.a. topics). I hope it's possible to remove this, but all publish-subscribe event notification frameworks use it, including Dojo AJAX Toolkit's dojo.publish.

The event source object can be put inside the event object so the listeners can filter it, which addresses one side of the problem.

Making endpoint URIs dynamic is another alternative, i.e. referring to its own Spring bean name via BeanNameAware.

Yet another alternative is having a router-consumer that routes the messages (events) to actual listeners. This way, listeners attach to the router similar to old JavaBean event notification way. But I think this is getting too complex.

Dynamic Endpoints

Here's some code to illustrate how to implement event notification with Apache Camel dynamic endpoints.

public class Sensor { 

    // dynamically created or injected
    private Endpoint sensorEndpoint;
    @EndpointInject
    private ProducerTemplate producer;
    
    private void fireSensor(SensorEvent sensorEvent) {
        producer.sendBody(sensorEndpoint, sensorEvent);
    }

}

public class Display {

    @Autowired
    private Endpoint sensorEndpoint;
    private Consumer sensorConsumer;
    
    @PostConstruct
    public void initialize() {
        // BeanProcessor should automatically
        // match the method based on argument type
        sensorConsumer = sensorEndpoint.createConsumer(
            new BeanProcessor(this, sensorEndpoint.getCamelContext()));
        sensorConsumer.start();
    }
    
    @PreDestroy
    public void destroy() {
        sensorConsumer.stop();
    }

    public void sensorUpdated(SensorEvent sensorEvent) {
        // ... code to update ...  
    }

}

It's far from elegant. Especially the part where the Event Driven Consumer has to be created, configured, started, and stopped manually.

It's also somewhat decoupled, since the consumer needs to only know the endpoint object or the endpoint URI. And the producer doesn't have to iterate all consumers and call their callbacks one by one.

Update: I've written a more advanced proof-of-concept event notification with Apache Camel here.