DEV Community

loading...

Discussion on: Implementing SOLID and the onion architecture in Node.js with TypeScript and InversifyJS

Collapse
remojansen profile image
Remo H. Jansen Author • Edited

I respect your point of view but my personal experience is radically different. When I have something like the following:

import { inject } from "inversify";
import { response, controller, httpGet } from "inversify-express-utils";
import * as express from "express";
import { AircraftRepository } from "@domain/interfaces";
import { Aircraft } from "@domain/entitites/aircraft";
import { TYPE } from "@domain/types";

@controller("/api/v1/aircraft")
export class AircraftController {

    @inject(TYPE.AircraftRepository) private readonly _aircraftRepository: AircraftRepository;

    @httpGet("/")
    public async get(@response() res: express.Response) {
        try {
            return await this._aircraftRepository.readAll();
        } catch (e) {
            res.status(500).send({ error: "Internal server error" });
        }

    }

    // ...

}
Enter fullscreen mode Exit fullscreen mode

There are a few things that I wouldn't get without types and without dependency injection:

  • I can do TDD / design by contract with confidence. If I'm implementing a type, the first thing that I can do is click on "Implement interface" on my IDE and it will create a default implementation. If my implementation is a violation of the interface I will get a compilation error.

  • While I implement this controller, another engineer in my team can be creating an implementation of AircraftRepository. We can work in parallel and we know that there will be no integration issues because the interface is a contract between us that cannot be violated by either side.

  • I can use declarative routing @controller("/api/v1/aircraft") without the need to implement a factory for my controller by hand.

  • I have a lot of metadata that can be used for things like generating UML diagrams or Open API definitions.

  • If I need to refactor something I can do it with confidence, a change in the contract will break all the related parts. I know exactly what is left for my refactoring to be completed.

  • I can also use InversifyJS to implement interception (This is not documented in the example but it is supported by inversifyJS). This allows implementing an aspect (e.g., logging) while keeping my entire code base free of logging concerns.

These advantages make a difference for me and I enjoy them. This is why I advocate this approach and I'm sure I'm not alone because there are a lot of emerging Node.js libs and frameworks in this space and some of them are becoming very popular (e.g, nestjs.com/).

Collapse
bmarkovic profile image
Bojan Markovic • Edited

I can agree that if you are used to that way of working and actually enjoy it (personally I'm a J2EE refugee and enjoy the fact that I've left that world of pain), sure, have a blast.

But the thing is that almost every meaningful feature in this approach already has a simpler, more streamlined and more idiomatic alternative in Javascript.

For example, DI is useful for testing. But there are testing-oriented DI implementations for JavaScript that do it much nicer than the square-peg-round-hole approaches like InversifyJS. I'm talking SinonJS, Testdouble.js and Steal.js.

There are numerous AOP solutions for JavaScript like Aspect.js or Meld that are both simpler and more idiomatic than DI inception based ones and things like Express' Debug clearly demonstrate that where AOP is needed it can be added without needless OOP overengineering and it's actually the anyithing-goes, dynamic nature of the language/runtime that massively helps in this regard (higher order functions alone make a world of difference).

In actuality an API is a contract. OO interfaces are really just one way to go about them and not necessarily a better one. But as IDE support is a given using interfaces, I can imagine circumstances where that might be preferable to documenting an API or adhering to agreements, but IRL I've never run across such a situation, yet I've dealt with incredibly slow progress and numerous pain-points in OO-heavy codebases using these patterns (despite working with stellar engineers).

I'm not even interested in going into a debate on typing. The thing is that there are no conclusive proofs that types reduce bugs at all (but abandoning OOP for functional programming apparently does), and one can sprinkle Typescript or native type annotations where one finds them needed without full-on commitment (where lack of strict types has actually helped dynamic languages and JSON to flourish in this world of distributed software and fast-changing APIs).

The "without the need to implement a factory" and "generating UML diagrams" bits in your comment do say a lot too.

Just my 2c.

Thread Thread
godsavethewww profile image
Derek Branizor • Edited

History lesson...Microsoft Windows is built as a bundle of exposed API's -- unlike linux with text config files -- and they caused immense pain in the 90's by breaking their bogus contracts. So I beg to differ, API's are not real contracts. In fact I'm sure the heinous behavior of Microsoft's APIs were one of the reasons Java made interfaces a big part of its paradigm.

Forem Open with the Forem app