History is subjective

I guess there are others who experienced similar things.

There were times in Software Development and SaaS in particular when most of the fun was around such things as OOP (Object-Oriented Programming), Java, Spring, and related design patterns. And most of the time we structured our programs around Inversion of Control and Dependency Injection principles. These principles form the soul and core of the Spring Framework. And Spring popularity made it standard de facto. But popularity usually leads to enormous sizes, complexity, legacy burden, a steep learning curve for newcomers, and so on.

For many of us, Spring became a kind of language we are based on, we got blindly addicted to all its core principles. It’s some kind of falling in love — we expect, insist, and build the same structure with the same mechanisms over and over again.

But everything has its benefits and drawbacks. Even Spring, being our savior from JEE (Java Enterprise Edition) hell, became our new trouble and pile of complexity with anti-patterns — but that’s another story…​

The case

Being switched to another platform, framework, or language we may end up being haunted by our previous experience, habits, and established beliefs. For instance, I needed to build a comparably small application with Node.js. And on the very first day, I caught myself being in a trap of the past — I tried to form the same "environment" I used to. I wanted to see similar Java/Spring annotations with all the magic behind them to get respective injections working, similar structure, naming, and so on. For someone it can be that very moment when changing, let’s say, a language we may start whining about its drawbacks (we feel and define it so) merely because it does not provide the same mechanism or syntax we loved in our favorite language we used recently. And the most motivated of us may start pulling the same things into a new language.

Surprise surprise, I started researching Node.js world looking for existing IoC/DI stuff. Well, I found them, but I quickly realized that I was going in the wrong direction. Of course, the reasoning comes from my personal experience, specific context, limits, and deadlines. I may recall at least two conceptual reasons. Some npm modules were, presumably, "a single person project" like, i.e. it’s not backed by a solid company (yeah, killedbygoogle.com still happens) or big community — I read it like "be ready to babysit on your own" and "it may have a lot of issues due to immaturity". Other modules offered exactly what I wanted — Java/Spring like annotations and similar fun. But the trade-offs are that I need to learn and understand a new magic behind such frameworks not to mess around and not to be lost in specific cases (it’s not Java and Spring after all); and I have to pull TypeScript into the project and be ready for all the related possible consequences. I’m not against TypeScript, but it was the funny times when an ordinary Angular (the II) project could get failed build simply after TypeScript update — good luck to spend a day figuring out why. But I knew my context — it’s going to be a very simple app made of several js files. It does not need such complexity or possible build/runtime issues when you get back to the project after 6-12 months (which is quite a case for JavaScript/Node.js projects with doubtful dependencies).

After all, a simple decoupling structure was all I needed for the project. Thus, I ended up with something like that:

const ioc = {};
ioc.conf = new Conf(ioc);                 // app behaviour config and limits
ioc.db = new Db(ioc);                     // database layer
ioc.authService = new AuthService(ioc);   // usual authentication stuff
ioc.emailService = new EmailService(ioc); // some SMTP integration
ioc.stripe = new Stripe(ioc);             // Stripe payment integration
ioc.routes = new Routes(ioc);             // HTTP endpoints — the app entrypoint

In such case, AuthService could look like this:

class AuthService {
 constructor(ioc) {
  this.conf = ioc.conf;
  this.db = ioc.db;
 }
 SomeAuthMethod() {
  this.conf.foo();
  this.db.bar();
 }
};

Summary

What I got with such a solution:

  • simple and clean code structure, without require("../../../<are-we-there-yet>/../../")

  • the structure promotes testable code from the very beginning — a usual outcome of IoC/DI itself (but testing is a separate disputable topic)

  • no heavy or tricky dependencies which may eat our time and money after some version upgrade disaster or when the world is so modern that our, for example, 1 year old project cannot be built and run anymore

  • no additional learning curve for my teammates — JavaScript knowledge only, nothing is hidden behind "by convention" magic

  • less potential bugs due to less magic and moving parts

P.S. That Service suffix is still around. Can you relate? It’s hard to completely avoid the past, but it was not the goal, and "the past" does not mean something bad — it depends.

 
 

Copyright © Igor Ostapenko
(handmade content)