Full-time full-stack software development: year 1 reflection
I've been working full-time as a full-stack software developer for a year now. It's been quite the journey of learning how professional programming actually works out - turns out it's not that different from hobbyist programming.
Regardless, there are a couple of cool things that I've been working on, and I thought I might as well document them here, for posterity.
Alerts / Incidents / Notifications
The first task I was assigned was to make the alerts / incidents / notifications system actually configurable and user-friendly. My first several atttempts were quite bad, but eventually I managed to understand the context they were used in, and the company's general ideology, to be able to put together something relatively cohesive.
It's now got a slick modern Material Design-based interface with drag'n'drop message templates with placeholders that are replaced when generating a notification for an Incident update.
Setting up Notification Subscriptions for Incidents is still a bit awkward, so hopefully I can come up with some good ideas for that in the next year.
Mobile app
The next area of development that I improved was the native mobile app. Well, "native" mobile app. It uses React Native, which is all kinds of pain and very occasionally, really cool. The mobile app is definitely the most modern part of our codebase, and can be quite refreshing to work on. At least, changing one part of it won't randomly break some other tangential aspect.
Optimisation
We use webpack
to bundle our React web app, which has several levers to pull to optimise loading, most particularly: chunking. This is a method for splitting up a single compiled JS file into distinct, separately loadable modules, which can be loaded as the code requests them. The ESM format is already relatively supportive of this, and with the help of a neat little library (loadable
), along with React's lazy
async imports, we get top-level lazy-loaded imports at key app boundaries. Our app is already split into distinct top-level containers for certain views, so this was relatively trivial to implement.
On the other hand, the levers available to me to control webpack
through CRACO
are very spongy and rather abstract / obtuse. It took a good many months of trying and re-trying in order to actually understand what on earth I was doing, especially as what you think you're specifying is actually one level of abstraction above what the output results in. As such, you can't directly see the raw output from your instructions, but only the second-level output from your rules. Once I understood this shift in understanding, it was much easier to tweak things towards a semi-optimal output.
The other aspect of optimisation was a brilliant piece of insight that came to me late one night (or maybe in the shower, even?). See, the first thing our app does on pretty much any page, is query for data. But, as we use a graph-like structure in our database, it needs some vantage point, some context object to query from. This is usually the user, or the user's customer. After authentication (which is always really fast, as AWS Amplify can cache the user's tokens and thus avoid a network request), we need to load the user's account, and then their context (which includes their associated customer). The former can take some time, as it is the first connection out to the backend, which means there's a few handshakes and startups that need to happen. The latter can be quite chunky, depending on just how much stuff is related to the user (a problem for later).
At the same time, we use Redux
to manage our core app state, such as the user and customer and whether we are authenticated or not. Thus, the inspiration: why not cache the user and customer, just how AWS Amplify does, and insert them into the app state once we receive authentication? Then, later, when the normal chain of events loads the user account and the context, they will update the app state with the canonical state as usual, and we can update the cache, too.
It's beautiful. Utterly simple, literally one line of code each for the user and the customer. In reality it's closer to 10-or-so lines, because we want to have some basic safety checks, but the logical concept of the process is blissfully simple and foolproof. That's the best part. Caching is a notoriously hard thing to do in computer science, and I had found a way of doing it perfectly. Even if the cache is outdated, the regular queries to get the canonical state will always work and will replace the incorrect, outdated cached version, which we would have erroneously used for a second or two. Even then, the only time those first queries could be incorrect (because the backend is 100% consistent) would be if the user was moved to a different customer, and we were running queries from the customer.
Anyway, that single change that I implemented in less than 15 minutes resulting in dropping our app startup time (i.e. time to first seeing data) from c. 4 seconds (on gigabit fibre) down to just under 2 seconds!
Header
A good web app should have a good header bar. I've poured plenty of hours into this very websites header bar, to get it just right. (It's not, and I could spend many more hours on it still — sorry.) When I started, the header bar had an eclectic mix of styled components which somehow all managed to feel out of place. There was a navigation menu toggle button (that only showed up when the navigation bar was toggleable), a context customer selector, and a user role selector, along with a logout button on the other end. The logout button remained even after logging out.
This was painfully unprofessional, so I sought to improve it at every chance I had. I spent plenty of extra time polishing it up, where some of my first targets were the logout button: it should vanish once you are actually logged out! Once that was sorted, I started to tidy up the styling of the user role selector and context customer selector. One was a button that didn't look like a button, and the other was a dropdown that was a mish-mash of Material Design and OS-native components... just awful.
Finally, I got permission / time to tidy the header, with the aim of making it customisable and extensible. I made every element with the same button-like Material Design style, with simple popups to say what they were, e.g. "Change role" and "Change customer" and "Logout." I made the navigation menu toggle button stick around the whole time, added an icon and name for the web app that links you to the dashboard, and ensured it all folds up neatly to fit on a mobile device. Simple stuff, that genuinely makes an enormous difference towards feeling like a real product.
Import / Export
The next area of (ongoing) work is to implement an atomic import/export utlity from/to Google Sheets. This is especially challenging, as there must be high-quality and reliable interaction between the UI and the backend performing the work. This was especially challenging to implement in our context of React and AWS Lambda's, as having an authenticated and reliable realtime communication link was not... obvious. Nor did AWS want me to do it, but darnit I'm a programmer, the computer does way I say, not the other way around!