Symphonious

Living in a state of accord.

Moolah Diaries – The Moment Dilemma

moment is one of those javascript libraries that just breaks my heart. It does so much right – nearly every date function you’d want right at your finger tips, no major gotchas causing bugs, clean API and good docs. I’ve used moment in some pretty hard code, very date/time orientated apps and been very happy.

But it breaks my heart because when you pull it in via webpack, it winds up being a giant monolithic blob. I only need a couple of date functions but with moment I wind up getting every possible date function you can imagine plus translations into a huge range of languages I don’t need. If I use moment, about 80% of Moolah’s client javascript is moment.

So I was quite pleased to find date-fns which also provides every date function you can imagine but is specifically design to be modular so you only pull in what you actually need. Works a treat, so Moolah uses that for the date operations it needs and it barely adds any size at all.

Except then when I went to use a graph library, it turns out that Chart.js which I was keen to try depends on moment. Sigh.

Moolah Diaries – Finding Transaction in the Past Month in MySQL

Ultimately the point of Moolah isn’t just to record a bunch of transactions, it’s to provide some insight into how your finances are going. The key question being how much more or less are we spending than we earn? I use a few simple bits of analysis to answer that question in Moolah, the first of which is income vs expenses over the past x months.

The simple way to do that is to show totals based on calendar month (month to date for the current month), but since my salary is paid monthly that doesn’t provide a very useful view of the current month since no income has arrived yet.

I really want to see data for the previous month as a sliding window.  So on the 3rd July the last month would be 4 June to 3 July (inclusive) and on the 25th of July it would be 26 June to 25 July. Given the way salary and bills are paid each month that provides a fairly stable view of income vs expense without fluctuating too much due to the time of the month.

Previously that was easy enough because all the transactions were in memory and we were iterating in JavaScript – flexible but doesn’t scale particularly well. With Moolah 2 we really want to do that in the database and avoid loading all the transactions.

The key bit of sql that makes it possible to achieve this sliding window for previous month is the group by clause:

GROUP BY IF(DAYOFMONTH(date) > DAYOFMONTH(NOW()), 
EXTRACT(YEAR_MONTH FROM DATE_ADD(date, INTERVAL 1 MONTH)),
EXTRACT(YEAR_MONTH FROM date))

There’s nothing particularly magic here – we just decide whether to push a transaction date into the next month based on whether it’s day of month is past the current day of month. The full transaction is in analysisDao.js.

Initially I limited the query to just the last 12 months worth but MySQL can iterate so much faster than JavaScript it can easily run through the full data set covering about 6 years. I probably should put some limit on it but I’m interested in how long it will last.

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 => store.watch(
    (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…