RizTech Academy logo
RizTech Academy
Kirana Store: PlanningLesson 4 of 430 min

Deciding the architecture, and writing down why

You have made a dozen significant decisions in the last three lessons. In six months you will not remember why, and neither will anyone who joins. This lesson is about writing them down in a way that survives — and it is the shortest, highest-return habit in this course.

The problem with not writing them down

Somebody opens the codebase and finds order items storing a product name instead of referencing the product. Three reactions are possible:

"That is a bug" — and they change it, breaking order history.

"That looks odd but I will not touch it" — so the codebase accumulates things nobody understands and nobody dares change.

"There must be a reason" — and they spend an hour in Git history and Slack looking for it.

All three are expensive. The first is the worst, and it is what happens when a decision looks like an accident.

Code shows what. Comments show how. Neither shows why. The reasoning — the alternatives you considered and rejected — is the part that decays fastest and is hardest to reconstruct.

An ADR

An Architecture Decision Record is one short file per significant decision.

docs/decisions/
    0001-store-money-as-integer-paise.md
    0002-snapshot-product-details-on-orders.md
    0003-cart-in-database-not-cookie.md
    0004-decrement-stock-at-order-not-at-cart.md

Numbered, in the repository, next to the code they explain. Not in a wiki, which drifts out of date and which nobody reads while looking at code.

# 2. Snapshot product details on order items

Date: 2026-09-27
Status: Accepted

## Context

An order item needs to show the product name, variant label and price paid.
Two options: reference the variant and read through it, or copy the values
onto the order item when the order is placed.

Products change. Names get corrected, prices rise, variants are discontinued
and deactivated.

## Decision

Copy `productName`, `variantLabel`, `sku` and `unitPricePaise` onto each
order item at the moment the order is placed. Keep `variantId` as an optional
link for reporting only — nothing that renders an order reads it.

## Consequences

**Good**

- An invoice always shows what the customer actually paid and ordered.
- Deactivating or renaming a product cannot change historical orders.
- Deleting a variant cannot orphan or corrupt an order.

**Bad**

- The data is duplicated. An order item's name can differ from the current
  product name, which is correct but looks wrong at first glance.
- A typo corrected in a product name is not corrected on past orders. This is
  intended.
- Reporting "sales by product" must join on `variantId`, which may be null.

## Alternatives considered

**Reference the live variant.** Rejected: an order's total would change when a
price changed, which is a commercial and legal problem, not merely untidy.

**Store a JSON blob of the variant.** Rejected: harder to query and no better
than explicit columns.

What makes it useful

"Alternatives considered" is the most valuable section, and the one people omit. Without it a reader cannot tell whether you thought about the obvious alternative or did not know it existed — and if they cannot tell, they will assume the second.

"Consequences — bad" matters nearly as much. A decision record listing only benefits reads as advocacy. Naming the costs is what makes it trustworthy, and it tells a future reader what to watch for.

A date and a status. Decisions expire. Superseded by 0012 is how a reader knows this is history rather than current practice.

Never edit a decision to reflect a change of mind. Write a new one that supersedes it. The record of what you believed and why is the point.

When to write one

Not for everything. The test: would somebody reasonably do this differently, and would changing it be expensive?

Yes to both means write one.

For the Kirana Store, the ones worth recording:

# Decision
0001 Money as integer paise, never floats
0002 Snapshot product details on order items
0003 Cart in the database, not a cookie
0004 Decrement stock at order placement, not at add-to-cart
0005 JWT in an httpOnly cookie, refresh token in the database
0006 Soft delete products with isActive rather than deleting
0007 Separate NestJS API rather than Next.js route handlers
0008 Monorepo with shared types
0009 Show the current price at checkout, and tell the customer it changed
0010 Offset pagination rather than cursor

Ten files, twenty minutes each. Cheap insurance.

Not worth an ADR: which date library, how a component is named, formatting. Those are conventions, and they belong in a linter or a contributing guide.

The ones with real trade-offs

Two from that list are worth seeing argued, because neither answer is obviously right.

0004 — when to decrement stock.

At add-to-cart: stock is reserved, so nobody gets to checkout and finds an item gone. But abandoned carts hold stock hostage, so you need expiry and a background job to release it, and the owner's stock number no longer matches what is on the shelf.

At order placement: simpler, the stock number means what the owner expects, and no background jobs. But a cart can go out of stock, so checkout must handle it — which is the criterion already written.

We chose placement, because the shop is small, the window is short, and matching the physical shelf matters more to the owner than a rare disappointed checkout. That reasoning is the decision. Somebody who knows only "we decrement at placement" cannot tell whether it was considered.

0009 — the price-change question.

Honour the old price: customer-friendly, and a cart held for three weeks becomes a loss. Also needs the price stored on the cart item, which contradicts the data model.

Silently charge the new price: simplest, and it generates complaints and chargebacks.

Show the current price and say it changed: slightly more work, no surprise, and the customer decides.

We chose the third. The cost is a checkout step that can change under the customer, which the interface must handle gracefully rather than pretend away.

Keeping them alive

Link to them from the code they explain:

// Snapshot, not a reference — see docs/decisions/0002.
productName: variant.product.name,

One line, and the next reader finds the reasoning in ten seconds instead of deciding it is a bug.

Review them when something surprises you. If a decision keeps causing trouble, that is data — write a new ADR superseding it rather than quietly working around it.

Check your work

Why code and comments are not enough: they show what and how. The reasoning, and the alternatives you rejected, is what decays fastest and is hardest to recover.

The most valuable section of an ADR: alternatives considered — without it a reader cannot tell whether you thought about the obvious option.

Why list the bad consequences: a record listing only benefits reads as advocacy rather than a decision.

Why never edit an ADR to reflect a change of mind: the record of what you believed and why is the point. Supersede it instead.

When to write one: when somebody could reasonably do it differently and changing it would be expensive.

Why we decrement stock at placement: the shop is small, the window is short, and the owner's stock number matching the physical shelf matters more than a rare disappointed checkout.

Why we show the current price rather than honouring the old one: a held cart would otherwise become a loss, and charging silently generates complaints.

Practice

  1. Create docs/decisions/ in your project.
  2. Write 0002 on order snapshots, in full, without re-reading the version above. Then compare — particularly your alternatives section.
  3. Write 0004 on stock timing. Argue both options before stating the decision.
  4. Write 0009 on price changes, with all three alternatives.
  5. Write one for a decision you disagree with from this course. Make the strongest case for the other option, then state which you would ship.
  6. Add a comment in your schema pointing at an ADR.
  7. Write an ADR that supersedes one of yours. Mark the original Superseded by.
  8. Give one of your ADRs to somebody who has not read this course and ask whether they could implement the decision from it.

That is module ten. You have a brief with explicit exclusions, stories with acceptance criteria that will become tests, a data model whose trade-offs you can defend, and the reasoning written down.

Next module: building the catalogue. From here, everything runs.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship