Sdílené jádro zpráv
Společné „potrubí“ obou služeb – outbox, inbox, dispatchery, RabbitMQ a telemetrie. Nezná žádný modul.
Na této stránce
V jedné větě: Sdílené jádro je pošta: umí zprávu bezpečně uložit, doručit a zajistit, že se zpracuje jen jednou. Co je v zásilce, ji nezajímá – to řeší moduly.
Komponentní diagram
flowchart LR
subgraph save["Ukládání (v transakci s daty)"]
mdb["ModuleDbContext<br/>SaveChanges → OutboxMessages"]
factory["OutboxMessageFactory<br/>+ TraceParent"]
end
subgraph deliver["Doručování (na pozadí)"]
worker["OutboxWorker<br/>každé 2 s"]
proc["OutboxProcessor<br/>jeden na modul (TDbContext)"]
ddisp["DomainEventDispatcher"]
bus["IIntegrationBus<br/>RabbitMqIntegrationBus"]
end
subgraph receive["Příjem"]
cons["RabbitMqConsumersWorker<br/>RabbitMqModuleConsumer"]
idisp["IntegrationMessageDispatcher"]
guard["InboxGuard<br/>ProcessedMessages"]
deco["IdempotentDomainEventHandler<br/>(dekorátor)"]
end
mq[["RabbitMQ"]]
handlers["Handlery modulů"]
mdb --> factory
worker --> proc
proc -->|doménová zpráva| ddisp
proc -->|integrační zpráva| bus
bus --> mq
mq --> cons
cons --> idisp
idisp --> guard
ddisp --> deco
deco --> guard
guard --> handlers
click mdb "/Docs/eda/outbox"
click proc "/Docs/eda/outbox"
click worker "/Docs/eda/outbox"
click bus "/Docs/eda/broker"
click cons "/Docs/eda/broker"
click guard "/Docs/eda/inbox"
click deco "/Docs/eda/inbox"
click factory "/Docs/eda/observabilita"
click ddisp "/Docs/eda/domenove-udalosti"
Komponenty
| Komponenta | K čemu slouží | Kód |
|---|---|---|
| ModuleDbContext | Předek databáze modulu. V SaveChangesAsync uloží data i zprávy do outboxu v jedné transakci. |
ModuleDbContext.cs |
| OutboxMessage / Factory | Řádek outboxu: typ, JSON, druh (doménová / integrační), pokusy, chyba, TraceParent. |
OutboxMessage.cs, OutboxMessageFactory.cs |
| OutboxWorker | Background service – každé 2 s zavolá zpracování outboxu všech modulů služby. | OutboxWorker.cs |
| OutboxProcessor | Vybere dávku (20) nezpracovaných zpráv, doručí je, označí. Max. 5 pokusů, pak poison message. | OutboxProcessor.cs |
| DomainEventDispatcher | Najde v DI všechny handlery doménové události a zavolá je. | DomainEventDispatcher.cs |
| RabbitMqIntegrationBus | Publikuje integrační zprávu do exchange pojmenované podle kontraktu, čeká na potvrzení brokeru. | RabbitMqIntegrationBus.cs |
| InProcessIntegrationBus | Totéž bez RabbitMQ – doručí rovnou v procesu. Používají ho testy. | InProcessIntegrationBus.cs |
| RabbitMqModuleConsumer | Poslouchá frontu minishop.{modul} a předá zprávu dispatcheru. |
RabbitMqModuleConsumer.cs |
| IntegrationMessageRouting | Kdo odebírá jaký kontrakt; hlídá, že příkaz má právě jednoho příjemce. | IntegrationMessageRouting.cs |
| IntegrationMessageDispatcher | Doručí integrační zprávu handlerům jednoho modulu, každý přes inbox. | IntegrationMessageDispatcher.cs |
| InboxGuard | „Sešit odškrtnutých zásilek“ – spustí handler jen jednou pro dvojici (ID zprávy, handler). | InboxGuard.cs |
| IdempotentDomainEventHandler | Dekorátor, který obalí každý doménový handler inboxem. | IdempotentDomainEventHandler.cs |
| MessagingTelemetry | Spany a metriky zpráv (zdroj MiniShop.Messaging). |
MessagingTelemetry.cs |
| OutboxHealthCheck | /health hlásí Degraded, když zpráva visí v outboxu déle než 5 minut. |
OutboxHealthCheck.cs |
| DatabaseMigrator | Vytvoří nebo aktualizuje databázi modulu přes EF Core migrace, i starou databázi bez historie migrací (baseline). | DatabaseMigrator.cs |
Migrace databází
Každý modul má vlastní SQLite databázi a vlastní EF Core migrace – očíslované kroky „z verze N na N+1“
(složky Ordering/Migrations, Inventory/Migrations, Journeys/Migrations, Accounts/Migrations v Infrastructure).
Přirovnání: stavební deník – místo stavby nového domu při každé přestavbě kuchyně se zapíše, co se upravilo.
Při startu služby zavolá
…DatabaseInitializermodulu DatabaseMigrator.Migratese podívá do tabulky__EFMigrationsHistorya spustí jen migrace, které tam ještě nejsou.Baseline: databáze z doby před migracemi (vytvořené přes
EnsureCreated) tabulky mají, historii ne. Migrátor je pozná podle značkové tabulky modulu (Orders,Products,JourneyEvents,AspNetUsers) a první migraci do historie zapíše jako provedenou – jinak by ji spustil znovu a spadl na existující tabulce.DatabaseMigrationTests hlídají, že model nemá změny bez migrace a že první migrace vytvoří přesně stejné schéma jako
EnsureCreated(jen tak je baseline správně).Nová migrace (z kořene repozitáře, poprvé
dotnet tool restore):dotnet ef migrations add <Nazev> --project src/MiniShop.Infrastructure --context OrderingDbContext --output-dir Ordering/MigrationsVygenerovaný kód migrace je potřeba přečíst: přejmenování sloupce EF někdy pozná jako smazání a přidání (= ztráta dat).
Jak se jádro zapojí do modulu
Modul jen řekne, jakou má databázi a jaké handlery. Zbytek udělá jádro:
// Ordering (Web): handler doménové události – vždy obalený inboxem
services.AddDomainEventHandler<OrderPlaced, OrderSagaOrchestrator>();
// Ordering (Web): handler integrační zprávy z fronty minishop.ordering
services.AddIntegrationMessageHandler<StockReservedV1, OrderSagaOrchestrator>(IntegrationModules.Ordering);
// Inventory (Inventory.Api): příkaz – smí mít jen jednoho handlera
builder.Services.AddIntegrationMessageHandler<ReserveStockV1, ReserveStockHandler>(IntegrationModules.Inventory);
Handlery se registrují jen přes tyto metody – kdyby se zaregistrovaly přímo do DI, obešly by inbox a při opakovaném doručení by proběhly dvakrát.
Proč vlastní implementace (a ne knihovna)
Pro produkci existují hotové knihovny (MassTransit, NServiceBus, Wolverine), které outbox, inbox i ságy umí. MiniShop je výukový – vlastní malá implementace ukazuje, co se v takové knihovně děje uvnitř. V reálném projektu by bylo rozumné sáhnout po knihovně.
Na internetu
- MassTransit – nejrozšířenější .NET knihovna pro zprávy
- Enterprise Integration Patterns: Messaging