Skip to main content
Version: Next

Create cache stores

Marten lets you easily create custom cache stores that you can then use as part of your application when it comes to perform caching operations.

Basic store definition

Defining a cache store is as simple as creating a class that inherits from the Marten::Caching::Store::Base abstract class and that implements the following methods:

  • #clear - called when clearing the cache
  • #decrement - called when decrementing an integer value in the cache
  • #delete_entry - called when deleting an entry from the cache
  • #increment - called when incrementing an integer value in the cache
  • #read_entry - called when reading an entry in the cache
  • #write_entry - called when writing an entry to the cache

For example, the following snippet implements an in-memory store that persists cache entries in a hash:

class MemoryStore < Marten::Cache::Store::Base
@data = {} of String => String

def initialize(
@namespace : String? = nil,
@expires_in : Time::Span? = nil,
@version : Int32? = nil,
@compress = false,
@compress_threshold = DEFAULT_COMPRESS_THRESHOLD
)
super
end

def clear : Nil
@data.clear
end

def decrement(
key : String,
amount : Int32 = 1,
expires_at : Time? = nil,
expires_in : Time::Span? = nil,
version : Int32? = nil,
race_condition_ttl : Time::Span? = nil,
compress : Bool? = nil,
compress_threshold : Int32? = nil
) : Int
apply_increment(
key,
amount: -amount,
expires_at: expires_at,
expires_in: expires_in,
version: version,
race_condition_ttl: race_condition_ttl,
compress: compress,
compress_threshold: compress_threshold
)
end

def increment(
key : String,
amount : Int32 = 1,
expires_at : Time? = nil,
expires_in : Time::Span? = nil,
version : Int32? = nil,
race_condition_ttl : Time::Span? = nil,
compress : Bool? = nil,
compress_threshold : Int32? = nil
) : Int
apply_increment(
key,
amount: amount,
expires_at: expires_at,
expires_in: expires_in,
version: version,
race_condition_ttl: race_condition_ttl,
compress: compress,
compress_threshold: compress_threshold
)
end

private getter data

private def apply_increment(
key : String,
amount : Int32 = 1,
expires_at : Time? = nil,
expires_in : Time::Span? = nil,
version : Int32? = nil,
race_condition_ttl : Time::Span? = nil,
compress : Bool? = nil,
compress_threshold : Int32? = nil
)
normalized_key = normalize_key(key.to_s)
entry = deserialize_entry(read_entry(normalized_key))

if entry.nil? || entry.expired? || entry.mismatched?(version || self.version)
write(
key: key,
value: amount.to_s,
expires_at: expires_at,
expires_in: expires_in,
version: version,
race_condition_ttl: race_condition_ttl,
compress: compress,
compress_threshold: compress_threshold
)
amount
else
new_amount = entry.value.to_i + amount
entry = Entry.new(new_amount.to_s, expires_at: entry.expires_at, version: entry.version)
write_entry(normalized_key, serialize_entry(entry))
new_amount
end
end

private def delete_entry(key : String) : Bool
deleted_entry = @data.delete(key)
!!deleted_entry
end

private def read_entry(key : String) : String?
data[key]?
end

private def write_entry(
key : String,
value : String,
expires_in : Time::Span? = nil,
race_condition_ttl : Time::Span? = nil
)
data[key] = value
true
end
end

Enabling the use of custom cache stores

Custom cache store can be used by assigning an instance of the corresponding class to the cache_store setting.

For example:

config.cache_store = MemoryStore.new