Skip to content
Kumar Chandrachooda
Microservices

One Shop, Every Shape

DevMentors built the same e-commerce shop twice - once as a monolith, once as nine services - with two gateways and two frontends. This series reads the whole estate as one artefact.

By Kumar Chandrachooda 26 Jul 2025 7 min read
One shop rendered in five silhouettes - monolith, services, two gateways, two frontends

Most reference architectures show you one shape. You get the microservices diagram, the clean-architecture onion, the CQRS flow, and you are left to imagine what the same requirements would have looked like decided differently. The unusual thing about DevMentors' DShop is that you do not have to imagine. The same small e-commerce shop — sign up, browse products, fill a cart, place an order — was built twice: once as a single monolith, once as nine independent services over a hand-rolled framework. Then it grew two API gateways and two frontends. The estate is a controlled experiment that the authors never quite labelled as one.

To be clear up front: I did not write DShop. It is a teaching estate published by DevMentors (Piotr Gankiewicz and Dariusz Pawlukiewicz), MIT-licensed and spread across seventeen sibling repositories under the DNC-DShop name. I am reading it as a source-reader — someone who has built this style of system and wants to see how theirs holds up when you open every file. Where the implementation is the story I quote it, attributed to its path and its era; everything else is fresh illustrative code. And era matters here more than usual, because DShop is a 2018–2019 time capsule — .NET Core 2.1 and 2.2 (2.2 reached end-of-life in December 2019), Consul and Fabio before Kubernetes was the default answer, Travis before GitHub Actions, and Blazor before it had a stable release or a .razor file. I cite versions exactly, because half the lessons here are lessons about time.

Two shops, drawn from the same requirements

This blog has already read two slices of DShop from the inside. The Framework Before the Framework reads DShop.Common, the shared kernel every service is built on — the messaging, discovery, persistence and auth plumbing that its authors later rewrote and shipped as Convey. Nine Services and a Message Bus reads the nine services themselves — one template stamped nine times, its drift, its sagas, its tests. This series is the third view: the estate as a whole. It reads the parts the other two series set aside — the monolith, the two gateways, the two frontends, the SignalR service nobody connected, and the compose files and scripts that try to make seventeen repositories behave like one.

The comparison sits right at the top. Here is the same shop in two builds, and the whole of the next part is the row-by-row version of this table:

Axis The monolith The nine services
Repos 1, four project layers 11+, one template each
Dispatch in-memory Autofac dispatcher RabbitMQ topic exchanges
Order placement HTTP 200, synchronous HTTP 202 + an operation id
Data one Mongo, six collections Mongo-per-service + Redis
Consistency immediate eventual, and the UI copes
Fate abandoned mid-2018, buy path broken still incomplete at the edges

Neither build is finished, and that is the point. The monolith was abandoned in June 2018 with a NotImplementedException sitting in the order path (that is part 5). The distributed build kept going for another year but never once had a single “these seventeen repos all work together as of date X” snapshot. Reading them side by side is more instructive than reading either one polished, because the seams show you which decisions were decisions rather than defaults.

The estate as a run-book

Before any code, DShop hands you something most reference projects don't: a genuine attempt at operability. The umbrella repository is not a code repository at all — it is a run-book. Its README.md is a real getting-started guide with a “so-called bare minimum” of MongoDB, RabbitMQ and Redis, three layered docker-compose files for progressively more infrastructure, and one load-bearing sentence that documents the entire request topology:

“just keep in mind that DShop.Api acts as a gateway to the whole system (except DShop.Services.Identity for authentication).”

That one line is the architecture. Everything except logging-in flows through the gateway; the frontends talk to exactly two hosts. The umbrella backs it with a fleet of shell scripts — git-clone-all.sh, git-pull-all.sh, dotnet-run-all.sh — that treat eleven polyrepos as a poor man's monorepo. The clone script even parallelises:

REPOSITORIES=(DNC-DShop DNC-DShop.Api DNC-DShop.Api.Next DNC-DShop.Common \
  DNC-DShop.Services.Customers DNC-DShop.Services.Identity ... )

echo ${REPOSITORIES[@]} | xargs -I {} -n 1 -P 0 \
  sh -c 'git clone https://github.com/devmentors/{}.git'

xargs -P 0 fans the clones out with no concurrency limit — clone eleven repositories at once. It is a small, honest piece of developer experience: the estate knows it is spread across many repos and gives you a script to gather them.

The tell is in what the script omits. The clone list names eleven repositories; the estate has seventeen. Storage, Discounts, the Monolith, the Angular web app and the Blazor app appear in no clone script and no compose file. Two of those — Storage and Discounts — are working services that the rest of the system publishes events to. They were added after the umbrella's picture of the world was frozen, and nobody went back to update it. That absence is not sloppiness to scold; it is the characteristic failure mode of a polyrepo estate with no single manifest of truth, and this series treats it as a finding rather than a footnote.

The other documentation is executable

The second piece of onboarding is more interesting than the README, because it runs. Every corner of DShop ships a .rest file — a plain-text collection of HTTP requests compatible with VS Code's REST Client extension. The monolith's DShop.sample.rest is the user journey, in order:

POST http://localhost:5000/sign-up
POST http://localhost:5000/sign-in
POST http://localhost:5000/customers
GET  http://localhost:5000/products
POST http://localhost:5000/cart/items
POST http://localhost:5000/orders

You can read the whole product from that list before opening a single controller: you sign up, sign in, complete a customer profile, browse products, add to the cart, place an order. Executable request collections like this are the highest-leverage documentation an API can carry — they never drift out of sync with reality the way prose does, because you run them against the running service. DShop leans on them everywhere, and where a repo has only a one-line README (the Blazor app, the Ntrada gateway, the monolith), the .rest file is doing all the real onboarding work.

There is one thing in that file to flag now and return to in part 9: the first request signs up with "role": "admin", and the estate honours it. The onboarding script teaches you to grant yourself administrator. Hold that thought.

Where the series goes

Sixteen parts, reading the estate as one object — monolith and swarm, both gateways, both frontends, the platform underneath:

  1. One Shop, Every Shape — you are here.
  2. The Monolith and the Swarm — the same shop, two builds, compared axis by axis.
  3. Swap the Dispatcher for a Bus — decomposition as one substitution: the in-memory dispatcher becomes RabbitMQ.
  4. Who Creates the Customer? — the consistency model changed, so the user flow changed with it.
  5. The Buy Path That Never Worked — the monolith's honest ledger, and the exception that was left in the order path.
  6. One Shop, Two Front Doors — the code-first gateway's dual dispatch: read over HTTP, write over the bus.
  7. A Gateway Written in YAML — the same gateway rebuilt as Ntrada configuration, and where the two disagree.
  8. The Header That Names the Future — 202 Accepted, X-Operation, and minting a resource id at the edge.
  9. The Security Inversion — all the auth at the gateway, none in the services, every port published.
  10. The Push Channel Nobody Plugged In — a whole SignalR service built to solve a problem both frontends solve with Task.Delay.
  11. Two Frontends, One API — Angular 6 versus Blazor 0.7 over the same shop.
  12. The Angular App That Forgot Its Auth Header — right architecture, broken plumbing.
  13. The Blazor App That Signs You Up Behind Your Back — naive architecture, working buy-loop, one alarming shortcut.
  14. A Pedagogy Ladder of Dockerfiles — three Dockerfiles as a teaching ladder, and the broken top rung.
  15. The Observability Stack of 2018 — Serilog, App.Metrics, Jaeger, and the invisible health endpoint.
  16. Cartography of an Estate — the whole map: what's wired, what isn't, and the version skew across seventeen repos.

If you have ever inherited a system that was clearly built more than once — a rewrite half-finished next to the thing it was rewriting — the next fifteen parts are a guided tour of exactly that, with all the seams left showing. We start where the estate started: with the monolith and the swarm, the same shop drawn twice.