On dev design for Node.js developers
I had an interesting bit of insight in the process of making a hand-off from my current role as a Principal pain-in-the-behind engineer to my successor:
Dev Designs are not clear to inexperienced devs!
Now I want to refine our dev design (doc and process), but before that, I want to clarify my thoughts on the topic first.
Why do we have a dev design?
When I joined the company last year, one of the things I’ve introduced in the product development cycle was a “dev design” process. The code base was large enough, but it seemed a bit… naïve, and I wanted to improve this situation.
We’re building software with a relatively classic MEAN+RabbitMQ stack. Several services, built with Node+Express+Mongoose in front of MongoDB, RabbitMQ for some interservice communications, and a couple of Angular webapps for front-end. So a mixed JavaScript/TypeScript monorepo, mostly.
The services are built straight-forward enough, but it’s a bit too literal. There are mongoose models for MongoDB - but overembedded, some dynamic keys, difference between internal vs collection-level integrity checks unclear etc. The “controller” layer are essentially express handlers, with some logic, some validations, some utilities and helpers thrown around in “shared”-type files around. Lots of “business logic” embedded in the Angular components, of all things. Infrastructure concerns not really separated. Generally what you’d expect from a team of people who didn’t code a lot - at least not in larger-scale JavaScript codebases - before.
I’ve started some trainings and workshops and docs, but I also wanted to have a check-list for everybody to easily follow. The checklist would make sure that we put the right code into the right files.
So that is the first big important point in this post:
Dev Design serves as a check-list for developers.
So what’s in the checklist?
The dev design I’ve created starts with making sure that you don’t do the rookie mistake which I, a veteran with decades of coding experience, always fall into: make sure you got the specs.
There’s a checkbox that says “JIRA specs are written, and test cases/acceptance criteria exists and is valid”. There’s another that says that the test cases are exhaustive, too.
This part doesn’t always get checked - but then at least we know, whoever wrote the dev design knows that they have to clarify some things, and come back to revisit the dev design later on, when they get full info. Sometimes you really can’t do this - it’s a big issue into a new domain. So that means you need to break up your ticket, and make sure you have a manageable scope of the problem you’re solving.
So, there’s another point to highlight:
One of the purposes of dev design is to ensure that the specifications are clear and concise.
The “main” part of the dev design was related to such intangible questions - at least in JavaScript codebases. Some examples:
- We want things like “ensure that the endpoint has a exhaustive query/params validator” there.
- We have rules like “ensure that any call to the database model is in a service layer”.
- We have checkboxes like “do not make calls to other models from a mongoose model”.
- We make you “ensure that there exists an index on the field you’re querying for”.
- The one I really like is “if you touch any function in a file called ‘shared’, move that function to the appropriate place.”
I like that last one even though it is kind of dogmatic. But when the code base is not super clean, it’s better to overdo it then not do it at all, in my experience.
Basically, make sure you sorted out between the different types of logic. That is really important when you have people who did not work in clean or concise codebases - you show them that there can be order, and there can be rules. And you make them consider future uses, and future changes. You want the person to think, “If I put this bit into a validator, then I don’t care what is in the params - I can just validate it and pass the params to my service”. And in a clean codebase that follows the separation-of-concerns principle in such an explicit and direct way, you learn that you can trust the invisible part of the codebase a lot better. (The side effect is that now your team starts to walk cautiously around the “old” parts of the codebase, nobody wants to touch it. “It’s so brittle, anything you touch breaks other unrelated things!”)
But we also have some very specifc problems. Like that line for “shared” files. We have a few more things like that, like “don’t use this”, or “do use that”.
So in dev-design, we’re considering technical debt. You kind of sneak that in to the developer, and they have to actively consider it when e.g. estimating how big a ticket that is.
I have another rule with a “hidden agenda” in the dev design, roughly like this:
- Look into existing code. What needs refactoring?
That means that we are proactively considering this. We’re not just bolting patches on workarounds on hotfixes - holistically looking at your code base and giving it enough attention is part of the process.
So another really important point in adding the dev design is that the dev design actively encourages wider impact of the new code on the codebase.
What’s that about “inexperienced developers” though?
I mentioned at the start that “the Dev Designs are not clear to inexperienced devs”. What did I mean by that?
Well, usually you have people in the team who are used to all the usual “best practices” and “SOLID principles” and all the other usefull and less useful acronyms and buzzwords. So this dev design check list will serve mostly like that - a check list, make sure you didn’t skip anything relevant.
Kinda like a pilot before takeoff - they always check “do I have enough fuel” and “is my compass functional” because it’s in the takeoff checklist - and since they do, they decrease the risk of falling out of the sky in less controlled manner then they originally intended.
But what about people who have no experience? If you have some Java devs working on Node.js, they get freaking mad - “where the hell are my annotations, how can I know that this parameter is a string”? Well, write a joi
validator. Then you have someone who did Rails before, “wtf no active record, how do I know my email is unique?” - well that’s why there’s the “check your MongoDB indexes” rule in there. You have a person who only did frontend before, and probably wrote Angular in such a way that there’s 3 components in the entire app, and all the logic is mixed up in methods. “What do you mean, separation of concerns? What even is a business layer?”
For such people a dev design can be a confusing thing. E.g. you check that a parameter exists:
if (!req.query.name) {
throw new Error('Gimme a name there buddy')
}
But this doesn’t validate that a “name” param exists, is minimum 3 long, and the first char can’t be a number. I mean, “the frontend checked that already anyway”. Yes they did, but because there was a bug, we now have trash in our database and we’re doing the third cleanup this year.
Anyway, I realize that the dev design may be a little vague. So you want to make it a lot more explicit than that. To build on my bad analogy, if you ask an experienced pilot to “check that the instruments are all functional”, they’ll go off and check for the six most important instruments.
If you ask me that, I will (probably incorrectly) remember that some guy in a museum recently said that “you need six instruments to fly a plane”, but I have no idea which six are really important.
Isn’t this overengineering things before you even get to engineering things?
One of the people on the team said to me, soon after the dev design rollout, “this is like getting a code review on your pull request, before you write down a single line of code”. They looked at the dev design concept, and took of like a rocket - reviewing their dev designs is an enjoyable task. I know exactly what they’ll do, and I know exactly what they’ll miss. I have no idea what the code will look like, but I can tell what structure, what shape will it have. And I can easily notice when something is missing.
Others, old dogs who “know this shit”, check the boxes, write down 3 lines like “I’ll refactor that service, I’ll extend the controller and migrate the field to a subschema” and are done. Good thing they’re old dogs so I can trust they know what they’re doing - and since they had checked the check-list, they probably got all the details right.
And yet other devs write down pseudocode for the entire Jira Epic. “Dude, I didn’t ask you to do this! Besides, you still didn’t separate your logic into different layers, this still sucks.”
And since I still see such dev designs in my queue, I know that my dev design itself is not yet done. It’s too tedious for experienced devs, it’s too confusing for the juniors.
But at least I have some better idea of what I want it to consider. But that’s a topic for another post.