Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to split code when using inversify js? #1504

Open
icy0307 opened this issue Mar 24, 2023 · 3 comments
Open

How to split code when using inversify js? #1504

icy0307 opened this issue Mar 24, 2023 · 3 comments

Comments

@icy0307
Copy link

icy0307 commented Mar 24, 2023

@injectable()
class Ninja implements Warrior {
    @inject(TYPES.Weapon) private _katana: Weapon;
    @inject(TYPES.ThrowableWeapon) private _shuriken: ThrowableWeapon;
    public fight() { return this._katana.hit(); }
    public sneak() { return this._shuriken.throw(); }
}
// file inversify.config.ts

import { Container } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };

Take this code for example, how could one import Shuriken dynamically to code split?

I've been looking into hierarchical DI systems, provider injection, and Asynchronous container modules, but none seems appropriate.

Shuriken could have other dependencies from di as well. so provider injection doesn't fit the bill.

hierarchical DI systems feels like that for every class that is intended to be loaded dynamically, a child container must be created.

How to split code in inversify?

@notaphplover
Copy link
Member

I would say there's not a single way of accomplishing this. This is more an architecture's question rather than an inversify question imo. What is going to be the responsibility of the splitted packages? Are there any asyncronous processes such as the connection to a database?

If you determine the responsibility of those packages includes providing some sort of DI mechanism, you'll be probably thinking about DI with hierachical packages. At first glance, every package could provide a container module to be loaded. Having said that, this approach is probably not good enough if you consider inversify.

For these reasons I consider NestJs way superior than inversify, at least for this use case: NestJS allow you to create modules able to import other modules and even handle async dependencies through different mechanisms. If you want a hierarchical DI approach and you have any sort of complexity, I would suggest you to use NestJS or a similar DI approach that allow you to establish modules hierarchy.

Having said that, maybe a DI hierarchy is not what you want because you only initialize instances in a few packages, the top most ones in the hierarchy, and those packages are really the only ones which need a DI container. In that case, inversify container modules might be good enough for you.

What is your use case? The ninja case is simple to understand, but maybe it does not describe the challenges you're facing.

@icy0307
Copy link
Author

icy0307 commented Mar 28, 2023

Thx for your detailed answer.
In my scenario, we are building a huge front-end app with DI mechanism. Some of the instances are used by the components that load asynchronously or below the fold. We want to be able to show the critical rendering part of the page as fast as possible, hence code splitting unnecessary code above the fold. But all my entity codes are referenced in the same container through composition root. What is the best practice to code splitting them?
Take google doc apps for example, let's assume every editable feature is loaded dynamically, should every asynchronous class be a container? Are there gonna be a lot of them for every "await import"?
Or should one use asynchronous Factory?
e.g.

type KatanaProvider = () => Promise<Katana>;

@injectable()
class Ninja implements Ninja {

    public shuriken: Shuriken;
    public katanaProvider: KatanaProvider;

    public constructor(
	    @inject("KatanaProvider") katanaProvider: KatanaProvider, 
	    @inject("Shuriken") shuriken: Shuriken
    ) {
        this.katanaProvider = katanaProvider;
        this.shuriken = shuriken;
    }

    public async fight() { 
      const katana =  await this.katanaProvider();
      return this.katana.hit(); 
    };
    public sneak() { return this.shuriken.throw(); };

}
container.bind<KatanaProvider>("KatanaProvider").toProvider<Katana>((context) => {
    return  async () => {
           const Katana = await import('./katana');
           let katana = context.container.get<Katana>("Katana");
           return katana;
        });
    };
});

var ninja = container.get<Ninja>("Ninja");

But when does katana's binding occur? Since no place should allow using katana directly, is the katana's binding necessary? But we want the katana to be injectable cause the katana's dependency, like the "wooden handle" is injectable.
The only appropriate place to do that seems to be inside the katana's file.
However, isn't that violate the 'composition root' rule?
@notaphplover What is your thought on this?

@notaphplover
Copy link
Member

notaphplover commented Mar 29, 2023

@icy0307 Inversify binding process is suposed to be sync by design unless NestJS one, so I think it's not the best library for your case.

I would expect that provider not to work properly.

In your case, every class binding happens asyncronously. To be honest, DI and fast loading times do not play very well. I'm more in the backend side, but I had a similar problem in a serverless infrastructure: warm times had to be low. If you are aiming to go with the DI strategy you're commenting, I understand you app is huge but that critical part does not have too many dependencies, otherwise this strategy would not work.

The approach I would follow would be bundling and pruning. Of course, I would not create a single bundler because your app is huge but, let's assume you have a critical part to be rendered asap and a non critical part.

In the most simple scenario, those two parts are totally independent whatsoever. In that case, two bundles solve the problem. I don't think that is your case so, let's asume there's share logic (classes / components) in those two parts. In this case, you will need to create multiple bundles to distribute the shared logic to both of your parts, the critical one and the non critical one. For example:

  • The app has the classes S1, S2, S3, S4 who are shared classes in the whole app
  • Also, the app has the classes C1, C2 which conforms the critical part
  • Finally, the app has the classes NC1, NC2...NC200 which conforms the non critical part

In this case, I would create the following bundles:

  • One with S1, S2, S3, S4 -> shared bundle. This bundle provides a ContainerModule.
  • One with C1, C2 -> critical bundle. This bundle provides a ContainerModule.
  • One or many with NC1, NC2...NC200 This bundles provides a ContainerModule.
  • A bootstrap bundle which imports the other bundles. It has a container that loads a bundler ContainerModule as soon as the bundler module is loaded. Instead of waiting for all bundles to be loaded, lazy load the non critical ones.

Of course, feel free to use whatever strategy you consider convenient to achieve this. A monorepo with multiple packages with a bundler seems a good way to go. Of course, you know better than me the projject, so maybe there is not a single shared bundle including S1, S2, S3, S4, maybe there're two shared bundles with {S1, S2, NC54} and {S3, S4} because it makes more sense.

I hope it helps. Of course, there's not a single way to go and I'm not a frontend expert, but I think the solution is not too bad. Unfortunatelly, we are running off topic, maybe we should discuss this outside an inversify issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants