A dim lit room. A bunch of hooded figures sit around the table.
If you didn't know better you'd swear they are doing an ancient ritual to wake up a sleeping monstrosity consisting mostly of tentacles, cones at odd angles and sexual innuendo.
But you do know better, these are just your nerdy friends and they are doing what you've come to know as...
Hive mind
"Hoodies never go out of style" you think as you take your position around the table.
You take a quick look around and, for a moment, feel transported to one of those logic puzzles from childhood:
"The person with the extreme allergies sits between the one that speaks in the third person and the one solving the Rubik's cube. The one that drinks soda non-stop is also a fan of typed languages...
The rules of "Hive mind" are pretty simple, people take turns telling stories around the table. Stories of magic and mystery, of insanity and ingenuity, of the highest highs and the lowest lows... but they all share a common pattern, there's something to be learned from them.
And so it begins...
Story 1: the forest and the trees
"OK, so we have events. These things capture something that happened in our app. They come in two flavors: frontend and backend".
"Frontend events are close to the end users and have a ton of context (what did they do just now, what did they do before that, etc.). They are also insecure and unreliable".
"Backend events are trustworthy and secure but their context is quite limited".
Problem statement:
how might we add some rich frontend context to our "User registered" backend event?
"Any ideas on how to solve this?"
...the speaker reads the room, the readers speak their mind...
(You, yes you 🫵 are one such reader, what do you think we should do?)
After the brain storming and thundering quiets down, the room seems to agree on two possible solutions:
- Make "User registered" a frontend event (that way we can add all the context we want, security be damned)
- Send extra tracking stuff to the “register user” endpoint and track from there (because we love our endpoints with all sorts of random extra stuff in them)
Dismay feels the air, the speaker continues...
"Pretty disappointing, right?"
"The reason for this is that the problem statement is wrong! It biases out of other possible solutions, let's take a step back..."
- Why do we want that extra context in the "User registered" event?
- Because we want to use it in a report
- Where is that report coming from?
- Our data warehouse
- Don't we have both backend and frontend events in the data warehouse?
- Yes!
- Can't we just
JOIN
them? - Yes!
"OK, so the problem statement could've been:
We want to be able to create a report on registered users that is trustworthy (backend) and includes behavioral tracking stuff we have in the frontend.
"See how the two solutions above still apply to this problem statement?"
"...but now we have other options at hand, like tracking two events, one from the backend and one from the frontend and then JOIN
them back in the report".
Morale: when all solutions are disappointing perhaps the problem is ill-defined and we need to ask ourselves what are we really trying to accomplish.
Story 2: the run query gig
One hooded figure takes out a strange contraption, presses a button and speaks into a microphone:
push push pop pop
push push pop (rest)
The contraption starts looping the phrase non-stop.
If you had to describe this to someone else you'd probably call it "Infinite stack beatboxing".
Others might call it "an OOM waiting to happen", but in this circle this is considered art.
The figure, now standing on the table, begins some free-style rapping:
This is the story of a thing gone wrong,
So I sing this song, 'cause y'all should know.
We were dealing with this ugly query,
It was big and hairy, it was slow and scary.
So we caged that beast in this neat old function,
That would hide dysfunction and prevent malfunction.
We called this function the runQuery
gig, ho!
The runQuery
gig, and it goes like this:
(snippet instrumental break)
function runQuery(
...some args...,
addSomeCrap: boolean,
): Stuff;
That nasty query's usage was not small,
So we replaced it all with a runQuery
call.
While we worked addSomeCrap
was true
,
But as the code improved that would get removed.
Unfortunately runQuery
had a trap,
'cause it had no map, and you could still add crap.
Surely enough someone did fall prey,
And to our dismay, added more new crap, no way!
So we saw the problem with our runQuery
gig, ho!
Our runQuery
gig, and it goes like this:
(reflective prosaic coda)
We had a problem with our nasty query so we chose to hide it behind and abstraction layer, one we called
runQuery
.We knew in the end
runQuery
would be efficient, but as we iteratively worked through the all the calling instances we needed a way to still convey its inefficient version (and soaddSomeCrap
was stilltrue
sometimes).What we failed to see is that
runQuery
became our abstraction's interface and that interface could be used for good and for evil, and turning one into the other was just onetrue
away.A better approach was to have two separate
runQuery
versions the proper one,runQuery
, and its evil twin,runQueryInefficientlyPullingAllSortsOfCrap
.We could then throw a nice comment in the latter explaining why you shouldn't use it, what to use instead and even throw in a
@deprecated
tag so that the IDE helps you spot the trap.
(Hooded figure bows, standing ovation, the curtain falls)
Top comments (0)