API Gateway

By Maurizio Farina | Posted on September 2017

This tutorial is devoted to the development of some specific features of an API Gateway using Spring framework.

In a microservices architecture the applications are developed following modularity approach; the application is decomposed into a set of services, each of which corresponds to a business capability. This approach allows the teams develop large and complex applications keeping focused on to deliver better software and faster.

The team can adopt new technology more easily since they can implement each service with the latest and most appropriate technology stack.

The microservices architecture also improves an application’s scalability by enabling each service to be deployed on the optimal hardware.

The challange in microservice architecture are:

  • managing transactions, domain models and queries across all microservices invoked can be hard.
  • a client can call each microservices directly, but there are many limitations with this option as knowing all endpoints, collecting information separately and merging the client-side result, microservices could not have web-friendly protocols, etc.

The first challange can be managed using Domain Driven Design, Event Sourcing and Command Query Responsibility Segregation (CQRS). This topic will be covered in a different post.

Let’s look at the challenge about service mesh.

The Problem: Service Mesh

The reasons for Microservices architecture:

  • Delivering features quickly
  • Developing business features as services (small, discrete)
  • Scaling services
  • Development team smaller and agile
  • Applications is developed using different technologies

Shifting from a monolithic application to a distributed microservice architecture become harder to manage features such as service discovery, load balancing, failure recovery, metrics, logs and monitoring. And more: rate limiting, access control, end-to-end authentication and so on.

The new architecture paradigma based on network of microservices needs an API Gateway to address the following features:

  • Securiy: Access control based on SSO
  • Discovery, Balancing and Fail-Over: all endpoints are managed by API Gateway
  • Reliability: Grants networking between micro services

API Gateway

API Gateway

The API Gateway is a single entry point responsible for routing, composition and protocol translation. Many features such as service discovery, request mocking, authentication, caching, documentation and so on can be developed direclty and only on API Gateway.

A list of API Gateway key features:

  • Monitoring and Analytics: Recording each calls and show data using analytics features
  • API Developer Portal: Customize API managed by the API Gateway
  • API Documentation: Publish API documentation for example using Swagger UI
  • Quotas and Rate Limiting: Manage users and their quotas
  • Authentication: Integrate API Gateway with internal and external services
  • Mock: Mock API results
  • Notifications: an internal event system
  • Service Discovery: integrate API Gateway with application such as Consul, Etcd and Eureka

Other features for big architecture: - Clustering API Gateway - Sharding API across Gateway instances.

There are also disadvantages in using a new service in our architecture:

  • Update API Gateway endpoints to expose all microservices endpoints
  • Another component to manage in highly availability avoiding to have bottleneck

There are many solutions already available either in the open-source or commercial domain or with a hybrid lay-off formula. For example, zuul is a gateway service built by Netflix.

In my opinion, the role of a API Gateway must be reviewed in a container-based architecture managed by an orchestrator, such as Docker Swarm or Kubernetes because many of features are already covered.

For example, the Docker Swarm allows a service to be reached by the same port on all nodes, balances requests across all available services and detects node failures. This can be achieve defining the service using Docker

1
docker service create --name webapp --replicas=3 --publish 80:8000 tomcat:9.0-alpine

In this tutorial we focus on develop an API Gateway from scratch using Spring Framework; the artifact realized is not a tipically API Gateway but a web application with a collection of features and able to include, via Spring Framework, micro services developed in different Java packages.

The subset of features are the following:

  • CORS mechanism via listener
  • Logging via interceptor
  • Metrics using Dropwizard library

Implementing CORS

The steps are to create a new HTTP filter and add the attributes to the header to enable CORS.

Adding a new filter using web.xml

1
2
3
4
5
6
7
8
<filter>
    <filter-name>CORSFilter</filter-name>
    <filter-class>com.listfeeds.components.CORSFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CORSFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

The filter definition above add a new filter CORSFilter implemented using the class com.listfeeds.components.CORSFilter.

The filter mapping above apply the filter to all requests (pattern: /*).

The com.listfeeds.components.CORSFilter class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.listfeeds.components;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

public class CORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

Logging all requests via interceptor

The scope is to log all incoming requests (writing logs when the request start and when the request end) adding information to the Log4j thread context. In the example we retrieve a parameter from the request and the value will be used in all logs about the request.

Adding interceptors to rest-servlet.xml. In this manner all requests managed by Spring will be handled before by the interceptors:

1
2
3
<mvc:interceptors>
    <bean class="com.listfeeds.interceptors.LogContextInterceptor" />
</mvc:interceptors>

The com.listfeeds.interceptors.LogContextInterceptor class used to log all incoming requests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.listfeeds.interceptors;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LogContextInterceptor extends HandlerInterceptorAdapter {

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

    public static final String LOG_IDENTIFYING_TOKEN = "logIdentifyingToken";

    @Override
    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {

        HandlerMethod methodHandler = (HandlerMethod) handler;
        log.debug("END EXECUTION method {} request: {}", methodHandler.getMethod().getName(), request.getRequestURI());

        Boolean settato = (Boolean) request.getAttribute(LOG_IDENTIFYING_TOKEN);
        if(settato != null && settato) {
            MDC.remove(LOG_IDENTIFYING_TOKEN);
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        try {
            if( MDC.get(LOG_IDENTIFYING_TOKEN) == null ) {

                /* Retrieve parameters useful for logging */
                @SuppressWarnings("unchecked")
                Map<String,String> pathVariables = 
                (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);

                String applicationId = null;
                if ( pathVariables != null ) 
                    applicationId = pathVariables.get("applicationId");

                if ( StringUtils.isEmpty(applicationId) ) 
                    applicationId = request.getParameter("applicationId");

                String loggingToken = 
                        String.format("ApplicationId: %s", applicationId);

                MDC.put(LOG_IDENTIFYING_TOKEN, loggingToken);
                request.setAttribute(LOG_IDENTIFYING_TOKEN, Boolean.TRUE);
            }


        }
        catch ( IllegalArgumentException e)
        {
            log.warn("Prehandle",e);
            return true;
        }
        finally {
            HandlerMethod methodHandler = (HandlerMethod) handler;
            //logger.debug("START EXECUTION " + methodHandler.getMethod().getName());
            log.debug("START EXECUTION method {} request: {}", methodHandler.getMethod().getName(), request.getRequestURI());


        }


        return true;
    }

}

Metrics

The scope is to annotate the controllers to collcect metrics information.

The steps are the following: - Define a new metrics - adding new annotations to use to annotate controllers - adding new interceptors to read annotations and calculate the execution time

Define new Metrics registry Adding Registry to the Spring application context. In this manner will be possbile to use Autowired annotation to use registry inside interceptors classes:

1
2
<bean id="metricRegistry" class="com.codahale.metrics.MetricRegistry" >
</bean>

Adding new annotations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.ericsson.mito.metrics;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value=ElementType.METHOD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Timed {
}

Adding interceptor defintion to the rest-servelet.xml

1
2
3
4
<mvc:interceptors>
    <bean class="com.listfeeds.interceptors.LogContextInterceptor" />
    <bean class="com.listfeeds.interceptors.TimedInterceptor" />
</mvc:interceptors>

The com.listfeeds.interceptors.TimedInterceptor class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.listfeeds.interceptors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.Timer.Context;
import com.listfeeds.annotations.Timed;

public class TimedInterceptor extends HandlerInterceptorAdapter {

    private static final String TIMER = "controllers.Timer";

    @Autowired
    private MetricRegistry registry;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        HandlerMethod methodHandler = (HandlerMethod) handler;

        Timed myAnnotation = methodHandler.getMethodAnnotation(Timed.class);

        if (myAnnotation != null) {
            Class<?> myclass = methodHandler.getMethod().getDeclaringClass();
            String methodName = methodHandler.getMethod().getName();
            Timer timer = getTimer(myclass,methodName,"timer");
            Context t = timer.time();
            request.setAttribute(TIMER, t);
        }

        return true;
    }

    @Cacheable
    private final Timer getTimer(Class<?> myclass, String methodName, String name) {
        return registry.timer(MetricRegistry.name(myclass,methodName,name));
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerMethod methodHandler = (HandlerMethod) handler;

        Timed myAnnotation = methodHandler.getMethodAnnotation(Timed.class);

        if (myAnnotation != null) {
            Context t = (Context) request.getAttribute(TIMER);
            t.stop();
        }
    }
}

The last step is to aadd a Reporter used for the metrics. The Dropwizard toolkit include more than one reportert in this example it will be used Slf4jReporter so all metrics collected will be written using log4j.

The following class have to be included in Spring component scan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.listfeeds.components;

import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.codahale.metrics.CsvReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Slf4jReporter;
import com.listfeeds.utils.PropertyUtils;

@Component
public class Initializer {

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

    @Autowired 
    MetricRegistry metricRegistry;

    @Autowired
    PropertyUtils propertyUtils;

    static Slf4jReporter reporterLOG = null;
    static CsvReporter csvReporter = null;

    public Initializer() {

    }

    @PostConstruct
    public void init() {


        assert metricRegistry != null : "metricRegistry component not initialized correclty";

        reporterLOG = Slf4jReporter
                .forRegistry(metricRegistry)
                .outputTo(LoggerFactory.getLogger("metrics.com.listfeeds"))
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.SECONDS).build();

        reporterLOG.start(5), TimeUnit.MINUTES);

    }
}

The reporter, highlighted above, will write metrics each 5 minutes.