- Why? - To improve performance (that is not call db an each provider request).
- Where? - At the point of fething data from
OfferProvider
component (repositories). - How? - By using
Rails.cache#fetch
Why we don't like caching?
- cache keys? (we don't know how to name the keys)
- cache invalidaiton? (we are fraid that after updating record, app will still serve old data)
- cache size? (ActiveRecord models when serialized can grow of of controll)
That 3 points above we makes caching not a trivial task and IMHO that's why developers tend to avoid it.
Hovewer, because in OfferProvider
:
- we don't write do db in any other way than throught repository
- we don't read in any other way than throught repository
- and we use dry-structs as data containers (not AR models)
In other words, because we encapsulate data access by Repository - cache becomes simple to manage.
In next sections I'd like to present my proposal.
Read:
def find(id, provider_id)
build_from_record(find_record(id, provider_id))
end
# with cache:
def find(id, provider_id)
Rails.cache.fetch('cache_key_will_be_described_later') do
build_from_record(find_record(id, provider_id))
end
end
Write:
def update(provider_id:, id:, changeset:)
record = find_record(id, provider_id)
record.update!(update_query_params(changeset.to_h))
build_from_record(record)
end
# with cache
def update(provider_id:, id:, changeset:)
record = find_record(id, provider_id)
record.update!(update_query_params(changeset.to_h))
invalidate_cache(provider_id)
build_from_record(record)
end
Cache key will depend on methods parameters.
eg. for:
find(id, provider_id)
cache key: "providers_#{provider_id}_#{id}_find"
Currently we have 3 methods in OfferProvider repository (create, update, delete). That simplifies cache invalidation. Cache will be invalidated only in these methods.
How data are stored in cache? Data is serialized on write to cache and deserialized on reading from cache. In our cache we will cache our dry-structs, so it's lightweight and we completely controll it.
@startuml
"Controller/Service" -> Repository : find_by(a, b)
Repository -> Record : find_by(a, b)
Repository <-- Record : record
Repository -> Factory : build_from_record(record)
Repository <-- Factory : entity
"Controller/Service" <-- Repository : entity
box "Persistence layer" #LightBlue
participant Repository
participant Record
participant Factory
end box
@enduml