In an accounting system, money can be transferred between accounts.
To avoid deadlock while double booking transactions (Transfers between accounts) Transfer from account to account is based on id order:
@Transactional
override fun doubleBookPrepaid(eventId: Long, srcPurposefulAccountId: PurposefulAccountId, trgPurposefulAccountId: PurposefulAccountId, amount: Money): Pair<Money, Money>? =
if (srcPurposefulAccountId.accountId < trgPurposefulAccountId.accountId) { // Locking minimal account ID first, to prevent deadlocks.
val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -amount)
val trgBooking = bookPrepaid(eventId, trgPurposefulAccountId, amount)
T(srcBooking, trgBooking)
}
else {
val trgBooking = bookPrepaid(eventId, trgPurposefulAccountId, amount)
val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -amount)
T(srcBooking, trgBooking)
}
How can I accomplish the same result for a three-leg transaction? In this kind of transaction, one account will transfer money to two accounts in the same transaction:
data class PurposefulAccountTransfer(val trgPurposefulAccountId: PurposefulAccountId, val amount: Money)
@Transactional
fun threeLegBookPrepaid(eventId: Long, srcPurposefulAccountId: PurposefulAccountId, purposefulAccountTransfer: PurposefulAccountTransfer, secondPurposefulAccountTransfer: PurposefulAccountTransfer) {
val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -(purposefulAccountTransfer.amount + secondPurposefulAccountTransfer.amount))
val trgFirstBooking = bookPrepaid(eventId, purposefulAccountTransfer.trgPurposefulAccountId, purposefulAccountTransfer.amount)
val trgSecondBooking = bookPrepaid(eventId, secondPurposefulAccountTransfer.trgPurposefulAccountId, secondPurposefulAccountTransfer.amount)
}
You just need to sort all of the commands to ensure that there is no cyclic dependencies (assuming T()
accepts vararg
):
fun threeLegBookPrepaid(eventId: Long, ...) {
val txs = sortedMapOf(
srcAccountId to bookPrepaid(eventId, srcAccountId , ...),
trg1AccountId to bookPrepaid(eventId, trg1AccountId, ...),
trg2AccountId to bookPrepaid(eventId, trg2AccountId, ...)
)
.map { it.component2() }
.toTypedArray()
T(*txs)
}
This can be easily generalized to any number of accounts.