Moolah Diaries – Maintaining Invariants with Vuex Mutations
By Adrian Sutton
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…