In the microservice approach, there are many aspects to be taken care of, and at the same time, we need to keep in mind that some of them are also used in the monolithic approach.

The most vital ones are as follows:

– Service Discovery,
– API Gateway (Routing),
– Server Side Load Balancing,
– Client Side Load Balancing,
– Circuit Breaker,
– External Configuration.

Service Discovery

Simply put, it is a service  that allows new services to be registered, and it also shares information about registered users. These are often implementations of the ‘key-value’ pattern, which requires high reliability and speed. A base service that allows to balance the load  through all available machines and, consequently, the automatic scaling of our applications.

The most popular Service Discovery implementations include:

Consul by HashiCorp,
Zookeeper,
etcd,

* written in ‘Go’ using the algorithm of ‘Rath’ consensus ‘algorithm to manage a highly-available replicated log’

Netflix Eureka,
AWS DNS-Based Servcie Discovery

* which is based on ‘Amazon Route 53, AWS Lambda, and ECS Event Stream’

Kubernetes Service Discovery

Service Discovery based on Eureka Server

The simplest ways of launching and starting the work with Eureka:

Spring Boot Cloud CLI

* Spring Cloud Eureka

– run Docker image
– creating a simple Spring Boot project with dependencies to  ‘Eureka Server’.

Let’s go step by step through the sources necessary to create and run the ‘Eureka Server’ project by ourselves.

  • The main class source code (and the most important annotation in this example ‘@EnableEurekaServer‘):
import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;


@EnableEurekaServer

@SpringBootApplication

public class ServiceDiscovery {


    public static void main(String[] args) {

        SpringApplication.run(ServiceDiscovery.class, args);

    }

}
      • Properties to define in ‘application.properties’
spring.application.name=eureka-service

server.port=8761

#By default, the registry will also attempt to register itself, so you\u2019ll need to disable that, as well.

eureka.client.fetch-registry=false

eureka.client.register-with-eureka=false

eureka.client.serviceUrl.defaultZone=http://discovery-container:8761/eureka/

In such a situation we should have the Eureka website available on: `http://host:8761`. The main service looks as follows:

 

It contains basic information such as: current time, replicas (registered, available, unavailable), i.e. the entire so-called server status, and basic information, i.e. memory use, available memory, the name of the environment in which it is running, the number of available processors, as well as the time of work since it was run.

Of course, from our perspective the most important aspect is the list of available services.

Client Service Discovery based on Spring Boot Web

Naturally, we will move on to simple services that will become registered in our Service Discovery.

  • A sample service code where the most important, from our perspective, is annotation ‘@EnableDiscoveryClient’:
@EnableDiscoveryClient

@RestController

@SpringBootApplication

public class Service {
 

  @Value("${HOSTNAME}")

  String hostname;

 
  @GetMapping("/info")

  String info() {

    return String.format("Hostname: %s", hostname);

  }

 
    public static void main(String[] args) {

        SpringApplication.run(Service.class, args);

    }

}
  • And `application.properties`
spring.application.name=service

server.port=${port::8080}

 
eureka.client.serviceUrl.defaultZone=${EUREKA_URL:http://discovery-container:8761/eureka}

eureka.instance.prefer-ip-address=true

API Gateway

API Gateway is a starting point of our application that redirects requests to relevant services in our environment. To sum up, it provides the public API.

The most popular implementations are shared by cloud providers. Of course, nothing stops you from using a different implementation, e.g.:

– Nginx,
– Zuul Netflix,
– Amazon API Gateway,
– Azure API Management.

API Gateway – based on Zuul Netflix

Zuul is a service running on a Java virtual machine, acting as a router as well as Server Side Load Balancing. Thanks to filtering: pre, route, post, error.

It enables many functionalities, such as:

– security,
– routing,
– monitoring,
– injecting data (e.g. to headlines).

Client Side Load Balancing – Ribbon

Ribbon can be used without dynamic information about available servers. Then, we are able to define the necessary properties in the ‘application.properties’ file with information between which servers the client should balance the load. Configuration example:

`listOfServers=localhost:8081,localhost:8082,localhost:8083`

In our example, we want `Ribbon` to work properly in the microservices architecture, which assumes a dynamic number of instances of a given service. The customer should be able to obtain this information while working.

  • Example `application.properties` of Ribbon being registered in ‘Eureka Serwer’ and refreshing the list of instances of the service that is of interest to it.
spring.application.name=client
server.port=8080
eureka.instance.hostname=client
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.default-zone=http://localhost:8761/eureka

hello.ribbon.eureka.enabled=true

hello.ribbon.ServerListRefreshInterval:15000

hello.ribbon.NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
  • The configuration used in the project:
public class HelloConfiguration {

    @Autowired

    IClientConfig clientConfig;


    @Bean

    IPing ribbonPing(IClientConfig config) {

        return new PingUrl();

    }


    @Bean

    IRule ribbonRule(IClientConfig config) {

        return new AvailabilityFilteringRule();

    }

}
  • The class starting with the Client:
@RibbonClient(name = "hello", configuration = HelloConfiguration.class)
@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonDiscoveryClient {

    private static Logger log = LoggerFactory.getLogger(RibbonDiscoveryClient.class);

     @LoadBalanced

    @Bean

    RestTemplate restTemplate() {

        return new RestTemplate();

    }


    @Autowired

    RestTemplate restTemplate;

     @Autowired

    DiscoveryClient discoveryClient;

     @Scheduled(fixedRate = 1000)

    void callRibbon() {

        final List<ServiceInstance> instances = discoveryClient.getInstances("hello");

        log.info("### SERVERS START:");

        for (final ServiceInstance instance : instances) {

            log.info("Instance: " + instance.getHost().toString());

            log.info("Port: " + instance.getPort());

            log.info("URI: " + instance.getUri().toString());

        }

        log.info("### SERVERS STOP:");

 
        ResponseEntity<String> entity = restTemplate.getForEntity("http://hello", String.class);

        log.info(entity.getBody());

    }

     public static void main(String[] args) {

        SpringApplication.run(RibbonDiscoveryClient.class, args);

    }

}

API Gateway – Routing

The starting point for our architecture. It redirects requests to relevant services. In our case, it will be the `Zuul` open-source project. Thanks to the filter mechanism, it is able to filter input traffic, allow easy monitoring and ensure security and authentication. It must provide high performance and scalability. For instance, by using cloud environments, we have equivalents. And so AWS has a dedicated API Gateway that provides similar functionalities.

In order to illustrate how routing works, we will build two different simple services and the API Gateway based on `Zuul` example.

The source code of the first service and `application.properties`:

@RestController

@SpringBootApplication

public class RouteBooks {


    @GetMapping

    String books() {

        return "books";

    }


    public static void main(String[] args) {

        SpringApplication.run(BooksCalc.class, args);

    }

}



spring.application.name=books

server.port=8090

The source code of the second service and `application.properties`:

@RestController

@SpringBootApplication

public class RouteCalc {


    @GetMapping

    String books() {

        return "calc";

    }



    public static void main(String[] args) {

        SpringApplication.run(RouteCalc.class, args);

    }

}



spring.application.name=calc

server.port=8090

The most important of our components – Zuul service and its `application.properties`

@EnableZuulProxy

@SpringBootApplication

public class ZuulServer {


    @Bean

    ZuulFilter simpleFilter() {

        return new SimpleFilter();

    }


    public static void main(String[] args) {

        SpringApplication.run(ZuulServer.class, args);

    }

}


public class SimpleFilter extends ZuulFilter {


    private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);


    @Override

    public boolean shouldFilter() {

        return true;

    }


    @Override

    public Object run() throws ZuulException {

        return null;

    }


    @Override

    public String filterType() {

        RequestContext ctx = RequestContext.getCurrentContext();

        HttpServletRequest request = ctx.getRequest();


        log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));


        return "pre";

    }


    @Override

    public int filterOrder() {

        return 1;

    }

}

application.properties

server.port=8080


zuul.routes.books.path=/ksiazki/**

zuul.routes.books.url=http://localhost:8090

zuul.routes.calc.url=http://localhost:8091

From now on, our requests will be redirected to the right service through a specific part of the URL.

Server Side Load Balancing

In order to illustrate it, we will use the technology stack discussed above: Eureka, Zuul and Spring Boot Web.

We want to get the following architecture (screen below), in which sites performing operations register in Service Discovery (Eureka). API Gateway (Zuul) inquires and refreshes information about the available instances. In order to make everything work, you need to create a customer who will query our services via API Gateway. The only difference to the previous approach is the addition of Service Discovery, Eureka Service in our case, which provides information about the registered sites.

The created architecture will allow easy scaling, still manual in this case. However, by means of integration with the orchestrator, e.g. Kubernetes, Docker Swarm or the cloud, it is easy to enable autoscaling depending on the system load.

Summary

There are certainly better and newer solutions for playing with microservices. It is best to work using a cloud solution, e.g. Amazon Web Services or Microsoft Azure. However, in the technology stack presented above, everything is free of charge – so we do not have to worry about unforeseen costs resulting from providing our credit card details.

The entry threshold seems quite small for a Java programmer. And the fun itself does not require strong equipment. In fact, all I have to say now is to wish you a lot of fun.