Full-time full-stack software development: year 2 reflection
I've been working full-time as a full-stack software developer for two years 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.
Canonical State calculation
A core aspect of the system is calculating (or deriving) the state of some business object from a particular device message uplink. As this can be rather convoluted, depending on the particular business requirements, I sought to determine the complete set of canonical steps, their dependency chain, and execution order. Using this knowledge, I implemented a template, where it becomes much clearer how and where to customise according to each business venture.
Device Console
Whilst the devices have a primary interface over LoRaWAN, they also have a secondary interface available to the engineers for development and debugging. This is typically accessed using a dedicated physical interface, or via USB on some devices. It would be helpful if certain aspects of this were available to the user, particularly for initial setup and configuration, or perhaps if the network is unavailable.
To this end, the device console that I previously implemented via NFC has been extended to work over BLE. Along the way, with a new engineer, I managed to design and implement a much more robust identification and communication protocol. I'm quite proud of this protocol, as it should allow for a much more polished user experience in the future with a nicely reliable underlying transport, all backed by BLE.
There were some last-minute frustrations with iOS not supporting the Bluetooth 5.0 Extended Advertising Packet Length feature, but this was circumvented by appropriate use of the Advertising Record and Scan Response, which each provides 28 and 31 bytes, respectively.
This also forced a reconsideration of what data to include or exclude, which lead to the idea of not incorporating the device type as a string: rather, the app will identify it via the same mechanism as for LoRaWAN uplinks. This has the added benefit of letting business ventures customise the displayed name, as mentioned below.
Data flow
Being a web app (and IoT at that), the UI is entirely reliant on fetching data from some backend datastore. Previously, each page was responsible for firing off a network query for the applicable data, waiting for it to return, and then displaying it. If you navigated away and back again, it would do it all over again — despite being a SPA!
This is a bit ridiculous, especially as the app has always had a mechanism of caching responses from the datastore that it kept between navigation. Thus, I have adjusted all the core components so that they pull from this cache first, foremost, always. At present, they also all fetch from the datastore and update the cache with the response. This means that if the cache already has the data from a prior navigation, or some prefetch mechanism, page navigation and load can be nearly instant.
Payload definitions
Previously, every business venture had their own dedicated devices, their own dedicated payload definitions for defining how to encode / decode bytes.
The new direction of having a standard set of devices shared amongst the business ventures means that payload definitions should be consistent across them. This is accomplished easily enough by moving them to a single source of truth, and implementing a utility to make them applicable to the particular business venture with one or two lines of code (literally).
This also included a general tidy-up and some extensions and additions to the payload definition attributes, which encode a chunk of information within a single message uplink / downlink.
Naming utilities
I have canonicalised names for types of things (objects and devices) into some standard utlity functions. These are configurable with overrides by the relevant business venture for each object type and device type. These overrides are applied by the UI only, at the presentation layer. All processing and storage always uses the exact same name, and thus it is transformed at the last possible moment for display.