Closed jbolda closed 8 months ago
As we get closer to what we might consider a stable release cycle, I don't think anything here is necessary to complete beforehand.
Years later we have better dealt with side effects with a library called effection
. In #717, we are switching to a library built atop this which is spiritual successor to redux. Closing this as applicability is stale with this move.
We are using a library called Microstates to manage the global state. We do take care of some local state in various components but that is a state that isn't needed throughout the app. As we've built up the initial architecture of finatr, earlier versions of Microstates led towards having many of the methods on the root app model. As both finatr and Microstates have matured, we are looking to split this up into smaller, more composable pieces. As it stands right now we have all of the reasonable features for the initial stable version of the cash flow analysis. Everything currently implemented in Microstates should meet our needs, and we either need to just start using the feature or we can add a function ourselves. There is one element that is on an experimental branch which we will touch on though. The next steps here will just improve the maintainability of the code and won't add user-facing features.
Part of the maintainability of the code is that someone without familiarity with the code can come in and quickly get up to speed, adding pages or tweaking parts of the state to add functionality. With many of the methods up at the root, it's hard to understand what pieces are coupled to what lower-level classes. The way that Microstates composes can be compared to a tree with branches. The root is the trunk, and classes reference other classes going further and further down the branch. One of the issues that we are seeing is that data that is needed by multiple branches ends up living at the root. If we want to perform side effects on execute functions against that data, it means we also have those methods located in at the same point in the branch or above it.
One of the more recent features that we have started utilizing from Microstates is relationships. The plan with it moving forward is that we will pass a path to a piece of data to create the reference. This is still on an experimental branch/repository and hasn't yet moved over to the main repository. We are using the alpha version of relationships which does not implement this path feature. This will let us reach across these branches in a way we couldn't before. So with this, we can put data further down the branches and split up all of these methods. We will still be able to access them if needed from other branches. This will let us compose pieces and split up the methods into the actual branches that they act on.
Another plan is to slowly migrate towards using the Microstates directly. In earlier implementations of our state, we called the
.state
method to just return the JavaScript object. This lets us work with basic JavaScript and use functions baked into the standard library that weren't already existing in Microstates. This also meant that we were jumping back and forth between plain JavaScript and a Microstate often which can get confusing. As Microstates has matured, all of the functions that we generally need/have used are now implemented in Microstates so that we can write our functions to expect a microstate instead of expecting JavaScript objects. As we move methods and the data further down the branches, we can also begin to rewrite the functions to expect Microstates and bounce between these two things less.One of the remaining items is that we use a lower-level library called big.js. We may consider switching it out for a more currency focused library such as dinero.js. Effectively all of our use cases for a library that fits in this "place" is to chain a bunch of functions together that return a value that we want to use. With newly added Microstates features (since we began using it), we can, instead of mapping each of these functions to a transition in Microstates, give Microstates a function to return and have that encapsulated into a Microstate. This would let us take an existing microstate, run a bunch of big.js functions on it, and then return a value to be encapsulated into a microstate. The value of this is that we can perform multiple functions with our lower-level libraries and only we return one actual transition.
This presumably will help with performance and will reduce the amount of "fighting" that we currently have with Microstates. When we first started using Microstates, any transition would return with the
AppModel
root and we would need to drill back down to the original branch we start on to run another transition if need be. A newer implementation of Microstates returns the branch that the method was transitioned on, e.g. the model that the method was called on. This did reduce some of the depth we needed to drill back down. However, we still end up calling transition after transition after drilling down a level or two when all we want to do is perform a chain of functions.Ultimately we want to set up the code where it's as relatively easy to switch out the lower level math functions. If we choose to switch to deniro.js or stay with big.js, I would expect that we stick with that library for quite a while. However, just having this separation of concerns makes the code easier to reason about.
To summarize, we split up the methods on the
AppModel
(the root) more by using the planned new feature with the relationship function to reach across branches for data. We rewrite methods and the lower-level functions to expect a Microstate instead of a JavaScript object making it less confusing with which we are working. Lastly, we use a function in Microstates to return only one transition based on a function or chain functions for theType
comprised of big.js or dinero.js. All of this ultimately is just for maintainability of the code, separating concerns, and making it easier for somebody new to the library to jump in and help out.