Table of Contents

Banks are serious business. They need serious databases to store serious transactions and serious account information. They can’t lose any money. Ever. They can also not create money. A bank must be in balance. All the time.

Conventional wisdom says a database needs to support transactions to be taken seriously. CouchDB does not support transactions in the traditional sense (although it works transactional), so you could conclude CouchDB is not well suited to store bank data. Besides, would you trust your money to a couch? Well, we would. This chapter explains why.

Accountants don’t Use Erasers

Say you want to give $100 to your cousin Paul for the New York Cheesecake he sent to you. Back in the day, you had to travel all the way to New York and hand Paul the money or you could send it via (paper) mail. Both are considerably inconvenient so people started looking for alternatives. At one point, banks started to offer they’d take care of the money arriving at Paul’s safely without headaches. Of course they charged for the convenience, but you’re happy to pay a little fee if you could save a trip to New York. Behind the scenes though, the bank would send somebody with your money to give it to Paul’s bank. The same procedure, but somebody else is dealing with the trouble. Also, banks sending money could be batched, instead of sending each order on its own, they could collect all transfers to New York over a week and send them over all in one go. In case of any problems, say the recipient is no longer a customer of the bank (remember, it took weeks to get from one coast to the other 150 years ago), the money was sent back to the originating account.

Eventually, the modern banking system was put in place and the actual sending money back and forth could be stopped (much to the disdain of road-thieves). Banks had money on paper which they could send around without actually sending valuables. The old concept is stuck in our heads though. To send somebody some of our money from our bank account, the bank needs to take the notes out of the account and bring it to the receiving account. But nowadays we’re also used to things happen instantaneously. It just takes a few clicks to order goods from eBay and have them placed into the mail, why should a banking transaction take any longer?

Banks are all electronic these days (and have been for a while). When we issue a money transfer, we expect it to go through immediately and we expect it do work in the way it worked back in the day. Take money from my account, add it to Paul’s account, if anything goes wrong, put it back in my account. While this is logically what happens, that’s not how it works behind the scenes; and haven’t since way before computers were used for banking.

When you go to your bank and ask it to send money to Paul, the accountant will start a transaction by noting down that you ordered the sending of the money. The transaction will include the date, amount and recipient. Remember that banks always need to be in balance. The money taken from your account cannot vanish. The accountant will move the money into an in transit account, that the bank maintains for you. Your account balance at this point is an aggregation of your current balance and the transactions in the in transit account. Now the bank goes and sees if Paul’s account is what you say it is and if the money could arrive there safely. If that’s the case, the money is moved in another single transaction from the in transit account to Paul’s account. Everything is in balance. Notice how there are multiple independent transactions and not one big transaction that combines a number of actions.

Now let’s consider an error case: Paul’s account no longer exists. When the banks finds out while performing the batch operation of all the in transit transactions that need to be performed. A second transaction is generated that moves they money back from the in transit account to your bank account. Not that the transaction that moved the money off you account is not undone. A second transaction that does the reverse action is created.

Another error case is you not having the sufficient funds to send $100 to Paul. This will checked by the accountant (or software) before creating any money-deducting transaction.

This is where the title of this section comes into play. For accountability a bank cannot pretend an action didn’t happen, but has to record every action minutely in a log. Undoing is done explicitly by performing a reverse action, not by reverting or removing an existing transaction.

The title of this section quotes Pat Helland, a senior architect of transactional systems who worked at Microsoft and Amazon (read: he knows his shit).

To rehash the transaction (we know the terminology is a bit misleading here) of moving money between accounts is broken up into smaller transactions that can be guaranteed to succeed or fail and if they fail, reverse actions are taken. That way the task of guaranteeing money arriving that is hard if not broken up, becomes manageable.

Turns out, these smaller transactions are possible to model in CouchDB. Above we mentioned that your account balance is an aggregated value. If we stick to this picture, things become downright easy. Instead of updating the balance of two accounts (yours and Paul’s, or yours and the in transit account), we simply create a single transaction document that describes what we’re doing and use a view to aggregate your account balance.

Let’s consider a bunch of transactions:

...
{"from":"Jan","to":"Paul","amount":100}
{"from":"Paul","to":"Steve","amount":20}
{"from":"Work","to":"Jan","amount":200}
...

Single document writes in CouchDB are atomic. This guarantees that out bank is always in balance. There are many more transactions of course, but these will do for illustration purposes. How do we read the current account balance? Easy, create a Map/Reduce view:

Example: Map Function
function(transaction) {
  emit(transaction.from, transaction.amount * -1);
  emit(transaction.to, transaction.amount);
}
Example: Reduce Function
function(keys, values) {
  return sum(values);
}

Doesn’t look too hard, does it? We’ll store this in a view balance in a _design/account document. Let’s find out Jan’s balance:

curl 'http://127.0.0.1:5984/bank/_design/account/_view/balance?key="Jan"'

CouchDB replies:

{"rows":[
{"key":null,"value":100}
]}

Looks good!

Now let’s see if our bank is actually in balance. The sum of all transactions should be zero:

curl http://127.0.0.1:5984/bank/_design/account/_view/balance

CouchDB replies:

{"rows":[
{"key":null,"value":0}
]}

Wrapping Up

This should explain that applications with strong consistency requirements can use CouchDB if it is possible to break up bigger transactions into smaller ones. A bank is a good enough approximation of a serious business, so that you can be safe modelling your important business logic into small CouchDB transactions.