Kafka Streams starter¶
This starter creates a raw input topic, a SQL projection/filter, and its output topic. A downstream custom application appears in the same project with its consumed columns. You can review those dependencies before deploying anything.
raw_orders (managed topic)
-> eligible_orders (managed topic + Kafka Streams runner)
-> fraud_app (external application declaration; metadata only)
The SQL runner does not need Flink or ksqlDB. It executes a restricted plan with
STRING, BOOLEAN, and signed 64-bit BIGINT fields in raw JSON records. Joins,
windows, aggregations, nested JSON, and arbitrary functions are not supported.
The Flink walkthrough remains available, and streamt init
without an executor choice keeps its existing Flink-oriented starter.
Bounded updates
The CLI supports creation, unchanged repeat apply, and one reviewed predicate-only update at a time. Projection, schema, image, identity and stateful changes remain blocked. A custom application declaration does not deploy its code.
Choose the local runtime explicitly¶
Install streamt, then inspect the packaged build contract:
This command needs neither a project nor running infrastructure. To build the runner image, start local Docker Engine with Buildx and run:
The build downloads pinned base images and Maven dependencies. It builds on
the local Docker daemon and returns an immutable data.image SHA-256 ID. It
does not publish or tag an image. The build inputs are included in the installed
package; a repository checkout and a host Java installation are not required.
Generate the project¶
Use the returned image ID in this command. Replace the example broker addresses and network name with your own existing local infrastructure:
streamt init --project-dir orders --project-name orders \
--executor kafka_streams \
--runner-image 'sha256:<64 hexadecimal characters from data.image>' \
--kafka localhost:9092 \
--kafka-internal broker:19092 \
--docker-network streaming-local \
--initial-offset earliest
cd orders
--runner-image is required and must be an immutable image ID or repository
digest. Init checks its format but does not inspect, build, or pull the image.
It writes stream_project.yml, README.md, sample_events.jsonl, and the
existing sources/, models/, and tests/ directories. Add --dry-run to
preview filenames without writes. Existing starter files are protected unless
you explicitly pass --force.
No Docker/Kafka resources are created during init. The two broker addresses
describe different clients: your CLI connects from the host; the runner connects
from its container. The configured network must be a local Docker bridge
network. If the internal address uses a container name such as broker, use a
user-defined bridge with that name resolvable there. Docker's default bridge
does not provide service-name DNS. See
Docker bridge networking.
The starter is plaintext and uses one partition with replication factor one. Those settings are for local testing. A single-broker fixture also needs Kafka's transaction and consumer-offset internal-topic replication settings configured for that broker. TLS/SASL broker acceptance and production hardening are not established by this walkthrough.
Read and validate the topology¶
The input is a managed model without SQL. Creating a new topic does not create a producer or seed any data. The SQL model contains:
name: eligible_orders
ownership:
mode: managed
materialized: topic
executor: kafka_streams
sql: |
SELECT id, amount
FROM {{ ref("raw_orders") }}
WHERE amount >= 100 AND paid = TRUE
fraud_app consumes this model and declares that it needs id: STRING and
amount: BIGINT. A projected-column change can therefore surface a broken
application declaration in offline validation. The custom application's code,
scheduling, and health remain outside streamt's control.
The offline plan assumes the managed resources are absent: two topics and one Kafka Streams job. It does not contact Kafka/Docker or compare live state. Compilation produces no Flink job and needs no Schema Registry for this path.
Create and verify¶
After the local runner image, broker, and network are ready, review a live plan:
streamt plan --out reviewed-plan.json
streamt apply --plan reviewed-plan.json
streamt status --health
streamt plan
An unchanged second plan should be a no-op. On first creation, streamt records
operation intent before initializing source partitions at the explicit starting
position. The runner uses auto.offset.reset=none; missing or out-of-range offsets fail
instead of silently replaying or skipping data.
Preflight waits for a fresh broker's group coordinator with read-only requests under one deadline. If readiness or group absence cannot be established, creation fails before runtime mutation. Streamt does not retry an uncertain offset write.
The generated README contains producer/consumer commands for the four records
in sample_events.jsonl. Only {"id":"large-paid","amount":150} should reach
orders.eligible.orders.v1 from that fresh sample. Read the output with
isolation.level=read_committed. Producing the file again appends new records;
it is not a deduplicated seed operation.
Raw values must contain exactly id, amount, and paid. A nullable amount
is explicit JSON null. Missing/extra fields, malformed JSON, wrong types, or
out-of-range integers stop the runner. Kafka keys are preserved; tombstones
are dropped. These are append-stream semantics, not table/upsert semantics.
Review and apply a filter change¶
In stream_project.yml, change amount >= 100 to amount >= 200 while keeping
the projection, topics and runtime unchanged:
streamt validate --strict
streamt plan --out filter-change.json
streamt apply --plan filter-change.json
streamt plan
The online full-project plan contains one predicate-only replacement and no other mutations. It uses reviewed format 6 to bind the old and new plans to the exact runner, volume and Kafka identities. Apply cleanly stops the old runner and starts its replacement using the same application ID and retained offsets. This introduces a processing pause. It neither resets progress nor retracts records already emitted. New records use the changed filter; the final plan should show no changes.
Direct apply, selectors, mixed mutations and --force cannot bypass this
contract. Keep the reviewed file until the operation has completed. If apply
loses a response, its JSON reports committed: null and gives commands containing
the original plan and exact operation UUID. Use state runner-status to inspect,
then state resume to continue that same operation. Keep the SQL and environment
unchanged while doing so. An already-completed result is verified from its receipt
and live candidate without redeployment. See the
runner interruption runbook
for commands, environment confirmation and remaining blockers.
Start from an existing topic¶
The other entry point is discovery and import. Imported sources remain external; discovery does not adopt their lifecycle or infer an unknown raw-JSON contract. Review their topic names and add matching declared columns before creating a SQL consumer.
The generated README includes this variant: replace the managed raw_orders
input with an imported external source, then use source() instead of ref().
The managed output and SQL runner remain in the same topology. Streamt does
not create, reconfigure, seed, or continuously reconcile the external source.
Runner preflight still checks the source identity and offsets needed by the
managed consumer.