# transaction has been commented out
# with transaction():
db_models = DBAccess.fetch_records(ids)
db_models[0].yo_mama_fat = True
# request ends, data poofs into the ether
tbh if this doesn't fail either immediately (no open transaction == error on modification attempt) or at GC time (if there's some kind of deferred logic), then I'd say this is an extremely bad framework and it does hold a major part of the blame. "You can mutate database-connected objects and sometimes they save back to the DB, sometimes they do not" is not reasonable behavior.(obviously these frameworks exist. quite a few of them. quantity does not in any way imply sanity.)
> 2. DO NOT pass DB models in and out of the DB layer.
Yea, the more I've used "thin" ORMs that give you plain objects, the more I've grown convinced they're the best choice basically all the time. Trying to be magical is cute, but it's guaranteed to be made of spicy unobtanium, and at some point it'll blow up in your face in an extremely convoluted way due to a simple cause that you'll notice is absolutely everywhere and you're just stuck being paranoid forever. There's no need to live like that.
Why not? Why should I be allowed to db.commit() midway through a transaction?
Combining that with spaghetti that does transaction magic at random places guarantees the sort of pain that makes cursing the entire human race seem like a pretty mild response.
Higher-level abstractions may prevent some footguns; e.g., an “atomic” decorator/annotation commits automatically after a successful call. They are somewhat easier to understand but come with their own limitations and caveats.
The problem isn't being able to commit. The problem is being able to commit and then not notice that you're no longer in the transaction. You could easily have `begin_transaction` return a `Transaction` object, having operations in the transaction happen on the object, and calling `commit` on it makes it throw an error if you try to use it again after. Maybe the reason that working with databases is "ridiculously hard" because the API isn't well-designed...
I do not know why that mentality exists in the industry, but I see it all the time... for the past 35 years.
This ability to compose transactions is their main benefit over other kinds of concurrency control!
Which works entirely fine in most cases. You're at greater risk of phantom reads and general "stuff that can occur while you hold open a transaction", but if you're not handling that correctly then you're not handling that correctly. It's only a matter of volume, not existence.
... with a clear exception for cases where you do need to truly end a transaction, like if you're relying on some other thread to do something on a different transaction that needs to see your changes, or when you risk a deadlock somewhere due to not releasing your lock. Those are both a risky patterns for a lot of reasons though, and worth avoiding at design-time if at all possible.
I think the option should be there, but it needs to be used responsibly.
Guess our pattern is different, not really felt the pain point the author is talking about. Or it's the language/framework, not sure whatever the example code is written in, but setting properties on entity records would never update the database in the ones I've used.
When we did things more manually we'd make sure that methods like `create_main_records` would start a transaction if not in one, and only commit if it started the transaction. This way we could nest without worry. Our database supported nested transactions but using this pattern we didn't feel the need.
Wired: fetch DB records and construct domain objects, pass them around, do mutation on the outermost level within an explicit transaction. (Typical best practice.)
Inspired: "functional core, imperative shell".
If your framework is holding your records in a collection and you keep track of phantoms that have been edited but not saved as they are edited then all this becomes a non issue. That way you do Collection->SaveTransaction or Collection->Save() and the collection does all the db commit, roll back nonsense.
Anyway the examples I don't even know if I could call that an ORM. Use a better one.
Show, don't tell. The article should've included an example of these helpers.
All in all, this feels poorly written, but also poorly thought out. The DB layer can and should throw an exception if you manually begin/commit inside a context manager. Please do blame it if it doesn't.