Projection Architecture Overview¶
Status: CURRENT Last consolidated by: BC-068D This document describes the current projection boundary and read-model responsibilities.
Purpose¶
Projections define the stable read models consumed by Frontend, Runtime adapters, APIs and future clients.
A Projection is not a new business truth.
A Projection is a deterministic representation built from upstream Platform data.
Position in the Platform¶
The Platform follows this conceptual flow:
Feed
-> Stage
-> Normalisation
-> Enrichment
-> Canonical Identity
-> Product
-> Offer
-> Projection
-> Frontend
Responsibility¶
The Projection layer prepares data for consumption.
It provides stable structures that can be rendered without recomputing business decisions.
When a Projection builder consumes upstream application data, it must do so through a neutral contract or snapshot dedicated to projection input. The builder must not import a concrete Application DTO directly.
Non Responsibilities¶
The Projection layer must not:
- mutate Product state
- resolve Product identity
- fetch raw feeds
- own Frontend rendering
- depend on WordPress templates
- hide upstream inconsistencies
- create business truth
Frontend Contract¶
The Frontend consumes Projections.
The Frontend must not:
- rebuild Product identity
- recalculate discounts
- choose the best offer
- merge merchant offers
- rebuild titles
- rebuild slugs
- query raw feed data
Compatibility shims that translate projections into historical WordPress array payloads remain explicit adapter concerns. They must not live inside Platform read services.
BC-064D1 also confirms that several projection DTOs are currently used as public contract types. This is documented as transitional debt: the long-term target remains neutral contractual DTOs or explicit transitional contracts outside implementation-bound projection namespaces.
Projection Types¶
The Platform may expose several Projection contracts:
- Product Projection
- Offer Projection
- Navigation Projection
- Homepage Projection
- Search Projection
- Comparison Projection
- Catalog Path Projection
- Catalog Child Projection
- Catalog Page Projection
Legacy catalog compatibility uses a consolidated CatalogPageProjection read model. It carries the page items, pagination, sort state, active filters, projected facets, resolved path metadata and secondary-navigation children needed by the historical renderer. The renderer still decides only presentation concerns such as HTML, canonical tags and breadcrumb markup.
The public WordPress frontend now consumes only projection-backed reads across its active stacks: Navigation, Product Canonical, legacy Catalog and Product Models. Route maintenance or rewrite persistence is not part of the projection flow and must stay in a technical bootstrap outside public renderers.
Projection rebuild writes remain explicit technical operations. For Product Models, the destructive replace of ccx_product_models_v1 is now isolated behind CCX_ProductModelProjectionWriteService and a legacy SQL writer scoped by vertical_id. This keeps the rebuild deterministic while preventing direct projection SQL writes from leaking back into the orchestrator.
The same rule now applies to Product Model enrichments:
- variants rebuilds write through
CCX_ProductVariantProjectionWriteService - specification rebuilds write through
CCX_ProductSpecificationProjectionWriteService - gallery rebuilds write through
CCX_ProductGalleryProjectionWriteService
The builders still decide the rows. The write services only own the destructive scoped replace of the target projection tables.
Invariants¶
A Projection must be:
- deterministic
- rebuildable
- stable for consumers
- independent from rendering technology
- independent from WordPress
- explicit about incomplete or unavailable data
Runtime Rule¶
A Runtime adapter may deliver Projections.
A Runtime adapter must not become the owner of Projection rules.
BC-064D3A / BC-064D3B¶
Catalog, Product and Offer public projection DTOs now have canonical ownership under src/Contracts/Projection.
Contracts\Projection\Catalog\*Contracts\Projection\Product\*Contracts\Projection\Offer\OfferProjection
Legacy concrete names under src/Projection remain thin compatibility wrappers only.
This keeps runtime/frontend payloads stable while allowing reader contracts to target neutral DTO namespaces.
BC-064D3C extends the same rule to Navigation:
Contracts\Projection\Navigation\NavigationProjectionContracts\Projection\Navigation\NavigationItemProjection
The historical Projection\Navigation\... names remain compatibility wrappers so public navigation rendering can keep the same adapter boundary and payload shape.
BC-064D3D completes the decoupling for mapper contracts:
FacetProjectionMappernow targets canonical Facet DTOs underContractsProductViewProjectionMappernow targets canonical Product/Offer DTOs underContracts
At the end of BC-064D3, src/Contracts no longer depends on src/Projection.
BC-064D4 certifies the final state:
- wrappers under
src/Projectionare compatibility-only inheritance shells; - canonical DTO ownership remains under
src/Contracts/Projection; - no new reflection bridge or extra autoload alias was introduced;
- the only historical alias still active is the explicit ProductCreatedV2 alias loaded from Composer files autoload.