What FeedR Gets Right, and What Production Would Add
The retrospective: after ten parts inside DevMentors' real-time feeds sample, the honest scorecard - five decisions worth stealing for real systems, the sharp edges catalogued in one place, and the concrete backlog that separates this teaching codebase from a production deployment. Part 11 closes the FeedR deep dive.
Ten parts ago I claimed FeedR was the best compact codebase I knew for studying real-time architecture in .NET. Having now dragged you through every subsystem — the YARP gateway, the channel-decoupled quotes feed, the Redis pub/sub seam, the gRPC stream, the weather adapter, the Pulsar tier, the correlation thread, the test trap, and the compose files — I owe you the closing argument. What did the DevMentors team actually get right in this small open-source sample, what would I change before trusting it with production traffic, and — the question that decides whether reading someone else's code was worth your time — what transfers?
Five decisions worth stealing
1. Two tiers of eventing, policed by the type system. The single best idea in the repository. Price ticks travel over fire-and-forget Redis pub/sub through IStreamPublisher; business facts travel over durable, acknowledged Pulsar through IMessagePublisher, gated by the IMessage marker constraint so a tick cannot compile onto the durable tier. Most systems have one bus and two kinds of traffic, and the mismatch surfaces as either a melted broker or a lost order. FeedR draws the line in the architecture and makes the compiler enforce it. I've since applied this split verbatim — different brokers optional, distinct interfaces mandatory — and it has paid for itself every time someone new joined the team and couldn't make the category error.
2. Seams with inert defaults. Every cross-cutting concern — streaming, messaging, serialization — is a small interface with a no-op default registration that real implementations override. Services boot with zero infrastructure, tests swap transports in one line, and the composition root reads as a table of contents for the system's capabilities. The DI last-wins mechanism carrying it is fragile to registration order (I'd use TryAdd semantics), but the design stance — degrade to “runs but doesn't distribute,” never “doesn't start” — is one I now consider table stakes for shared libraries.
3. The in-process rehearsal of the distributed pattern. The quotes service decouples its HTTP endpoint from its long-running generator with a Channel<T> command queue — producer, pipe, single consumer — which is exactly the shape the system then repeats between services with Redis. A junior engineer can debug the in-process version in an afternoon and then already understands the distributed one. Codebases that teach their own architecture through repetition of one shape at two scales are rare, and I don't think this one did it by accident.
4. Translation at every boundary. The weather adapter's private vendor DTOs, the per-service duplication of CurrencyPair instead of a shared contracts assembly, the gRPC fixed-point mapping at the proto edge — everywhere data crosses a border, FeedR re-states it in local terms rather than leaking a foreign shape inward. The costs are real (convention-based contracts with no compensating tests — see the other column), but the instinct is correct and chronically under-practiced.
5. Boring, inspectable composition. The gateway is JSON you can review in a PR. The environment story is three appsettings.*.json layers and one variable. The infrastructure/services compose split matches how humans actually develop. Nothing in the operational surface requires tribal knowledge, and after two decades of reading systems where the routing lives in code someone wrote cleverly in 2019, I rank “the topology is a config file” as a feature on par with any runtime capability.
The sharp edges, in one place
The series named them as it went; here's the consolidated code-review, because a list you can hand to a team is more useful than critiques scattered across ten articles:
- The gRPC bridge busy-spins —
TryTakein a hotwhileloop pins a core per connected client; the bounded-channel rewrite in part 5 deletes the spin, adds backpressure with an explicit drop policy, and fixes an event-handler leak on the singleton generator. - “Streaming” that is pub/sub. The
Redis.Streamingnamespace wrapsPublishAsyncon a pub/sub channel: at-most-once, no replay, no consumer groups. Right choice for ticks, wrong name for the guarantee — and misread guarantees become production incidents. Name transports for their semantics. - The correlation chain breaks silently at the Redis hop. Ticks carry no context; the Pulsar publisher's
HttpContextfallback mints a fresh GUID in background call paths. The plumbing downstream is correct and receives a meaningless value — the exact failure shape distributed traces exist to prevent, reproduced in miniature. - Fire-and-forget everywhere. Discarded tasks in the channel dispatcher, in the Redis handler, behind the
Action<T>(notFunc<T, Task>) subscriber signatures — every one an unobserved-exception drain. One logging continuation extension and async handler signatures fix the class of bug. - Acknowledge-before-completion semantics. The Pulsar consumer acks after a synchronous handler that may have merely queued async work — and an exception in the handler kills the consume loop with no supervision, retry, or dead-letter path.
- A committed API key (with an apologetic TODO), a Polly policy that retries 400s,
SetResultwhereTrySetResultbelongs, an awaitedTaskCompletionSourcewith no timeout,COPY . .Dockerfiles that defeat layer caching. Each small; each a genuine incident I've watched happen somewhere at full scale.
I want to be precise about the register of this list: none of it makes FeedR a bad sample. Most of it makes it a great one, because each flaw sits at the exact junction where demo constraints and production constraints diverge, and the repository is small enough that you can see the junction clearly. A flawless sample would teach less.
The production backlog
If I inherited FeedR tomorrow with a mandate to ship it, here's the honest backlog, roughly ordered:
- Health checks and readiness —
AddHealthChecks+ compose/K8s probes; currently a hung service is indistinguishable from a healthy one. - Observability as a system — OpenTelemetry traces riding W3C
traceparentacross HTTP automatically, explicit propagator inject/extract around the Redis and Pulsar seams, structured logging with correlation scopes instead of hand-interpolated IDs, and metrics (tick throughput, subscriber lag, consumer backlog) that turn part 8's archaeology into a dashboard. - Delivery guarantees where money lives — the aggregator publishing
OrderPlacedafter a nondurable hop needs an outbox (or the tick tier upgraded to actual Redis Streams with consumer groups for replay); the notifier needs ack-on-success, nack-with-retry, and a dead-letter topic. - Contract safety — the duplicated DTOs need a compensating control: consumer-driven contract tests or schema validation in CI, so a renamed property fails a build instead of silently defaulting a field at 2 a.m.
- Security — authentication at the gateway (the one place it needs to exist), TLS everywhere, secrets out of source and into a vault, and the committed key rotated (deleting the line doesn't unpublish git history).
- Resilience at the edges — circuit breakers and jitter on outbound HTTP, reconnect/resubscribe handling on the Redis multiplexer, supervised restart for every consume loop, graceful shutdown that drains streams before Kestrel dies.
- Backpressure as policy — bounded channels with chosen full-modes at every producer/consumer junction, replacing the unbounded defaults that work until the day they very much don't.
- A real client — the console app validates the gRPC stream; a browser dashboard would force the SignalR-vs-gRPC-Web decision part 5 deferred, and I'd bet on SignalR landing in the codebase within a sprint.
That's a quarter's roadmap for a small team — a fair measure of the real distance between “runs on my laptop, correctly designed” and “carries production traffic.” Notice what's not on the list: restructuring. The seams, tiers, and boundaries all hold. You'd harden this system, not redesign it, and that is about the highest compliment a sample architecture can earn.
What reading it taught me
Two closing thoughts, one about the system and one about the practice.
About the system: FeedR's deepest lesson is that guarantees, not transports, are the architecture. Redis, Pulsar, gRPC, and YARP are the cast, but every important decision in the codebase is really a statement about delivery semantics — what may be lost, what must survive a restart, what needs a receipt, what a slow consumer is entitled to. Get those statements explicit and the technology choices become swappable; leave them implicit and no amount of brand-name infrastructure will save you. The sample's best moments (the two-tier split) and worst ones (the “streaming” name on pub/sub semantics) are both about exactly this.
About the practice: reading other people's systems — really reading them, episode tags and TODOs and commented-out experiments included — is the highest-leverage study habit I know in this profession, and small honest samples beat big polished ones for it. FeedR is roughly two thousand lines. It contains, by my count from this series: four concurrency patterns, three transports, two delivery tiers, one excellent test pattern, and a dozen labeled junctions where demo and production diverge. Credit to Dariusz Pawlukiewicz, Piotr Gankiewicz, and the DevMentors project for building it in public and leaving the seams showing. Go read the source — it's on GitHub, it runs with one compose command and a handful of dotnet runs, and after eleven parts of commentary you'll disagree with me about at least one thing in it.
That disagreement is the point. See you in the next series.