Where to go next
From simplifications to a roadmap
Section titled “From simplifications to a roadmap”Every simplification Recap → named is a door to walk through next. Each of these extends the system you already have without rewriting it — the boundaries you built are the seams you extend along.
Harden the edges
Section titled “Harden the edges”-
Authentication & authorization at the gateway. Put identity in front of everything: validate a JWT (or an OIDC access token) in a gateway middleware, reject unauthenticated requests before they reach a gRPC call, and forward the verified identity as gRPC metadata. This teaches where a cross-cutting concern belongs in a system with one front door — at the gateway →, not duplicated in every service — and how to propagate identity across a trust boundary.
-
Observability across every hop. The one thing this system makes hard is seeing a request that spans five services and two brokers. Add OpenTelemetry tracing that propagates a trace context through the gRPC calls and across the Kafka/RabbitMQ envelopes (carry the trace id on
events.Event), so a single order’s whole journey — gateway → Order → outbox → Kafka → Payment → saga & Notification → worker — is one connected trace. Layer on Prometheus metrics (consumer lag, retry counts, breaker state from Resilience →) and structured logging with the trace id, and the black box becomes legible.
Make the money real
Section titled “Make the money real”-
A real payment gateway with persisted idempotency. This is the callout Process & Publish → made explicit. Replace the
TotalCentsrule with an actual card-network call, fronted by apaymentstable with a unique constraint onorder_id(or an explicit idempotency key sent to the provider), checked before charging — so a redeliveredorder.createdfinds the existing row and returns the stored result instead of charging twice. This is the difference between “idempotent because it’s pure” and “idempotent because it remembers.” -
Tighten delivery guarantees. Both broker hops are at-least-once today. Explore the transactional-outbox refinements that get you closer to effectively-once processing, and give the Notification worker a real retry-with-delay path (the TTL+DLX cycling or retry-count header Acks, Retry & Dead Letters → described but didn’t build), so a transient provider outage retries automatically instead of dead-lettering on the first failure.
Grow the coordination
Section titled “Grow the coordination”-
Distributed compensation and an orchestration saga. The choreography saga → you built shines for a two-hop loop, but its weakness — the sequence isn’t written down anywhere — grows with each step. Add a step that can fail after an earlier one succeeded (reserve inventory, then charge, then ship) and you’ll feel the pull toward an orchestrator: a service holding an explicit state machine per saga, issuing commands and running compensations when a later step fails. Building it is the best way to understand the trade-off this course only described.
-
Event and contract versioning. As the system evolves,
events.Eventpayloads and the.protocontracts The Contracts → will need to change without breaking existing consumers. Practice additive schema evolution, a schema registry for the Kafka events, and how a consumer tolerates a field it doesn’t recognize.
Scale and ship it
Section titled “Scale and ship it”-
Autoscaling the workers. The Notification worker is a competing-consumers design The Send Worker → precisely so you can run more of them. Wire a Kubernetes HPA — on CPU, or better, on RabbitMQ queue depth — so the worker fleet grows under a backlog and shrinks when it clears, and watch the work redistribute automatically.
-
CI/CD. Automate the loop this course ran by hand: test Testing →, build the images Docker & Compose →, and roll the Helm release Kubernetes → on every merge, with the tests as the gate.
-
A real notification provider. Swap the worker’s log line for an email/SMS integration, and build the recipient resolution Consuming events → left as a stub — a lookup from
order_idto a customer’s contact details, or acustomer_idcarried on the event from the start.
Closing
Section titled “Closing”You started at an architecture diagram and a set of deliberate trade-offs, and you built every box in it — then hardened, tested, containerized, and deployed the whole thing. The system that felt like a lot of moving parts at the start is now one you can reason about hop by hop, extend along its own seams, and defend decision by decision. This course’s lessons — every code block you copied into your own shopmicro/ module along the way — live at github.com/avetavos/realworld-shopmicro; there’s no separate pre-built application repo to fork, because the point was building it yourself, one module at a time. Take the project you just assembled, break it, and build the next box yourself.
That’s the course. Go build something real.