Rise From the Ashes — Incremental APIs With Phoenix

Much has been said about the Elixir language and the Phoenix framework —Phoenix’s performance without compromise makes it a compelling choice in a world where problems are increasingly concurrent and real-time.

Despite Phoenix’s attractiveness, chances are you have business requirements that disallow you from dropping work on existing APIs that still need to be supported. And so you might consider Phoenix no longer viable, and dismiss it to your ever-growing list of shiny toys to check out — someday.

What if we could incrementally replace our legacy API with Phoenix, one endpoint at a time? We could make a compelling argument by showcasing performance and maintainability gains, without committing to a rewrite.

In science fiction (and perhaps someday reality), transforming an uninhabitable planet into an inhabitable one is known as terraforming, or literally “Earth-shaping”. It’s an apt metaphor for a little plug I co-authored with Dan McClain.

Using Terraform, you can create a Phoenix app that handles one or more endpoints, and forward un-handled requests to your legacy API. If some network latency is not a significant issue, you might find this plug useful.

Discovery

The basic idea is this — if a route is defined on our Phoenix app, we’ll handle the request. If it’s not, we forward the request on, and send its response back to the user.

In your Phoenix app, every incoming connection goes through a pipeline that you can readily examine at lib/my_app/endpoint.ex. You can see how a connection flows through the different plugs specified in this file. The connection is then piped into the router:

Add Terraform to your router

Terraforming the legacy API

By adding the Terraform plug to your router, it will then re-route those missing route connections to a plug that you define. And because this plug uses Plug.Router, you have an elegant DSL in which you can handle them:

Write a Terraformer to forward requests

Basic example aside, we’d probably want to do an actual request on your API, and you can do so by writing a client for it. There are many ways to do it, and I’ve had success in doing so with HTTPoison and its Base module. For example:

Use a client for your legacy API

And here is what a client might look like:

GitHub client example written with HTTPoison

This forwards all un-handled GETs to our legacy API via our client, and sends the response back with the right headers as well. Since this is just a plug, you have access to the connection, so you can easily handle things like sessions and auth as well.

What about performance?

I ran some unscientific benchmarks on my 2014 13" MBP, and the results between the route backed by a Phoenix controller and our Terraform handler were insignificant.

This was done via Siege, with 255 concurrent connections doing 40 parallel requests each. That said, there will obviously be additional latency from having the Phoenix app send and return responses from your older API, so use your best judgement.

Give it a spin

Is Phoenix as productive, reliable, and fast as many claim? With Terraform, trying out Phoenix on your current API is almost risk-free. Handle a single, simple endpoint and compare it with the performance and developer experience of your current stack. You can also view a simple demo of Terraform in action here.

Thanks for reading, and see you at ElixirConf!