A comparison of Camel vs Spring EIP JAVA DSL in terms of Starbucks story implementation - (http://www.enterpriseintegrationpatterns.com/ramblings/18_starbucks.html)
1. Camel and Spring
Integration (aka SI)
JAVA DSL’s comparison
by implementing Starbucks story
(http://www.enterpriseintegrationpatterns.com/ramblings/18_starbucks.html)
- by Gediminas Siutilas
2. Requirements for Starbucks story
1. Multiple orders should be submitted for
asynchronous processing.
2. Order for the same customer contains hot
and cold drinks.
3. To prepare a cold drink 1 second is required.
4. To prepare a hot drink 5 seconds are
required.
5. Customer should be served only when both
drinks are ready for the same customer.
3. SI JAVA DSL (Spring official - no lambdas)
@MessagingGateway
public interface Cafe {
@Gateway(requestChannel = "orders.input")
void placeOrder(Order order);
}
@Autowired
private CafeAggregator cafeAggregator;
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedDelay(1000).get();
}
@Bean
@SuppressWarnings("unchecked")
public IntegrationFlow orders() {
return IntegrationFlows.from("orders.input")
.split("payload.items", (Consumer) null)
.channel(MessageChannels.executor(Executors.newCachedThreadPool()))// 16
.route("payload.iced",
new Consumer<RouterSpec<ExpressionEvaluatingRouter>>() {
@Override
public void accept(RouterSpec<ExpressionEvaluatingRouter> spec) {
spec.channelMapping("true", "iced")
.channelMapping("false", "hot");
}
})
.get();
}
@Bean
public IntegrationFlow icedFlow() {
return IntegrationFlows.from(MessageChannels.queue("iced", 10))
.handle(new GenericHandler<OrderItem>() {
@Override
public Object handle(OrderItem payload,
Map<String, Object> headers) {
Uninterruptibles.sleepUninterruptibly(1,
TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName()
+ " prepared cold drink #" +
coldDrinkCounter.incrementAndGet()
+ " for order #" + payload.getOrderNumber()
+ ": " + payload);
return payload;
}
})
.channel("output")
.get();
}
@Bean
public IntegrationFlow hotFlow() {
return IntegrationFlows.from(MessageChannels.queue("hot", 10))
.handle(new GenericHandler<OrderItem>() {
@Override
public Object handle(OrderItem payload,
Map<String, Object> headers) {
Uninterruptibles.sleepUninterruptibly(5,
TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName()
+ " prepared hot drink #" +
hotDrinkCounter.incrementAndGet()
+ " for order #" + payload.getOrderNumber()
+ ": " + payload);
return payload;
}
})
.channel("output").get();
}
@Bean
public IntegrationFlow resultFlow() {
return IntegrationFlows.from("output")
.transform(new GenericTransformer<OrderItem, Drink>() {
@Override
public Drink transform(OrderItem orderItem) {
return new Drink(orderItem.getOrderNumber(),
orderItem.getDrinkType(),
orderItem.isIced(),
orderItem.getShots());
}
})
.aggregate(new Consumer<AggregatorSpec>() {
@Override
public void accept(AggregatorSpec aggregatorSpec) {
aggregatorSpec.processor(cafeAggregator, null);
}
}, null)
.handle(CharacterStreamWritingMessageHandler.stdout())
.get();
}
@Component
public static class CafeAggregator {
@Aggregator
public Delivery output(List<Drink> drinks) {
return new Delivery(drinks);
}
@CorrelationStrategy
public Integer correlation(Drink drink) {
return drink.getOrderNumber();
Step-by-Step: http://spring.io/blog/2014/12/01/spring-integration-java-dsl-pre-java-8-line-by-line-tutorial
4. SI JAVA DSL (Spring official - with lambdas only JAVA8)
@MessagingGateway
public interface Cafe {
@Gateway(requestChannel = "orders.input")
void placeOrder(Order order);
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedDelay(1000).get();
}
@Bean
public IntegrationFlow orders() {
return f -> f
.split(Order.class, Order::getItems)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<OrderItem, Boolean>route(OrderItem::isIced, mapping -> mapping
.subFlowMapping("true", sf -> sf
.channel(c -> c.queue(10))
.publishSubscribeChannel(c -> c
.subscribe(s ->
s.handle(m -> sleepUninterruptibly(1, TimeUnit.SECONDS)))
.subscribe(sub -> sub
.<OrderItem, String>transform(item ->
Thread.currentThread().getName()
+ " prepared cold drink #"
+ this.coldDrinkCounter.incrementAndGet()
+ " for order #" + item.getOrderNumber()
+ ": " + item)
.handle(m -> System.out.println(m.getPayload())))))
.subFlowMapping("false", sf -> sf
.channel(c -> c.queue(10))
.publishSubscribeChannel(c -> c
.subscribe(s ->
s.handle(m -> sleepUninterruptibly(5, TimeUnit.SECONDS)))
.subscribe(sub -> sub
.<OrderItem, String>transform(item ->
Thread.currentThread().getName()
+ " prepared hot drink #"
+ this.hotDrinkCounter.incrementAndGet()
+ " for order #" + item.getOrderNumber()
+ ": " + item)
.handle(m -> System.out.println(m.getPayload()))))))
.<OrderItem, Drink>transform(orderItem ->
new Drink(orderItem.getOrderNumber(),
orderItem.getDrinkType(),
orderItem.isIced(),
orderItem.getShots()))
.aggregate(aggregator -> aggregator
.outputProcessor(group ->
new Delivery(group.getMessages()
.stream()
.map(message -> (Drink) message.getPayload())
.collect(Collectors.toList())))
.correlationStrategy(m ->
((Drink) m.getPayload()).getOrderNumber()), null)
.handle(CharacterStreamWritingMessageHandler.stdout());
}
Step-by-Step: http://spring.io/blog/2014/11/25/spring-integration-java-dsl-line-by-line-tutorial
5. Camel JAVA DSL (Official)
public void configure() {
from("direct:cafe")
.split().method("orderSplitter").to("direct:drink");
from("direct:drink").recipientList().method("drinkRouter");
from("seda:coldDrinks?concurrentConsumers=2").to("bean:barista?method=prepareColdDrink").to("direct:deliveries");
from("seda:hotDrinks?concurrentConsumers=3").to("bean:barista?method=prepareHotDrink").to("direct:deliveries");
from("direct:deliveries")
.aggregate(new CafeAggregationStrategy()).method("waiter", "checkOrder").completionTimeout(5 * 1000L)
.to("bean:waiter?method=prepareDelivery")
.to("bean:waiter?method=deliverCafes");
}
Step-by-Step: http://camel.apache.org/cafe-example.html
As you can see there’s a lot registered services to local JDNI scope like waiter,
barista and etc. More details at: https://github.com/apache/camel/tree/master/examples/camel-
example-cafe/src/main/java/org/apache/camel/example/cafe
6. Camel JAVA DSL
(My version “All In Route”, exposing everything to the route)
private static RouteBuilder createRoute() {
return new RouteBuilder() {
public void configure() throws Exception {
from(ROUTE_INTPUT).
split(body().method("getItems"))
.setHeader("isIced", body().method("isIced"))
.choice()
.when(header("isIced").isEqualTo("true"))
.to("seda:coldDrinks")
.when(header("isIced").isEqualTo("false"))
.to("seda:hotDrinks")
.otherwise()
.transform(simple("UNKNOWN!!!")).to("stream:out");
from("seda:coldDrinks?concurrentConsumers=1").delay(1000L).to("direct:deliveries");
from("seda:hotDrinks?concurrentConsumers=1").delay(5000L).to("direct:deliveries");
from("direct:deliveries").
process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
OrderItem payload = exchange.getIn().getBody(OrderItem.class);
System.out.println(
Thread.currentThread().getName()
+ " prepared "+ (payload.isIced()?"cold":"hot")+" drink #"
+ hotDrinkCounter.incrementAndGet()
+ " for order #" + payload.getOrderNumber()
+ ": " + payload);
exchange.getIn().setBody(
new Drink(payload.getOrderNumber(),
payload.getDrinkType(),
payload.isIced(),
payload.getShots()));
}
}).
setHeader("order", body().method("getOrderNumber")).
aggregate(header("order"),new CafeAggregationStrategy()).completionSize(2).
process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
List<Drink> drinks = exchange.getIn().getBody(List.class);
exchange.getIn().setBody(
new Delivery(drinks));
}
}).
to("stream:out");
}
};
}
7. Camel Java DSL:
Pros:
Easy to read and understand.
No need for Generics on every joint.
Cons:
Limited use of JAVA DSL itself inside the route. Yes it supports many
DSL’s but when it comes let say to aggregation with new types of POJO’s, you
endup writing processors producing code boilerplate.
SI JAVA DSL (with Lambdas)
Pros:
Flexible use of JAVA DSL inside the flow.
Flexible access of exchange context inside the flow.
Cons:
Hard to read and understand the flow due to Generics.
SI JAVA DSL with no Lambdas is not even under comparison tables as it too obscure, hard to read and immature
comparing to Camel JAVA DSL.