A Guesthouse Booking Form That Survives Bad Village Wi-Fi

这篇内容暂时只有 English 版本,以下是原文。

The guesthouse has eight rooms in a Kapadokya village, and the owner takes bookings on a phone over Wi-Fi that drops every time the wind picks up. The original form was a single POST to a PHP script: the guest tapped submit, the phone sat on a spinner, the connection died at 90 seconds, and the guest tapped again. We ended up with duplicate reservations for the same three nights in late September, and the owner had to sort it out by hand from WhatsApp screenshots.

What I changed was not the server but the client side. The form now writes a record to localStorage before sending, tags it with a UUID generated on the phone, and the server treats that UUID as the idempotency key. If the POST fails, the page keeps the record and retries. If the guest reloads, the pending record is still there.

const key = crypto.randomUUID();
localStorage.setItem('pending:' + key, JSON.stringify(payload));

async function submit(payload, key) {
  const res = await fetch('/api/book', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key },
    body: JSON.stringify(payload)
  });
  if (!res.ok) throw new Error(res.status);
  localStorage.removeItem('pending:' + key);
}

On the server, a unique index on idempotency_key does the actual work. The second request with the same key hits the constraint, and we return the existing booking instead of inserting a new one. That is the whole trick: the phone can retry as often as it likes, and the database decides what is a duplicate. The cost is a 36-byte column and one extra index; the benefit is that a bad connection stops being a correctness problem.

There are places where this fails. If the guest closes the browser tab before the first retry, the pending record is gone and they may book again on another device with a new UUID. I have also seen the localStorage quota fill up on an old Android phone with a full photo gallery, though that took a lot of other junk first. And if two people at the reception desk type the same guest’s name at the same moment, the UUIDs differ and the server happily accepts both. The idempotency key protects against retries, not against humans.

One more thing I got wrong at first: I put the key in a hidden form field, and the browser autofilled it from a previous booking. Now it is generated in JavaScript at submit time, never rendered into the HTML.

The question I keep coming back to: how do you handle the case where the guest’s phone is dead and the owner takes the booking on paper, then enters it later? That is where I still have no clean answer.

0
赞同度 100% · 0 条评论 · 2 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。