Skip to content

Instantly share code, notes, and snippets.

@rtablada
Created June 3, 2016 18:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rtablada/db74a027c82aac2c0d17697fa7fc9819 to your computer and use it in GitHub Desktop.
Save rtablada/db74a027c82aac2c0d17697fa7fc9819 to your computer and use it in GitHub Desktop.

Middleware vs Plugs

Lately I've been experimenting with Elixir and Phoenix and have started to ask myself "what's the difference between Phoenix's Plugs vs middleware in other server-side paradigms"

To start I'm going to talk about Phoenix's Plugs vs synchronous middleware (like what's found in Laravel). For the most part, this will also apply to asyc middleware, but there are a few caveats that I'll cover.

As a comparison, we will have a hypothetical system with a few operations to respond to HTTP requests:

  • Authorize via JWT or some token auth
  • Parse JSON body as an object
  • Normalize and Serialize API payloads to match DB schemas

Plugs are Unidirectional

In a more traditional middleware, the request flows into the system then flows back out through each layer of middleware. In Laravel this is achieved by running the next callback and passing in the request (which is usually modified), and finally returning some response result. For instance the Authorize JWT middleware could be something like this:

public function handle($req, Closure $next)
{
  if (isValidToken($request->header('Authorization'))) {
    return $next($req);
  }

  return response()->error(401, 'Unauthorized access');
}

This works fine, except that this allows for abuse since Middleware can modify both the request and the response before AND after the next piece of middleware. Take for instance a possible implementation of JSON normalization and serialization:

public function handle($req, Closure $next)
{
  $normalized_request = normalizeJSONRequestBody($req);

  $result = $next($normalized_request);

  return response()->json(serializeJSONResponse($result));
}

In some ways this flexibility can be powerful. But, it also leads to the possibility of strange side effects. There have been times where I've had to completely replace third-party middleware because it modified both the request and response in a single place. This boils down to an issue of usability vs single responsibility.

Compare this to plugs in Phoenix. One thing to note is that plugs pass the connection which is a set of both the request and response. Plugs modify the connection then pass things off to the next plug. The connection doesn't come back. This enforces single responsibility for plugs.

That's not to say that you couldn't modify both the request and the response in one plug. But instead, it means you aren't able to modify the connection in response to something further down the pipeline. This means our JSON normalization and serialization would have to be broken into two separate plugs:

def normalize_json(conn, params) do
  conn |> normalize_json_request_body
end

def serialize_json(conn, params) do
  conn |> serialize_json_response
end

Then in a pipeline or controller, these plugs would have to be put in such that normalize_json is plugged in before the controller action and serialize_json is plugged in after the controller is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment