Regarding "SQL is better suited to this use case because it has transactions" comments:
Before we had 3-tier architectures, people would have designed a shopping cart use-case as a single SQL transaction that would last maybe 10 minutes. The DB would make sure everything stays consistent until the final commit. The GUI would keep an open connection to the DB the whole time.
In the web age, you want stateless services and HA. It means a transaction can't last more than a single web page. It becomes more challenging to design a shopping cart, because the DB can't handle a long-running transaction anymore.
Writing a correct system that reserves the items you put in a shopping cart and doesn't leak items and doesn't sell the same item twice is not easy. A transaction Rollback will not do the cleanup for you, because there's no long running transaction anymore.
So SQL transactions can't help as much as you think.
Mongodb doesn't have transactions, but updates are atomic, which allows CAS and optimistic locking use cases. I agree it's less than ideal when you need to provide ACID behavior, but don't believe it's easy with SQL transactions. It's not.
The author regrets the book's suggestion of putting each object in stock in its own document, and I agree it's probably a recipe for disaster. Atomic updates make this design absurd.
You could easily db.products.update({_id: productId}, {$inc: {inStock: -5}, $addToSet: {pendingCarts: {cartId: cartId, quantity: 5, timestamp: new Date()}}}). This has the exact same atomic behavior as a SQL transaction to remove 5 from the stock and add a new "shopping cart entry" in another table.
(you still need to expire cancelled shopping carts, and you may need a transactional way of completing the order: it's also manageable if designed as an idempotent operation)
Anyway don't over-simplify this use case and believe "a single big SQL ACID transaction would handle the problem". That's just not true.
New families of DB technologies generally traded certain things off (like ACID guarantees and transactions) in exchange for other things like scalability or flexibility. When someone comes back, and in user space reimplements the things that the DB intentionally traded off, you get the worst of all worlds. It's quite a bit like flattening one end of a screwdriver so that you can make it work to drive nails. Yes, you can make that work, and in some rare circumstances where you're trapped on a desert island that might be your only option.
The rest of us will just use a hammer.
My prior startup tried to do a security product with backend in Mongo. It really needed transactions and to avoid N+1 issues.
DB team insisted on writing "DAOs" that ended up pulling 1GB+ of data back from Mongo to merge in EACH of 100+ data points from a scanned machine. Similar issues in UI presentation. With multiple threads doing each of these things simultaneously there were many out of memory dumps. I analyzed these multiple times and told the DK VP Engineering what the problems were, and they didn't follow up for 6 months. He was gone soon after.
I remember writing documentation for a Perl system in 2001 - it was using MySQL MyISAM tables and the main developer had a few hundred thousand lines of Perl that acted as the same kind of client attempt at transactions. It was a mess and huge amounts of money were spent on trying to get the thing to work. A few months later InnoDB came along and made it apparent that trying to write transaction logic in the client was a very bad idea, which seems to be the point of this article.
Should have a disclaimer, founder is the founder of RavenDB and it's clear he's cherry picking things and blaming it on the database vendor, instead of whomever wrote that example.
TLDR:
Using a database that doesn't offer ACID, in a manner that requires ACID has non-trivial associated costs. This may also leave you open to a number of strange situations, where inventory quantities are unknown or incorrect.
Thanks for bringing up nightmares. Inventory on the web is one thing, think about how you'd do this for inventory in person at the store when a customer has product A in their hand and cash in the other. The inventory system says there aren't any in stock - should you sell them one? Of course.
Are you tracking individual lots of inventory and the costs you paid for them? Against which lot did you sell this one? You don't have any - so how do you calculate the margin for this item you sold but don't know how much it cost or where it came from? If it is returned, do you restock that inventory?
Mongo, SQL - they have there differences, but doing inventory management is tricky no matter what technology you use.
Particularly fitting comment:
"Mongodb, the ultimate Maybe monad. With a built in fromMaybe mempty call for your convenience."
Per Hmemcpy and Michael Snoyman on Twitter.
Did I read that right? A document per item in inventory? This seems horribly inefficient.
They should not try to emulate SQL databases, there are other ways to manage inventory without transactions.
One way is to add a field to an item that show its status: whether it is in a warehouse, in someone's cart, ordered or sold. Then adding an item to a cart means updating those fields. There probably is a way to do several similar updates atomically.
Another way is to use append-only collection, that keeps a list of events, like "Item X added to cart Y", "Item X sent to delivery".
But I guess when there are more entities and relations this would become too complex to manage. While SQL databases have no problems with hundreds of tables and thousands of columns.
MongoDB has purpose, but inventory management is not one of those purposes.
Just to make it clear, the issue the OP is pointing out is with whoever wrote this inexcusable piece of code in the book, not MongoDB itself. Even if the authors later clarify there's the possibility of error, just publishing such misleading and grotesque solution for creating transactions in a non ACID database is very poor judgement.
MongoDB should not be used for an online ordering system, period. But if the programmer had no better alternative than Mongo, then please use Mongo's atomic operations [1] and nested documents to make sure nasty Bad Things don't happen.
[1] https://docs.mongodb.com/manual/tutorial/model-data-for-atom...