graphiti
graphiti copied to clipboard
Add support for caching in Graphiti
Graphiti doesn't have much of a caching story right now. For a while I used @richmolj's suggestion of ActionCaching (#160) often I ended up rolling my own logic which sometimes in the case of resources with sideloads required a bunch of duplicated effort outside of graphiti (parsing params, determining sideloads, etc) that I knew graphiti was already kinda doing.
This PR exposes some things that make caching the most time-intensive part of the request cycle (the rendering) with Graphiti a snap. Most of this leverages built-in Rails caching methods, but we have to do some extra work to make sure we respect sideloads.
Changes:
-
expose
cache_key
method to resource instance this generates a combined stable cache key based on resource identifiers specified by the resource class, the specified sideloads, and any specifiedextra_fields
orfields
,pages
, orlinks
which will affect the response. -
expose
cache_key_with_version
method to resource instance same as above, but with the last modified dates added in. If any included resource'supdated_at
changes, this key will change. -
expose
updated_at
method to resource instance returns the maxupdated_at
date of the resource and any specified sideloads -
expose
etag
method to resource instance generate a Weak Etag based on thecache_key_with_version
response. Withetag
andupdated_at
methods on a resource instance, usingstale?(@resource)
will respect them. -
for cached resources, rendering logic in Graphiti is wrapped in a cache block
Rails.cache.fetch(@resource.cache_key, version: @resource.updated_at) { [expensive rendering] }
.(Using
cache_key
andversion
together by default instead of usingcache_key_with_version
as the key better ensures we won’t flood a cache store with dead keys)
For an end user to have easy cached resources that behave just like Rails, they’d set up their cache store and do the following:
class EmployeesController < ApplicationResource
cache_resource
end
class EmployeesController < ApplicationController
def index
@employees = Employees.all(params)
respond_with @employees if stale?(@employees)
end
end
@richmolj This concludes my blast of PRs for a while 😝 #422 and #423 were just leading to this in order to keep things topical.
There might be a few things still to add to this, but thought this was a good enough starting point to talk about it. This has made a huuuugge difference in my app, and I'm super stoked about this addition.
json-api resources has a kinda-similar caching strategy, but with more configuration. I liked how they defined it on the Resource definition. I initially had it as a :cache
argument being passed into the .find
or .all
method and this ended up feeling better.
jsonapi-renderer which we're leveraging for rendering has a caching strategy that I'm specifically working around in this PR, because I couldn't get it to work at all. The idea of individually caching each resource response fragment sounds good in theory, but cutting it off at this point made sense until a reason to do otherwise presented itself
:tada: This PR is included in version 1.7.0 :tada:
The release is available on:
-
v1.7.0
- GitHub release
Your semantic-release bot :package::rocket: