Back to Feed
article 4m read

Deleting my last template just makes the server put the default back

The scheduling app's rule templates moved to the server, with the list call seeding a starter whenever a company's list is empty. Empty also means an admin deleted everything, so the starter comes back, and that same read writes to shared settings.

Article DepthPractice

Flow

How it works now versus how I'd build it

  1. 1Starter template

    Now: made whenever the list is empty. Instead: made once when a company is created, or shown as a built-in item that is never saved.

  2. 2Storage

    Now: the whole list is saved as one chunk every time. Instead: one row per template.

  3. 3Ids

    Now: biggest id plus one, so a deleted id can come back. Instead: the database hands them out and never reuses them.

  4. 4Saving under an old name

    Now: makes a second template with the same name. Instead: one clear rule, enforced by the database.

  5. 5Two people saving at once

    Now: likely the later save wins and the earlier one is lost. Instead: each edit touches only its own row.

Delete the last template in my scheduling app, load the list again, and the default is back. Same name, same id.

Nobody reported this and I never saw it happen. It's just what the code I wrote does. When I talk about two requests at once below, that's something the code allows, not something I saw.

How the templates got to the server

The app has an admin page where you put break and lunch rules in order and save them as a template. Templates used to live in each browser, so every laptop had its own set. Earlier that day I also added a table of break defaults for each company.

Then I moved templates to the server: one list per company, saved as one chunk of JSON. The list call also does a little setup. If a company has no templates, it builds a starter from the break defaults and saves it.

The only test is whether the list is empty.

Empty means two things

An empty list can mean "this company is brand new." It can also mean "an admin deleted everything on purpose." My code can't tell which. So deleting the last template and loading the page brings the starter right back.

If you still have another template, deleting the starter works. It only returns when the list hits zero. That's a small annoyance for a starter template. It would be a much bigger deal if the same trick guarded something like who gets admin rights.

A read that writes

Anyone in the company can load the list, but only admins can save or delete. Since loading the list can save, a normal user's page load can change company settings.

Saving the whole list every time

Every save and delete loads the full list, changes one item and writes the full list back. If two people save at once, I'd expect the second save to wipe out the first, with no error.

New ids are "biggest id plus one." Delete the newest template, make a new one, and it gets the old id back. Anything still pointing at that id now points at the wrong template.

A save that stopped replacing

In the browser version, saving under a name you already had replaced the old one. Now the page sends no id, so the server makes a new template. Save twice with one name and you get two that look the same in the menu. I don't think anyone picked that. It came along with the move.

Old numbers and copied numbers

The starter writes its timing as text, once. If the company's break defaults change, the starter still shows the old times.

The defaults change did fix one real mismatch: the server's backup lunch length had been shorter than what the browser used. But the default numbers still live in several places, and the "only one global default" rule isn't enforced by the database.

If I were starting this list over

If you're building something like this, these are the things I'd want from the start:

  • Set up defaults once, when the company is created, or show them as a built-in item that is never saved.
  • Let the list call only read.
  • Store one row per template, and let the database hand out ids.
  • Decide what saving under an existing name should do, and make the database enforce it.

No test covers the endpoint, so nothing flags it when an admin empties the list and gets handed back a starter they just threw out.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Related idea

Device limit counts first and saves later, and the test never sends two at once

The licensing code I shipped checks how many devices a key already has, then inserts the new one in a separate step, so two activations racing for the last slot can both get in. The test activates devices one at a time and can't see it, a device racing itself gets a server error, and a key-only check passes for keys never bound anywhere. I found this by reading, not by running anything concurrent.

via #concurrency · #state-management