uniform
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.k6n.net/mats/platform/catalog"
|
||||
)
|
||||
|
||||
// catalogProjectionCache is the cart's per-pod, cold-start-ready catalog
|
||||
// projection cache, backed by platform/catalog.ProjectionStore. It consumes the
|
||||
// catalog.projection_published wire — a full snapshot (begin/chunk/end, framed by
|
||||
// epoch) plus incremental deltas — into its OWN local .bin and serves catalog
|
||||
// facts by SKU (price, tax_class, inventory_tracked, image, …) at add-to-cart /
|
||||
// checkout-reserve decisions. Stock is NOT cached — queried synchronously from
|
||||
// inventory per docs/inventory-shape.md.
|
||||
//
|
||||
// Clustering: the cart runs N replicas. Each pod has its OWN exclusive bus queue
|
||||
// (rabbit.ListenToPattern declares an exclusive server-named queue, so every pod
|
||||
// receives the full stream) and its OWN pod-local .bin. It's a replicated read
|
||||
// cache — no leader, no shared volume; N pods writing one file would corrupt it.
|
||||
// See docs/catalog-projection.md.
|
||||
//
|
||||
// The cart stores the full catalog.Projection (identity transform): its
|
||||
// add-to-cart overlay (projection_overlay.go) consumes nearly every field, so
|
||||
// there's nothing to subset. Deletes ride as tombstones in the store's overlay
|
||||
// (IsDeleted) so a removed SKU isn't re-fetched before the next snapshot folds
|
||||
// the delete into the base. Cold-grace: an SKU not yet in the base/overlay
|
||||
// resolves via the existing PRODUCT_BASE_URL HTTP fetch + overlay.
|
||||
type catalogProjectionCache struct {
|
||||
store *catalog.ProjectionStore[catalog.Projection]
|
||||
}
|
||||
|
||||
// identityProjection is the cart's mandatory transform — it stores the full
|
||||
// Projection because the overlay uses almost all of it.
|
||||
func identityProjection(p catalog.Projection) catalog.Projection { return p }
|
||||
|
||||
// newCatalogProjectionCache opens the per-pod store under dir. dir MUST be
|
||||
// pod-local (container fs / an emptyDir in k8s) — never a shared volume.
|
||||
func newCatalogProjectionCache(dir string) (*catalogProjectionCache, error) {
|
||||
s, err := catalog.NewProjectionStore(dir, "cart-projection.bin", identityProjection, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &catalogProjectionCache{store: s}, nil
|
||||
}
|
||||
|
||||
// HandleFrame feeds one catalog.projection_published event (snapshot frame or
|
||||
// delta) into the store; framing/epoch ordering live in platform/catalog.
|
||||
func (c *catalogProjectionCache) HandleFrame(meta map[string]string, payload []byte) error {
|
||||
return c.store.HandleFrame(meta, payload)
|
||||
}
|
||||
|
||||
func (c *catalogProjectionCache) Get(sku string) (catalog.Projection, bool) { return c.store.Get(sku) }
|
||||
|
||||
// IsDeleted reports whether the bus has explicitly deleted sku (overlay
|
||||
// tombstone) — call sites skip the HTTP fallback for it.
|
||||
func (c *catalogProjectionCache) IsDeleted(sku string) bool { return c.store.IsDeleted(sku) }
|
||||
|
||||
// Len reports cached entries (base + overlay) — debug/log gauge only.
|
||||
func (c *catalogProjectionCache) Len() int {
|
||||
_, base, overlay := c.store.Stats()
|
||||
return base + overlay
|
||||
}
|
||||
Reference in New Issue
Block a user