EngineerGENERALSTAGE-SPECIFICILLUSTRATIVE

The Cart Disappears

The cart works in memory; the application exits; the cart is gone. Whether it should survive is a requirement question, not a storage question — and answering it is what makes persistence a database problem you can now go and learn.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

The cart works and then the process exits and it is gone. Should it survive — and how do you decide before choosing where to keep it?

The situation

You built the cart from the examples: addItem, removeItem, changeQuantity, total, clear. The tests pass. You add a laptop, restart the dev server to pick up a change, and the cart is empty. You know this is "persistence" and you know persistence is "a database", and the next thing you do is open a tutorial about connecting to Postgres.

The reflex

Add a database. It is the obvious word for the obvious symptom, it is what every real store has, and wiring one in produces a connection string, a table and an ORM — all of which feel like the cart becoming real.

Why it stalls

The table exists and the question "should the cart survive?" was never asked. A demo cart, a test cart and an anonymous shopper's cart have three different answers, and the database answered all three with "yes, forever" before anyone chose.

What the reflex produces — and fails to produce
  • The table exists and the question "should the cart survive?" was never asked. A demo cart, a test cart and an anonymous shopper's cart have three different answers, and the database answered all three with "yes, forever" before anyone chose.
  • The cart's owner was never decided. The in-memory cart had one shopper because there was one process; the table has a cart_id column with nothing to put in it, so the tutorial's user_id is copied and the store now requires login to add an item, which no requirement said.
  • Every operation became a round trip before anyone knew whether it needed one. The five pure functions that took a cart and returned a cart now take a connection, and the tests that ran in milliseconds need a running database — the first cost of persistence arrived before its first benefit.
  • The stale product problem is now permanent. A cart saved on Monday holds a product deleted on Tuesday; in memory that could not happen, and nothing in the tutorial mentioned it, because the tutorial never restarted.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Separate the observation from the decision. The observation is "state in a process dies with the process" — true of every variable you have ever declared. The decision is "which of this state must outlive the process, for whom, and for how long?" Persistence is the name of the answer, and it has levels, not one value.
  • Ask the lifetime question of the concept, not of the code: from the cart's own identity record — "how long does it exist? from the first add until checkout or abandonment" — the survival requirement follows. A cart that should still be there tomorrow needs to outlive today's process; a cart that exists to demonstrate addItem does not.
  • Name what changes when the answer is yes. Survival is not free: the cart now needs an id to be found by, an owner to be found for, a serialised form to be written, a load step at start, and a rule for what to do with an item whose product has since vanished. Each is a new requirement the in-memory cart did not have; write them down before writing storage code.
  • Only then ask "where?" — and ask it as a ladder, from the least that satisfies the requirement upward. In memory, browser storage, server memory, database: each answers a different survival requirement at a different cost, and The Persistence Ladder walks the rungs. The database is one rung, chosen for reasons, not the synonym for "make it real".

What actually happened

Before reaching for anything, look at exactly what the exit did. The device below is the one the execution module used to make a mutation visible; here the "operation" is the process ending. Nothing about the cart was wrong — the array, the quantities and the rules were all correct up to the last instruction. What changed is that the thing holding them stopped existing.

Naming the change precisely is what turns the reflex into a question. "The cart is gone" suggests a repair; "the memory holding the cart was released" suggests a decision about which memory should hold it.

The process exits
before
cart = { items: [ { productId: 'laptop', quantity: 2 }, { productId: 'mouse', quantity: 1 } ] } — held in a variable in the running process; all rules hold; total(cart) = 2020.
The process exits (a restart, a closed tab, a crash). →
after
No variable, no object, no items. The next process starts with createCart() → { items: [] }.
what changed The memory holding the cart was released; nothing was written anywhere first. · The identity of the cart — which was the variable — is gone with the variable; there is no id to ask for it by. · Nothing about the cart's rules or behaviour changed; the code is as correct as it was.

The question hiding in the symptom

The board separates what is known about the cart — its identity, lifetime and owner, from the concept record — from what the exit revealed you do not yet know. Every unknown is sharpened into something answerable by a requirement or a small experiment, and none of them is answered by "use Postgres".

After the cart disappeared
known
  • The cart's lifetime, from the concept: from the first add until checkout or abandonment.
  • The cart's owner in V1: one anonymous shopper in one browser.
  • The five operations are pure and tested against the examples; nothing in them knows about storage.
  • The array serialises to JSON directly; the map would need a conversion step.
assumed
  • ~Checkout will read the cart from wherever it lives — to be checked once checkout is designed; if checkout runs on the server it may need a server-side cart.
  • ~One currency, one device, no login in V1.
unknown → question → experiment
  1. ? The cart should not disappear.

    becomes After which events must a shopper find their cart intact: a reload, a closed browser, a new device, a login?

    experiment Ask whoever owns the store; write the answer as four yes/no lines beside the identity record.

  2. ? We need persistence.

    becomes What is the least storage that survives the events answered "yes" — and what does it cost per operation?

    experiment Serialise the cart to localStorage on every change and load it on start; note what still works, what now fails, and whether any test needed to change.

  3. ? Stored carts might be wrong.

    becomes When a stored cart references a product no longer in the catalog, which operation notices, and what should it do?

    experiment Store a cart, delete the product from the in-memory catalog, reload, call getItems and total; observe where the error surfaces.

Two of the three unknowns are answered by a requirement, not by technology. The third is an experiment that takes minutes and produces a failure mode the cart did not have before.

Yes, no, or not yet

The decision is three-valued, and the criteria matter more than the answer. "Not yet" is a real option: a cart whose survival requirement is unknown is better left in memory, with the question recorded, than persisted to the wrong rung.

Should the cart survive the process?

Given the cart's lifetime and owner, should this version persist it — and to satisfy which event?

No — in memory

when V0: the cart exists to prove the five operations; a test or a demo; no shopper ever comes back to it.

cost Nothing today; the survival question returns the moment a person uses it twice.

Yes — one device, no login

when A shopper must find the cart after a reload or a closed browser, and nobody on the server needs to see it yet.

cost Serialise on every change, load on start, and the stale-product failure arrives from storage.

Yes — trusted by the server

when Checkout, stock checks or a logged-in shopper on a second device need the cart the server holds.

cost An id, an owner, a schema, a round trip per operation and two tabs that can now race.

Not yet — record the question

when The survival events have not been answered by the person who owns the store.

cost Every operation stays pure and cheap; the load / save wrapper is written later, against a known requirement.

Persistence wraps the cart; it does not enter it
1-- the five functions are unchanged
2function addItemPersisted(productId, quantity):
3 cart = load() -- from wherever this rung keeps it
4 cart = addItem(cart, productId, quantity)
5 save(cart)
6 return cart

load and save are the only lines that know about the rung. Moving from browser storage to a database changes them and nothing else — which is only true because the survival decision was kept out of addItem.

The implementation ladder

Concept, examples, pseudocode, code, tests, production — for the concept this lesson is about. Code is the fourth tab, not the first.

concept Shopping Cart beginner
Build it step by step →

Shopping Cart = A temporary collection of products the user intends to purchase, held between browsing and checkout.

Identity, ownership, lifetime
  • Does a cart have identity? Yes, weakly. Two carts with the same items are still two carts, because each belongs to someone and will become a different order. It needs an id once it leaves memory; in memory the variable is the identity.
  • Who owns it? A shopper — a logged-in user or an anonymous session. The owner is part of the state because "my cart" has to be findable again.
  • How long does it exist? From the first add until checkout or abandonment. Whether it survives a reload, a closed browser or a login is not a property of the concept; it is a persistence decision made later, and each answer changes where the cart lives.
  • Should it survive reload? Usually yes for a store, usually no for a demo. V1 in memory says no; V2 browser storage says yes on one device; V3 server storage says yes everywhere the user is logged in.
  • Should it survive login? Only if an anonymous cart and a logged-in cart are merged — a rule that does not exist in V1 and appears as a modification later.
State it must remember
  • itemscollection of CartItemkeepThe cart is its items; without them nothing else means anything.
  • items[].productIdidkeepThe reference to what is being bought. The catalog owns the product; the cart only points at it.
  • items[].quantityinteger > 0keepTwo laptops is one entry with quantity 2, not two entries — the rule "one entry per product" needs a quantity to hold.
  • owneruser id or session iddependsSo the cart can be found again by the person it belongs to.
  • items[].productNamestringderiveIt would be convenient to render the cart without a catalog lookup.
  • items[].pricemoneydependsThe total needs a price per item.
  • totalmoneydropEvery screen shows the total.
  • currencycodedependsPrices need a currency to be added.
  • createdAttimestampdropAbandoned carts might be expired or emailed about.
Operations
  • update Add item the updated cart
  • delete Remove item the updated cart
  • update Change quantity the updated cart
  • read View items the list of entries — product id and quantity — for rendering
  • domain Calculate total the sum of price × quantity over the entries
  • delete Clear cart the empty cart
Rules that must always hold
  • Every quantity is greater than zero.
  • One logical entry per product.
  • The total is never negative.
  • An unknown product cannot be added.
  • Quantity cannot exceed available stock — if inventory is enforced here.

How to do it

Most important first.

  • Write the symptom and the question on separate lines: "the cart is gone after a restart" / "should a shopper's cart be there after they close the browser?" If you cannot answer the second from a requirement, it is a question for whoever asked for the store, not for a tutorial.
  • Read the concept's identity record before touching storage: identity, ownership, lifetime. The survival answer is usually implied by the lifetime line (Who Owns It, and How Long Does It Exist?).
  • List what "survives" adds: an id, an owner, a serialised shape, a load-on-start step, a stale-data rule. Compare it against What Must Persist — most concepts have less that must survive than it first seems.
  • Keep the five functions pure. Persistence wraps them — load, call, save — rather than living inside them; the tests you already have keep running without a database (Pure Logic First).
  • Make the first persisted version the smallest one that satisfies the requirement, and write beside it the requirement that would move it up a rung (Growing From the MVP).

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The cart before: { items: [{ productId: 'laptop', quantity: 2 }] } held in a variable. The process exits. The cart after: nothing — the variable, and the object it pointed at, no longer exist. The observation is complete and it says nothing about databases.
  • The requirement: the store's core workflow is browse → add → checkout, and a shopper who adds a laptop, reads a review in another tab and comes back expects the laptop to still be there. Survival across a reload is required. Survival across devices is not required until login exists, and login is not in V1.
  • What survival adds, for this store: a serialised cart (the array serialises to JSON directly — one reason the array was chosen over the map), a save on every change, a load on page start, and the new failure "a stored product no longer exists in the catalog". The owner question does not arrive yet: one browser, one cart, the browser is the owner.
  • The rung that satisfies it: browser storage. Not the database — the database answers "survive across devices for a logged-in user", a requirement this version does not have. The note beside the choice: "moves to the server when login or checkout needs a cart the server can trust".

How you know it worked

What now exists that did not before, and what question you can now ask.

  • You can say which requirement the persistence serves — "survive a reload on one device" — and which requirement it does not serve yet.
  • The five cart functions still run in the tests without any storage, and the persistence is a thin load / call / save around them.
  • A list exists of what survival added — id, owner, serialised form, load step, stale-product rule — each traceable to the decision to survive.
  • You can name the requirement that would move the cart one rung up, and it is a requirement, not a feeling that the current rung is not serious enough.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?Which part of this concept's state must outlive the process, and for whom?
  • ?What does surviving add — an id, an owner, a serialised shape, a load step, a staleness rule — that the in-memory version never needed?
  • ?What is the least storage that satisfies the survival requirement I actually have?
  • ?Which requirement, when it arrives, will make this rung insufficient?

What can go wrong

How the move itself fails
  • Persistence is added because it seems unserious not to. The demo cart gains a database it never needed, and every test now needs infrastructure; the move is to answer the survival question honestly, and "no" is a legitimate answer for V0.
  • The survival question is answered "yes" and treated as the whole decision. Survive where, for whom, for how long, visible to the server or not — the ladder in The Persistence Ladder exists because "yes" alone does not pick a rung.
  • The pure functions are rewritten around the storage. addItem now takes a connection and issues an UPDATE; the rule "one entry per product" is now an SQL question, and the tests from the examples no longer run. Keep the logic and wrap it.
  • Survival is added and the stale-data failure is not. The cart loads a product that no longer exists and total throws on the checkout page — the failure the in-memory cart could not have and the persisted one always can (What Can Go Wrong With a Cart).
What the move costs
  • Asking the survival question first means the demo has no persistence at all for a while, which looks less finished than a demo with a database behind it.
  • Keeping the functions pure and wrapping them costs a layer — a load and a save around every call — that an ORM-first design hides inside the model.
  • The smallest rung that satisfies today's requirement will be replaced; the reload-surviving browser cart is thrown away when login arrives, and that is the price of not building the login-era cart before login exists.
Misreads
  • "So persistence means a database." A database is one rung of a ladder that also includes browser storage and server memory; the rung is chosen by who must see the cart and what must survive, not by seriousness.
  • "The cart disappearing is a bug." It is the expected behaviour of process memory and a missing requirement, which is different. The bug appears only once the requirement exists and the cart still disappears.
  • "Once it survives, it is done." Survival introduces the stale-product failure, the owner question and the merge-at-login question; it closes one problem and opens three, and the three are the rest of this module.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALThe observation "process state dies with the process" and the question "which of it must outlive the process, for whom, for how long?" apply to any concept with state — a cart, a draft message, a search filter, a job's progress.
  • STAGE-SPECIFICIn a greenfield V0 the honest answer can be "it need not survive"; in an existing store with checkout already on the server, the cart's survival rung is largely decided by where checkout expects to find it.
  • ILLUSTRATIVEThe laptop, the review in another tab and the Monday-to-Tuesday deleted product are invented to show the shape; no real store or data is described.

Where the depth lives

This domain asks the question and hands the answer off by name.

Software Designdomain-modeling
Further
  • The manifesto's layers at /manifesto/layers put "where does state live?" one layer below the framework: the ORM is syntax over the decision this lesson makes.