Kopie katalogu (projekce)

Ordering si drží vlastní kopii katalogu (název, cena, popis, kategorie, dostupnost), kterou udržují události skladu – i pro výlohu a hledání.

Na této stránce

V jedné větě: Ordering nesahá do databáze skladu – drží si vlastní kopii (CatalogProduct) jen s tím, co objednávka a výloha potřebují (název, cena, popis, kategorie, dostupnost), a aktualizuje ji podle událostí ProductUpdatedV1/V2, StockAvailabilityChangedV1 a ProductRemovedV1.

Jaký problém řeší

Při objednávce je potřeba znát název a cenu produktu. Možnosti:

Varianta Problém
číst tabulku skladu sdílená databáze = skrytá závislost, sklad nemůže měnit schéma
zavolat sklad přes HTTP když sklad neběží, nejde objednávat
vlastní kopie z událostí data jsou pár sekund pozadu (eventual consistency) – to je přijatelná cena

Přirovnání: Prodejna má vlastní ceník. Sklad posílá oběžník při každé změně ceny; prodejna si ceník přepíše. Nemusí kvůli každému zákazníkovi volat do skladu.

Jak to funguje

sequenceDiagram
    participant U as Klient REST API skladu
    participant I as Inventory.Api
    participant MQ as RabbitMQ
    participant H as CatalogProjectionHandler
    participant C as CatalogProducts (ordering.db)

    U->>I: změň cenu produktu
    I->>I: Product.Update → ProductChanged → ProductUpdatedV1 do outboxu
    I->>MQ: ProductUpdatedV1 (OccurredAtUtc = čas změny)
    MQ->>H: fronta minishop.ordering
    H->>C: Apply(název, cena, čas) – jen když je změna novější

Na co si dát pozor

  • Pořadí zpráv není zaručené (retry, opakování). Kopie proto porovná čas změny a starší změnu ignoruje.
  • Smazaný produkt se nemaže, jen označí (IsRemoved). Kdyby se smazal, pozdě doručená starší změna by ho „vzkřísila“.
  • Cena v objednávce je snímek. Položka si uloží název a cenu v okamžiku objednání – pozdější změna ceny starou objednávku neovlivní.
  • Obnova: když je kopie prázdná (nová ordering.db) nebo neúplná (produkt bez dostupnosti), Web po startu pošle SyncCatalogV1 a sklad znovu pošle všechny produkty. Viz Obnova katalogu.

Výloha: popis, kategorie a dostupnost (e-shop krok 2)

Výloha (/Shop) potřebuje víc než objednávka. Kopie katalogu se proto rozšířila – a ukazuje dva vzory:

1. Nová verze kontraktu místo změny staré. Popis a kategorie nejdou přidat do ProductUpdatedV1 (kontrakt se po zveřejnění nemění). Sklad posílá ProductUpdatedV2 s celým stavem produktu – a zatím i V1 vedle ní (expand → contract, viz Integrační zprávy). Projekce zpracuje obě; V2 má odvozené vlastní ID, jinak by ji inbox projekce zahodil jako duplicitu V1 (MessageIds).

2. Událost navržená podle potřeby příjemce. Výloha nepotřebuje přesný počet kusů – jen pásmo:

Pásmo Zásoba ve skladu
Skladem víc než 5 ks
Poslední kusy 1–5 ks
Vyprodáno 0

Sklad pošle StockAvailabilityChangedV1 jen při změně pásma (doménová událost StockAvailabilityChanged v Product), ne při každém prodaném kusu. Dostupnost má v kopii vlastní čas (AvailabilityChangedAtUtc) – změna ceny a vyprodání jsou nezávislé zprávy a nesmí se přepisovat. ProductUpdatedV2 nese dostupnost jako snímek, takže nový produkt ji zná hned. Tok viz Změna dostupnosti.

Přirovnání: Prodejna nepotřebuje inventurní soupis skladu – stačí jí cedulka „poslední kusy“ nebo „vyprodáno“. Sklad volá, jen když se má cedulka vyměnit.

Dostupnost ve výloze může být o pár sekund pozadu. O objednávce rozhoduje až rezervace v sáze – výloha to zákazníkovi říká.

Obrázky (e-shop krok 2B)

Obrázek je soubor ve skladu – do zprávy jde jen jeho jméno (ProductUpdatedV2.ImageFileName), kopie katalogu ho uloží jako každé jiné pole. Výloha pak obrázek načte z adresy Webu /media/products/{soubor}, který ho stáhne ze skladu. Jméno obsahuje otisk obsahu, takže nový obrázek = nové jméno = nová zpráva – a prohlížeč si každý obrázek smí uložit natrvalo. Podrobnosti v modulu Inventory.

Přirovnání: V katalogu zásilkové služby je u zboží jen číslo fotky. Fotky leží v archivu a tiskárna si je vyzvedne, až je potřebuje – nikdo je nerozesílá s každou změnou ceny.

Hledání: read model tvarovaný pro čtení (CQRS)

Výloha hledá jen v kopii katalogu (CatalogQueryService) – funguje i při výpadku skladu. SQLite ale porovnává bez ohledu na velikost písmen jen ASCII a o diakritice neví („mys“ by nenašlo „Myš“). Kopie si proto při každé změně uloží SearchText – název, popis a kategorii malými písmeny bez diakritiky (CatalogSearchText) – a stejně se upraví i hledaný text. To je podstata CQRS: data pro čtení jsou uložená tak, jak se z nich čte, ne jak se zapisují.

  • Filtr (text, kategorie, „jen skladem“) dělá databáze; řazení podle ceny a české řazení názvů až aplikace (SQLite neřadí decimal) – u stovek produktů to nevadí.
  • Kategorie ve filtru vznikají z kopie (jen ty, ve kterých je něco v nabídce). Kategorie samotné spravuje sklad; Ordering zná jejich jméno jen ze zpráv o produktech – přejmenování kategorie proto sklad oznámí novou ProductUpdatedV2 pro každý její produkt.

Známá omezení

  • Nový produkt je v nabídce objednávek za ~2–4 s, změna dostupnosti stejně.
  • Obnova produkty jen přidává/aktualizuje – zmeškané ProductRemovedV1 neopraví.
  • Sklad zatím posílá V1 i V2 (dvojnásobek zpráv o produktech); odebrání V1 je samostatný krok.
  • Hledá se podřetězcem (instr) bez indexu a bez překlepů – pro stovky produktů stačí, pro tisíce by byl potřeba fulltext (SQLite FTS5) nebo vyhledávací služba.

Na internetu