The liquid harmony with solid Node - Part 1
Recently I watched a tech video. The speaker in there did complained about asynchronous coding style with callbacks and mentioned Node.js as a platform. In fact he said that Node.js === “callback hell”. He’s not the only one out there. He’s partially right. And wrong. There is callback hell in code for Node.js. But not because of the platform, callbacks or javascript, but because of lack of fundamental software design knowledge.
So I decided to start a series on that topic.
Soon you can find following parts under the hashtag #SOLIDnode. I will try to follow this constraints:
- no magic. so you’ll see no promises, fibers, proxies, whatever here.
- concentration on vanilla js, Node.js core API and few 3rd Party libraries, that extend Core API
- I’ll be honest: there are limits. I won’t hide these.
A hint: Most of this practices can be used in browser too, tools like Browserify can help you to use Node.js API and libs there.
Let’s start. Today with part 1:
Callbacks in hell - We met earlier
Why do we speak about a callback hell? In javascript or Node.js we are often facing code, that uses callback chains in very painful way. But somewhere else you’ll find similar but different struggle. The term spaghetti-code isn’t bound to any language or platform. Every language or platform offers us concepts and tools to do software development, but often we fail to use the right tools right.
This is because in software development, we’re constantly facing 2 orthogonal complexities. The first complexity is the domain, the task our software have to solve. We have to understand the domain right, so we can program a system, that does exactly, what’s expected. The transition from one representation of the domain to the other is itself a complex task. This is software development, the second complexity. To reduce the complexity of the development, we need to apply our experience, the patterns and practices we learned earlier. If we have learned the right ones for the given system and the given domain, then our job is much easier. But most often we tend to think in a certain paradigm, that we have learned once, and then to fail to adjust our thinking.
This happens when we code callback spaghetti in node. Look at that code:
server.onRequest(function(params, response){
db.getUser(params, function(user){
if (user == null)
return response.send(USER_NOT_FOUND)
db.getRelated(user.related_id, function(related){
if (related == null)
return response.send(RELATED_NOT_FOUND)
// do something with related
db.save(related, function(){
response.send(OK)
})
})
})
})
re-indent and blend out the braces:
server.onRequest(function(params, response){
db.getUser(params, function(user){
if (user == null) return response.send(USER_NOT_FOUND)
db.getRelated(user.related_id, function(related){
if (related == null) return response.send(RELATED_NOT_FOUND)
// do something with related
db.save(related, function(){
response.send(OK)
})})})})
Yes, we all met this paradigm before. We’re still coding in imperative synchronous paradigm, and even worse, we think about the systems that we create often in this paradigm. Imperative programming is essential, this is how computers work for us. But mostly not how the Systems work that we imagine. Many languages did abstracted away assembler from our screens but not the way to think. But we have also discovered other ways, paradigms. They are just not as intuitive to us. We are used to be imperative in our day life. “Cut the vegetables, push the button, turn left”. Even the next native to us, the object-oriented way, is harder to accept for us. What about declarative way? “Let a be oranges and b apples then ab is fruit salad”. Or what about asynchronous processing? “I need a salad from this fruits, call me when i can get it.”
So, the first step we need to go is to learn how to switch our thinking. How and when to escape from a paradigm, to apply another one. How to be open minded.
We have learned a few principles through experience and hard lessons. They are abstract enough to be applicable in most of the situations we encounter. We just have to know, to remember and to apply them. I talk about principles of (object-oriented) software design. It appears that, still some of them are valid in functional languages, they just implemented different. Lucky for us, Javascript is object-oriented and kind-of functional. In fact JS lacks some of concepts from both paradigms, but it combines what it got to a flexible and powerful amalgam. As result we can apply many oo-patterns and principles much easier.
So we should remember a few mantras:
- modularize, identify responsibilities
- encapsulate state and hide internals
- decouple from identity, commit on contracts and abstract interfaces
- remove mutable state where possible and appropriate
- separate along boundaries of responsibility, concern and domain
So far for the introduction. In the next part we’ll take a look closer on the callbacks and closures.
And one more thing: for all pathos, stay practical. Paracelsus said once:
“The right dose differentiates a poison and a remedy.”
Even the mantras above are poisonous, they can lead to over-engineering, module chaos, needlessly monadic code etc. Use the right dose.