cache-handler
cache-handler copied to clipboard
Add interface for cache?
When I was looking to start working on a similar module, I stumbled across this one and I love the work that is being done on this module, this is going to be a great addition to the Caddy modules list!
The only thing I was wondering is if there could be an interface for the caching backend, similar to the certmagic storage interface so the default storage, olric, could be swapped out with something like redis?
That's a cool idea, but sounds tricky; the cache has to meet certain criteria to be useful (and I'm not sure even olrich supports all of the features/functionality we want/require yet). What interface do you propose?
Might be really hard to abstract out the relevant interfaces... probably better to just have a separate plugin that is a redis version.
Or not :) We don't know how hard it is until we try. What are the required functions of a cache module?
I think the interface would match up to something along the lines of the following:
type Cacher interface {
// Put will create a new cache item if it exists or not.
Put(key string, content []byte, duration time.Duration) error
// Add will add a cache item to the cache if the key does not
// exist.
Add(key string, content []byte, duration time.Duration) error
// Get returns a item from the cache or an error if the item
// does not exist
Get(key string) ([]byte, error)
// Forget removes an item from the cache, if the item cannot be
// removed it will return an error.
Forget(key string) error
}
This is heavily inspired by the Laravel Cache interface, which is amazing, minus a few methods we might not want/need.
We also need to be able to retrieve the TTL of an entry (for the Cache-Status header). We'll also probably need a tag mechanism at some point to support invalidation / purging.
Already available in Souin https://github.com/Darkweak/Souin btw
If I may, I think that https://www.php-fig.org/psr/psr-6/ fits the model in many ways:
- Pool: The Pool represents a collection of items in a caching system. The pool is a logical Repository of all items it contains. All cacheable items are retrieved from the Pool as an Item object, and all interaction with the whole universe of cached objects happens through the Pool.
- Item An Item represents a single key/value pair within a Pool. The key is the primary unique identifier for an Item and MUST be immutable. The Value MAY be changed at any time.
We may also want an Adapter interface implements the caching mechanism to store the information using a transport (fs, database, redis, olric etc.) which would give access to the Pool.
In conjunction with a few reads, we can improve the above model to:
type CacheItem interface {
GetKey() string
Get() []byte
IsHit() bool
Set(value []byte)
SetTTL(duration time.Duration)
GetTTL() time.Duration
}
/// This would be the transport itself (OlricAdapter, RedisAdapter)
type Adapter interface {
GetItem(key string) (CacheItem, error)
HasItem(key string) bool
ForgetItem(key string) error // I like Forget better then Delete
Save(CacheItem item) error
/// Removes all items
Clear() error
}
PSR-6 implements GetItems or SaveDefered but I don't think that we need these mechanism right away and would add them later on if needed. Thoughts?
Also must read on cache abstractions:
- https://www.php-fig.org/psr/psr-6/#error-handling
- https://www.php-fig.org/psr/psr-16/
- symfony cache contracts: https://github.com/symfony/symfony/tree/3f0f21c3619f36fe22fba6d9e800258f3bc3252a/src/Symfony/Contracts/Cache
It's a good start!
We also need to take into account the Vary header. To do so, we need a second level of cache. Several responses can be stored for a single tuple (method, URL). So we must first retrieve the Vary header form any entry matching this tuple in the cache, then compute the final cache key by adding to the method and the URL the normalized keys and values specified in Vary.
It's maybe doable with this interface, but maybe could we also do something taking this specific use case into account. AFIU Souin doesn't support Vary yet.
Can't Vary just be a part of the key? For example:
GET /book
Vary: User-Agent
Cache key: Headers['User-Agent'] . GET . /books
No. Vary is typically a response header, not a request one.
I did something to achieve this behaviour here https://github.com/Darkweak/Souin/pull/54/files#diff-3e6ce3aa20a99fc94989cf961ba2647ba8e2ff8cb4a092be15befe3efb583303