Integrační zprávy a kontrakty
Veřejné, verzované zprávy mezi službami – události („stalo se“) a příkazy („udělej“).
Na této stránce
V jedné větě: Integrační zpráva je smlouva mezi službami: jednoduchý záznam (
record) jen s daty, s verzí v názvu (…V1), který se po zveřejnění už nemění.
Události vs. příkazy
Událost (IIntegrationEvent) |
Příkaz (IIntegrationCommand) |
|
|---|---|---|
| Gramatika | minulý čas: StockReservedV1 |
rozkazovací způsob: ReserveStockV1 |
| Kolik příjemců | 0 až N – odesílatel neví kdo | právě jeden – služba, které příkaz patří |
| Odpověď | žádná | očekává se událost (StockReservedV1 / …FailedV1) |
| Přirovnání | oznámení na nástěnce | objednávka u dodavatele |
Pravidlo „jeden příkaz = jeden příjemce“ hlídá IntegrationMessageRouting už při startu.
Všechny kontrakty
| Kontrakt | Druh | Oblast | Pole |
|---|---|---|---|
ProductRemovedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, ProductId: Int32 |
ProductUpdatedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, ProductId: Int32, Name: String, Price: Decimal |
ProductUpdatedV2 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, ProductId: Int32, Name: String, Description: String, Price: Decimal, CategoryId: Int32, CategoryName: String, ImageFileName: String, Availability: String, AvailabilityChangedAtUtc: DateTime |
ReleaseStockV1 |
příkaz | Inventory | CommandId: Guid, OccurredAtUtc: DateTime, OrderId: Int32 |
ReserveStockV1 |
příkaz | Inventory | CommandId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, Lines: IReadOnlyList<StockLineV1> |
StockAvailabilityChangedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, ProductId: Int32, Availability: String |
StockReleasedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32 |
StockReservationFailedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, Reason: String |
StockReservedV1 |
událost | Inventory | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32 |
SyncCatalogV1 |
příkaz | Inventory | CommandId: Guid, OccurredAtUtc: DateTime |
OrderCancelledV1 |
událost | Orders | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, CustomerEmail: String, Lines: IReadOnlyList<OrderLineV1> |
OrderCompletedV1 |
událost | Orders | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, CustomerEmail: String |
OrderPlacedV1 |
událost | Orders | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, CustomerEmail: String, TotalPrice: Decimal, Lines: IReadOnlyList<OrderLineV1> |
OrderShippedV1 |
událost | Orders | EventId: Guid, OccurredAtUtc: DateTime, OrderId: Int32, CustomerEmail: String, Carrier: String, TrackingNumber: String |
Tabulka je vygenerovaná z kódu (MiniShop.Contracts) při zobrazení stránky.
Kód: MiniShop.Contracts. Projekt nemá žádné závislosti, takže ho může referencovat kdokoli.
Překlad: z doménové události na integrační
Doménové události jsou interní a smí se měnit. Ven proto jde překlad – jen data, která ostatní potřebují:
flowchart LR
d1["OrderPlaced<br/>(drží celou Order)"] --> t["PublishOrderIntegrationEventsHandler<br/>překladač"]
t --> i1["OrderPlacedV1<br/>OrderId, e-mail, cena, řádky"]
d2["ProductChanged"] --> ctx["InventoryDbContext.ToOutboxMessage<br/>překlad při uložení"]
ctx --> i2["ProductUpdatedV1<br/>ProductId, název, cena"]
click t "/Docs/architektura/moduly/ordering"
click ctx "/Docs/architektura/moduly/inventory"
- Ordering překládá v handleru PublishOrderIntegrationEventsHandler – je to jediné místo, kde se rozhoduje, co se okolní svět o objednávce dozví.
- Inventory překládá přímo při uložení v InventoryDbContext – o kolo workeru rychleji.
Pravidla verzování
- Kontrakt se po zveřejnění nepřejmenovává a nemění. Příjemce může běžet ve starší verzi.
- Větší změna = nový kontrakt
…V2; chvíli se posílají oba, dokud všichni příjemci nepřejdou. - Hlídá to IntegrationContractTests – test zná přesný tvar každého kontraktu a změnu neprojde bez vědomého rozhodnutí.
Příklad: ProductUpdatedV1 → ProductUpdatedV2 (expand → contract)
E-shop krok 2 potřeboval ve zprávě o produktu popis, kategorii a dostupnost. Postup ve dvou fázích:
| Fáze | Odesílatel (sklad) | Příjemce (Ordering) |
|---|---|---|
| expand (krok 2) | posílá obě verze – každá zpráva má vlastní ID | zpracuje obě; V1 kvůli zprávám, které při nasazení čekaly ve frontě |
| contract (později) | přestane posílat V1 | handler V1 se odstraní |
Přirovnání: Úřad vydá nový tiskopis „vzor 2“. Nějakou dobu přijímá oba, protože lidé mají doma vyplněný starý. Teprve když starý nikdo nenosí, přestane ho přijímat.
Jedna doménová událost (ProductChanged) tak vede na dvě zprávy v outboxu – ToOutboxMessages modulu jich může vrátit víc.
ID druhé zprávy je odvozené z ID události (MessageIds):
stejné při opakovaném uložení, ale jiné než u V1 – inbox příjemce rozlišuje zprávy podle (ID, handler) a V2 by jinak
zahodil jako duplicitu.
Proč takhle
- Služby se nasazují nezávisle – kontrakt je jediné, na čem se musí shodnout.
- Interní model (entita
Order) se může libovolně měnit, protože ven nikdy přímo nejde.