Apache Karaf Microservices

Apache Karaf Microservices

Apache Karaf is a small OSGi based runtime which provides a lightweight container onto which various components and applications can be deployed. It can be run as a standalone container. It provides some enterprise-ready features like shell console, remote access, hot deployment, dynamic configuration. It can be the perfect solution for microservices. The idea of microservices on Apache Karaf has already been introduced a few years ago. “What I am promoting is the idea of µServices, the concepts of an OSGi service as a design primitive.” – Peter Kriens March 2010.

Karaf on Docker

First, we need to run a Docker container with Apache Karaf. Surprisingly, there is no official repository with such an image. I found image on Docker Hub with Karaf here. Unfortunately, there is no port 8181 exposed – default Karaf web port. We will use this image to create our own with 8181 port available outside. Here’s our Dockerfile.

FROM java:8-jdk
MAINTAINER Piotr Minkowski <piotr.minkowski@gmail.com>
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64

ENV KARAF_VERSION=4.0.8

RUN wget http://www-us.apache.org/dist/karaf/${KARAF_VERSION}/apache-karaf-${KARAF_VERSION}.tar.gz; \
mkdir /opt/karaf; \
tar --strip-components=1 -C /opt/karaf -xzf apache-karaf-${KARAF_VERSION}.tar.gz; \
rm apache-karaf-${KARAF_VERSION}.tar.gz; \
mkdir /deploy; \
sed -i 's/^\(felix\.fileinstall\.dir\s*=\s*\).*$/\1\/deploy/' /opt/karaf/etc/org.apache.felix.fileinstall-deploy.cfg

VOLUME ["/deploy"]
EXPOSE 1099 8101 8181 44444
ENTRYPOINT ["/opt/karaf/bin/karaf"]

Then, by running docker commands below we are building our image from Dockerfile and starting new Karaf container.

$ docker build -t karaf-api .
$ docker run -d --name karaf -p 1099:1099 -p 8101:8101 -p 8181:8181 -p 44444:44444 karaf-api

Now, we can login to new docker container (1). Karaf is installed in /opt/karaf directory. We should run client by calling ./client in /opt/karaf/bin directory (2). Then we should install Apache Felix web console which is by default available under port 8181 (3). You can check it out by calling on web browser http://192.168.99.100:8181/system/console. Default username and password is karaf. In webconsole you can check full list of features installed on our OSGi cantainer. You can also display that list in karaf console using feature:list command (4). After webconsole installation, you decide if you prefer using Karaf command line or Apache Felix console for further actions. For our sample application, we need to add some OSGi repositories and features. First, we are adding Apache CXF framework repository (5) and its features for http and RESTful web services (6). Then we are adding repository for jackson framework (7) and some jackson and Jetty server features (8).

$ docker exec -i -t karaf /bin/bash (1)
$ cd /opt/karaf/bin
$ ./client (2)
karaf@root()> feature:install webconsole (3)
karaf@root()> feature:list (4)
karaf@root()> feature:repo-add cxf 3.1.10 (5)
karaf@root()> feature:install http cxf-jaxrs cxf (6)
karaf@root()> feature:repo-add mvn:org.code-house.jackson/features/2.7.6/xml/features (7)
karaf@root()> feature:install jackson-jaxrs-json-provider jetty (8)

Apche Karaf Microservices

Our environment has been configured. Now, we can take a brief look on sample application. It’s really simple. It has only three modules account-cxf, customer-cxf, sample-api. In the sample-api module we have base service interfaces and model objects. In account-cxf and customer-cxf there service implementations and OSGi services declarations in Blueprint file. Sample application source code is available on GitHub. Here’s account service controller class and its interface below.

public class AccountServiceImpl implements AccountService {

   private List<Account> accounts;

   public AccountServiceImpl() {
      accounts = new ArrayList<>();
      accounts.add(new Account(1, "1234567890", 12345, 1));
      accounts.add(new Account(2, "1234567891", 6543, 2));
      accounts.add(new Account(3, "1234567892", 45646, 3));
   }

   public Account findById(Integer id) {
      return accounts.stream().filter(a -> a.getId().equals(id)).findFirst().get();
   }

   public List<Account> findAll() {
      return accounts;
   }

   public Account add(Account account) {
      accounts.add(account);
      account.setId(accounts.size());
      return account;
   }

   @Override
   public List<Account> findAllByCustomerId(Integer customerId) {
      return accounts.stream().filter(a -> a.getCustomerId().equals(customerId)).collect(Collectors.toList());
   }

}

AccountService interface is in sample-api module. We use JAX-RS annotations for declaring REST endpoints.

public interface AccountService {

   @GET
   @Path("/{id}")
   @Produces("application/json")
   public Account findById(@PathParam("id") Integer id);

   @GET
   @Path("/")
   @Produces("application/json")
   public List<Account> findAll();

   @GET
   @Path("/customer/{customerId}")
   @Produces("application/json")
   public List<Account> findAllByCustomerId(@PathParam("customerId") Integer customerId);

   @POST
   @Path("/")
   @Consumes("application/json")
   @Produces("application/json")
   public Account add(Account account);

}

Here you can see OSGi services declaration in the blueprint.xml file. We have declared AccountServiceIpl bean and set that bean as a service for JAX-RS endpoint. Endpoint uses JacksonJsonProvider as data format provider. There is also important OSGi service declaration with AccountService referencing to AccountServiceImpl. This service will be available for other microservices deployed on Karaf container for example, customer-cxf.

<cxf:bus id="accountRestBus">
</cxf:bus>

<bean id="accountServiceImpl" class="pl.piomin.services.cxf.account.service.AccountServiceImpl"/>
<service ref="accountServiceImpl" interface="pl.piomin.services.cxf.api.AccountService" />

<jaxrs:server address="/account" id="accountService">
   <jaxrs:serviceBeans>
      <ref component-id="accountServiceImpl" />
   </jaxrs:serviceBeans>
   <jaxrs:features>
      <cxf:logging />
   </jaxrs:features>
   <jaxrs:providers>
      <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
   </jaxrs:providers>
</jaxrs:server>

Now, let’s take a look on customer-cxf microservice. Here’s OSGi blueprint of that service. JAX-RS server declaration is pretty similar as for account-cxf. There is only one addition in comparision with earlier presented OSGi blueprint – reference to AccountService. This reference is injected into CustomerServiceImpl.


<reference id="accountService" 		interface="pl.piomin.services.cxf.api.AccountService" />

<bean id="customerServiceImpl" 		class="pl.piomin.services.cxf.customer.service.CustomerServiceImpl">
   <property name="accountService" ref="accountService" />
</bean>

<jaxrs:server address="/customer" id="customerService">
   <jaxrs:serviceBeans>
      <ref component-id="customerServiceImpl" />
   </jaxrs:serviceBeans>
   <jaxrs:features>
      <cxf:logging />
   </jaxrs:features>
   <jaxrs:providers>
      <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
   </jaxrs:providers>
</jaxrs:server>

CustomerService uses OSGi reference to AccountService in findById method to collect all accounts belonged to the customer with the specified id path parameter and also exposes some other operations.

public class CustomerServiceImpl implements CustomerService {

   private AccountService accountService;

   private List<Customer> customers;

   public CustomerServiceImpl() {
      customers = new ArrayList<>();
      customers.add(new Customer(1, "XXX", "1234567890"));
      customers.add(new Customer(2, "YYY", "1234567891"));
      customers.add(new Customer(3, "ZZZ", "1234567892"));
   }

   @Override
   public Customer findById(Integer id) {
      Customer c = customers.stream().filter(a -> a.getId().equals(id)).findFirst().get();
      c.setAccounts(accountService.findAllByCustomerId(id));
      return c;
   }

   @Override
   public List<Customer> findAll() {
      return customers;
   }

   @Override
   public Customer add(Customer customer) {
      customers.add(customer);
      customer.setId(customers.size());
      return customer;
   }

   public AccountService getAccountService() {
      return accountService;
   }

   public void setAccountService(AccountService accountService) {
      this.accountService = accountService;
   }

}

Each service has packaging type bundle inside pom.xml and uses maven-bundle-plugin during build process. After running mvn clean install on the root project all bundles will be generated in target catalog.You can install them using Apache Felix web console or Karaf command line client in that order: sample-api, account-cxf, customer-cxf.

Testing

Finally, you can see a list of available CXF endpoints on Karaf by calling http://192.168.99.100:8181/cxf in your web browser. Call http://192.168.99.100:8181/cxf/customer/1 to test findById in CustomerService. You should see JSON with customer data and all accounts collected from account microservice.

Conclusion

Treat this post as a short introduction to microservices conception on Apache Karaf OSGi container. I show you how to use CXF endpoints on Karaf container as some kind of service gateway and OSGi services for the inter-communication process between deployed microservices. Instead of OSGi reference, we could use the JAX-RS proxy client for connecting with the account-service from the customer-service. You can find some basic examples of that concept on the web. There are also available more advanced solutions for service registration and discovery on Karaf, for example, remote service call with Apache ZooKeeper. I think we will take a closer look at them in subsequent posts.

0 COMMENTS

comments user
Sinha

How to use spring boot in karaf.

    comments user
    Piotr Mińkowski

    What you exactly mean by use Spring Boot in Karaf? Spring Boot starts the application with Spring, Karaf is OSGi container, something ala application server. Eventually you may start Karaf with Spring Boot but I haven’t heard about such a library or solution.

comments user
imran

I am trying to create a Dockerfile that will automatically install apache karaf and configure it to work and its working fine.
I want to install list of features. I can do it with below
docker exec -it 7447419c89da /opt/karaf/bin/client
but I want to automate the process. What command can I run that will allow me to install the features?

comments user
jayant

hi piotr, nice article, my understanding is there is a osgi container(karaf in this case) inside which we have our bundles (jars with manifest) and each bundle has its own application context, my question is can i have both mix of spring cloud annotations i.e @enableZuulServer n so so and also at the same time have a blueprint xml for other implementation based classes in a module?

    comments user
    Piotr Mińkowski

    Hi,
    I’m not sure if I understand your question properly. However, you could mix Spring Cloud microservices with OSGI microservices deployed on Karaf, for example by using the same discovery server – ZooKeeper.

      comments user
      Chad Yan

      Microservices technology is trying to break enterprise application into small simple services. The idea of combine enterprise application server with microservice together always confusing me.

        comments user
        Piotr Mińkowski

        And me too 🙂

Leave a Reply