Your API on steroids using Spyke and HTTP caching

In a previous article I introduced Spike, a great ruby gem that allows you to interact with REST services in an ActiveRecord-like manner. Today I will show you how we can take advantage of Spyke’s simplicity and Faraday’s middleware to build a performing API using HTTP caching techniques.


Before we get going, if you haven’t already, take a moment to read about Spyke and how simple it can be to start using it to interact with your existing REST API. Also, since this article is about HTTP caching, I strongly recommend you get a deep understanding on how it works by taking a few minutes to read this great article.

Why should I care?

Have you noticed how much HTTP API there is out there? In this world where everything is connected it makes sense for a company to build its own API to allow external third parties to interact with their business. Or maybe you only need a private, internal API to allow communication between services (micro-services anyone?). That’s fine. But you should probably care about the performance of your API and this is where HTTP caching might help you.

In fact all kinds of API cannot benefit from HTTP caching. Depending on your data and your business, you might be in a situation where your data changes too quickly and you don’t want to bother with caching it anyway. For the purpose of this article, let’s assume we want to build a Content Management Service that would store some contents (think of plain old text or HTML fragments).

This kind of data is really easy to cache since it doesn’t change that often.

Server side

If you’ve read the great explanations about HTTP caching available in this series of articles, you now have a better understanding at how it should work. Our goal on the server side is to render fresh data only if it’s necessary. Otherwise we will be saving network bandwidth by just returning a well known 304 Not Modified HTTP header telling our client that our content has not changed since the last time.

Let’s dive into some code. Rails makes it easy for us by using the stale? method right from the controller:

def show  
  @content = Content.find(params[:id])

  if stale?(@content, public: true)
    respond_with @content, serializer: ContentSerializer, root: :data
  end

  expires_in(5.minutes, public: true)
end  

Couple of things to explain here.

The stale? method takes care of knowing if the content we passed to it is fresh or not. It’s using the updated_at timestamp field under the hood in order to determine whether or not the content has changed since the last time it was queried. The client will need to send a Last-Modified HTTP header with the timestamp it last queried that particular content for this to work.

The expires_in method is responsible for setting the Cache-Control HTTP header in order for the client to know for how long it is allowed to cache that content before making a new request to the server to refresh its cache.

And finally here you’ve probably noticed I am using ActiveModelSerializers gem to handle the JSON serialization of my content.

That’s all you have to do on the server side actually. All the magic will happen when we’ll be telling our client gem to sends the required HTTP headers.

Client side

Since Spyke is using Faraday under the hood to make the HTTP calls, we have all the Faraday’s middlewares at our disposal. In order to deal with HTTP caching, we will be using the Faraday HTTP Cache middleware available.

Let me remind you how we’ve setup Spyke in the previous article:

require "faraday"  
require "spyke"  
require "consumer/json_parser"

module Consumer  
  Spyke::Base.connection = Faraday.new(url: "http://your_api.com/api/v1/") do |c|
    c.request :json
    c.use Consumer::JsonParser
    c.adapter Faraday.default_adapter
  end
end  

Simply add the Faraday middleware to your gemspec file:

spec.add_dependency "faraday-http-cache"  

And finally setup Faraday’s adapter to use it:

c.use Faraday::HttpCache, store: Rails.cache_store, logger: Rails.logger, serializer: Marshal  

Note that we’ve delegated the cache store and the logger to Rails here. In the previous article we’ve put the Spyke configuration in a file called consumer/consumer.rb but since we now deal with Rails, you might consider moving the configuration into consumer/railtie.rb:

require "spyke"  
require "faraday"  
require "faraday-http-cache"  
require "consumer/json_parser"

module Consumer  
  class Railtie < Rails::Railtie

    config.after_initialize do
      Spyke::Base.connection = Faraday.new(url: "http://your_api.com/api/v1/") do |c|
        c.request :json
        c.use Consumer::JsonParser
        c.use Faraday::HttpCache, store: Rails.cache_store, logger: Rails.logger, serializer: Marshal
        c.adapter Faraday.default_adapter
      end
    end
  end
end  

The above will ensure Spyke is configured just after all your Rails initializers have been run.

And that’s pretty much it, all the hard work will be done by Faraday::HttpCache middleware (setting and checking HTTP headers). If you want to try this in development, don’t forget to activate your cache store by using the Dalli gem for example.

The first time you will call your server it should return a 200 response with the content data in the payload. If you call your server again within 5 minutes you will see that it is not making any requests to your API thanks to Faraday::HttpCache middleware and the expires_in statement on the server side.

Wait for 5 minutes and issue a third request.

You should see this time that your server received a new request but instead of returning a 200 response, it should have returned a 304 Not Modified since the content did not change in the meantime. If you would had changed it (by touching it or updating the updated_at timestamp) you would have seen a 200 response instead.

What we’ve learn

Today we saw how simple it can be to implement the HTTP caching layer on a REST API. The fact that Spyke uses Faraday under the hood allowed us to use the Faraday::HttpCache middleware to handle all the heavy lifting regarding HTTP caching headers.

To go even further, we could cache the serialized JSON on the server side in order to avoid re-building it every 5 minutes if the content did not change in the mean time. But I leave that to you as an exercise :)

Show Comments