Status: Planned - See Python implementation for working example
This example will demonstrate building a durable Kafka/RedPanda worker using the TypeScript SDK. A Python implementation already exists and serves as the reference for this TypeScript port.
Traditional Kafka consumers process messages sequentially. If the first message triggers a 1-hour operation and the next would take 30 seconds, the short job waits unnecessarily.
If a worker crashes mid-operation across multiple steps, it may redo completed work on restart, causing duplicates or inconsistent state.
Resonate solves both problems:
- Concurrent processing - Handle multiple messages in parallel
- Durable execution - Resume exactly where it left off after crashes
Producer → Kafka Topic → Resonate Worker → Kafka Topic → Next Consumer
(to_delete) (concurrent) (deleted)
The worker:
- Pulls messages from
records_to_be_deletedtopic - Spawns concurrent Resonate workflows (one per message)
- Each workflow processes independently with automatic retries
- Pushes completion messages to
records_that_were_deletedtopic
Messages contain record IDs to delete. The worker:
- Reads message from Kafka
- Starts durable workflow for that record ID
- Simulates batch deletion (unknown data volume)
- Retries on failures automatically
- Publishes completion message when done
Multiple workflows run concurrently, so slow deletions don't block fast ones.
- Kafka Client: kafkajs
- Resonate SDK: @resonatehq/sdk v0.9.6+
- RedPanda: Local Kafka-compatible message broker
src/
├── producer.ts # Message producer
├── consumer.ts # Kafka consumer + Resonate worker
├── workflows.ts # Deletion workflow (durable)
└── kafka-client.ts # Kafka connection setup
function* batchDeleteRecord(
ctx: Context,
recordId: string
): Generator<any, void, any> {
let remaining = true;
while (remaining) {
// Durable batch deletion with automatic retry
remaining = yield* ctx.run(deleteBatch, recordId);
if (!remaining) {
// Publish completion message
yield* ctx.run(publishCompletion, recordId);
}
}
}// Consume messages and spawn workflows
consumer.on('message', async (message) => {
const recordId = message.value.toString();
// Non-blocking: starts workflow and continues
await resonate.beginRpc(
`delete/${recordId}`,
'batchDeleteRecord',
recordId,
resonate.options({ target: 'poll://any@workers' })
);
});| Traditional Worker | Resonate + Kafka Worker |
|---|---|
| Sequential processing | Concurrent processing |
| Loses progress on crash | Resumes from checkpoint |
| Manual retry logic | Automatic retries |
| Head-of-line blocking | Independent workflows |
| Hard to scale | Horizontal scaling ready |
The Python implementation demonstrates the same concepts using the Python SDK. Key differences:
- Python uses
async/awaitsyntax - TypeScript uses generator functions (
function*withyield*) - Both SDKs provide the same durability and concurrency guarantees
Until the TypeScript version is built, reference the Python implementation:
- example-load-balancing-ts - Worker pool patterns
- example-async-rpc-py - Cross-process workflows
- example-countdown-kafka-ts - Kafka triggers
This TypeScript port is planned but not yet implemented. To contribute:
- Review the Python implementation for reference
- Follow patterns from resonate-develop-typescript
- Use kafkajs for Kafka client integration
- Include Docker Compose setup with RedPanda
- Add tests for concurrency and crash recovery
Interested in building this? Open an issue or reach out on Discord.