Repository navigation
Slice 004: NotifyBaker over RabbitMQ on both twins - #13
Merged
Merged
Conversation
Erik's call, 2026-09-05: the baker notification crosses a real RabbitMQ broker on BOTH twins, so the talk can show what one message costs a layered codebase against a slice. Same broker both sides; only the idiom changes. No new endpoint, no contract change. - docs/slices/004-notify-baker-over-rabbitmq.md: the slice spec (contract unchanged from 003, required before-twin structure, after-twin shape, fixture-only suite changes, out of scope). - openspec change proposed, applied, and archived in one pass; design.md records the settled decisions (queue per twin, publish after commit with no outbox, consumer in WebApi re-dispatching through MediatR, ExistsForOrderAsync idempotency, UseDurableOutbox on the publish rule, one RabbitMQ container per twin collection). - openspec/specs/orders: the exactly-one-baker-task requirement now says the notification is delivered through a RabbitMQ queue in both twins before the task is written. Scenario list unchanged. - openspec/config.yaml: four slices; dotnet test starts PostgreSQL and RabbitMQ through Testcontainers.
Before twin (raw RabbitMQ.Client 7.2.2, no framework, no outbox): - IMessagePublisher port in Application; NotifyBakerMessage; a CreateBakerTask command + handler as the consumer's re-dispatch target, idempotent through IBakerTaskRepository.ExistsForOrderAsync. - RabbitMqOptions, RabbitMqConnection (one lazy connection per host, disposed with it), RabbitMqMessagePublisher (channel per publish, durable queue, camelCase JSON, persistent delivery, default exchange). Options bound by hand: the section binder lives in a package this project does not reference and the freeze allows no third addition. - NotifyBakerConsumer BackgroundService in WebApi: own channel, same queue declare, manual ack, DI scope, ISender.Send, log-and-nack on failure. - PlaceOrderCommandHandler: IBakerTaskRepository out, IMessagePublisher in; publish after SaveChangesAsync. The dual-write gap is owned in one comment. After twin, Program.cs only: UseRabbitMq unconditional, PublishMessage<NotifyBaker>().ToRabbitQueue(...).UseDurableOutbox(), ListenToRabbitQueue(...). UseDurableLocalQueues removed; no local message remains. PlaceOrder.cs and NotifyBaker.cs untouched. Contract suite, fixtures only: Testcontainers.RabbitMq 4.14.0, one rabbitmq:4 container per twin collection, connection string into both hosts, DisableAllExternalWolverineTransports dropped from the after fixture. No scenario changed. Package ADDITIONS at Erik's call, never bumps: RabbitMQ.Client 7.2.2 (the version WolverineFx.RabbitMQ 6.30.0 already depends on) and Testcontainers.RabbitMq 4.14.0. Existing pins untouched. Build 0 warnings. Contract suite 54/54, unit tests 15/15, three runs. dotnet test --no-build: 13 s before, 19 s cold / 14 s warm after. Negative check: with the after twin's listener commented out, the exactly-one-baker-task scenario fails on that twin and still passes on the other; reverted.
- docs/file-inventory.md: slice 004 section. Before twin 8 .cs created / 6 edited (3 non-.cs wiring edits outside the count); after twin 0 created / 1 edited (2 non-.cs). Whole-twin recount: before 2,061 raw / 1,705 non-blank (was 1,733 / 1,432), after 703 / 581 (was 695 / 574). - docs/build-log.md: dated entry with the decisions as applied, the options-binder surprise, the describe output, suite timings, the live broker check, and the negative check. - README and CLAUDE.md: both twins use RabbitMQ for the baker notification; the suite starts its own broker; Messaging row in the tech-stack tables; slate lists slice 004 as an extension of PlaceOrder.
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.
Why
Slice 003 left "tell the baker" as a cascaded outbox message on the after twin and an inline row write on the before twin, and the talk asked the room to imagine that inline line is a call to an external system. This slice stops asking: the baker notification crosses a real RabbitMQ broker on BOTH twins. Same broker on both sides; only the idiom changes. No new endpoint, no contract change, no scenario change. Spec:
docs/slices/004-notify-baker-over-rabbitmq.md.What it costs each twin
.cscreated.cseditedProgram.cs).cswiring (outside the count)Before twin: raw
RabbitMQ.Client, hand-written publisher behind an Application port and aBackgroundServiceconsumer in WebApi that re-dispatches through MediatR. Publish after commit, no outbox; the dual-write gap is owned in one comment. Idempotent throughExistsForOrderAsync.After twin:
UseRabbitMqunconditional,PublishMessage<NotifyBaker>().ToRabbitQueue(...).UseDurableOutbox(),ListenToRabbitQueue(...).PlaceOrder.csandNotifyBaker.csuntouched.Verification
dotnet test --no-buildwall clock: 13 s before this slice, 19 s cold / 14 s warm after (onerabbitmq:4Testcontainer per twin collection; Docker is still the only prerequisite).layercake-*-baker-tasksqueues with 1 consumer, 0 backlog, 1 published, 1 acked; CritterWatch queues untouched.placing_order_produces_exactly_one_baker_taskfails on that twin and still passes on the other. Reverted.Packages
Two ADDITIONS at Erik's call, never bumps:
RabbitMQ.Client7.2.2 (the exact versionWolverineFx.RabbitMQ6.30.0 already depends on) andTestcontainers.RabbitMq4.14.0. No existing pin moved.