Aller au contenu

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\NavigationProjection
  • Contracts\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:

  • FacetProjectionMapper now targets canonical Facet DTOs under Contracts
  • ProductViewProjectionMapper now targets canonical Product/Offer DTOs under Contracts

At the end of BC-064D3, src/Contracts no longer depends on src/Projection.

BC-064D4 certifies the final state:

  • wrappers under src/Projection are 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.