fnhouse: An Introduction

Jason Wolfe edited this page Jan 13, 2016 · 2 revisions
Clone this wiki locally

fnhouse!

fnhouse: Where Web Handlers Get Their Jollies

tl;dr: We are releasing fnhouse, a small library that builds on top of Schema and Plumbing in order to easily build web handlers that explicitly declare their inputs and dependencies and validate input and output data (coercing it when necessary). Join the discussion on Hacker News and let us know what you think.

As announced in our Clojure/West talk, we are releasing fnhouse, a small open-source project that builds on top of our Schema and Plumbing libraries. fnhouse makes it easy to build well-documented and safe web APIs out of lightly annotated Clojure functions.

Ever since we released our low-level Plumbing and Schema libraries, we have been getting requests to demonstrate how we use these libraries in production to build more complex systems. The release of fnhouse directly addresses these requests. At its heart, fnhouse builds on top of ring to provide a concise way to write web handlers safely, readably, without global variables, without repeating yourself, and with hooks to do much more.

As an example, here is a fnhouse handler:

(defnk entries$POST
  "Add a new entry to the guestbook"
  {:responses {200 [Entry]}}
  [[:request body :- Entry]
   [:resources guestbook]]
  (swap! guestbook conj body))

This handler is easy to read, and it explicitly declares the structure and type of its arguments (body is of type Entry) and the resources on which it depends: guestbook. The Schema annotations on the arguments to the handler allow for schema checking to validate that the inputs have the right structure.

Using handlers like these, we can write our API as a set of lightly annotated functions. Without any additional code, these functions and their annotations can be used to generate human-readable documentation, perform simple routing for building a a full-fledged web service, and support coercion middleware that whips uncooperative data into shape. Fnhouse ships with example implementations of all of these applications that we use in production at Prismatic, and it is easy to swap any of them out for your library of choice. We are also excited to announce the soon-to-be-released Coax, a library that reads fnhouse annotations and generates client code for the objects passed into and out of the API. Coax makes it so that you don't have to manually rewrite the model files on your client whenever there is a change to your web handlers.

Importance of Validation

Fnhouse handlers are annotated with Schema so that they are safe from the get-go. Any web service that serves requests issued by clients out in the wild must be robust to noisy inputs. Schema is a powerful library that allows you to naturally describe the shape of input data using Clojure's native data structures. Beyond simply providing a nice annotation tool, Schema can validate that data is of the correct form at runtime.

For example, here is a simple Schema:

(def Entry
 "A simple Schema for a guestbook entry"
 {:name String
  :age Long
  :lang (s/enum :clj :cljs)})

Fnhouse handlers are implemented in terms of keyword functions. In the Plumbing library, we introduced the keyword function macro, defnk. Keyword functions provide a nice syntax for safely destructuring values from nested maps with keyword keys. Keyword functions integrate with Schema to support annotations describing the type of the inputs. They will validate the input to ensure that it contains the required values, and they will throw an exception when data is missing or does not match the Schema.

As an example, let's take another look at the handler presented above that has its body argument annotated with the Entry Schema:

(ns guesthouse.guestbook
  "Handlers for maintaining a guestbook"
  (:use plumbing.core))

(defnk entries$POST
  "Add a new entry to the guestbook"
  {:responses {200 [Entry]}}
  [[:request body :- Entry]
   [:resources guestbook]]
  (swap! guestbook conj body))

We can use the Plumbing library to inspect the inputs to this function, and we see this:

> (plumbing.fnk.pfnk/input-schema entries$POST)
{Keyword Any,
 :resources {Keyword Any, :guestbook Any},
 :request {Keyword Any,
           :body {:name java.lang.String,
                  :age java.lang.Long,
                  :lang (enum :cljs :clj)}}}

The function is fairly self-aware! It knows about its inputs, and the Schema that those inputs should match.

If you look, there is a bunch of other information contained in the definition of the function above, and the fnhouse library provides a (not so distorted!) reflection of that information that can be readily used by downstream applications.

Introducing fnhouse

Fnhouse is a simple library that extracts information about a handler from its metadata. For the entries$POST function above, fnhouse will extract the following useful information:

{;; HTTP-related info
 :path "/entries/"
 :method :post

 :description
   "Add a new entry to the guestbook"

 ;; information about the HTTP request that the handler expects
 :request
  {:body Entry
  ;; our function does not expect any query-params or uri-args, but in general they are supported
   :query-params {}
   :uri-args {}}

 :responses {200 [Entry]}

 ;; additional metadata about the Clojure implementation
 :resources {:guestbook s/Any}

 :source-map
 {:line   23
  :column 1
  :file   "guestbook.clj"
  :ns     "guesthouse"
  :name   "entries$POST"}}

That's the core of what fnhouse does. It extracts a declarative description known as HandlerInfo from handlers implemented with keyword functions, which serves as a single source of truth describing your API. The HandlerInfo is well defined -- there is a single Schema describing its shape -- and so it provides a solid foundation on which to build. Although the core functionality of the fnhouse library itself is simple, the HandlerInfo enables a whole host of applications.

Applications

With fnhouse, we are releasing a few applications that demonstrate of how these annotations can be used. We use these applications at Prismatic, and we're excited to see new use cases from the community. The first of these batteries-included applications is a document generation library that presents the extracted annotations on the web in a human-friendly format. The second is a simple routing library for routing requests to the appropriate handlers in order to spin up a web service built with fnhouse handlers. The last library that comes included is a ring middleware that coerces input and output data to match the Schemas present on the fnhouse handlers. We want to stress that although all of these applications are fully functional and integrate directly with fnhouse, they can also be easily be swapped for alternate versions if you prefer.

Documents

The developers building out the clients of your API shouldn't need to trudge through your code to understand the expected inputs and outputs of your web handlers. So with fnhouse, we are shipping a library that generates HTML documentation about the interface to fnhouse handlers. For example, look at this:

HTML Docs

Because we extract source map information from the handlers, we can easily support linking directly to the source. In addition to documenting the handlers, the library generates documentation for all of the Schemas used in your API to serve it up in a single place.

Routing

It is easy to spin up a web server that is built out of of fnhouse handlers. Spinning up a web server that serves requests to /guestbook/entries is as simple as:

(-> ;; take your resources
    resources
    ;; pass them into the slurped up handlers
    ((handlers/nss->handlers-fn {"guestbook" 'guesthouse.guestbook}))
    ;; compile the handlers into a router
    routes/root-handler
    ;; run jetty
    (jetty/run-jetty options))

The fnhouse routing library works right out of the box. It efficiently routes requests to the appropriate handler based on the URI of the request and path of the handler. Like many of the other standard routing libraries, it supports URI-arguments and wildcards. It is modular: if needed, the routing module can easily be swapped out for a different library.

Schema Coercion

We showed in the latest update to Schema (0.2.0) how data could be coerced into the desired format using the magic of Schema transformations. With fnhouse, we bring the power of Schema coercion to web handlers using a special Schema coercion middleware that has been battle-tested in production at Prismatic.

A common case is, after deserializing JSON, you get strings and doubles where really you want keywords and longs. For example, after deserializing, you might get something like this:

{:name   "John"
 :age    "31.0"
 :lang   "clojure"}

When really you want something like:

{:name   "John"
 :age    31
 :lang   :clojure}

The Schema coercion middleware will read your desired schema, and intelligently coerce the input data into the desired form using fnhouse's default JSON Schema coercion. However, JSON coercion is just one example. The Schema coercion middleware is extensible: it's easy to provide your own input and output coercions.

Coax

Coax is a library that is coming quick on the heels of fnhouse. In many ways, it is the killer application of fnhouse annotations.

A common challenge with changing server-side APIs is that client code needs to be updated in tandem. This challenge grows with the number of clients you support. If you have Android, iOS, and ClojureScript clients, then every change in the API results in downstream changes to three separate code bases.

To reduce the impedance mismatch between Clojure Schema and your client models, Coax accepts Schemas as input and generates the matching client models. These models also come with basic serialization and deserialization methods, and can be integrated into a custom, auto-generated API client. Coax can also be extended to generate other intermediate formats like Swagger or Thrift, which means it'll play nice with your existing data formats.

Stay tuned for the upcoming release!