Fork me on GitHub

Any advice on whether to use transaction functions or regular functions to build transaction data for a sizeable API? Some of the functions will need access to the current db value. Any reason not to go all in on transaction functions?

Linus Ericsson11:03:34

I would recommend using transaction functions (but only as a last resort). The development story for using transaction functions is terrible. The flexibility is also not great. You will want to use :db.fn/cas most of the time. You will build complex update logic (use rules engines if you want).

Adam Lewis15:03:19

I'll second everything @UQY3M3F6D just said, great points about development ergonomics! Using regular functions to check consistency and then using :db.fn/cas in the transaction to guarantee the underlying assumptions of those checks didn't change is a great way to go. We use very few installed transaction functions, and they all look like generic databasey utility things, not business logic things. Some examples of txn functions we've built: • uniqueRandomcasNewE (works like :db.fn/cas but takes a temp-id in the new-value position) • atomicAddretractOrFailensureRetract (works like regular retract but for cardinality-many attributes and doesn't fail on non-existent entities)


Thank you both for those tips, much appreciated! So what would the "last resort" point be here?


I guess I'm failing to see why the dev story is terrible? Except for the need to install them. It seems to me you can write them and test them like you would regular tx-data-producing functions?

Adam Lewis17:03:55

I think it is best to think of transaction functions as primitives. Just like you need hardware support to build a semaphore in software, you need transaction support to get ACID semantics. Transaction functions need some special attention, they probably need to be fast, they better be correct, and they are kind of a pain to maintain, but they are also essential.

Adam Lewis17:03:39

The dev story falls apart a bit when you try to do change management, versioning,'re better off not trying to revise them

Adam Lewis17:03:16

To look at it a different way, think of all the ways that databases are cumbersome as giant blobs of global state. e.g. you might have two different versions of the same software accessing the same database instance simultaneously.


Thank you! I guess I'm too inexperienced for this to be obvious. Parsing your example, the problem seems to be that tx functions are stored in the db, so one needs to simultaneously change the db and the software accessing it (which might not be feasible)?


Clearly, I had misunderstood the purpose of tx functions – they are not meant to encode (most) business logic.


In datomic, installing fns is tiresome. In datascript, though, due to its local nature, experience might be much better. E.g. you can pass function pointer directly in tx data, no need to install anything:

(let [inc-age #(update % :age inc)]
  (d/transact! conn [[:db.fn/call inc-age "Bob"]]))

👍 1

Perfect! Thank you 🎉

Adam Lewis13:03:21

Thanks @U050UBKAA -- I only realized after I replied I was in the #datascript channel not #datomic :man-facepalming: