Fork me on GitHub
#datomic
<
2015-12-27
>
zentrope01:12:01

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

zentrope01:12:31

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.

zentrope01:12:09

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

zentrope01:12:58

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.

zentrope01:12:37

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.

currentoor06:12:29

@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.

zentrope06:12:25

Yep. That makes sense to me.

zentrope06:12:51

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

zentrope06:12:45

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.

zentrope06:12:01

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

currentoor06:12:14

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

currentoor06:12:41

Regardless of what is or isn’t there previously.

currentoor06:12:07

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

zentrope06:12:33

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.

zentrope06:12:04

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

zentrope06:12:25

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

zentrope06:12:32

Datomic does the right thing, there.

currentoor06:12:13

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.

currentoor06:12:34

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

zentrope06:12:18

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

zentrope06:12:11

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

zentrope06:12:25

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

zentrope06:12:50

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.

zentrope06:12:48

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.

zentrope06:12:34

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

raymcdermott10:12:08

the docs at the web site are clear that DB functions are atomic http://docs.datomic.com/database-functions.html ; convincing yourself (and others) that your functions have the correct outcome is possible but tougher

raymcdermott10:12:43

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.

tcrayford11:12:56

@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)

raymcdermott12:12:18

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

raymcdermott12:12:51

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!)

curtosis17:12:24

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"]
...}

curtosis17:12:39

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

curtosis17:12:39

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