In this post I am going to discuss multi-legged transactions. Multi legged transactions occurs when items are transferred to and from multiple accounts. For example, If I withdraw $100.00 from my chequing account and put $50.00 in to a retirement savings account and the other $50.00 in to a utility payment account. If you would rather just read the source code, I have provided a download.
In order to successfully complete a multi legged transactions, the total of all exchanges must balance to 0. It’s important to remember that Accounts don’t just apply to monetary values. You can have Gas Volume account with a unit of measure in MCF, or an Oil Volume account measured in BOED. When transferring money from a money account to a gas account, you are essentially buying gas which means you must convert money in to it’s equivalent amount of gas at current price of gas. We’ll talk about different strategies of how you can accomplish this.
There are also times when you want to group accounts together to create a hierarchy of accounts. For example, I could have an expenses account which aggregates entries from a utility payment account, a tax account, and a food expenses account. This is a type of Summary Account which can aggregate one or more Accounts. Accounts at their lowest level are called Detail Accounts. Detail accounts track a the entries for a single account.
Let’s start with an example. When transferring funds from one account to another it should increase the balance of the destination account and decrease the balance of the source account. For this example both of our accounts will use the same currency.
Take a look at the “because” block in the above code. We are depositing a quantity of 100 CAD in to one account, and withdrawing a quantity of 100 CAD from another account. This transaction balances to zero, so when we post the transaction it shouldn’t have any problems. Pretty straight forward so far. We’ve made some key design decisions in these tests. Instead of modeling an account just for money, we are using a Quantity object. This allows to potentially withdraw 80 CAD from one account and deposit 1 BOED of oil in to another account. Before jumping in to the Transaction class let’s take a quick peek at Quantity.
In our current implementation Quantity’s can be added to one another, and they can be subtracted from one another. Each quantity represents a single amount of something. That something is represented as a Unit Of Measure. For example, 100 Canadian dollars can be represented as a quantity of 100 with a unit of measure of CAD. 1000 BOED of oil can be modeled as a quantity of 1000 with a unit of measure of BOED. 6 MCF of gas can be represented as a quantity of 6 with a unit of measure of MCF. Each unit of measure can be converted to another unit of measure. When we add 100 CAD to 1000 BOED we may want the result to be measured in CAD or BOED. This requires a conversion using the price of oil at the time of conversion. Let’s talk about one strategy to do this using a exchange rate table.
Unit of Measure
We need a way to sneak in a rate table lookup during runtime, usually the way we would do this is by pushing in a domain service to use to lookup the current days rate. What we want to avoid is having our Domain model reaching out to an external third party directly to look up the rates. But we want to be able to provide a rate table lookup strategy at run time.
The “provide_rate” method allows us to push in a rate lookup, in a manner that doesn’t couple us to the implementation. The actual implementation might open up a connection to a remote host and pull down the current rates, or it might cache the rates and serve them. Either way this becomes completely open for extension. We can now drop in different units of measure like BOED, Currency, MCF etc.
Ok now let’s get back on track, we were talking about multi legged transactions. When we are building a transaction we need a way to record the potential entries before actually posting them to each respective account. When we deposit or withdraw anything we record Potential transactions. When we post the transaction we ensure that the balance is zero. If it’s all good then, we commit each potential transaction to the respective accounts.
Now I skipped a bunch of tests, but you can download the source to check out the rest. Each potential transaction records the account that is the target of the entry, and whether it was a deposit or a withdrawal.
When the potential transaction is committed it simple adds the equivalent entry to the target account. When the account calculates the balance it sums up each entry. Withdrawal entries decrement the amount, and deposits increase the amount. The balance is returned in the unit of measure that the account manages. If it’s a monetary account, then a monetary quantity is returned.