Náš monolitický order systém měl endpoint, který při vytvoření objednávky synchronně volal inventory, payment, shipping a notification službu. Když jedna spadla, spadlo všechno. Event-driven architektura ten řetěz přeťala.
Request-response a jeho limity¶
Klasická architektura: služba A zavolá službu B, čeká na odpověď, pak zavolá službu C. Je to jednoduché, přímočaré a funguje to skvěle do okamžiku, kdy služba B má latenci 3 sekundy nebo služba C je úplně nedostupná.
V request-response modelu jsou služby časově provázané (obě musí běžet současně) a prostorově provázané (volající musí znát adresu volaného). Čím víc služeb v řetězci, tím křehčí systém. Jeden pomalý článek zpomalí celý chain. Jeden mrtvý článek ho zastaví.
Co je event-driven architektura¶
Místo přímých volání služby publikují eventy — zprávy o tom, co se stalo. „Objednávka vytvořena.” „Platba přijata.” „Zásilka odeslána.” Jiné služby se přihlásí k odběru eventů, které je zajímají, a reagují na ně asynchronně.
Order služba publikuje OrderCreated event. Payment služba
ho zachytí a zpracuje platbu. Inventory služba ho zachytí a rezervuje
zboží. Notification služba ho zachytí a pošle email. Každá nezávisle,
každá svým tempem. Když notification služba leží, objednávka projde
— email se pošle, až služba naběhne.
Apache Kafka jako páteř¶
Pro event-driven architekturu potřebujete message broker — místo, kam eventy putují a odkud se konzumují. RabbitMQ je tradiční volba pro message queuing. My jsme zvolili Apache Kafka, protože nabízí něco víc: distribuovaný, replikovaný log s retencí.
# Kafka topic pro objednávky
Topic: orders
Partitions: 12
Replication factor: 3
Retention: 7 days
# Event schema (Avro)
{
"type": "record",
"name": "OrderCreated",
"fields": [
{"name": "orderId", "type": "string"},
{"name": "customerId", "type": "string"},
{"name": "items", "type": {"type": "array", "items": "OrderItem"}},
{"name": "totalAmount", "type": "double"},
{"name": "timestamp", "type": "long"}
]
}
Kafka nemazá zprávy po přečtení — drží je po nastavenou dobu. Nový consumer může přečíst historii od začátku. Potřebujete přidat analytics službu, která zpracuje všechny objednávky za poslední měsíc? Nasaďte ji a nastavte offset na začátek. Žádný replay, žádný export z databáze.
Event Sourcing — pravda je v eventech¶
Tradiční přístup: uložíte aktuální stav do databáze. Event sourcing: uložíte sérii eventů, které k aktuálnímu stavu vedly. Stav odvodíte přehráním eventů. Jako účetní kniha versus zůstatek na účtu.
Výhody jsou zásadní. Kompletní audit trail — víte nejen jaký je stav, ale proč. Temporální dotazy — „jaký byl stav objednávky včera v 15:00?” Přehrajete eventy do daného bodu. Debugging — reprodukujete produkční chybu přehráním přesné sekvence eventů.
Nevýhody jsou také zásadní. Komplexita — je to jiný způsob myšlení a hodně vývojářů s ním bojuje. Event schema evoluce — jak měníte strukturu eventu, aniž rozbijete existující consumery? Eventual consistency — read model může být dočasně nekonzistentní s write modelem.
CQRS — oddělte čtení a zápis¶
Command Query Responsibility Segregation — oddělený model pro zápis (commands) a čtení (queries). Write model přijímá příkazy a generuje eventy. Read model se aktualizuje z eventů a je optimalizovaný pro dotazy.