Fintrospect is a Scala web-framework with an intelligent HTTP routing layer, based on the Finagle RPC framework from Twitter. Via a shared contract, it provides a simple way to implement fast webservice endpoints and HTTP clients which are:

  • Type-safe : auto-marshalls all request parameters/bodies into the correct types (including primitives + JSON/XML etc...)
  • Auto-validating : the presence of required and optional request parameters and bodies are checked before entering service-layer code
  • Auto-documenting : runtime generation of endpoint documentation such as Swagger JSON or web sitemap XML. Generates JSON Schema for example object formats to be included in these API docs.
  • Uniform : reuse the same contract to define both incoming or outgoing Finagle HTTP services. This also allows extremely low effort fake servers to be created

Additionally, Fintrospect provides a number of mechanisms to leverage these routes:

  • Easily build type-safe HTTP responses with a set of custom builders for a wide variety of message formats:
    • JSON: Argo, Argonaut, Circe, GSON, Jackson, Json4S, Play JSON, Spray JSON
      • Auto-marshaling of case classes instances to/from JSON (for Argonaut/Circe/Json4S/Play).
      • Implement simple PATCH/PUT endpoints of case class instances (Circe only).
    • Native implementations of XML, Plain Text, HTML, XHTML
    • MsgPack binary format
  • Serve static content from the classpath or a directory
  • Template View support (with Hot-Reloading) for building responses with Mustache or Handlebars
  • Anonymising headers for dynamic-path based endpoints, removing all dynamic path elements. This allows, for example, calls to particular endpoints to be grouped for metric purposes. e.g. /search/author/rowling becomes /search/author/{name}
  • Interacts seamlessly with other Finagle based libraries, such as Finagle OAuth2
  • Utilities to help you unit-test endpoint services and write HTTP contract tests for remote dependencies

broad concepts

The main concepts in play:

  • RouteSpec: defines the self-describing contract of an HTTP endpoint
  • ServerRoute: the binding of a RouteSpec to a Finagle Service implementing some business-logic, to create an inbound HTTP endpoint
  • RouteClient: the binding of a RouteSpec to a Finagle Service representing an HTTP client, to create a simple function for making outbound HTTP calls
  • ParameterSpec: defines the acceptable format for a request parameter (Path/Query/Header/Form-field). Provides the auto-marshalling mechanic for serializing and deserializing objects to and from HTTP messages
  • BodySpec: similar to ParameterSpec, but applied to the body of an HTTP message
  • RouteModule: defines a set of ServerRoute instances which are grouped under a particular request path. These modules can be combined and then converted to a Finagle Service and attached to a Finagle HTTP server. Each module provides an endpoint under which it's own runtime-generated documentation can be served (eg. in Swagger format)
  • ResponseSpec: defines the characteristics of a possible response generated by a ServerRoute

regarding finagle

Since Fintrospect is build on top of Finagle, it's worth acquainting yourself with it's concepts, which can be found here.

&tldr; finagle primer

  1. Finagle provides protocol-agnostic RPC and is based on Netty
  2. It is mainly asynchronous and makes heavy usage of Twitter's version of Scala Futures
  3. It defines uniform Service and Filter interfaces for both client and server APIs that are effectively a single method...
    Service:  def apply(request : Request) : Future[Response]
    Filter:   def apply(request : RequestIn, service : Service[RequestOut, ResponseIn]) : Future[ResponseOut]
  4. Filters can be chained together and then applied to a Service, which results in another Service. This is useful to apply layers of functionality such as caching headers, retry behaviour, and timeouts.

a note on style

  • The code in this guide has omitted imports that would have made the it read more concisely. The sacrifices we make in the name of learning... :)