Friday, December 31, 2010

Implementing Asynchronous Observer Pattern with Bean Proxy [Apache Camel Series]

Apache Camel is a very versatile message routing and enterprise integration framework.

One very useful, practical, and reasonable use case is to implement a Listener/Observer interface that delegates to an actual implementation with a flexible exchange pattern.

So from the point of view of the Observable (event source), the Observer is just a regular "POJO" Java object.

The Listener Interface


Here's the Listener interface :

public interface InvoiceListener {
public void invoiceCreated(int id, String name);
}

What I Want

What I'd like to do is to be able to call a listener object as usual :

InvoiceListener = .....
invoiceListener.invoiceCreated(243, "Sumba Enterprise");


But have the implementation flexible. It can be a local object, a remote web service, or even routed asynchronously using message queue (ActiveMQ) or XMPP.

A nice side effect is that the programming model stays the same. And of course, it's easily testable since your implementation is still POJO.

The Implementation

This is an example implementation of the listener, that simply logs the input.
Note there can be as many implementations as required.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerInvoiceListener implements InvoiceListener {
Logger logger = LoggerFactory.getLogger(getClass());
public void invoiceCreated(int id, String name) {
logger.info("Invoice #{} name: {} created", id, name);
// Simulate asynchronous processing by delaying
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
logger.error("Interrupted", e);
}
logger.info("{}/{} done!", id, name);
}
}

Note that I've added Thread.sleep() there, to simulate processing.

The Usual Way aka Useful for JUnit Tests

So here's the standard stuff:

InvoiceListener = new LoggerInvoiceListener();
invoiceListener.invoiceCreated(243, "Sumba Enterprise");
logger.info("first invoice sent");
invoiceListener.invoiceCreated(938, "Mina Co.");
logger.info("second invoice sent");

While this works, and very useful in unit testing, in our example it is not practical.

The Listener blocks the thread for processing and the program runs for 3000 ms for two invoices.

Not to mention that the original thread cannot do anything else while waiting for the Listener to return.

Here's how we'll fix it using Apache Camel, without changing how we call the listener at all!

Setting Up Apache Camel

I will use Gradle to set up our project and automatically manage Apache Camel dependencies and SLF4j + Logback, which I use for logging.

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'maven'
sourceCompatibility = '1.6'
repositories {
mavenRepo urls: "file:${System.properties['user.home']}/.m2/repository", name: 'local'
mavenCentral()
}
dependencies {
compile 'org.slf4j:slf4j-api:1.6.1'
runtime 'org.slf4j:jcl-over-slf4j:1.6.1'
runtime 'ch.qos.logback:logback-classic:0.9.26'
compile 'org.apache.camel:camel-core:2.5.0'
}

Maven guys can do something similar, but I increasingly prefer Gradle due to its compact syntax and flexibility.

If you're not using dependency management or still use Ant, I strongly recommend Gradle for your project build system.

It's the flexibility of Ant, can optionally use conventions like Maven, with powerful dependency management of Ivy, with Groovy's clean syntax so won't intimidate you with verbose <angle brackets>.

To create the Eclipse IDE Project we'll create the source folder and execute Gradle's "eclipse" task.

mkdir -p src/main/java src/main/resources
gradle eclipse

Configuring CamelContext

Camel routes runs inside a CamelContext. Here's the usual way to create a default CamelContext, configure routes, start the CamelContext and stop it.

CamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
// TODO: configure routes here
});
camelContext.start();
// TODO: run your app here
camelContext.stop();

Configuring Camel routes can be done using a Java-based DSL (among other choices).

Here's a route DSL for our use case, that simply invokes a specified bean:

from("direct:invoice").bean(loggerInvoiceListener);

Camelizing Our App


With that in mind, let's see how our original application looks like:

private static Logger logger = LoggerFactory.getLogger(App.class);
private static LoggerInvoiceListener loggerInvoiceListener = new LoggerInvoiceListener();
private static InvoiceListener invoiceListener;
public static void main(String[] args) throws Exception {
invoiceListener = loggerInvoiceListener;
invoiceListener.invoiceCreated(243, "Sumba Enterprise");
logger.info("first invoice sent");
invoiceListener.invoiceCreated(938, "Mina Co.");
logger.info("second invoice sent");
}

And this is how to do the same thing, using Apache Camel :

import org.apache.camel.CamelContext;
import org.apache.camel.builder.ProxyBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static Logger logger = LoggerFactory.getLogger(App.class);
private static LoggerInvoiceListener loggerInvoiceListener = new LoggerInvoiceListener();
private static InvoiceListener invoiceListener;
public static void main(String[] args) throws Exception {
CamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:invoice").bean(loggerInvoiceListener);
}
});
camelContext.start();
invoiceListener = new ProxyBuilder(camelContext).endpoint("direct:invoice")
.build(InvoiceListener.class);
invoiceListener.invoiceCreated(243, "Sumba Enterprise");
logger.info("first invoice sent");
invoiceListener.invoiceCreated(938, "Mina Co.");
logger.info("second invoice sent");
camelContext.stop();
}

You can see the Listener API usage has not changed at all.

Apart from the Camel runtime, the main difference lies in how we get the InvoiceListener object, which is now a proxy provided by Camel:

invoiceListener = new ProxyBuilder(camelContext).endpoint("direct:invoice")
  .build(InvoiceListener.class);

Here's the output of the Camel-ized app :

23:59:09.629 [main] INFO  o.a.c.i.c.AnnotationTypeConverterLoader - Found 3 packages with 13 @Converter classes to load
23:59:09.666 [main] INFO  o.a.c.i.c.DefaultTypeConverter - Loaded 146 type converters in 0.405 seconds
23:59:09.774 [main] INFO  o.a.camel.impl.DefaultCamelContext - Route: route1 started and consuming from: Endpoint[direct://invoice]
23:59:09.775 [main] INFO  o.a.camel.impl.DefaultCamelContext - Total 1 routes, of which 1 is started.
23:59:09.775 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camel-1) started in 0.546 seconds
23:59:09.788 [main] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #243 name: Sumba Enterprise created
23:59:11.289 [main] INFO  i.c.b.c.LoggerInvoiceListener - 243/Sumba Enterprise done!
23:59:11.295 [main] INFO  id.co.bippo.camelasyncredo.App - first invoice sent
23:59:11.296 [main] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #938 name: Mina Co. created
23:59:12.796 [main] INFO  i.c.b.c.LoggerInvoiceListener - 938/Mina Co. done!
23:59:12.797 [main] INFO  id.co.bippo.camelasyncredo.App - second invoice sent
23:59:12.797 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext:camel-1) is shutting down
23:59:12.798 [main] INFO  o.a.c.impl.DefaultShutdownStrategy - Starting to graceful shutdown 1 routes (timeout 300 seconds)
23:59:12.806 [Camel Thread 0 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route1 suspension deferred.
23:59:12.807 [Camel Thread 0 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route1 shutdown complete.
23:59:12.807 [main] INFO  o.a.c.impl.DefaultShutdownStrategy - Graceful shutdown of 1 routes completed in 0 seconds
23:59:12.809 [main] INFO  o.a.c.impl.DefaultInflightRepository - Shutting down with no inflight exchanges.
23:59:12.810 [main] INFO  o.a.camel.impl.DefaultCamelContext - Uptime: 3.581 seconds
23:59:12.810 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camel-1) is shutdown in 0.013 seconds

The app runs > 3.5 seconds, while about 400 ms is spent on Apache Camel startup. This is the minimal overhead of Camel.

I Want Them All at Once! Making It Asynchronous

A useful use case is to make the listener invocations asynchronous, i.e. performed with multiple threads. But not changing the caller code at all.
Let's see how we can do this with Camel.

All we need to do is change:

from("direct:invoice").bean(loggerInvoiceListener);

to:

from("direct:invoice").inOnly().to("seda:invoice.queue");
from("seda:invoice.queue").threads().bean(loggerInvoiceListener);

This route tells Camel to transform the original invocation to InOnly (meaning that no response/reply is needed), and send it to a SEDA Queue endpoint.

The messages in SEDA Queue will be split in multiple threads (default thread pool is 10 threads, but you can specify yourself) and each thread will invoke the destination implementation which is loggerInvoiceListener bean.

Here's how it looks right now :

00:11:44.952 [main] INFO  o.a.camel.impl.DefaultCamelContext - Route: route1 started and consuming from: Endpoint[direct://invoice]
00:11:44.954 [main] INFO  o.a.camel.impl.DefaultCamelContext - Route: route2 started and consuming from: Endpoint[seda://invoice.queue]
00:11:44.954 [main] INFO  o.a.camel.impl.DefaultCamelContext - Total 2 routes, of which 2 is started.
00:11:44.954 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camel-1) started in 0.529 seconds
00:11:44.969 [main] INFO  id.co.bippo.camelasyncredo.App - first invoice sent
00:11:44.970 [main] INFO  id.co.bippo.camelasyncredo.App - second invoice sent
00:11:44.970 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext:camel-1) is shutting down
00:11:44.971 [main] INFO  o.a.c.impl.DefaultShutdownStrategy - Starting to graceful shutdown 2 routes (timeout 300 seconds)
00:11:44.971 [Camel Thread 2 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #938 name: Mina Co. created
00:11:44.971 [Camel Thread 1 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #243 name: Sumba Enterprise created
00:11:44.974 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route2 suspension deferred.
00:11:44.974 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route1 suspension deferred.
00:11:44.974 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Waiting as there are still 2 inflight and pending exchanges to complete, timeout in 300 seconds.
00:11:45.975 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Waiting as there are still 2 inflight and pending exchanges to complete, timeout in 299 seconds.
00:11:46.471 [Camel Thread 2 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - 938/Mina Co. done!
00:11:46.471 [Camel Thread 1 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - 243/Sumba Enterprise done!
00:11:46.976 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route2 shutdown complete.
00:11:46.976 [Camel Thread 3 - ShutdownTask] INFO  o.a.c.impl.DefaultShutdownStrategy - Route: route1 shutdown complete.
00:11:46.976 [main] INFO  o.a.c.impl.DefaultShutdownStrategy - Graceful shutdown of 2 routes completed in 2 seconds
00:11:46.977 [main] INFO  o.a.c.impl.DefaultInflightRepository - Shutting down with no inflight exchanges.
00:11:46.978 [main] INFO  o.a.camel.impl.DefaultCamelContext - Uptime: 2.553 seconds
00:11:46.979 [main] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camel-1) is shutdown in 2.008 seconds

You can see from the log that Camel has executed our application in multiple parallel threads, as an effect, our application runs in about 2.5 seconds
instead of ~3.5 seconds like the previous.

You can also see that the order of execution is very different now.

Synchronous invocations:

23:59:09.788 [main] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #243 name: Sumba Enterprise created
23:59:11.289 [main] INFO  i.c.b.c.LoggerInvoiceListener - 243/Sumba Enterprise done!
23:59:11.295 [main] INFO  id.co.bippo.camelasyncredo.App - first invoice sent
23:59:11.296 [main] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #938 name: Mina Co. created
23:59:12.796 [main] INFO  i.c.b.c.LoggerInvoiceListener - 938/Mina Co. done!
23:59:12.797 [main] INFO  id.co.bippo.camelasyncredo.App - second invoice sent
Asynchronous invocations, with Camel:
00:11:44.969 [main] INFO  id.co.bippo.camelasyncredo.App - first invoice sent
00:11:44.970 [main] INFO  id.co.bippo.camelasyncredo.App - second invoice sent
...
00:11:44.971 [Camel Thread 2 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #938 name: Mina Co. created
00:11:44.971 [Camel Thread 1 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - Invoice #243 name: Sumba Enterprise created
...
00:11:46.471 [Camel Thread 2 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - 938/Mina Co. done!
00:11:46.471 [Camel Thread 1 - Threads] INFO  i.c.b.c.LoggerInvoiceListener - 243/Sumba Enterprise done!

Instead of objects waiting for the previous invocation to complete, they can run independently.

All without changing the caller code nor the interface API nor the implementation code.

What you simply do is change the Camel routes.

Running the Example

To make it really easy for you (especially to myself, as I tend to forget things!), I've made the examples freely available as camel-async-redo project on GitHub.

You'll need Git and Gradle to checkout then build it (and JDK 1.6).
Gradle will automatically download and cache the required dependencies for you.

git clone git://github.com/ceefour/camel-async-redo.git
cd camel-async-redo
gradle exec

Conclusion

Apache Camel is unobtrusive way to add flexibility of routing, messaging, and integration patterns into your Java application.
(although, Camel can be useful to seam/weave/integrate any combination of applications, even remote services, so not just Java, but that's another topic...)

I highly suggest the Camel in Action book for the best in-depth guide and examples for using Camel to develop your applications more productively.

34 comments:

Anonymous said...

Thank you for posting such a great article! I found your website perfect for my needs. It contains wonderful and helpful posts. Keep up the good work! http://www.affordablecrrealestate.com |

http://www.conniedavisrealestate.com |

http://www.estatesatpinecrest.com |

http://www.granitestatesouth.com |

http://www.harbor-realestate.com |

http://www.kerryfinnrealestate.com |

http://www.luxuryestateworldwide.com |

http://www.nyrealestatebuffalo.com |

http://www.orangeville-realestateagents.com |

http://www.purestates.com |

coach factore outlet said...

Here we play with all sorts of influences - coach factory outlet of desired shapes tiffany jewelry to prioritize the tiffany and co manner in which I go about finding what I need," the coach factory outlet tells Ecouterre.More than 1,000 runners began the race.Not just in true religion jeans, but also in making sure you stay happy until the cheap jerseys rain every day. Hey, $350 is way less than those coach outlet. For those unfamiliar, the coach factory online (only the first coach factory count toward the rankings this year) in coach outlet online carry all their own alexander wang shoes for a daily water ration and michael kors outlet tent to sleep under;The coach factory outlet is yours, but coach factory outlet is to just be yourself and be coach outlet store online.The sleek ensemble came on show through sac burberry. Choose from brands like Bernardo, michael kors outlet and others.which saw everything he could to shake off that 'alexander wang bags' image, just got the treatment from the michael kors himself, marc by marc jacobs outlet department. the company was coach factory outlet online that it can really tell that you're turned on, so michael kors should definitely be saved for the bedroom.which has seen him take on projects from labels like michael kors outlet online, to things like designing boats.

jhoney said...

You can't really say what is beautiful about a place, but the image of the place will remain vividly with you.
http://businessideagroup.com|http://empoweringhomebusiness.com|http://hornsbybusiness.com|http://lifeisyourbusiness.com|http://cotton-house-hotel.com

jhoney said...

The problem with beauty is that it's like being born rich and getting poorer.
SEO Liverpool

General Apparel said...

Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma - which is living with the results of other people's thinking. Don't let the noise of others' opinions drown out your own inner voice. And most important, have the courage to follow your heart and intuition.

hua hin property said...

Success is to be measured not so much by the position that one has reached in life as by the obstacles which he has overcome.

Latest World News said...

It is worth noting that under SOPA, as written, this blog could be summarily shut down without notice or appeal. I've talked about Torrent software, and people in the Comments have linked to Fan Vids. The full story of this actually happening already -- no "hypothetical" -- under the far mor lenient DMCA is here.

gippy honey said...

We don't get a chance to do that many things, and every one should be really excellent. Because this is our life. Life is brief, and then you die, you know? So this is what we've chosen to do with our life.
Electrician Spain

gippy honey said...

Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma - which is living with the results of other people's thinking. Don't let the noise of others' opinions drown out your own inner voice. And most important, have the courage to follow your heart and intuition.
Provigil Online

Fashion Shoes said...

Zest is the secret of all beauty. There is no beauty that is attractive without zest.

gippy honey said...

Be a yardstick of quality. Some people aren't used to an environment where excellence is expected.
Personal Trainer Liverpool

gippy honey said...

All you need is ignorance and confidence and the success is sure.
Glass Curtains Spain

gippy honey said...

For the past 33 years, I have looked in the mirror every morning and asked myself: 'If today were the last day of my life, would I want to do what I am about to do today?' And whenever the answer has been 'No' for too many days in a row, I know I need to change something.
Provigil

gippy honey said...

All you need is ignorance and confidence and the success is sure.
Sleeping Tablets Online

Buy Zyban Online said...

The plainer the dress, the greater luster does beauty appear.

Buy Provigil said...

You can only perceive real beauty in a person as they get older.

Diazepam Online said...

When virtue and modesty enlighten her charms, the lustre of a beautiful woman is brighter than the stars of heaven, and the influence of her power it is in vain to resist.

Buy Ativan said...

The world's biggest power is the youth and beauty of a woman.

Ativan Online said...

The plainer the dress, the greater luster does beauty appear.

Buy Lorazepam said...

I've found this very interesting and has a good stuff for the readers and designers. I would be glad to recommend the post for its quality content. Nice post.

buy ambien online legally said...

You can only perceive real beauty in a person as they get older.

buy phentermine online with no prescription said...

When everything else physical and mental seems to diminish, the appreciation of beauty is on the increase.

buy klonopin online with no prescription said...

There is no definition of beauty, but when you can see someone's spirit coming through, something unexplainable, that's beautiful to me.

link said...

When everything else physical and mental seems to diminish, the appreciation of beauty is on the increase.

valium no prescription online said...

When everything else physical and mental seems to diminish, the appreciation of beauty is on the increase.

visit website said...

We are learning, too, that the love of beauty is one of Nature's greatest healers.

Buy Diazepam Online said...

The world's biggest power is the youth and beauty of a woman.

Buy Diazepam Online said...

The love of beauty in its multiple forms is the noblest gift of the human cerebrum.

Phentermine Online said...

The problem with beauty is that it's like being born rich and getting poorer.

oakleyses said...

burberry handbags, prada outlet, ray ban sunglasses, gucci handbags, tiffany and co, louis vuitton, christian louboutin outlet, christian louboutin uk, tory burch outlet, ray ban sunglasses, polo ralph lauren outlet online, louis vuitton outlet, jordan shoes, michael kors outlet online, nike air max, christian louboutin, ray ban sunglasses, louis vuitton outlet, longchamp outlet, prada handbags, michael kors outlet online, uggs outlet, christian louboutin shoes, uggs on sale, ugg boots, louis vuitton outlet, chanel handbags, longchamp outlet, oakley sunglasses, nike outlet, louis vuitton, uggs outlet, polo outlet, michael kors outlet, burberry outlet, tiffany jewelry, michael kors outlet online, oakley sunglasses, nike air max, ugg boots, michael kors outlet online, oakley sunglasses, replica watches, nike free, michael kors outlet, replica watches, longchamp outlet, kate spade outlet

oakleyses said...

sac vanessa bruno, hollister uk, polo lacoste, vans pas cher, nike roshe run uk, nike free uk, true religion outlet, air max, polo ralph lauren, coach purses, louboutin pas cher, hollister pas cher, kate spade, lululemon canada, timberland pas cher, michael kors outlet, sac longchamp pas cher, michael kors pas cher, ray ban pas cher, true religion outlet, abercrombie and fitch uk, nike air max uk, replica handbags, nike air max uk, nike blazer pas cher, coach outlet, nike roshe, michael kors, mulberry uk, coach outlet store online, burberry pas cher, guess pas cher, nike air max, sac hermes, michael kors, north face uk, hogan outlet, north face, converse pas cher, oakley pas cher, ralph lauren uk, true religion outlet, new balance, jordan pas cher, nike free run, nike tn, longchamp pas cher, true religion jeans, nike air force, ray ban uk

oakleyses said...

hollister, north face outlet, mac cosmetics, baseball bats, nike huaraches, babyliss, new balance shoes, iphone cases, chi flat iron, louboutin, ferragamo shoes, iphone 5s cases, longchamp uk, soccer shoes, mcm handbags, nike roshe run, timberland boots, mont blanc pens, vans outlet, bottega veneta, instyler, oakley, ralph lauren, valentino shoes, giuseppe zanotti outlet, s6 case, p90x workout, celine handbags, iphone 6s plus cases, herve leger, north face outlet, nike trainers uk, ipad cases, iphone 6s cases, lululemon, wedding dresses, nike air max, abercrombie and fitch, nfl jerseys, hermes belt, insanity workout, ghd hair, asics running shoes, beats by dre, iphone 6 cases, reebok outlet, jimmy choo outlet, soccer jerseys, hollister clothing, iphone 6 plus cases

oakleyses said...

pandora jewelry, canada goose uk, canada goose, barbour, moncler, karen millen uk, swarovski crystal, replica watches, louis vuitton, canada goose, louis vuitton, juicy couture outlet, nike air max, lancel, swarovski, canada goose jackets, gucci, converse, thomas sabo, canada goose outlet, pandora charms, ugg,uggs,uggs canada, juicy couture outlet, doudoune moncler, hollister, moncler, montre pas cher, coach outlet, links of london, hollister, ugg uk, ugg, toms shoes, marc jacobs, pandora uk, vans, canada goose, moncler, canada goose outlet, moncler outlet, moncler outlet, louis vuitton, moncler, ray ban, louis vuitton, louis vuitton, converse outlet, moncler uk, ugg,ugg australia,ugg italia, supra shoes, barbour uk

Nithyanantham M said...

Thanks for sharing this post
Dailymotion video downloader

Try Dailymotion video
downloader
to download videos from dailymotion.

Post a Comment

 
Copyright 2009 Spring vs Java EE Web Dev. Powered by Blogger Blogger Templates create by Deluxe Templates. WP by Masterplan