Open Gitter Chat

Microservices

Hazelcast In-Memory Data Grid (IMDG) can be used as the backbone of a Microservices architecture. Hazelcast IMDG is a simple, single JAR file with no external dependencies.

One of the core values of Hazelcast from its inception was simplicity and ease of use, and that still exists today. When you move an application or system into a distributed environment, having a simple and configurable backbone makes the task a lot easier.

Hazelcast is a library. Libraries are easier to use and understand enabling the development process to be more efficient. This simplicity and ease of use is a hallmark of Hazelcast and the reason it adapts well to the Microservices architecture.

Hazelcast IMDG is highly scalable (e.g. IMap and JCache). Hazelcast also supports other standard Java standard Collections such as Lists, Sets, Queues and some other constructs such as Topics and Ringbuffers, that can be easily leveraged for inter-process messaging. All of these attributes can offer functionality that is highly desirable within a Microservices platform.

Hazelcast IMDG can be deployed two ways:

  1. Embedded, where you add the JAR to your application and the Hazelcast Member instance is embedded in your application.
  2. Client-Server, where Hazelcast is run on its own. Here you can run Hazelcast from the command line, a Docker container or a cloud deployment such as AWS and Azure.

Code Examples

Vertx

Java

import io.vertx.core.*;
import io.vertx.core.http.*;
import io.vertx.core.shareddata.AsyncMap;
import io.vertx.core.spi.cluster.ClusterManager;
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager;

import java.util.Optional;
import java.util.function.Consumer;

public class VertxExample {

   public static void main(String[] args) {
       ClusterManager clusterManager = new HazelcastClusterManager();
       VertxOptions options = new VertxOptions().setClusterManager(clusterManager);

       Vertx.clusteredVertx(options, res -> {
           if (res.succeeded()) {
               Vertx vertx = res.result();
               vertx.deployVerticle(new Server());
           }
       });
   }

   public static class Server extends AbstractVerticle {
       @Override
       public void start() throws Exception {
           HttpServer httpServer = vertx.createHttpServer();
           httpServer.requestHandler(req -> {
               String key = req.getParam("key");
               req.response().setChunked(true);
               switch (req.method()) {
                   case GET:
                       map("my-map", map -> map.get(key, response(req)));
                       break;
                   case POST:
                   case PUT:
                       map("my-map", map -> map.put(key, req.getParam("value"), response(req)));
               }
           });
           httpServer.listen(8080);
       }

       private <T> Handler<AsyncResult<T>> response(HttpServerRequest req) {
           return res -> {
               HttpServerResponse response = req.response();
               if (res.succeeded()) response = response.setStatusCode(200).write(
                           Optional.ofNullable(res.result()).map(v -> v.toString()).orElse("null"));
               else response = response.setStatusCode(500).write(res.cause().toString());
               response.end();
           };
       }

       private void map(String mapName, Consumer<AsyncMap<String, String>> consumer) {
           vertx.sharedData().<String, String> getClusterWideMap(mapName, res -> {
               if (res.succeeded()) consumer.accept(res.result());
           });
       }
   }
}

Kotlin

import io.vertx.core.*
import io.vertx.core.http.*
import io.vertx.core.shareddata.AsyncMap
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager

fun main(args: Array<String>) {
   val clusterManager = HazelcastClusterManager()
   val vertxOptions = VertxOptions().apply { this.clusterManager = clusterManager }

   Vertx.clusteredVertx(vertxOptions) { res ->
       res.result().deployVerticle(Server())
   }
}

class Server : AbstractVerticle() {
   override fun start() {
       vertx.createHttpServer().requestHandler { req ->
           val key = req.getParam("key")
           req.response().isChunked = true
           when(req.method()) {
               HttpMethod.GET ->
                   map("my-map") { it.get(key, response(req)) }
               HttpMethod.POST, HttpMethod.PUT ->
                   map("my-map") { it.put(key, req.getParam("value"), response(req)) }
           }
       }.listen(8080)
   }

   fun <T> response(req: HttpServerRequest): Handler<AsyncResult<T>> = Handler {
       val (statusCode, body) = if (it.succeeded()) 200 to it.result() else 500 to it.cause()
       req.response().setStatusCode(statusCode).write(body.toString()).end()
   }

   fun map(mapName: String, consumer: (AsyncMap<String, String>) -> Unit) =
           vertx.sharedData().getClusterWideMap<String, String>(mapName) { if (it.succeeded()) consumer(it.result()) }

JavaEE / Microprofile

import fish.payara.cdi.jsr107.impl.NamedCache;
import fish.payara.micro.PayaraMicro;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;

import javax.cache.Cache;
import javax.inject.*;
import javax.ws.rs.*;
import javax.ws.rs.core.Application;
import java.io.InputStream;
import java.util.*;

@Path("/")
@Singleton
@ApplicationPath("/")
public class JavaEEHazelcast extends Application {

   public static void main(String[] args) throws Exception {
       InputStream inputStream = ShrinkWrap.create(WebArchive.class)
                                           .addClass(JavaEEHazelcast.class)
                                           .addAsResource("META-INF/beans.xml")
                                           .as(ZipExporter.class)
                                           .exportAsInputStream();

       PayaraMicro.bootstrap().deploy("cache", inputStream);
   }

   @Override
   public Set<Class<?>> getClasses() {
       Set<Class<?>> classes = new HashSet<>();
       classes.add(JavaEEHazelcast.class);
       return classes;
   }

   @Inject
   @NamedCache(cacheName = "my-cache")
   private Cache<String, String> cache;

   @GET
   @Produces("text/plain")
   public String get(@QueryParam("key") String key) {
       String value = cache.get(key);
       return value == null ? "null" : value;
   }

   @POST
   @Produces("text/plain")
   public void post(@QueryParam("key") String key, @QueryParam("value") String value) {
       put(key, value);
   }

   @PUT
   @Produces("text/plain")
   public void put(@QueryParam("key") String key, @QueryParam("value") String value) {
       cache.put(key, value);
   }
}

SpringBoot

Java

package com.hazelcast.bootiful;

import com.hazelcast.config.Config;
import com.hazelcast.config.EntryListenerConfig;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.map.listener.EntryAddedListener;
import com.hazelcast.map.listener.EntryRemovedListener;
import com.hazelcast.map.listener.EntryUpdatedListener;
import com.hazelcast.map.listener.MapListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.web.bind.annotation.RequestMethod.*;

@SpringBootApplication
@EnableCaching
@Slf4j
public class MicroserviceApplication {

   @Bean
   public Config getConfig(MapListener listener) {
       final Config config = new XmlConfigBuilder().build();
       EntryListenerConfig listenerConfig = new EntryListenerConfig();
       listenerConfig
               .setIncludeValue(true)
               .setImplementation(listener);
       config.getMapConfig("my-cache").addEntryListenerConfig(listenerConfig);
       return config;
   }

   @Bean
   public IMap<String, String> getCacheMap(HazelcastInstance hazelcast) {
       return hazelcast.getMap("my-cache");
   }

   /**
    * | HTTP     | CRUD      | Hazelcast   | httpie
    * ----------------------------------------------
    * | POST     |  create   | set         | `http POST :8080/caching/1 value==test`
    * | GET      | read      | get         | `http :8080/caching/1`
    * | PUT      | update    | put         | `http PUT :8080/caching/1 value==test2`
    * | PATCH    | update    | replace     | `http PATCH :8080/caching/1 oldValue==test2 newValue=="hey hey"`
    * | DELETE   | delete    | remove      | `http DELETE :8080/caching/1`
    * ----------------------------------
    */
   @RestController
   @RequestMapping("caching")
   public class MyController {

       @Autowired
       private IMap<String, String> cacheMap;

       @RequestMapping(method = POST,
               path = "{key}",
               params = {"value"})
       public void post(@PathVariable String key,
                        @RequestParam String value) {
           cacheMap.set(key, value);
       }

       @RequestMapping(
               method = GET,
               path = "/{key}")
       public String get(@PathVariable("key") String key) {
           return cacheMap.get(key);
       }

       @RequestMapping(
               method = PUT,
               path = "{key}",
               params = {"value"})
       public String put(@PathVariable String key, @RequestParam String value) {
           return cacheMap.put(key, value);
       }

       @RequestMapping(
               method = PATCH,
               path = "/{key}",
               params = {"oldValue", "newValue"})
       public boolean patch(@PathVariable("key") String key,
                            @RequestParam("oldValue") String value,
                            @RequestParam("newValue") String newValue) {
           return cacheMap.replace(key, value, newValue);
       }

       @RequestMapping(
               method = DELETE,
               path = "/{key}")
       public String delete(@PathVariable String key) {
           return cacheMap.remove(key);
       }

   }

   @Component
   public class MyListener implements
           EntryAddedListener<String, String>,
           EntryUpdatedListener<String, String>,
           EntryRemovedListener<String, String> {

       @Override
       public void entryAdded(EntryEvent<String, String> event) {
           log.info("Entry added [{} : {}]", event.getKey(), event.getValue());
       }

       @Override
       public void entryUpdated(EntryEvent<String, String> event) {
           log.info("Entry updated [{} : {}]. Old value {}", event.getKey(), event.getValue(), event.getOldValue());
       }

       @Override
       public void entryRemoved(EntryEvent<String, String> event) {
           log.info("Entry removed [{} : {}]", event.getKey(), event.getOldValue());
       }
   }

   public static void main(String[] args) {
       SpringApplication.run(MicroserviceApplication.class, args);
   }
}

Groovy

package com.hazelcast.bootiful.groovy

import com.hazelcast.config.Config
import com.hazelcast.config.EntryListenerConfig
import com.hazelcast.config.XmlConfigBuilder
import com.hazelcast.core.EntryEvent
import com.hazelcast.core.HazelcastInstance
import com.hazelcast.core.IMap
import com.hazelcast.map.listener.EntryAddedListener
import com.hazelcast.map.listener.EntryRemovedListener
import com.hazelcast.map.listener.EntryUpdatedListener
import com.hazelcast.map.listener.MapListener
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

import static org.springframework.web.bind.annotation.RequestMethod.*

@SpringBootApplication
@EnableCaching
@Slf4j
@CompileStatic
class MicroserviceApplication {

   @Bean
   Config getConfig(MapListener listener) {
       EntryListenerConfig listenerConfig = new EntryListenerConfig()
       listenerConfig.setIncludeValue(true).setImplementation(listener)
       def config = new XmlConfigBuilder()
               .build()
       config.getMapConfig("my-cache").addEntryListenerConfig listenerConfig
       config
   }

   @Bean
   IMap<String, String> getCacheMap(HazelcastInstance hazelcast) { hazelcast.getMap "my-cache" }

   /**
    * | HTTP     | CRUD      | Hazelcast   | httpie
    * ----------------------------------------------
    * | POST     | create    | set         | `http POST :8080/caching/1 value==test`
    * | GET      | read      | get         | `http :8080/caching/1`
    * | PUT      | update    | put         | `http PUT :8080/caching/1 value==test2`
    * | PATCH    | update    | replace     | `http PATCH :8080/caching/1 oldValue==test2 newValue=="hey hey"`
    * | DELETE   | delete    | remove      | `http DELETE :8080/caching/1`
    * ----------------------------------
    */
   @RestController
   @RequestMapping("caching")
   class MyController {

       @Autowired
       private IMap<String, String> cacheMap

       @RequestMapping(method = POST, path = "{key}", params = ["value"])
       def post(@PathVariable String key, @RequestParam String value) { cacheMap.set key, value }

       @RequestMapping(
               method = GET,
               path = "/{key}")
       def get(@PathVariable("key") String key) { cacheMap[key] }

       @RequestMapping(method = PUT, path = "{key}", params = ["value"])
       def put(@PathVariable String key, @RequestParam String value) { cacheMap[key] = value }

       @RequestMapping(method = PATCH, path = "/{key}", params = ["oldValue", "newValue"])
       def patch(@PathVariable("key") String key,
                 @RequestParam("oldValue") String value,
                 @RequestParam("newValue") String newValue) {
           cacheMap.replace key, value, newValue
       }

       @RequestMapping(method = DELETE, path = "/{key}")
       def delete(@PathVariable String key) { cacheMap.remove key }

   }

   @Component
   class MyListener implements
           EntryAddedListener<String, String>,
           EntryUpdatedListener<String, String>,
           EntryRemovedListener<String, String> {

       @Override
       void entryAdded(EntryEvent<String, String> event) {
           log.info "Entry added [{} : {}]", event.getKey(), event.getValue()
       }

       @Override
       void entryUpdated(EntryEvent<String, String> event) {
           log.info "Entry updated [{} : {}]. Old value {}", event.getKey(), event.getValue(), event.getOldValue()
       }

       @Override
       void entryRemoved(EntryEvent<String, String> event) {
           log.info "Entry removed [{} : {}]", event.getKey(), event.getOldValue()
       }
   }

   static void main(String... args) {
       SpringApplication.run MicroserviceApplication.class, args
   }
}

Hazelcast.org

Main Menu