If you recall from this post, I wrote about cloud native manifesto and tradeoffs that arise when migrating the monolithic based applications to the cloud. I have to admit it was pretty abstract and theoretical approach, but the fun starts now! We’ll see how to decompose a simple monolithic application (an already implemented todo list management web application) into independent services to deliver the microservice oriented architecture.

Defining the bounded contexts

When migrating a monolithic code base the first step is to identify the bounded contexts. If you are familiar with DDD you should also know about the aforementioned concept. Every bounded context should be encapsulated by a separate microservice where every of them must have an independent data store. In our case that’s an unique microservice which will offer the needed business logic (the management of todo lists). In a complex real world application there would be a dozen of microservices. Think about a big online store platform. We could create a microservice responsible for product operations, one for the payment life cycle management, product recommendations, a wishlist service, shopping cart, etc.

The fastest way to get started with microservices on the JVM platform is through Spring Cloud Netflix project which brings the Netflix components for building robust distributed systems. It provides a series of annotations for Spring Boot applications to save us from heavy lifting. Unfortunately, there is no integration for XML configuration model and I'm not really a Spring Boot fan (at least for now). I'm looking forward to see the support for templates and declarative programming model, maybe something based on cloud-netflix namespace, like <cloud-netflix:eureka-server host="home" port="8671" endpoint="eureka" />

Service discovery

The service registration and discovery are one of the essential constructs of the microservice based architecture. There are a few service discovery and coordination servers on the market, like consul, etcd, zookeeper, and the Eureka discovery server. The embedded Eureka server can be easily deployed by placing the @EnableEurekaServer annotation on the Spring Boot application. To test it yourself go to Spring Initializr and generate a new Spring Boot application with Eureka Server starter support.

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

By default, the Eureka server will attempt to register itself as client too. To disable the client side registration use this settings in application.yml

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

After the server has started you can navigate to http://localhost:8761 and explore the Eureka dashboard.

Creating a microservice

We are now ready to create the actual microservice and register it within Eureka. If you still remember, the todo application stores the corresponding tasks in MongoDB and it exposes the operations via RESTful API. The microservice implementation is almost a replica of the code found in the monolithic application (see source code for details). Create another Spring Boot application skeleton with Web, MongoDB and Eureka Discovery starters. Annotate the Spring Boot application’s main class with @EnableEurekaClient annotation to register the service instance and make it capable to query other registered services.

@SpringBootApplication
@EnableEurekaClient
public class TodoMicroserviceApplication {

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

The following configuration in application.yml is required to locate the Eureka server and setup the MongoDB settings, like database name, port and the host where mongod process is listening for requests.

spring:
  data:
    mongodb:
      port: 27017
      host: localhost
      database: rabbit-todo
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

Once it has been deployed successfully, the microservice instance should appear in the Eureka dashboard.

Consuming the microservice

Spring Cloud Netflix makes it also trivial to access our registered microservices. An already well known RestTemplate abstraction can be used to consume the service API. Use @Autowired annotation to inject the template and Spring will configure it to be microservice aware (be able to locate the service by name).

@Autowired
private RestTemplate restTemplate;

@RequestMapping(value="/todos", method = RequestMethod.GET)
public @ResponseBody List<Todo> todos() {
    List<Todo> todos = restTemplate.getForObject("http://todo-microservice/todos",
    List.class);
    return todos;
}

The code above queries the service registry to locate the todo-microservice service and request the available todo lists.

Gateway and routing

Another important building block of the microservice architecture are the routing capabilities. It allows the web application to proxy the requests to the service by accessing the specified HTTP endpoint which is mapped to microservice identifier or the service registry URL. Spring Cloud ships with Zuul JVM router which is part of Netflix component’s suite. As you could guess, we can enable the embedded Zuul proxy by annotating the Spring Boot application’s main class with @EnableZuulProxy. The definition of routing paths can be configured in the application.yml.

zuul:
  routes:
    todos:
       path: /api/v1/todo/**
       serviceId: todo-microservice

Any request matching the /api/v1/todo/** pattern is routed to the microservice instance with todo-microservice identifier. We can rid off TodoController and see the UI is working as usual. The benefits of this pattern go from centralized authentication, audit logging, global request interception, intelligent routing scenarios, the ability to apply the refactoring on the back end without altering any client specific entry point URL.

Circuit breaker

The last but not least pattern we are going to dissect is the circuit breaker. The safety and fault isolation are crucial in a cloud native microservice based architecture. We don’t want the malfunctioning services to compromise the rest of the infrastructure or trigger any cascading failures. To keep the code resilient in case of service crashes, Spring Cloud integrates with Netflix Hystrix failure isolator. @EnableCircuitBreaker annotation arms the application with Hystrix circuit breaker. It’s trivial to connect our methods to the circuit breaker by annotating them with @HystrixCommand where we usually indicate the fallback method.

@RequestMapping(value="/todos", method = RequestMethod.GET)
@HystrixCommand(fallbackMethod = "todosFallback")
public List<Todo> todos() {
    List<Todo> todos = restTemplate.getForObject("http://todo-microservice/todos",
    List.class);
    return todos;
}

public List<Todo> todosFallback() {
    return Collections.emptyList();
}

By default, Hystrix will open the circuit and prevent the call from being made if there are 20 failures in the timespan of 5 seconds. If you want to see it in action, navigate to Hystrix dashboard after you had stopped the microservice. Try to make some calls by refreshing the page, and you’ll see the circuit being opened. As long as we have provided the fallback method, an empty list will be rendered in the browser.

Microservice patterns in Go

Although the JVM-based platforms are dominating the (microservice) architecture landscape, the Go language’s ecosystem is working its way up to enterprise.

It’s efficient, intuitive and with built-in concurrency support. It’s compiled but still feels like coding with dynamic programming language.

When it comes to microservice frameworks, I had found go-kit and go-micro the most feature rich ones. After some time of research I had opted for go-kit, even it doesn’t offer some useful features when it comes to service registration or the more sophisticated RESTful based transport. That’s why I ended up implementing both of them. Without further ado, let’s see the code.

I had advocated for service registry capability in go-kit, however, here is what I came up with (although this is a minimal implementation and definitely not production ready).

type ConsulRegistrator struct {
    Config *consul.Config
}

func (cr *ConsulRegistrator) Register(service string, port int) error {

    if cr.Config == nil {
      cr.Config = consul.DefaultConfig()
    }
    client, err := consul.NewClient(cr.Config)
    if err != nil {
      return err
    }

    err = client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
        ID: service + ":" + strconv.Itoa(port),
        Name: service,
        Port: port,
    })

    if err != nil {
      return err
    }
    return nil
}

The code can be found in registry/consul package (see the fork). Go-kit supports the HTTP transport out of the box. Although, there’s no way to create truly RESTful API as one we have built on Spring service. I figured out it was quite easy to wrap an existing go-kit endpoints with Gorilla routes.

type Demux struct {
    Ctx context.Context
    Mux *mux.Router
}

func (demux Demux) NewRoute(
    path string,
    e endpoint.Endpoint,
    method string,
    dec httptransport.DecodeRequestFunc) {
    handler := httptransport.NewServer(
      demux.Ctx,
      e,
      dec,
      encoder,
    )
    route := func(w http.ResponseWriter, r *http.Request) {
      handler.ServeHTTP(w, r)
    }
    demux.Mux.HandleFunc(path, route).Methods(method)
}

func encoder(writer http.ResponseWriter, response interface{}) error {
    return json.NewEncoder(writer).Encode(response)
}

Putting the pieces together

In order to build a functional microservice adhere to these steps:

  • Create an interface / implementation which represents the business requirements for your service.
type TodoService interface {
    FindAll() ([]Todo, error)
}

type TodoServiceImpl struct {
    TodoRepo TodoRepository
}

func (t TodoServiceImpl) FindAll() ([]Todo, error) {
    todos, err := t.TodoRepo.FindAll()
    if err != nil {
      return nil, err
    }
    return todos, err
}
  • Declare the request and response structs for every method in our interface.
type FindAllRequest struct {
}

type FindAllResponse struct {
  Todos []Todo `json:"todos"`
  Err string `json:"err,omitempty"`
}
  • Expose the endpoints. Every endpoint encapsulates the RPC method of our service. Having the Gorilla router integrated inside go-kit makes it trivial to publish the service interface.
func FindAllEndpoint(service TodoService) endpoint.Endpoint {
  return func(ctx context.Context, request interface{}, vars map[string]string) (interface{}, error) {
    todos, err := service.FindAll()
    if err != nil {
      return rpc.FindResponse{[]Todo{}, err.Error()}, nil
    }
    return rpc.FindResponse{todos, ""}, nil
  }
}

dmx := demux.Demux{Ctx: ctx, Mux: mux.NewRouter()}
dmx.NewRoute("/todos", todo.FindAllEndpoint(service), "GET", decodeFindAllRequest)
  • Finally, register the service in Consul discovery server. This allows the service instance to be discovered from Spring Boot app using the Spring Cloud Consul integrations.
cr := registrator.ConsulRegistrator{Config: nil}

err := cr.Register("todo-microservice", 8000)
if err != nil {
  log.Fatal(err)
}

The synergy of a polyglot ecosystem

The microservice architectures are often perceived as the next generation of SOA. I would also call it SOA on steroids. Well, in fact, they have a lot in common. Traditionally, most of the SOA services had been using SOAP as a standard data interchange protocol. Nowadays, the SOA based services can also communicate over REST, just like their microservice counterparts. We already know the microservices are language agnostic pieces. SOA based services can also coexist in a polyglot environment which is often orchestrated by ESB (Enterprise Service Bus) or the message broker.

Unlike microservices, SOA services are not aware of the single responsibility principle. The size and scope of the latter is measured in LOC (Lines of Code), not the functionalities they try to accomplish. Another subtle difference between microservices and SOA are their operational requirements. In a microservice world the PaaS solution and the DevOps culture are a must. The services have to be replicated over multiple nodes and they have to be able to scale in question of seconds. Another common practise is the packaging of microservices inside isolated execution environments known as containers (here is where Docker excels as the most popular implementation in the container ecosystem). From the point of view of technical changes, the operational requirements may be the most challenging factor for organizations when it comes to migrating from monolithic to microservice architectures.