Using ActiveRecord to create atomic transactions

Imagine the following situation:

You were asked to create a new wallet feature to an application.

This wallet must allow money transfer between users. Basically, you take money from an user wallet and move to another user wallet.

So, you wrote down the following snippet in your wallet controller:

def transfer
  amount = params[:amount]
  @client1.withdrawal(amount)
  @client2.deposit(amount)
  render status: :ok
end

Everything tested, working well, you’re happy and go enjoy your weekend with that feeling of mission accomplished.

Next Monday you arrive at work, and your boss is crazy looking for you:

U$$5.000.000,00 have disappeared from the system!

You were charged to pay the loss, but you don’t have all the money…

So, the FBI, CIA and Batman are called to arrest you and send you to prison, without access to internet, Netflix and Spotify… What a nightmare!

But what the hell happened?
After leaving the prison, you’ve decided to debug your code and figured out the problem: a network problem caused a few deposit transactions to fail. So, the money was withdrawn correctly, but many deposit operations failed, so the money was lost!

How to deal with that?

This is a classic situation of atomic transactions.
Atomic transaction is the concept of wrapping together 2 or more transactions and dealing with them as a single unit. So, the situation is resumed to:

  • All transactions committed successfully: atomic transaction was successful!

  • At least one transaction failed: atomic transaction failed!

Back to our original problem, it can be fixed by simple wrapping withdraw and deposit transactions in a single atomic action. So, if one of them fails, all transactions that are part of the atomic action are rolled back, and the system returns to a safe state, where the money is returned to the original client.

ActiveRecord itself provides an easy way to create atomic transactions, as shown below:

def transfer
  amount = params[:amount]

  Wallet.transaction do
    @client1.withdrawal(amount)
    @client2.deposit(amount)
  end

  render status: :ok
end

Now, our system is protected against money loss due fail transactions. Two simple lines that could saved you from a season in prison 😛

Keep in mind

Here are some tips when using atomic transactions:

  • **Transactions are not distributed across database connections:
    **Atomic transactions are not capable to deal with transactions from multiple databases. In order to deal with such situation, another approaches must be analyzed.

  • **Save and destroy actions are automatically wrapped in an atomic transaction:
    **When saving or destroying a record, atomic transactions aren’t necessary, since they’re already wrapped in an atomic transaction by ActiveRecord.

We want to work with you. Check out our "What We Do" section!