The Admin Process
by Laurence Chen
My friend Karen joined an online community of product managers and took on the task of managing a mentor-mentee matchmaking system. She has years of experience as a product manager but lacks a background in software development. Driven by her strong passion for the community, she managed to develop a Python version of the mentor-mentee matching system using ChatGPT. Of course, the story isn’t that simple. The turning point came when she was stuck, no matter how she phrased her questions to ChatGPT, and she came to me for help.
Her stumbling block was this: she was asking ChatGPT top-down questions, so ChatGPT, using probabilities and statistics, generated high-level sequential logic code, essentially the policy part. This generated code would then need to call underlying library functions, the mechanism part, to perform bipartite matching calculations. This is where ChatGPT got stuck. For such tasks, even humans sometimes have to write some glue code to handle the impedance mismatch between policy code and mechanism code, so it was expected that ChatGPT would hit a wall.
After receiving Karen’s project, I conducted experiments, observations, testing, and adjustments. In short, I manually wrote some glue code from the bottom up, and then Karen successfully reached the finish line.
I only found a suitable term to describe this phenomenon of policy code and mechanism code mismatch after years of programming experience. But it seems I had sensed this phenomenon’s existence long ago. Initially, without a feasible solution in mind, I chose bottom-up as the ultimate solution for everything. A few years later, after learning about unit tests, I finally felt that combining top-down and bottom-up approaches was more feasible, because when top-down code is assembled with bottom-up code, unit tests make the behavior of the code visible, significantly reducing assembly time, debugging, and the feedback loop.
As Clojure programmers, we have an unfair advantage—many problems that require unit tests in other languages are automatically solved once we start the REPL.
Extending the Concept of the glue code
If we think about it, when policy code is combined with mechanism code, there’s an impedance mismatch to handle. Isn’t it a similar issue when we want to run the entire application on different machines, change the database contents, modify the data schema, or update an API version?
So how can we extend the concept of the glue code to such scenarios? With a similar logic, we need to write small programs, or admin processes, that can automate tasks involving numerous commands, make the outcomes of those commands visible, and ideally integrate with the Editor.
In Rule 12 of the 12-factor app rule 12, admin processes are discussed, advocating that these background management tasks should run in the same environment as the app’s long-running processes, preferably be developed in the same language, and updated and deployed together to ensure dependency isolation and eliminate synchronization issues.
In the Clojure ecosystem, because the Clojure CLI’s startup speed is relatively slow, many Clojurists choose to use Babashka for admin processes—this is not an issue, as isolating dependencies in Babashka isn’t too difficult.
In many of Gaiwan’s projects, I can find an interesting little script called bin/dev
. They’re all named the same, but their functions differ slightly. Here’s an example:
#!/usr/bin/env bb
(require
'[clojure.java.io :as io]
'[clojure.java.shell :as shell]
'[clojure.pprint :as pprint]
'[clojure.string :as str]
'[lambdaisland.cli :as cli]
'[lambdaisland.shellutils :as su]
)
(def init {})
(defn hello
"A shell hello function"
[opts]
(println (::cli/argv opts))
)
(def commands
["hello" #'hello])
(def flags
["-v, --verbose" "Increase verbosity"
"-n, --dry-run" "Show shell commands, but don't execute them"])
(cli/dispatch
{:init init
:commands commands
:flags flags})
If you run ./bin/dev hello a b c
in the shell, you’ll get output as [a b c]
.
This bin/dev is a mini framework where you can define the functions you need. For more details about the framework itself, you can refer to Well Behaved Command Line Tool.
Reducing Cognitive Load with Admin Processes
Admin processes can be designed as one-off processes to handle automated tasks, such as:
bin/dev deploy
deploys on a remote machinebin/dev generate-auth-keys
generates authentication keysbin/dev up/down/clean
wraps Docker commandsbin/dev tail-logs
logs into a remote host to view logsbin/dev hosts
logs into a remote host to retrieve host status information using Cloud provider commands
Each Cloud provider offers slightly different commands. Once the commonly used commands are wrapped into an admin process, you won’t have to revisit the documentation after a while, even if the commands are no longer familiar. Additionally, the README can be simplified—since a single bin/dev hosts
command can retrieve the current status of running machines.
On the other hand, you might also consider designing a REPL wrapper, for example:
bin/dev prod-repl
logins to a remote machine and connects to its socket REPLbin/dev mysql repl
starts a REPL for the remote database
If every time you want to start a MySQL REPL you have to type a command like mysql -u <local database username> -h <database server ip address> -p
, it can get slightly annoying, right? Wrapping it up makes it so much more pleasant.
The Ever-Releasing Dopamine
A well-designed bin/dev
can make a significant contribution to developers’ productivity. When developers can seamlessly work by just reading the README and using admin processes like bin/dev
—without needing to pause and troubleshoot machine-related issues—the dopamine released in their brains will quickly convert into actual progress.