Fork me on GitHub

I don't get the "atomic" argument. Each transaction (consisting of lots of updates and retracts) is executed one at a time.


If two users are editing "the same order with line items" at the same time, and each one hits save, one of them gets applied first.


So, you still have to see "if the database has changed" or whatever, whether it's inside a transaction function, or outside.


The issue is that two people have a copy of the order they're fiddling with and the DB can change that order out from under them while they're still fiddling.


Seems like you still need app logic in one way or another (just before a transaction, or in a tx function) to decide if the second order-save can happen, given that it's now based on an out-of-date assumption about the order.


@zentrope: atomic won’t always be sufficient, like the example you mentioned, but there are cases where it is necessary and sufficient. Consider the case where you need to add $10 to an account balance and store the updated value. The current value is $100. Two users pay the account $10. If we read the data in application code then two simultaneous peers could read the current value of $100 and both write transactions like:

[:db/retract eid :account/balance 100]
[:db/add eid :account/balance 110]
Then the second transaction will not be accurate. But if you read the current value of the account balance in the transaction function then incrementing will always be accurate.


Yep. That makes sense to me.


In the case of order/items, I guess you'll have to reject the request when someone else beat the user to the update?


I think in your case, you're just asserting/retracting based on what's there. But it's possible the second user's update will put something back that the first user deleted.


So, you'd have to do some sort of compare-and-set strategy?


I don’t think so, cuz my implementation is like clojure.core/reset! for atoms. It makes sure the last transaction “wins".


Regardless of what is or isn’t there previously.


My function generates retraction transactions if it needs to, otherwise just additions.


Right. But reading the order/items out into a client, then sending an update request with drops/asserts already calculated adds up to the same thing. The last one will win.


I guess if there's a retract and the entity is already retracted, Datomic will error?


I know that asserting the same fact over again if nothing chances is okay.


Datomic does the right thing, there.


If the data changes while you’re reading it in the client then I believe datomic will error when you try to send pre-determined retractions.


That’s why I’m doing reads in the transactor.


Hm. I'm trying to force that in my app. Same user editing the same master/detail structure.


Retracting an entity that isn't there isn't breaking.


I'm not sure if that's a good or bad thing. ;)


I guess if you assume that "retractEntity" is just making a statement about the system, and if it's already accomplished, so much the better.


Ah! But if I add two new "details" to the master, each with the same name, I get a dup. Then again, I can get a dup with just one user: I don't prevent it anywhere.


So, I'm convinced by your technique, but I don't see any penalties in ignoring it.


the docs at the web site are clear that DB functions are atomic ; convincing yourself (and others) that your functions have the correct outcome is possible but tougher


OTOH I found the DB Function to be a major PITA from a flow / code / debug perspective so that's a big downside. The tooling aspect (handling errors / log output for example) in database functions should have some attention.


@raymcdermott: a thing to note is that you can run database functions in your local codebase and write tests for them there (that's what I've done)


well yes but once they’re installed in the DB you lose some capabilities … glad to be corrected if I’m doing it wrong


you also need to cut and paste the code into the DB function which is a little weird (again, let me know if I’m doing that wrong!)


I'm not sure I have a full grasp of lookup refs yet... is there a way to use them "upsert-wise" when creating an entity to find-or-create a ref'd entity? I'm thinking something like this:

{:invoice/number (make-invoice-number)
 :invoice/company [:company/code "CODE"]


where I want to find [?e :company/code "CODE"], creating it if not found, and set :invoice/company to ref to it


can I do that with straight tx syntax, or does it need a database function?