Open icy0307 opened 1 year ago
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.
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?
@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:
In this case, I would create the following bundles:
ContainerModule
.ContainerModule
.ContainerModule
.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
Take this code for example, how could one import
Shuriken
dynamically to code split?I've been looking into
hierarchical DI systems
,provider injection
, andAsynchronous container modules
, but none seems appropriate.Shuriken
could have other dependencies from di as well. soprovider 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?