Living in a state of accord.

Moolah Diaries – Data Parity

Moolah just reached it’s first key milestone – it’s reached “data parity” with the old version. Basically it has enough functionality that it’s viable to import the data from the old version and get the same balances and totals. Using real data that’s been generated over the last 5 years has revealed a few important things.

The good:

  • The balances add up so things really are working
  • It really does load way faster
  • The old version allowed some data inconsistencies that the new version will prevent

The bad:

  • There’s quite a few features still missing from the display – more than I’d realised
  • There’s a few places that are much slower than expected because of some rather careless algorithm/data structure choices. Fortunately there’s some trivial changes that should give pretty dramatic improvements.

The next major milestone will be when I can actually switch over from the old system. Hopefully that’s not too far off.

Moolah Diaries – Tracking Account Balances

Moolah has two separate Vuex modules, transactions and accounts. That’s a fairly reasonably logical separation, but in the new Moolah, it’s not a complete separation. Moolah only ever loads a subset of transactions – typically the most recent transactions from the currently selected account.  As a result, accounts have their own current balance property because they can’t always calculate it off of their transactions.

That means that sometimes the transaction Vuex module needs to make changes to state owned by the account module which is a rather unpleasant breach of module separation. While we ensured transaction balances remained consistent inside each mutation, we can’t do that for account balances because Vuex (presumably deliberately) makes it impossible for a mutation to access or change state outside of the module itself. Instead, I’ve used a simple Vuex plugin that watches the balance on the most recent transaction and notifies the account module when it changes:

import {mutations as accountsMutations} from './accountsStore';
export default store =>
    (state) => {
        return state.transactions.transactions.length > 0 
? state.transactions.transactions[0].balance
: undefined; }, (newValue, oldValue) => { if (store.getters["accounts/selectedAccount"] !== undefined &&
newValue !== undefined) { store.commit('accounts/' + accountsMutations.updateAccount, { id: store.state.selectedAccountId,
patch: {balance: newValue} }); } });

Most of the complexity there is dealing with the case where we don’t yet have transaction or don’t have a selected account. This essentially makes the syncing between transaction balances and account balances a separate responsibility that belongs to this plugin.

The plugin doesn’t completely handle transfers between accounts. The balance for the account currently being edited updates correctly, but the balance for the account on the other side of the transfer doesn’t update. I’m not particularly happy with the solution, but at least for now the responsibility of reacting to transfer changes is in the transaction module’s actions. The action can easily calculate the effects of transaction changes on the balances of other accounts and use Vuex’s dispatch to send a notification over to the account module to perform the update. Mutations can’t dispatch events so it has to be the action that does it.

This leaves responsibility for updating the current account’s balance with the plugin and the responsibility for updating other account’s balances with the transaction module which is ugly. Perhaps the transaction module actions should be responsible for all account balances. Probably the right answer is to extract a separate class/function that the transaction module notifies at the end of each action with the state of the transaction before and after. Then that new class/function is responsible for updating account balances. Will need to prod the code a bit to see if that really can be done…



Moolah Diaries – Maintaining Invariants with Vuex Mutations

Previously on the Moolah Diaries I had plans to recalculate the current balance at each transaction as part of any Vuex action that changed a transaction. Tracking balances is a good example of an invariant – at the completion of any atomic change, the balance for a transaction should be the initial balance plus the sum of all transaction amounts prior to the current transaction.

The other invariant around transactions is that they should be sorted in reverse chronological order. To make the order completely deterministic, transactions with the same dates are then sorted by ID. This isn’t strictly necessary, but it avoids having transactions jump around unexpectedly.

My original plan was to ensure both of these invariants were preserved as part of each Vuex action, but actions aren’t the atomic operations in Vuex – mutations are. So we should update balances and adjust sort order as part of any mutation that affects transactions. Since only mutations can change state in Vuex, we can be sure that if our mutations preserve the invariants then they will always hold true. With actions, there’s always the risk that something would use mutations directly and break the invariants.

So lesson number 1 – in Vuex, it should be mutations that are responsible for maintaining invariants. That’s probably not news to anyone who’s used Vuex for long.

We could take that a step further and verify that the invariants always hold true using a vuex plugin:

const invariantPlugin = store => {
if (process.env.NODE_ENV !== 'production') {
store.subscribe((mutation, state) => {
// Verify invariants

However, the way vuex recommends testing actions and mutations means registered plugins don’t get loaded. Tests that cover views that use the store will most likely stub out the actions since they’ll usually make HTTP requests.  So mostly the invariant check will only run during manual testing. That may still be worth it, but I’m not entirely sure yet…


Moolah Diaries – Multi-tenant Support for Testing

Experience at LMAX has very clearly demonstrated the benefits of good test isolation, so one of the first things I added to Moolah was multi-tenant support. Each account is a completely isolated little world which is perfect for testing.

Given that I’ve completely punted on storing passwords by delegating authentication to Google (and potentially other places in the future since it’s using bell to handle third party authentication), there’s actually no user creation step at all which makes it even easier. As a result it’s not just acceptance tests that are benefiting from the isolation but also the database tests which no longer need to delete everything in the database to give themselves a clear workspace.

It’s all quite civilised really.

Moolah Diaries – Vuex for Front End State

Most of the work I’ve done on Moolah so far has been on the server side – primarily fairly boring setup work and understanding the best way to use any number of libraries that were new to me sensibly. The most interesting work however has been on the front-end. I’ve been using Vue.js and Vuetify for the UI after Vue’s success in the day job. The Moolah UI has much more data-interdependence between components than what we’ve needed at work though so I’ve introduced Vuex to manage the state in a centralised and more managed way.

I really quite like the flow – Vue components still own any state related directly to the UI like whether a dialog is shown or hidden etc but all the business model is stored in and managed by the Vuex store. The Vue components dispatch actions which perform computation, make requests to the backend or whatever is required then commit any changes to the store (via mutations). The usual Vue data-binding then kicks in to update the UI to reflect those changes.

The big advantage of this is that it naturally pulls business logic out of .vue files, preventing them getting to big. Without Vuex that basically depends on having the discipline to notice when a .vue file is doing too much and then untangling and splitting out the business logic.  Vuex provides a much clearer and more consistent way to delineate business logic from view code because you can’t modify state directly from the Vue component and it then becomes natural to split out an action.

Vuex’s module support also makes it easy to avoid your Vuex store from becoming the big ball of mud that does everything.

However, I’m still searching for a good, efficient way to calculate and update the current balance for each transaction. The actual calculation is simple enough – the balance for any transaction is the sum of the amount of every transaction before it in the account. Simplistically we could just start from the first transaction and iterate through calculating all the balances in a single O(n) pass. However, recalculating the balance for every transaction on each change is incredibly wasteful and is a big part of why the original version of Moolah takes so long to get started – it’s calculating all those balances. Each transaction balance actually only depends on two things, the transaction amount and the balance of the previous transaction. Since most new or changed transactions are at or near the very end of the transaction list, we should be able to avoid recalculating most of the balances.

I don’t think Vue/Vuex’s lazy evaluation will be able to avoid doing a lot of extra recalculation, not least of all because the only way to represent this would be a transactionsWithBalances computed view and it would output the entire list of transactions so would recalculate every balance on every change.

However, it’s reasonably straight forward to build the lazy evaluation manually, but where does that sit in the Vuex system? I’m guessing pre-calculated balance is just a property of every transaction in the state and actions take responsibility for updating any balances they might have affected.

I’m leaning towards having a dedicated ‘updateBalances’ action that can be triggered at the end of any action that changes transactions and is given the first transaction that requires it’s balance recalculated. Since every transaction after that depends on the balance of the one before they’ll also need updating.

I think that works and am now reminded about how useful it is to write a diary like this as a way to think through issues like this.