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