Skip to content
Kumar Chandrachooda
Microservices

The Buy Path That Never Worked

The DShop monolith was abandoned with a NotImplementedException sitting directly in the order path. Reading its honest ledger is a masterclass in what an unfinished codebase confesses about itself.

By Kumar Chandrachooda 21 Aug 2025 5 min read
A shopping-cart outline with a broken link in the checkout chain

Every abandoned codebase leaves a confession, and the DShop monolith's is unusually direct: you cannot place an order in it, and you never could. Not “it has a subtle bug in the order path” — the order path calls a method whose entire body is throw new NotImplementedException(). The monolith was fifteen commits and died in June 2018, and it died with its most important feature wired to a method the author never filled in. This part reads the monolith's honest ledger, because an unfinished system tells you things a finished one hides — chiefly, which parts the author considered done and which they were still arguing with themselves about.

The exception in the order path

Placing an order goes through CreateOrderHandler. Read it, then read what it calls:

public async Task HandleAsync(CreateOrder command)
{
    var cart = await _cartService.GetAsync(command.CustomerId);
    var productIds = cart.Items.Select(i => i.ProductId);
    var products = await _productsService.GetAsync(productIds);   // <-- here
    var totalAmount = products.Sum(p =>
    {
        var quantity = cart.Items.FirstOrDefault(i => i.ProductId == p.Id).Quantity;
        return quantity * p.Price;
    });
    var orderNumber = new Random().Next();
    await _ordersService.CreateAsync(command.Id, command.CustomerId,
        orderNumber, productIds, totalAmount, "USD");
}

The handler reads the cart, then asks the products service to hydrate the cart's product ids into full products so it can price them. That call — _productsService.GetAsync(productIds) — is the whole ballgame, and here is the method it lands in:

public async Task<IEnumerable<ProductDto>> GetAsync(IEnumerable<Guid> ids)
{
    throw new NotImplementedException();
}

There is no way around it. Every order goes through CreateOrderHandler, CreateOrderHandler calls GetAsync(ids), and GetAsync(ids) throws. Placing an order in the DShop monolith has never once succeeded, from the first commit to the last. The single-product GetAsync(Guid id) overload right above it is fully implemented; the batch overload the order path actually needs is a stub. BrowseAsync — the product listing — is the same stub. The author built the pieces a demo would touch first (create a product, get one product) and left the pieces the buy-loop needs as NotImplementedException, then stopped.

There is a small extra tell. GetAsync(ids) is declared async but has no await — the compiler emits warning CS1998 for exactly this shape. The stub was written in a hurry, from the interface signature, as a placeholder to make the project compile, and the plan to come back never happened.

The design debate left in the code

The best line in the whole estate is not a bug; it is a comment that isn't a comment. In ProductsService.UpdateAsync, when a product can't be found:

if (product == null)
{
    throw new ServiceException("Maybe we shall introduce some maybe pattern or derived exceptions?");
}

That string is passed where an error code belongs. Someone was mid-thought — should a missing product be an option type? a derived exception? — and instead of resolving the debate or leaving a // TODO, they committed the debate itself as the exception's payload. It is honest in a way polished code never is: it shows you the author standing at a fork, unsure, shipping the uncertainty. If a scanner or a client ever surfaced that Code, a user would read the team's internal API-design argument as their error message. Comments are one thing; this is a design discussion leaking into a runtime string.

The smaller scars around it

Once you know the buy path never ran, the other rough edges read as evidence for the same story — that this code was never exercised end to end:

  • Order numbers by dice roll. var orderNumber = new Random().Next(); — a fresh Random per order (so two orders in the same tick can seed identically), producing a 31-bit int with no uniqueness guarantee. In a system that had ever placed a second order, a collision would have surfaced. It never did, because the line after it throws.

  • Currency by fiat. The order is created with a hard-coded "USD". No customer, product or configuration carries a currency; the shop is single-currency by accident, and the accident is spelled in a string literal.

  • A cart update that NREs. Cart.UpdateProduct is meant to swap a product already in the cart. Its guard is inverted just enough to hurt:

    public void UpdateProduct(Product product)
    {
        var item = GetCartItem(product.Id);
        if (item != null)
            _items.Remove(item);
        _items.Add(CartItem.Create(product, item.Quantity));   // item may be null
    }
    

    If the product isn't already in the cart, item is null, the if is skipped, and the very next line dereferences item.Quantity. The method throws precisely in the case its name suggests it should handle gracefully. Like the rest, it survives only because nothing calls it on a real cart.

  • The error middleware that says too much. The monolith's ErrorHandlerMiddleware computes a sanitised message, then serialises exception.Message anyway and returns 400 for everything — the same defect the distributed framework ships and that Series 1 dissects. Since the order path throws NotImplementedException, the buy button — had a frontend ever pressed it — would have returned 400 { "message": "The method or operation is not implemented." }.

To be fair, and the lesson

To be fair to the monolith: it is a teaching artefact that was superseded before completion. It predates most of the microservices work; the author moved on to the distributed build and the monolith became a fossil, not a product. And it is, in places, the most rigorous code in the estate — real aggregates, a UserFactory, a UniqueEmailSpecification, an order state machine. Judging it as shipped software misreads what it is. The distributed build eventually made ordering work, spread across Orders, Products and Operations with a saga; the monolith is the sketch that never got its checkout coloured in.

But there is a durable lesson in reading it, and it is about how unfinished code confesses. A NotImplementedException on a hot path is not a bug to fix; it is a load-bearing statement that the path has never been run. The order handler compiled, it passed code review (such as there was), it looked plausible — and it was structurally incapable of succeeding. Compilation and plausibility are not evidence of execution. The only evidence of execution is execution, and the surest way to find the NotImplementedException in someone else's estate is to trace the one feature the README promises and see whether anything actually walks the whole path. In DShop's monolith, nothing did.

Next, we cross to the build that did place orders — and meet the front door that made it possible: one shop, two front doors, where reads and writes leave the gateway by different exits.