Where should an entity-mirror cache live?
Table of Contents
Summary
An entity-mirror cache (partitioned_cache<PartitionKey,Key,Value>
- the
nats-event-cachecodegen facet, see
the story that introduced
them) is declared with cached_by: <consumer> on the entity's
own model, never on a competing model authored inside the consumer
– the single source of truth for "who caches this entity" stays
with the entity, like every other fact about it. Where <consumer>
should point depends on how many things actually need the cached
data: the entity's own producer component never needs to cache
itself (it has direct DB access), a single specific consumer gets
the cache in its own component (e.g. party_cache lives in
ores.iam.core, because IAM's authorization logic is party's only
consumer), and when multiple UI-layer components need the same
cached entity, the cache belongs in a shared thin, DB-free
<domain>.client component (mirroring ores.marketdata.client's
role) rather than being duplicated per UI or arbitrarily parked in
whichever UI happened to need it first.
Detail
The three placement cases
- Producer caching itself – never.
ores.refdata(or whichever component owns the entity) has direct DB access; a NATS-fed in-process mirror of its own data buys it nothing. - Single specific consumer – the consumer's own component.
party_cachelives inores.iam.core, not inores.refdata, because party data is consumed specifically for IAM's own authorization/visibility logic – a concern that belongs to IAM, not to refdata or to any UI layer. - Multiple UI-layer consumers – a shared thin
<domain>.clientcomponent, created if one doesn't already exist. If a cache were placed inside e.g.ores.qtbecause that's the first UI that needed it, a second UI (ores.shell,ores.wt, …) needing the same cached entity would either duplicate the cache class and its NATS wiring, or awkwardly depend on the first UI's component for unrelated code.ores.marketdata.clientalready exists for this exact reason on the CRM side (NATS-only, DB-free, shared by every UI that talks toores.marketdata); the refdata equivalent (ores.refdata.client) follows the same shape. This isn't a new naming convention invented for caching specifically –clientis already a documented specialist sub-component role inprojects/modeling/component_architecture.org's sub-component catalogue ("provides the consumer-facing API without coupling consumers to core internals"), previously only instantiated asores.iam.client; a cache is one concrete shape that role can take, not a reason to invent a separate.cachesuffix.
Targeting a non-.core subcomponent: cached_by's dotted form
The facet's generated output path is
projects/ores.{cache_component}/{cache_subcomponent}/include/ores.{cache_component}.{cache_subcomponent}/service/cache/{entity}_cache.hpp
(see the ores.cpp.nats-event-cache facet). cache_subcomponent defaults to core (case 2 above:
cached_by: iam → ores.iam.core, every consumer's shape until
ores.refdata.client existed) but is derived from cached_by itself
whenever it carries a dot – cached_by: refdata.client splits into
cache_component: refdata, cache_subcomponent: client, the same
"component.subcomponent" convention component_include already
uses elsewhere. No separate model flag: both are purely derived, like
every other _upper=/=_dir variable the facet exposes.
See also
- Generic entity-mirror cache + codegen facet – the story that
built
partitioned_cacheand thenats-event-cachefacet, withparty_cacheas the worked single-consumer exemplar. - How does a Qt client cache stay fresh? – the analogous
subscribe-and-reload pattern for GUI-tier caches (
ChangeReasonCache,BadgeCache), and why this facet is the wrong tool for that tier.