Camel is a powerful open-source integration framework based on known Enterprise Integration Patterns. Camel define routing and mediation rules in a variety of domain-specific languages or even in XML. It uses URIs to work directly with any kind of Transport or messaging model such as HTTP and JMS and also allows you to define your own.
What I particularly like is the small library with minimal dependencies which makes embedding the framework within a Java application seamless. Furthermore, you work with the same API regardless of the Transport used. This makes a plug-and-play application where one endpoint can be swapped for another quite possible.
The demonstration application that I will discuss here is inspired by my new employer, Mediswitch. One of their products connects various medical aids and other third parties with all kinds of service providers (each using a different communication medium). We will define two endpoints (a rather ambiguous term referring to either a URL/URI or an entire service) for our application:
- The first endpoint is an HTTP-GET client. I like these in tests as they can be simply called from a web browser or using good old curl on the command line.
- A server using Thrift that services the request from the first endpoint and returns the result via the central switch.
For this application we will require the following:
- A Component (already implemented for us) to handle the incoming HTTP-GET requests.
- A Processor for each of the possible request that is received by the consumer component (i.e. the HTTP-GET side). For this application we will service four different requests:
- Getting a member's details.
- Modifying a member's detail.
- Deleting a member (I called it 'zap' for some reason).
- Process a claim submitted.
- A Processor in between that does something additional with the request.
- A mechanism to wire all these things up and a main class that kicks off this process.
We do not need to write the Component, so next is the Processor to handle the Thrift call:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class GetMemberDetailProcessor extends AbstractMedicalServiceProcessor { | |
@Override | |
public void process(Exchange exchange) throws Exception { | |
HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class); | |
String id = request.getParameter("id"); | |
if (id == null) { | |
throw new ServletException("id cannot be empty"); | |
} | |
logger.debug("Request from {}, trying to retrieve member, [{}].", request.getRequestURL(), id); | |
ThriftMember member = getThriftClient().getMemberDetail(id); | |
exchange.getOut().setBody(String.format("Member, %s successfully retrieved", member.toString())); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class AbstractMedicalServiceProcessor implements Processor { | |
private static final String HOST = "localhost"; | |
private static final int PORT = 9000; | |
protected static Logger logger = LoggerFactory.getLogger(AbstractMedicalServiceProcessor.class); | |
private MedicalService.Client client; | |
public AbstractMedicalServiceProcessor() { | |
try { | |
logger.debug("Setting up Thrift client to query server on {}:{}.", HOST, PORT); | |
TTransport transport = new TFramedTransport(new TSocket(HOST, PORT)); | |
TProtocol protocol = new TBinaryProtocol(transport); | |
client = new MedicalService.Client(protocol); | |
transport.open(); | |
logger.debug("Thrift client sucessfully configured."); | |
} catch (TTransportException e) { | |
e.printStackTrace(); | |
} | |
} | |
protected MedicalService.Client getThriftClient() { | |
return client; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DummyProcessor implements Processor { | |
protected static Logger logger = LoggerFactory.getLogger(DummyProcessor.class); | |
@Override | |
public void process(Exchange exchange) throws Exception { | |
logger.debug("********************************************************"); | |
logger.debug("Detail of this request:"); | |
logger.debug(exchange.getFromEndpoint().getEndpointUri()); | |
logger.debug(exchange.getIn().getHeaders().toString()); | |
logger.debug("********************************************************"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MedicalServiceRouteBuilder extends RouteBuilder { | |
@Override | |
public void configure() throws Exception { | |
from("jetty:http://localhost:8080?matchOnUriPrefix=true").process(new DummyProcessor()).choice() | |
.when(header("CamelHttpPath").isEqualTo("/modifymember")).process(new ModifyMemberProcessor()) | |
.when(header("CamelHttpPath").isEqualTo("/zapmember")).process(new ZapMemberProcessor()) | |
.when(header("CamelHttpPath").isEqualTo("/getmemberdetail")).process(new GetMemberDetailProcessor()) | |
.when(header("CamelHttpPath").isEqualTo("/claim")).process(new ClaimProcessor()); | |
/* | |
from("jetty:http://localhost:8080/modifymember").process(new DummyProcessor()).process(new ModifyMemberProcessor()); | |
from("jetty:http://localhost:8080/zapmember").process(new DummyProcessor()).process(new ZapMemberProcessor()); | |
from("jetty:http://localhost:8080/getmemberdetail").process(new DummyProcessor()).process(new GetMemberDetailProcessor()); | |
from("jetty:http://localhost:8080/claim").process(new DummyProcessor()).process(new ClaimProcessor()); | |
*/ | |
} | |
} |
To wire everything up, requires a CamelContext object, adding the routes and starting the context, which starts threads and internal processes to get Camel going:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CamelMedicalService { | |
public static void main(String[] args) throws Exception { | |
CamelContext context = new DefaultCamelContext(); | |
context.addRoutes(new MedicalServiceRouteBuilder()); | |
context.start(); | |
// Wait a minute and then stop all (since context.start is non-blocking). | |
Thread.sleep(TimeUnit.MINUTES.toMillis(10)); | |
context.stop(); | |
} | |
} |
Now we can test the application. After starting the Thrift server (a separate little application to allow testing of our central switch application), we can issue a command from either the browser or the command line. Let's use a browser this time:
The log messages (after trimming down on the org.apache entries in the log4j.xml file), looks like this (notice the entries written by the DummyProcessor):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
09:58:03,576 DEBUG [AbstractMedicalServiceProcessor] Setting up Thrift client to query server on localhost:9000. | |
09:58:03,585 DEBUG [AbstractMedicalServiceProcessor] Thrift client sucessfully configured. | |
09:58:03,585 DEBUG [AbstractMedicalServiceProcessor] Setting up Thrift client to query server on localhost:9000. | |
09:58:03,585 DEBUG [AbstractMedicalServiceProcessor] Thrift client sucessfully configured. | |
09:58:03,586 DEBUG [AbstractMedicalServiceProcessor] Setting up Thrift client to query server on localhost:9000. | |
09:58:03,586 DEBUG [AbstractMedicalServiceProcessor] Thrift client sucessfully configured. | |
09:58:03,587 DEBUG [AbstractMedicalServiceProcessor] Setting up Thrift client to query server on localhost:9000. | |
09:58:03,587 DEBUG [AbstractMedicalServiceProcessor] Thrift client sucessfully configured. | |
09:58:07,992 DEBUG [DummyProcessor] ******************************************************** | |
09:58:07,992 DEBUG [DummyProcessor] Detail of this request: | |
09:58:07,992 DEBUG [DummyProcessor] http://localhost:8080?matchOnUriPrefix=true | |
09:58:07,992 DEBUG [DummyProcessor] {id=123, Host=localhost:8080, Content-Type=null, CamelHttpPath=/getmemberdetail, CamelHttpMethod=GET, CamelHttpServletResponse=HTTP/1.1 200 | |
, CamelHttpServletRequest=[GET /getmemberdetail?id=123]@1767965669 org.eclipse.jetty.server.Request@696103e5, CamelHttpQuery=id=123, Accept=*/*, CamelHttpUrl=http://localhost:8080/getmemberdetail, User-Agent=curl/7.27.0, CamelHttpUri=/getmemberdetail} | |
09:58:07,992 DEBUG [DummyProcessor] ******************************************************** | |
09:58:07,996 DEBUG [AbstractMedicalServiceProcessor] Request from http://localhost:8080/getmemberdetail, trying to retrieve member, [123]. | |
09:58:26,076 DEBUG [DummyProcessor] ******************************************************** | |
09:58:26,076 DEBUG [DummyProcessor] Detail of this request: | |
09:58:26,076 DEBUG [DummyProcessor] http://localhost:8080?matchOnUriPrefix=true | |
09:58:26,076 DEBUG [DummyProcessor] {Content-Type=null, id=123, CamelHttpServletRequest=[GET /claim?id=123&amount=200]@847205607 org.eclipse.jetty.server.Request@327f54e7, CamelHttpUrl=http://localhost:8080/claim, CamelHttpServletResponse=HTTP/1.1 200 | |
, User-Agent=curl/7.27.0, CamelHttpPath=/claim, CamelHttpMethod=GET, CamelHttpUri=/claim, amount=200, Accept=*/*, CamelHttpQuery=id=123&amount=200, Host=localhost:8080} | |
09:58:26,076 DEBUG [DummyProcessor] ******************************************************** | |
09:58:26,077 DEBUG [AbstractMedicalServiceProcessor] Request from http://localhost:8080/claim, trying to claim for member, [id=123, amount=200.0]. |
In summary, Camel makes routing between unknown endpoints as easy as possible. Notice how the internal Jetty boilerplate code is completely absent from the code. It is all handled internally and setting it up essentially required a single line of code. The chaining between Processors also makes it easy to intercept a request for some pre-processing before passing it on.
No comments:
Post a Comment