infra+dotnet: replace Kafka with in-process Channel<T> message bus (HOL-23) - #102
Merged
Conversation
…OL-23)
Two infra containers (kafka + zookeeper) and a JVM stack went away
in service of the same producer/consumer call shape. The .NET backend
already runs in all-in-one mode where producer and worker share a
process; the only thing Kafka was buying us at hobby scale was a JVM
broker, two extra containers, and ~750 MiB of resident memory.
New abstraction (HoldFast.Shared.Messaging):
- IMessageBus — PublishAsync<T>(topic, key, value, ct) + SubscribeAsync(topic, ct)
- InProcessMessageBus — singleton with one Channel<(string, string)> per
topic, lazily created. Unbounded (queue depth stays trivial at hobby
scale; observed growth is the signal to swap in a Kafka-backed
implementation).
- MessageConsumerBase<T> — replaces KafkaConsumerService<T>; reads
channel, JSON-deserializes, dispatches to ProcessAsync.
Producer side:
- KafkaProducerAdapter — same IKafkaProducer interface, now wraps
IMessageBus instead of KafkaProducerService. Class name kept for now
to keep the diff narrow; rename pending follow-up.
Consumer side:
- All six consumers (SessionEvents, ErrorGrouping, FrontendErrors,
Logs, Metrics, Traces) re-based from KafkaConsumerService<T> to
MessageConsumerBase<T>. Only the constructor signature changed
(IOptions<KafkaOptions> → IMessageBus); ProcessAsync bodies are
unchanged.
Removed:
- HoldFast.Shared/Kafka/{KafkaConsumerService, KafkaProducerService,
KafkaTopicBootstrapService}.cs
- HoldFast.Shared.Tests/Kafka/KafkaOptionsTests.cs
- Confluent.Kafka package reference
- Program.cs: Kafka DI block + KafkaTopicBootstrap registration
- compose.yml: kafka + zookeeper services and their volumes
- compose.hobby-dotnet.yml: Kafka__BootstrapServers env mapping
- .env.example: KAFKA_*, ZOOKEEPER_IMAGE_NAME
- env.sh: KAFKA_ADVERTISED_LISTENERS, KAFKA_SERVERS exports
- start-infra.sh: kafka+zookeeper from SERVICES list
Tradeoffs accepted:
- No durability — messages live in memory; backend restart drops the
in-flight queue. Acceptable at hobby scale; pushPayload still 200s,
SDK retry covers transient loss.
- Single-process only — producer and consumer must be in the same
.NET host (matches our all-in-one runtime mode).
- No replay / consumer groups / rebalancing.
Verified end-to-end:
- All 3,027 .NET tests pass (was 3,037; -10 from the deleted
KafkaOptionsTests).
- Smoke test (./infra/docker/smoke-test-ingest.sh) passes against the
reduced stack.
- docker compose ps now shows 4 HoldFast containers (backend,
clickhouse, frontend, postgres) — was 6 after HOL-22, was 9 originally.
- Total stack RAM at idle: ~910 MiB (was 12+ GiB before HOL-18).
Stacks on HOL-22. Subtask of HOL-17. Closes HOL-23.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BrewingCoder
force-pushed
the
issue-23-drop-kafka
branch
from
May 9, 2026 14:42
be73ec7 to
ee6da0b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two infra containers (kafka + zookeeper) and a JVM stack went away in service of the same producer/consumer call shape. The .NET backend already runs in all-in-one mode where producer and worker share a process; the only thing Kafka was buying us at hobby scale was a JVM broker, two extra containers, and ~750 MiB of resident memory.
New abstraction (
HoldFast.Shared.Messaging)IMessageBus—PublishAsync<T>(topic, key, value, ct)+SubscribeAsync(topic, ct)InProcessMessageBus— singleton with oneChannel<(string, string)>per topic, lazily created. Unbounded (queue depth stays trivial at hobby scale; observed growth is the signal to swap in a Kafka-backed implementation).MessageConsumerBase<T>— replacesKafkaConsumerService<T>; reads channel, JSON-deserializes, dispatches toProcessAsync.Consumer mechanical refactor (6 files)
All six consumers (SessionEvents, ErrorGrouping, FrontendErrors, Logs, Metrics, Traces) re-based from
KafkaConsumerService<T>toMessageConsumerBase<T>. Only the constructor signature changed (IOptions<KafkaOptions>→IMessageBus);ProcessAsyncbodies unchanged.Removed
HoldFast.Shared/Kafka/{KafkaConsumerService, KafkaProducerService, KafkaTopicBootstrapService}.csConfluent.Kafkapackage reference (HoldFast.Shared.csproj)Program.cs: Kafka DI block + KafkaTopicBootstrap registrationcompose.yml: kafka + zookeeper services + kafka-data/zoo-* volumescompose.hobby-dotnet.yml:Kafka__BootstrapServersenv.env.example:KAFKA_*,ZOOKEEPER_IMAGE_NAMEenv.sh: KAFKA exportsstart-infra.sh: kafka+zookeeper from SERVICES listHoldFast.Shared.Tests/Kafka/KafkaOptionsTests.cs(class deleted)Tradeoffs
IMessageBusimplementation in (Kafka, Redis Streams, RabbitMQ) without touching consumers.Verified
./infra/docker/smoke-test-ingest.sh) passes end-to-end against the lean stackdocker compose psnow shows 4 HoldFast containers (backend, clickhouse, frontend, postgres)Test plan
docker compose down && docker compose up -d→ 4 containers come upConnection refused localhost:9092errors anywhereIn-process consumer started for topic ...for each of session-events, backend-errors, frontend-errors, logs, metrics, tracesStacks on #101 (HOL-22). Subtask of HOL-17. Closes HOL-23.
🤖 Generated with Claude Code