0

I have 2 collections. a) Products: Stored Products, b) Barcode: Stores a prefix and a counter value which collectively forms a string and is used as ID for a new product. enter image description here enter image description here

app.post('/saveProduct', 
body('name').not().isEmpty().trim().escape(),
body('description').not().isEmpty().trim().escape(),
async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        // If error detected throw error
        errors.array()['location'] = "";
        return res.status(400).json({ errors: errors.array() });
    }else{
        let productData = {};
        
        const BarcodeCollection = db.collection('Barcodes').doc('Code');
        try {

            let t_res = await db.runTransaction(async t => {
                // Get the barcode which is to be used for the currect Product save request
                const Barcodes = await t.get(BarcodeCollection);

                // Assign Request object to ProductData Object
                productData = {
                    "name": req.body.name,
                    "description": req.body.description,
                    "barcode": Barcodes.data().prefix+Barcodes.data().nextCode
                };

                // Increment Barcode nextCode
                await t.update(BarcodeCollection, { nextCode: FieldValue.increment() });

                // Use Product Barcode as ID and Store Product Data Oject
                await db.collection('Products').doc(productData.barcode).set(productData);

            });

            console.log('Transaction Successful:');
            return res.send({"status": 200, "message": "Product Saved", "barcode": productData.barcode});
        }catch(e){
            console.log('Transaction failure: '+ e);
            return res.send({"status": 400, "message": "Something went wrong. Please try again later"});
        }
    }
});

The above code is what I am using.

Issue: The code I am using works fine but sometimes when there are multiple requests made within milliseconds. It overwrites the previously entered saved Product with a new Product. For example. I just stored Product with ID ABCD1003, within milliseconds I get another request and somehow it overwrites ABCD1003 instead of creating a new barcode as ABCD1004. If I detect whether the nextCode already exists in the system; if true then add 1 and save. There is always a chance that by the time I add 1, ABCD1004 might be used by another product and end up overwriting since requests are made within milliseconds.

How can I prevent overwriting? Note: I require the barcode to be unique if not sequential

Tabby
  • 388
  • 1
  • 11

1 Answers1

2

The only scalable way to generate document IDs is to make them randomized. This is exactly why Firestore provides the add method on a collection reference. It will randomize the document ID and is virtually guaranteed to be unique. Also note that writing documents with IDs that are sequential cause performance problems with Firestore collections.

The problem is that you need to use the transaction object (t) to write the new document. Currently you are adding it normally. It doesn't matter if the write occurs within the transaction callback - it needs to participate in the transaction along with the document that maintains the count.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Yes, i can use add instead of set. But I require the barcode to be unique if not sequential. – Tabby May 19 '22 at 20:55
  • Also, I am using a dedicated document to store the sequence. `Barcodes/Code` This is being locked by transaction, read, stored as a barcode and incremented by 1 and saved. – Tabby May 19 '22 at 21:08
  • You need to use the transaction object to write the new document. – Doug Stevenson May 19 '22 at 21:36
  • `t` is the transaction object. I thought `t.get(BarcodeCollection)` locks it and `t.update(BarcodeCollection, { nextCode: FieldValue.increment() })` saves it. Atleast that is what is written in the documentation. – Tabby May 19 '22 at 21:55
  • 1
    Use the transaction object also to *write the new document*. Right now, you are not. `t.set()`. – Doug Stevenson May 19 '22 at 23:32