The Camel Glue for MicroServices

Featured image

Recently, at E-Bee, I developed a framework for enabling Micro Services using Apache Camel. I have always been a big fan, nay, a fanatic, of modularisation. Combine the modularisation concept with the notion of Micro services and you would stipulate that all modules need to expose Micro services. All the services that are available from one module must be exposable as Micro Services to another dependent module. One module can only consume another module via its exposed services. No back door entry permitted! This means that one module cannot sneak in and take a peek at the other module’s database. Or shared memory. Or cache. If module1 depends on module2, this means that it can only depend on module2’s APIs. Nothing else! We have always had a great modular architecture in E-Bee. Each module has use two coding units a.k.a. two Eclipse projects - viz.  module1-api and module1-service. module1-api contains all the API interfaces whilst module1-service will contain all the implementations of these interfaces. module1-service will depend on module1-api for obvious reasons. It has to implement the APIs. However, module1-api must not obviously depend on module1-services.  Same goes for  module2-api and module2-service. Now, module1, since it depends on module2, will have a dependency to module2-api. However, module1-service will not depend on module2-service directly. It is dependent only on the API contracts. (shown below) Typically we had always used the Spring container for Dependency Injection. Spring injects module2-service classes into module1 service classes. dependency-injectionBut as we started to move towards Micro services, my desire was to allow module1-service to interact with module2-service either directly (as in a POJO invocation) or over the wires using a remote invocation. How we do that must be transparent. So how are we going to accomplish this? The key is to expose all the services using messages and leverage messaging to communicate from module1-service to module2-service. Then there must be a layer that allows this to happen. I decided to use Apache ActiveMQ for messaging and Apache Camel for allowing seamless communication between clusters of services. For those unfamiliar to Camel, it is a simple framework that uses an awesome DSL (Domain Specific Language) to set up routes. Routes allow the passage of “messages” between the producers and consumers. Producers request for various services and send a message along a route. This message is processed by a consumer which composes a reply and sends it back to the producer.

So, in our new framework my idea was to use Camel for communicating between module1 and module2. For this to happen, I set up a simple route in Camel. I called this route somewhat bombastically as the “Main Highway”. The Main Highway takes a message and routes it to the consumer and returns a reply. So, if module1-service wants to communicate with module2-service, all it has to do is to send a message along the Main highway. Now, I hooked up this Main Highway to JMS and HTTP as well so that any message destined for module2-service can also come via JMS or a HTTP request. The JMS supports both Serializable Java Objects and JSON. As it can be seen, the main highway has an entry point. This entry point is reachable only within the same JVM. (Camel calls such an entry point as a direct entry point).

The JMS and the HTTP entry points are also wired to the main highway. They have transformers that convert JSON into a “canonical” model that is understood within the framework. Main Highway also contains several components that can route the request to the end consumer which then consumes the request and composes a reply. The consumer can also be surrounded by multiple interceptors that enforce horizontal policies such as Security, Caching etc.  camel-routesThis abstraction allows the producer to avail the services exposed by the consumer in multiple ways:

  1. The producer can directly send a message within the same JVM to the entry point of the Main Highway.
  2. The producer can use the HTTP end point to communicate with the consumer.
  3. The producer can also communicate via the JMS end point. These options make the camel architecture extremely rich.

The Service Registry

How do services know about each other? They need to publish the information about their existence somewhere. This becomes complicated when there are multiple services residing in multiple deployable units (WARS or EARS in the Java world). How does module1-service residing in a war called module1.war know about the module2 services offered presumably in another war called module2.war? The service registry provides this feature. It is a service that can be called to find out about other services. This service of course needs to be well known so that it does not become “chicken and egg” to discover the registry service itself. We achieved this using REDIS as a Global repository for all services. The picture below illustrates the interactions. registry The different clusters host different kinds of services. There can be multiple nodes in a cluster. These nodes publish their service information into REDIS. Details include service name, operations and the parameters required to invoke the operation. The Queue name at which these services are listening is also published. The global registry is read on start up by each of the nodes and cached locally for the life of the application. Any updates to the global registry is also published via REDIS channels so that all the nodes across all the clusters can update themselves with this information. This allows the services to invoke each other across the ecosystem.

Camel Proxy

A class called Camel Proxy is also created using JDK proxies. This class connects to the global registry to discover where each service is hosted. If the service is local, it directly calls the service by invoking the “Main Highway” end point. Else, it uses JMS (with Java Serializable objects) to invoke a service in a remote server. This allows seamless integration between micro services without the clients being aware of whether a service is hosted locally or remotely. Hawtio is able to seamlessly integrate with Camel JMX beans via the Jolokia library. Since the REST library in Camel supports Swagger, we are able to automatically publish all our services using Swagger.

Comments

raja shankar kolluru: Hi Bharath I prefer Camel simply because it is completely open source (No paid enterprise editions etc.) Besides, Camel gives me a within the JVM routing capability with quick access to transformation capabilities and external access points to numerous transports which is what I want. The solution that I have built is basically a simple POJO framework that allows you to writing business logic without being encumbered by considerations such as exposing them to various transports and which also provides horizontal services. The Global Registry allows the ability to automatically publish your services.

Bharath: Hi Raja, good article. I have been using camel and activemq as standalone entities. Good to see you have glued them as framework. I have also used Mulesoft as light weight API layer. How does your solution differ from mulesoft?

Balaji: Raja, This is an excellent article on micro services and its implementation when there is no formal defination of it. Also, very active and valid questions on using OSGi Architecture for micro services. Though it may not comply to true micro services defination, definetly OSGi has lot of moudlarization to component and services. In addition, distributed OSGi wrapped in Apache Karaf as mention in post should also complement micro services. I have used Apache Camel and Apache CXF, both are great framework and products. Overall thumbs up for wrapping camel and services with REDIS

Sam Echelau: Raja, This does give me a lot of food for thought. I have been contemplating about how to do this and Camel was one of the frameworks I was considering. This article gives me a few ideas on how to do this. I will ping you privately for some code samples if necessary. But thanks.

Gaurav: Nice article and very interesting read. View questions: 1. If module 1 and module 2 co-exists why to expose them as separate micro services. They could be separate jars which I can understand helps in release cycle management 2. Micoservice having its own database warrants distributed transaction handling. How to address this? I am assuming here that module 1 and module 2 have there own db. 3. How to design domain model for model 1 and 2 entities, specially if there are foreign key references ? Example Account and Address are separate services but an account cannot exist without address but a address can be associated with more than in account So in context of this article Account - module 1 and Address - module 2 How above problems can be addressed using this example.

raja shankar kolluru: Hi Gaurav Good to hear from you :) Great questions. 1. If module1 and module2 co-exist in the same module, then there is definitely no need to expose them as micro services to each other. They can be micro -services for the rest of the world to consume however. The Camel Proxy that I mention above understands that these are “local” and uses the Main Highway which is a local route in Camel. Basically, it calls the service after making sure that the horizontal stuff is also taken care of (like logging, auditing and the like) 2. Transaction handling is important but needs to be designed differently in a distributed system. We should aspire to keep the transactions disparate rather than tying them all together to form a monstrous global distributed transaction. Micro services enforces this paradigm by forcing you to think of systems as separate with their own transaction semantics. What you may think as a “global transaction” will turn out to be a bunch of local transactions which can co-exist independently. For instance, if a customer checks out a product and in the process creates an order with a new shipping address, you may be tempted to think of this as three separate related transactions: a. Create an order b. Mark the cart as transitioned to an order. c. Update customer shipping address. But the reality is that your order management system may be different from your cart management system which may in turn be different from your customer management system. By combining these three different transactions into a global transaction, you will make your system unnecessarily coupled and unwieldy. Think of them as three different transactions connected with guaranteed messaging. You will see that a lot of the complexity goes away. 3. Again, the same observations as #2 above apply. Do you necessarily need to tie your address and customers table at the hip? Why cant you use addresses without a customer? For instance, do you see a need for an address service that deals with anonymous cart addresses? If you still want to keep them coupled, then it is best to not make a micro service out of the address. You, instead keep them together within the same deployable unit (like a WAR, EAR and the like). Hope this clarifies. Raja

Gaurav: Thanks Raja

Hariharan Nagarajan: Good one, sir. In a way, micro services look like an improved form of the osGi Bundle architecture. However, do you see issues of osGi architecture sprining up here too ? Like class loading issues - if multiple versions of module 1 are exposed, or installed, does the framework have a way of recognizing the latest? Or is the handling left to Apache Camel?

raja shankar kolluru: Great question Hari. The OSGi Bundle architecture is an awesome way of co-existing services that support multiple versions of an API within the same JVM. In the Micro services world, the services can actually reside in separate JVMs. Camel has been used by Karaf and the like to support OSGi bundles. But I would ideally treat the old version and the new version of a service as separate micro service clusters and keep them in two separate JVMs so we don’t have to deal with the complexity of OSGi. I would just instead route the request to the old or new JVM depending on the version of the service that is being requested. I hope that clarifies.

Hariharan Nagarajan: Yes, sir, your answer helps, and provides me relief that I can go ahead with my recent plan of learning micro services. LOL. The problem with any osGi container implementation - my exposure mainly with AEM - is that it doesn’t restrict the installation of multiple versions of the same bundle, at the same time not providing enough confidence around class loading. The second problem is the lack of freedom to allow multiple bundles (osgi Bundles were intended to behave like co-existing services!) exist remotely, until of course Karaf did something around that. With Apache Camel + Micro Services, I think it is a good combo you have mentioned, and relieves us of these problems. Also as you indicate, micro-services themselves existing within same JVM, and the same project (POM) doesn’t solve their purpose of creation - Apache Camel serves a good platform to allow them to develop individually, and work together.

raja shankar kolluru: Very true Hari. I have always felt innately uncomfortable about OSGi. I would rather deal with something that I can manage more comfortably rather than rely on this container that provides these features over the JVM. In fact that is one of the leading reasons for discomfort for me on AEM based architectures.

Hariharan Nagarajan: Sir, a couple other questions: 1) When Camel would already take care of enabling orchestration - like module 1 would already know what services of module 2 to use, and invoke the respective API, why would you need REDIS? 2) Why should module 1 api and module 1 services exist in separate projects? We could remove that one level of orchestration, by keeping them together, and of course exposing only the API to outer world.

raja shankar kolluru: Good questions Hari. 1. We need REDIS because module1 and module2 exist in separate wars. Let us say that they are listening to two queues viz. module1Q and module2Q respectively. So how does module1 know that module2 is listening to module2Q? It knows this because module2 published its configurations (like its name, description, the URL it exposes, the services and operations it supports and the queues that it listens to ) in REDIS. Both the wars are internally orchestrated using Camel. But the communication between wars is only possible if one war knows about the other war’s end points which are published in REDIS. 2. This is a great question. I have answered this in another post. Please see http://itmusings.com/architecture/on-program-contracts. We can talk more if you have questions after reading that post. In general, it is always a good idea to externalise the contracts into a separate jar file. It is very light weight anyways since including a jar file in your war is not a big deal. But it saves a ton of trouble.

Hariharan Nagarajan: Yes, sir. Both answers are good. And, I get your overall approach to the roadmap you have in mind. :) REDIS helps more around managing the process of development between multiple teams, and same about the approch with program contracts. This approach would resolve scaling the project both strength-wise, and technically in the long run. Thanks!