Skip to content

Instantly share code, notes, and snippets.

@StevenACoffman
Last active October 14, 2017 14:11
Show Gist options
  • Save StevenACoffman/68e4c6311827cd770feb5151c406d54b to your computer and use it in GitHub Desktop.
Save StevenACoffman/68e4c6311827cd770feb5151c406d54b to your computer and use it in GitHub Desktop.
Apps Gateway
A Note about Microservices Design

Ideally, each service should have only a small set of responsibilities. Robert 'Uncle Bob' Martin talks about designing classes using the Single Responsibility Principle (SRP). The SRP defines a responsibility of a class as a reason to change, and states that a class should only have one reason to change. It make sense to apply the SRP to service design as well.

Another analogy that helps with service design is the design of Unix utilities. Unix provides a large number of utilities such as grep, cat and find. Each utility does exactly one thing, often exceptionally well, and can be combined with other utilities using a shell script to perform complex tasks.

Adrian Cockcroft (former Netflix Cloud Architect, current Amazon VP of Cloud Architecture) says that a microservice is something that one developer should be able to write/re-write from scratch in two to three weeks.

Helpful patterns for designing microservices:

Microservices have their own challenges:

  • The number of service instances and their locations (host+port) changes dynamically
  • Partitioning into services can change over time and should be hidden from clients
  • Services might use a diverse set of protocols, some of which might not be web friendly

The Sequoia platform implements a number of microservice patterns:

Service Location

Service location is service discovery and service registration.

  • Service discovery is finding other services
  • Service registration is making your service discoverable by others.

Sequoia uses Eureka for service location, and non-java apps use Eureka through a Sidecar Pattern to intermediate service location, among other things.

You should read this: service discovery in a microservices architecture

Apps Gateway as an api gateway

The Apps Gateway is an implementation of the API gateway pattern that is the single entry point for all clients. The API gateway handles requests in one of two ways. Some requests are simply proxied/routed to the appropriate service. It handles other requests by fanning out to multiple services.

Our Apps Gateway is implemented via Netflix Zuul.

Further Reference:

How Does Zuul Work?

At the center of Zuul is a series of filters that are capable of performing a range of actions during the routing of HTTP requests and responses. The following are the key characteristics of a Zuul filter:

  • Type: most often defines the stage during the routing flow when the filter will be applied (although it can be any custom string)
  • Execution Order: applied within the Type, defines the order of execution across multiple filters
  • Criteria: the conditions required in order for the filter to be executed
  • Action: the action to be executed if the Criteria are met

Types of filters:

  • PRE filters execute before routing to the origin. Examples include request authentication, choosing origin servers, and logging debug info.
  • ROUTE filters handle routing the request to an origin. This is where the origin HTTP request is built and sent using Apache HttpClient or Netflix Ribbon.
  • POST filters execute after the request has been routed to the origin. Examples include adding standard HTTP headers to the response, gathering statistics and metrics, and streaming the response from the origin to the client.
  • ERROR filters execute when an error occurs during one of the other phases.

You are probably always going to write only PRE filters. Our only ROUTE filter is the HttpForwarder.

class SuchABadFilter extends ZuulFilter {

    def domains = [
            "www.jstor.org",
            "firefly.jstor.org"
    ]
    
    @Override
     String filterType() {
       return 'pre'
     }

    @Override
    int filterOrder() {
       return 430
    }

    @Override
    boolean shouldFilter() {
        RequestContext ctxt = RequestContext.currentContext
        HttpServletRequest req = ctxt.request
        return !ctxt.appsServiceName && domains.contains(req.serverName) &&
                (req.requestURI.startsWith("/clockss-manifest"))
    }

    @Override
    Object run() {
      RequestContext ctxt = RequestContext.currentContext
      HttpServletRequest req = ctxt.request
      // Do some stuff 
      ctxt.appsServiceName = "MY_EUREKA_SERVICE_NAME"
      ctxt.routeRequestURI = req.requestURI
    }
}

Things to note

  • Filters communicate with one another through the request context.
  • Setting the request context appServiceName and routeRequestURI lets other filters do the obscure parts
  • Don't filter if ctxt.appsServiceName is already set, since some other filter already ran
  • Pick a filter order larger than 200 and smaller than 1000 or you need to do more yourself
  • {SERVICE_NAME}.apps.test.cirrostratus.org already works for any eureka registered service because of AppsServiceDetection.groovy

Best practice filters:

  • StaticTrafficCop.groovy - Static application, single page application
  • FrontEndAppBaseFilter.groovy - Example of nicely extensible pattern for complicated routing

Filters of note and their order

ORDER NAME
100 CORS.groovy
200 PreserveRequestedHost.groovy
500 AppsServiceDetection.groovy
600 StaticBanana.groovy
601 StaticTrafficCop.groovy
730 FrontEndAppBaseFilter.groovy
1000 AppsRouting.groovy
1010 RunStatic.groovy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment