Skip to content

Slice 004: NotifyBaker over RabbitMQ on both twins - #13

Merged
erikshafer merged 3 commits into
mainfrom
slice-004-notify-baker-over-rabbitmq
Sep 6, 2026
Merged

erikshafer merged 3 commits into
mainfrom
slice-004-notify-baker-over-rabbitmq

Conversation

@erikshafer

Copy link
Copy Markdown
Owner

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

Before twin After twin
.cs created 8 (port, message, command + handler, options, connection, publisher, consumer) 0
.cs edited 6 1 (Program.cs)
Non-.cs wiring (outside the count) 3 2
Whole-twin lines, raw / non-blank 2,061 / 1,705 (was 1,733 / 1,432) 703 / 581 (was 695 / 574)

Before twin: raw RabbitMQ.Client, hand-written publisher behind an Application port and a BackgroundService consumer in WebApi that re-dispatches through MediatR. Publish after commit, no outbox; the dual-write gap is owned in one comment. Idempotent through ExistsForOrderAsync.

After twin: UseRabbitMq unconditional, PublishMessage<NotifyBaker>().ToRabbitQueue(...).UseDurableOutbox(), ListenToRabbitQueue(...). PlaceOrder.cs and NotifyBaker.cs untouched.

Verification

  • Build 0 warnings. Contract suite 54/54 on both hosts, unit tests 15/15, three runs.
  • dotnet test --no-build wall clock: 13 s before this slice, 19 s cold / 14 s warm after (one rabbitmq:4 Testcontainer per twin collection; Docker is still the only prerequisite).
  • Live with the compose stack: one order per twin, exactly one baker task each; broker management API shows both layercake-*-baker-tasks queues with 1 consumer, 0 backlog, 1 published, 1 acked; CritterWatch queues untouched.
  • Negative check: with the after twin's listener commented out, placing_order_produces_exactly_one_baker_task fails on that twin and still passes on the other. Reverted.

Packages

Two ADDITIONS at Erik's call, never bumps: RabbitMQ.Client 7.2.2 (the exact version WolverineFx.RabbitMQ 6.30.0 already depends on) and Testcontainers.RabbitMq 4.14.0. No existing pin moved.

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.
@erikshafer erikshafer self-assigned this Sep 6, 2026
@erikshafer
erikshafer merged commit 74034cd into main Sep 6, 2026
1 check passed
@erikshafer
erikshafer deleted the slice-004-notify-baker-over-rabbitmq branch September 6, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant