With not a lot going on in terms of my Pebble apps (still very much in a ‘if it ain’t broke’ situation), my hobbyist attentions in recent months turned to my Raspberry Pi. With not a lot of exciting ideas for hardware hacking, it occurred to me that software applications of the device might be a bit more interesting.
Beginning with moving the backend services for News Headlines and Tube Status out of a $5 Digital Ocean Droplet to a $0 Raspberry Pi under my desk (with a few forwarded ports, of course), I’ve steadily refined the standard pattern used to write and maintain these apps. At the most there have been six, but today there are five:
- News Headlines Backend – pushing headlines pins.
- Tube Status Backend – pushing delay alerts pins.
- LED Server – providing a localhost RESTful interface to the Blinkt! hat on the physical Pi between apps.
- Attic – a new app, serving and receiving simple JSON objects for storage, backed by a Gist.
- Monitor – responsible for monitoring uptime of the other services, and providing Greater Anglia and TfL Rail outage alerts to myself via my watch. Monitor actually just schedules regular invocations of its plugins’
update interface function, making it extremely extensible.
With my adventures in Node and discovering convenient or standardised ways of doing things like modules, data storage/sharing, soft configuration, etc. these apps have all been refined to use common file layouts, common modules, and a standard template. With its relatively stable state of maturity, I’d like to share this with readers now!
What? It’s not February 2017 anymore? The pattern has matured even further, but I’ve only now found the time to write this blog post? Well, OK then, we can make some edits…
Disclaimer: This isn’t an implementation of any actual accepted standard process/pattern I know of, just the optimum solution I have reached and am happy with under my own steam. Enjoy!
As you can see from any of the linked repositories above, the basic layout for one of my Node apps goes as follows:
.gitignore // 'config.json'
src folder contains
modules (modules that are specific to the app), and
common (using common modules shared between all apps, such as
log.js (standard logger,
pid logging, and
unhandledRejection handlers), as well as
main.js, which initialises the app.
This pattern allows all apps to use common modules that can be guaranteed not only the presence of each other, but of a common
config.json that they can all use to draw configuration information (such as log level, API keys, latitude and longitude, etc.).
Of particular interest is the
config.js module, which all modules that use
config.json information include instead of
config.json. It is untracked in git, and so can safely contain sensitive keys and other values. It also guarantees that keys required by modules are present It also provides some additional benefits:
- Ensuring the
config.json file is present
- Allowing modules that include it to requireKeys to be present in the
config.json file, that they themselves require. Here is an example.
- Stop app launch if any of these keys are not present
- Allow access to the app’s launch directory context.
For example, a fictitious module may require an API key to be present in the
ENV member of
const config = require('../common/config');
config.js behaves, if this structure is not present in
config.json, the app will not start, and will tell the operator (i.e: me!) that the value should be provided. Handy!
Any of these Node apps (and any new apps that come along in the future) can make use of a library of drop-in standard modules, many of which can be found in action in any of the linked repositories at the top of this post), including:
event-bus.js – Provide a pub/sub ‘event bus’ style of communication between modules
fcm.js – Send an event to Firebase Cloud Messaging to show me a notification
led-server-client.js – Communicate with the localhost Blinkt! LED Server instance
scraper.js – Scrape some text using a series of ‘before’ markers, and one after ‘marker’
config.js – Access ‘smart’ configuration with additional capabilities
gist-sync.js – Synchronise a local JSON file/set with a remote Gist
leds.js – Directly drive the connected Blinkt! hat
db.js – Emulate a simple get/set/exists interface with a local JSON file
ip.js – Look up the address of the ‘mothership’ server (either Server Pi or a Digital Ocean Droplet)
log.js – Standard logger, asserts, uncaught/unhandled catching.
So with this standard pattern to my Node apps, it makes it a lot easier to manage the common modules as they are updated/improved, manage SCM untracked soft configuration values (as well as make sure I provide them after migration!), and allow modules to be as drop-in as possible. As with most/all of my hobbyist programming, these approaches and modules are the result of personal refinement, and not from any accepted standard, which is my preferred style when I am the only consumer. Maximise the learnings!
Expect more sporadic information as these apps develop, and enjoy the pins!