Váš monolit funguje. Objednávky se zpracovávají, platby procházejí, zákazníci dostávají e-maily. Ale pokaždé, když chcete přidat novou funkci, trvá to měsíce. Každý deployment je noční můra. A když spadne jedna služba, spadne všechno. Tohle je příběh o tom, jak z toho ven — a proč event-driven architektura není jen buzzword, ale produkčně ověřený pattern.
Tradiční monolitická architektura funguje na principu request-response. Služba A zavolá službu B, počká na odpověď, pak zavolá službu C. Všechno je synchronní, tightly coupled a závislé na dostupnosti každé komponenty v řetězci. Funguje to — dokud systém neroste.
Problémy se objeví ve třech rovinách. Za prvé, temporal coupling: všechny služby musí být online současně. Za druhé, škálovatelnost: nemůžete škálovat jednu část systému nezávisle na ostatních. A za třetí, evolvability: změna v jedné službě kaskádově ovlivní všechny, které na ní závisí.
Event-driven architektura (EDA) tyto problémy řeší fundamentálně jiným přístupem. Místo „zavolej a čekej" říká: „stalo se něco — koho to zajímá, ať si to přečte." Producent emituje event, neví ani nepotřebuje vědět, kdo ho konzumuje. Konzumenti si zprávy čtou ve vlastním tempu. Loosely coupled, asynchronní, resilientní.
Když mluvíme o event-driven architektuře v enterprise měřítku, mluvíme o Apache Kafka. Existují alternativy — RabbitMQ, Apache Pulsar, NATS, AWS EventBridge — ale Kafka se stala de facto standardem pro systémy, které potřebují zpracovávat miliony eventů za sekundu s garancí doručení a persistence.
Kafka není message queue. Je to distributed commit log — trvalý, uspořádaný, replikovatelný záznam všeho, co se v systému stalo. Eventy se nemažou po přečtení. Zůstávají v logu podle nastavené retention policy (dny, týdny, navždy). To znamená, že nový consumer může začít číst od začátku a „přehrát" si celou historii.
// Kafka Producer — odeslání OrderCreated eventu
Properties props = new Properties();
props.put("bootstrap.servers", "kafka-1:9092,kafka-2:9092");
props.put("key.serializer", StringSerializer.class);
props.put("value.serializer", KafkaAvroSerializer.class);
props.put("schema.registry.url", "http://schema-registry:8081");
props.put("acks", "all");
props.put("enable.idempotence", "true");
var producer = new KafkaProducer<>(props);
var event = OrderCreated.newBuilder()
.setOrderId("ORD-2026-00142")
.setCustomerId("CUST-8837")
.setAmount(new BigDecimal("24990.00"))
.setCurrency("CZK")
.setTimestamp(Instant.now())
.build();
producer.send(new ProducerRecord<>(
"orders.created", event.getOrderId(), event
));
// Kafka Consumer — consumer group pro inventory službu
@KafkaListener(
topics = "orders.created",
groupId = "inventory-service",
containerFactory = "kafkaListenerFactory"
)
public void handleOrderCreated(OrderCreated event) {
log.info("Reserving stock for order {}", event.getOrderId());
try {
inventoryService.reserveStock(event);
publishEvent("inventory.reserved",
new StockReserved(event.getOrderId()));
} catch (InsufficientStockException e) {
publishEvent("inventory.reservation-failed",
new ReservationFailed(event.getOrderId(), e.getMessage()));
}
}
Klíčové detaily: acks=all zajistí, že event je zapsán na všechny repliky před potvrzením. enable.idempotence=true eliminuje duplicity při network retries. A consumer groups umožňují horizontální škálování — přidáním dalších instancí se partitions automaticky redistribuují.
Event-driven architektura přirozeně vede ke dvěma pokročilým patternům, které mění způsob, jak přemýšlíme o datech.
V tradičním přístupu ukládáte aktuální stav: objednávka má status „zaplaceno." V event sourcing ukládáte sekvenci eventů, které ke stavu vedly: OrderCreated → PaymentReceived → OrderConfirmed. Aktuální stav je derivovaný — computed view nad historií eventů.
Výhody jsou zásadní. Máte kompletní audit trail — víte nejen co se stalo, ale kdy, v jakém pořadí a proč. Můžete přehrát historii a rekonstruovat stav systému k libovolnému bodu v čase. A můžete přidat nové projekce nad existující data bez migrace databáze.
// Event schema (Avro) — orders.avsc
{
"type": "record",
"name": "OrderEvent",
"namespace": "cz.core.orders.events",
"fields": [
{"name": "eventId", "type": "string"},
{"name": "eventType", "type": {
"type": "enum",
"name": "OrderEventType",
"symbols": ["CREATED","PAID","SHIPPED","DELIVERED","CANCELLED"]
}},
{"name": "orderId", "type": "string"},
{"name": "timestamp", "type": {"type": "long", "logicalType": "timestamp-millis"}},
{"name": "payload", "type": "string"},
{"name": "version", "type": "int", "default": 1}
]
}
Command Query Responsibility Segregation odděluje write model (příkazy, které mění stav) od read modelu (dotazy, které stav čtou). V kombinaci s event sourcing to znamená: příkazy produkují eventy do Kafka, a read modely jsou materializované pohledy optimalizované pro konkrétní dotazy.
Praktický příklad: e-shop zapisuje objednávky jako eventy. Jeden consumer buduje SQL projekci pro zákaznický detail objednávky. Druhý buduje Elasticsearch index pro fulltextové vyhledávání. Třetí počítá real-time metriky v ClickHouse. Stejná data, tři optimalizované pohledy, nulový coupling.
V monolitu máte databázové transakce. V distribuovaném systému ne. Saga pattern řeší distribuovanou konzistenci jako sekvenci lokálních transakcí, kde každý krok má definovanou kompenzační akci pro případ selhání.
Příklad: vytvoření objednávky zahrnuje rezervaci skladu, autorizaci platby a odeslání potvrzení. Pokud platba selže, saga automaticky spustí kompenzaci — uvolní rezervaci na skladě a vytvoří cancelled event. Existují dva přístupy:
V praxi u enterprise systémů preferujeme orchestration. Důvod je pragmatický: když saga selže ve třetím kroku z pěti, chcete jasně vidět, kde se to zaseklo, a mít jedno místo pro retry logiku. Choreography funguje pro jednoduché flows (2–3 kroky), ale u komplexních business procesů se stává neudržitelnou.
V distribuovaném systému eventy selžou. Nevalidní data, nedostupná služba, bug v consumer logice. Otázka není jestli, ale jak na to reagujete.
Dead Letter Queue (DLQ) je topic, kam se přesouvají eventy, které se nepodařilo zpracovat po nastaveném počtu pokusů. Místo nekonečného retry loopu nebo ztráty dat event „zaparkuje" v DLQ, kde čeká na manuální nebo automatizovanou opravu.
V momentě, kdy máte desítky služeb produkujících a konzumujících eventy, potřebujete kontrakt. Schema Registry zajišťuje, že producent a consumer se shodnou na formátu zprávy — a že evoluce schématu nezlomí existující consumery.
Nativní integrace s Kafka ekosystémem. Schema evolution s backward/forward compatibility. Kompaktní binární formát. Confluent Schema Registry jako standard. Ideální pro Kafka-first architektury.
Silná typovost, výborný code generation pro Java, Go, Python, TypeScript. Menší overhead pro nested struktury. Lepší tooling pro gRPC. Preferujeme pro polyglot prostředí s více jazyky.
Naše doporučení: pokud jste all-in na Kafka ekosystém (Confluent Platform, Kafka Streams, ksqlDB), zvolte Avro. Pokud máte polyglot prostředí s gRPC komunikací mezi službami, Protobuf dává větší smysl. V obou případech je klíčové nastavit compatibility mode (BACKWARD nebo FULL) na Schema Registry — bez toho je evoluce schémat časovaná bomba.
Jednou z nejsilnějších stránek event-driven architektury je schopnost stavět real-time analytiku přímo nad proudem eventů — bez batch ETL procesů, bez nočního čekání na reporty.
Typický stack vypadá takto: Kafka jako zdroj eventů → Kafka Streams nebo Apache Flink pro stream processing (agregace, windowing, enrichment) → ClickHouse nebo Apache Druid jako analytická databáze → Grafana nebo custom dashboard pro vizualizaci.
Praktické use cases, které implementujeme:
Event-driven architektura není něco, co zapnete přes noc. Je to transformační journey, která vyžaduje změnu v myšlení i v toolingu. V CORE SYSTEMS máme ověřený postup, jak klienty touto transformací provést.
Začínáme event storming workshopem — technika, kde s doménovými experty mapujeme business procesy jako sekvence eventů. Výstupem je event model: jaké eventy existují, kdo je produkuje, kdo konzumuje a jaké jsou závislosti. Tohle je základ, na kterém stavíme technickou architekturu.
Pak volíme strangler fig pattern pro postupnou migraci z monolitu. Neděláme big bang rewrite. Místo toho identifikujeme bounded context, který má největší business hodnotu jako samostatná služba, extrahujeme ho, napojíme přes Kafka, a teprve když funguje v produkci, pokračujeme s dalším.
Náš standardní stack pro event-driven systémy zahrnuje: Apache Kafka (Confluent Platform nebo Amazon MSK), Schema Registry s Avro/Protobuf, Kafka Streams nebo Flink pro stream processing, PostgreSQL nebo MongoDB pro materialized views a OpenTelemetry s distributed tracing pro observability napříč celým event flow.
Každý systém má od začátku: DLQ s monitoringem, schema compatibility checks v CI/CD, end-to-end tracing přes correlation ID, a runbook pro operátory — protože event-driven systém, který nikdo neumí provozovat, je horší než monolit.
Event-driven architektura mění způsob, jak přemýšlíme o datech a komunikaci mezi službami. Místo sdílených databází máme sdílené eventy. Místo synchronních řetězců máme asynchronní reaktivní systémy. Místo „aktuální stav" máme kompletní historii.
Ale pozor: EDA není silver bullet. Přináší vlastní komplexitu — eventual consistency, distributed debugging, schema management, ordering guarantees. Klíč k úspěchu je pragmatický přístup: začněte s jedním bounded contextem, dokažte hodnotu, a pak škálujte. Přesně tak, jak to děláme v CORE SYSTEMS.