DEV Community

Cover image for One item per row: designing bulk order creation

One item per row: designing bulk order creation

Maruf on October 24, 2026

A merchant who ships forty parcels a day doesn't want to fill in a form forty times. Most of them already keep those orders somewhere else, in a sp...
Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

The rule I would add from my own batch work is treating the batch response itself as durable state: when a network drop hits after half the batch is created, the client replays the whole batch and must reconcile per-order results instead of assuming the batch failed. Your 409 on a changed payload is the part that saves you there, because a silent return of the old order would look like success while the merchant's correction never landed. Do you store the per-order result alongside the key, so a replay returns the same created and failed split?

Collapse
 
marufing profile image
Maruf •

Umm, yeah. In this design, each order gets its own key, so retries are safe per order, not just per batch. For created orders, the result is stored with the key, so a replay returns the same “created” instead of making a new one. Failed orders are not stored on purpose, so a replay checks them again and they go through once the merchant fixes them.

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

That per-order split is the part I would keep, because a batch-level key hides which line actually landed. I would still put a boundary on the failed-order replay: in my own batch work I saw a replay keep rechecking a line the merchant had not actually fixed, so it looked busy while nothing changed. Do you expire the created keys, or keep them until the merchant confirms the order?

Thread Thread
 
marufing profile image
Maruf •

Fair point, that would look like it’s working when nothing changed. In this design the sheet only resends a failed order if the merchant actually edited it. If nothing changed, it stays red with the same error, so there’s no fake “busy” loop. For created keys, they expire after about a day. That covers retries for the same sheet, and after that the merchant can reuse the order reference without a clash.

Thread Thread
 
robertpaschal profile image
Odinaka Robert Nnamani •

Keeping the failed order red unless the merchant edited it is the clean part, and a day for created keys is a tidy retry window.

Collapse
 
amorizz profile image
Amorizz •

The "only a created order claims the key" rule has one more edge: two requests with the same key in flight at once. A double-clicked confirm, or a client retry fired while the first request is still running. Both can read "no key yet" before either commits. A pattern that holds up is letting Postgres arbitrate with UNIQUE (merchant_id, idempotency_key) and INSERT ... ON CONFLICT DO NOTHING RETURNING id. The second insert waits on the first transaction, gets no row back, and you then select the existing order and compare the payload hash. Worth one test that fires the same batch twice in parallel.

Collapse
 
marufing profile image
Maruf •

Ha, a double click could easily do that. I think, letting the db decide with a unique (merchant_id, idempotency_key) is much cleaner than an “in progress” flag. The second request just waits, then finds the order that’s already there. Will do that parallel test, thanks!

Collapse
 
challan116ux profile image
challan116-ux •

Making the sheet match the file the merchant already has, instead of the shape that looked tidy in the design, is the decision everything else hangs on — every import tool that invents its own layout is really asking the user to do the translation work the software was supposed to do. Treating a repeated order reference on non-adjacent rows as an error rather than a silent merge is also exactly right: a merge guesses at intent, an error asks, and with orders the cost of a wrong guess is a parcel. The validate-everything-before-creating-anything rule is what makes the no-duplicates promise believable, because it turns a mid-batch failure into a clean retry instead of an archaeological dig through which twenty rows already became real orders.

Collapse
 
marufing profile image
Maruf •

Appreciate this! And agreed, guessing with orders gets expensive fast.

Collapse
 
gautamtalksdev profile image
Gautam K •

Using the merchant's own order reference as the idempotency key is neat, and it raises two edge cases worth a test each.

First, if a merchant restarts numbering, say each day's sheet begins at A-101 again, a new order gets last week's order back instead of being created. Scoping the key by merchant and storing a hash of the payload catches that: same key with a different payload should be a clear conflict, not a silent return of the old order.

Second, only a created order should claim the key. If a failed attempt stores it, the fixed resubmit you describe gets the old failure back.

Collapse
 
marufing profile image
Maruf •

Thanks, really good points! The restarted numbering one is sneaky, I hadn’t thought of that. And yeah, only a created order should claim the key, otherwise the resubmit flow breaks. Updated d post with both, appreciate it!