Pedestal

Architecture Overview

Motivation

We created Pedestal as a web framework. At the base, every web framework must solve certain common problems:

  1. Interpret incoming requests

  2. Dispatch to application code

  3. Produce a well-formed response

There is an unstated requirement that these all work in a variety of deployment environments:

  • Jar

  • Exploded jar

  • Container

  • Unikernel

And with a variety of network connectors:

  • Tomcat

  • Jetty

  • Undertow

  • Netty

  • nginx

  • Vert.x

  • etc.

One more dimension of complexity arises from the relevant styles of application we want to support:

  • API + SPA

  • Streaming events

  • Server-rendered pages

Solving these problems led us to an architecture that is based on interceptors, the context map, and an adaptor to the HTTP connector.

pedestal fundament

Interceptors

In their original form, interceptors were a way to reify the callstack of request processing. That is to say, what would normally be a layered jawbreaker of function closures became a sequence of data structures. This model has allowed us to extract an increasing amount of logic from the core framework into interceptors. For example, routing is usually implemented as a core function of a web framework. Because an interceptor can enqueue more interceptors to execute, Pedestal implements routing as an ordinary interceptor.

Over time, we have moved most of the functionality of a typical web framework into interceptors:

  • Content negotiation

  • Request body parsing

  • Routing

  • Parsing query parameters

  • Assigning content type to the response

Application logic is also implemented in interceptors.

Interceptors are called in core.async go blocks, so one interceptor may be called on a different thread than the next. All bindings will be conveyed to each interceptor.

An interceptor may return a channel instead of the context map. In that case, the channel is treated like a promise to deliver the context map in the future. Once the channel delivers the context map, the chain executor closes the channel.

The key protocol for interceptors is:

Stock Interceptors

The pedestal-service library includes a large set of interceptors that are specialized for HTTP request handling.

See the following namespaces for stock interceptors:

See the following namespaces for routing interceptors:

Context Bindings

Interceptors expect certain keys to be present in the context map. These context bindings are part of the contract between provider and interceptors.

Chain Provider

Along with moving core logic into interceptors, we have move the HTTP connection handling out of interceptor processing to create an interface for the chain provider.

The chain provider sets up the initial context and queue of interceptors. It starts execution.

Pedestal includes a servlet chain provider out of the box. It connects any servlet container to an interceptor chain. The create-servlet function orchestrates this work. This is strictly a convenience function that takes a service map of everything needed to run an HTTP service.

It is possible to create other chain providers. The fast-pedestal sample shows how to do this with Jetty.

See the following namespaces for the HTTP chain provider:

Network Connectors

The servlet chain provider immediately works with every HTTP server that works with servlets. This allows many deployment scenarios.

Sometimes it is advantageous to work directly with a server by implementing a custom chain provider.

The servlet chain provider (and main interface to network connectors) is in: