Enterprise Java seems to be a step back from the others when it comes to microservices architecture. Some weeks ago, I took a part in Code Europe – the programming conference in Warsaw. One of the speakers was Ivar Grimstad, who was talking about MicroProfile – an open initiative for optimizing Enterprise Java for a microservices architecture. This idea is very interesting, but at the moment, it is rather at the beginning of the road.

However, while I was reading about the microprofile initiative, I came across information about JavaEE framework developed by Slovenian company – KumuluzEE. The solution seemed to be interesting enough that I decided to take a closer look at it. Well, we can read on the website that KumuluzEE is the Java Duke’s Choice Award Winner, so there is still a hope for JavaEE and microservices

What’s KumuluzEE

Can KumuluzEE be a competitor to the Spring Cloud framework? It is certainly not as popular and advanced when it comes to solutions for microservices like Spring Cloud, but it has basic modules for service registration, discovery, distributed configuration propagation, circuit breaking, metrics, and support for Docker and Kubernetes. It uses CDI on JBoss Weld containers for dependency injection and Jersey as a REST API provider. Modules for configuration and discovery are based on Consul or etcd, though they are at the early stage of development (1.0.0-SNAPSHOT).

But let’s try it out.

Preparation

I’ll show you a sample application that consists of two independent microservices — account-service and customer-service. Both of them expose a REST API, and one of the customer-service methods invokes a method from account-service. Every microservice registers itself in Consul and is able to get configuration properties from Consul. The sample application source code is available on GitHub. Before we begin, let’s start a Consul instance using a Docker container.

docker run -d --name consul -p 8500:8500 -p 8600:8600 consul



We should also add some KumuluzEE dependencies to the Maven pom.xml .

<dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-core</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-servlet-jetty</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-jax-rs-jersey</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-cdi-weld</artifactId> </dependency>



Service Registration

To enable service registration, we should add one additional dependency to our pom.xml . I chose Consul as a registration and discovery server, but you can also use etcd ( kumuluzee-discovery-consul ).

<dependency> <groupId>com.kumuluz.ee.discovery</groupId> <artifactId>kumuluzee-discovery-consul</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>



Inside the application configuration file, we should set discovery properties and a server URL. For me, it is 192.168.99.100.

kumuluzee: service-name: account-service env: dev version: 1.0.0 discovery: consul: agent: http://192.168.99.100:8500 hosts: http://192.168.99.100:8500 ttl: 20 ping-interval: 15





Here’s the account microservice's main class. As you probably guess, the annotation @RegisterService enables registration on the server.

@RegisterService("account-service") @ApplicationPath("v1") public class AccountApplication extends Application { }



We start the application by running:

java -cp target/classes;target/dependency/* com.kumuluz.ee.EeApplication

Remember to override the default port by setting the environment property: PORT. I started two instances of account and one of the customer microservice.

Service Discovery

The customer microservice exposes the API, but it also invokes the API method from account-service, so it has to discover and connect to this service. The Maven dependencies and configuration settings are the same as for account-service. The only difference is the resource class. Here’s the CustomerResource fragment, where we are invoking endpoint GET /customer/{id}.

@Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("customers") @RequestScoped public class CustomerResource { private List<Customer> customers; @Inject @DiscoverService(value = "account-service", version = "1.0.x", environment = "dev") private WebTarget target; ... @GET @Path("{id}") @Log(value = LogParams.METRICS, methodCall = true) public Customer findById(@PathParam("id") Integer id) { Customer customer = customers.stream().filter(it -> it.getId().intValue() == id.intValue()).findFirst().get(); WebTarget t = target.path("v1/accounts/customer/" + customer.getId()); List<Account> accounts = t.request().buildGet().invoke(List.class); customer.setAccounts(accounts); return customer; } }





There is one pretty cool thing about discovery with KumuluzEE. As you can see in the @DiscoverService , we can specify a version and environment for account-service instance. The version and environment for the microservice is read automatically from the config.yml during registration in discovery server. So, we can maintain many versions of a single microservice and freely invoke them from other microservices. Requests are automatically load balanced between all microservice match conditions from the annotation @ServiceDiscovery .

We can also monitor metrics, such as response time, by declaring @Log(value = LogParams.METRICS, methodCall = true) on the API method. Here’s a log fragment for account-service:

2017-07-28 13:57:01,114 TRACE ENTRY[ METHOD ] Entering method. {class=pl.piomin.services.kumuluz.account.resource.AccountResource, method=findByCustomer, parameters=[1]} 2017-07-28 13:57:01,118 TRACE EXIT[ METHOD ] Exiting method. {class=pl.piomin.services.kumuluz.account.resource.AccountResource, method=findByCustomer, parameters=[1], response-time=3, result=[pl.piomin.services.kumuluz.account.model.Account@1eb26fe3, pl.piomin.services.kumuluz.account.model.Account@2dda41c5]}



Distributed Configuration

To enable KumuluzEE Config, include the Consul implementation by adding the following dependency to your pom.xml .

<dependency> <groupId>com.kumuluz.ee.config</groupId> <artifactId>kumuluzee-config-consul</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>



I do not use a Consul agent running on the localhost, so I need to override some properties in config.yml . I also defined one configuration property, blacklist :

kumuluzee: config: start-retry-delay-ms: 500 max-retry-delay-ms: 900000 consul: agent: http://192.168.99.100:8500 rest-config: blacklist:



Here’s the class that loads the configuration properties and enables dynamic updates on any change in the configuration source by declaring @ConfigValue(watch = true) on a property.

@ApplicationScoped @ConfigBundle("rest-config") public class AccountConfiguration { @ConfigValue(watch = true) private String blacklist; public String getBlacklist() { return blacklist; } public void setBlacklist(String blacklist) { this.blacklist = blacklist; } }



We use the configuration property blacklist in the resource class for filtering all accounts by blacklisted ids.

@GET @Log(value = LogParams.METRICS, methodCall = true) public List<Account> findAll() { final String blacklist = ConfigurationUtil.getInstance().get("rest-config.blacklist").orElse("nope"); final String[] ids = blacklist.split(","); final List<Integer> blacklistIds = Arrays.asList(ids).stream().map(it -> new Integer(it)).collect(Collectors.toList()); return accounts.stream().filter(it -> !blacklistIds.contains(it.getId())).collect(Collectors.toList()); }



The configuration property should be defined in the Consul UI Dashboard under the KEY/VALUE tab. KumuluzEE enforces a certain format of key names. In this case it has to be:

environments/dev/services/account-service/1.0.0/config/rest-config/blacklist

You can update a property value and test changes by invoking http://localhost:2222/v1/accounts.

Final Words

Creating microservices with KumuluzEE is pretty easy. I showed you the main capabilities of this framework. KumulezEE has also modules for circuit breakers with Hystrix, streaming with Apache Kafka, and security with OAuth2/OpenID. I will keep a close eye on this library, and I hope it will continue to be developed.