How it works
The explanation, with examples you can run.
Kafka Streams
StreamsBuilder builder = new StreamsBuilder();KStream<String, Order> orders = builder.stream("orders"); // key: customer idKTable<String, Long> paidPerCustomer = orders .filter((customerId, order) -> order.status().equals("paid")) .groupByKey() .count(Materialized.as("paid-per-customer")); // a state storepaidPerCustomer.toStream().to("paid-per-customer");application.id=paid-counterbootstrap.servers=kafka1:9092processing.guarantee=exactly_once_v2The ideas:
- KStream is a stream of events; KTable is a changelog seen as a table, the latest value per key. Joins between them are what make Streams more than a consumer loop.
- Tasks and partitions. The topology is split into tasks, one per input partition. Instances with the same
application.idform a consumer group and share the tasks, so you scale by starting more instances, up to the partition count. - State.
count,aggregateand joins keep state in local stores (RocksDB by default). Every update is also written to a compacted changelog topic. When a task moves, its new owner replays the changelog to rebuild the store; standby replicas keep warm copies elsewhere. - Exactly-once. With
exactly_once_v2, reading input, updating state and writing output happen in Kafka transactions (the delivery topic). External calls inside the topology are not covered. - Repartitioning. Grouping by a different key writes records through an internal repartition topic, which costs a round trip through the brokers.
Streams runs inside your service, with no separate processing cluster. Engines such as Apache Flink offer more (other sources, larger state, SQL), at the cost of running one.
Kafka Connect
A Connect cluster is a group of worker processes. You submit a connector configuration over its REST API, and the workers run the connector's tasks, record source offsets in Kafka topics and restart failed tasks. Not run here; a CDC source connector for PostgreSQL using Debezium might look like:
{ "name": "orders-cdc", "config": { "connector.class": "io.debezium.connector.postgresql.PostgresConnector", "database.hostname": "db", "database.dbname": "shop", "topic.prefix": "shop", "table.include.list": "public.orders", "key.converter": "org.apache.kafka.connect.json.JsonConverter", "value.converter": "org.apache.kafka.connect.json.JsonConverter", "transforms": "route", "transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter", "transforms.route.regex": "shop.public.(.*)", "transforms.route.replacement": "cdc.$1" }}(Credentials omitted.) The parts you configure for any connector:
- Connector class and tasks. Source connectors poll a system and produce; sink connectors consume and write.
- Converters turn records into bytes and back: JSON, or Avro and Protobuf with a schema registry.
- Single message transforms rename topics, mask fields or route records, one record at a time. Anything heavier belongs in Streams or a consumer.
- Errors.
errors.tolerancedecides whether one bad record stops the connector; sink connectors can also send failed records to a dead letter queue topic.
Connectors are separate plugins, many maintained outside Apache Kafka (Debezium for CDC, others for JDBC, object storage and search engines), so check each one's delivery guarantees and licence.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.