inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.34k stars 719 forks source link

Dump resolved dependencies for performances gains #59

Closed theofidry closed 8 years ago

theofidry commented 8 years ago

I may be wrong but from what I see dependencies are being resolved at runtime right? If that's the case, wouldn't it be possible to "dump" a file with all the dependencies resolved?

jamesadarich commented 8 years ago

Hey @theofidry,

I think your question is can all dependencies resolved at one time (but if I'm answering the wrong questions then please let me know)

This should happen when you call kernel.resolve for your application root then this will happen. i.e.

If I have the following


kernel.bind(new TypeBinding<IA>("IA", A);
kernel.bind(new TypeBinding<IB>("IB", B);
kernel.bind(new TypeBinding<IC>("IC" C);

interface IA {}
class A implements IA {
    constructor(@Inject("IB") b) { }
}

interface IB {}
class B implements IB {
   constructor(@Inject("IC") c) {}
}

interface IC {}
class C implements IC {
}

by calling kernel.resolve("IA") you will be returned an A with a B which has a C all already instantiated. If this is all you need then there is no need to call Inversify again as everything has already been resolved and should not be causing you any more performance issues. :)

theofidry commented 8 years ago

I think your question is can all dependencies resolved at one time (but if I'm answering the wrong questions then please let me know)

No actually I assumed that was the case (otherwise would be quite heavy performant wise right?) :p My concern is more:

  1. load the page and require a service => resolution overhead
  2. require another service => no resolution overhead because resolution has already occurred one time
  3. reload page and require a service => resolution overhead

So what I meant be being resolved at runtime is: the first resolution occurs when you try to retrieve a service. My question was, wouldn't it be possible to call a command or something, so that the dependencies are being resolved, and the result is dumped into a file. As a result, in production you would use this dump and when calling a service, even if it's for the first time, you have no resolution overhead.

remojansen commented 8 years ago

Hi @theofidry thanks for this suggestion. I like it but I don't think we should add this to InversifyJS directly. I see this working better as a plugin for module bundlers (webpack, browserify, etc).

I'm not fully sure about this being 100% possible because we are planing to have support for some complex binding resolution constraints. Things like: "When IFoo is requested, inject an instance of Foo only if the parent request class name named FooBar". If we have this kind of injections it might be possible to pre-resolve everything and dump it into a file.

Also we could end with lots of pre-resolved instances and that could become a memory issue.

So I would say that we are going to explore it but is not one of our top priorities right now. We are going to focus on finishing everything in the roadmap before exploring this. Please don't be disappointed if it takes some time for us to experiment with it :wink:

remojansen commented 8 years ago

PS @Jameskmonger thanks for keeping an eye on the issues :smile:

theofidry commented 8 years ago

I see this working better as a plugin for module bundlers (webpack, browserify, etc).

Indeed that would work out quite well in this case.

If we have this kind of injections it might be possible to pre-resolve everything and dump it into a file. Also we could end with lots of pre-resolved instances and that could become a memory issue.

Indeed this is an issue. But this could be solved by marking such services as lazy loaded, i.e. being resolved only when requested. This way, the user could find a balance between resolution overhead and memory usage.

Keep it up :)

Jameskmonger commented 8 years ago
  1. load the page and require a service => resolution overhead
  2. require another service => no resolution overhead because resolution has already occurred one time
  3. reload page and require a service => resolution overhead

Wouldn't that result in the initial page load time being increased, @theofidry ?

theofidry commented 8 years ago

Ha, not sure to have been clear enough @Jameskmonger.

Right now, dependencies are being resolved when a service is called. So if you require a service to render the page, you will have a time taken by Inversify to resolve the dependencies on your page load time (which I called "resolution overhead" previously). Obviously, Inversify is smart enough for not having to resolve dependencies a second time: calling the service again will get you the service instance right away (no "resolution overhead"). However, if you reload your page, dependencies will have to be resolved again.

As a result, the way it is, you have a resolution overhead on each page load. Hence my suggestion to "dump" the container with resolved dependencies. The caveat is that you will have a bigger memory footprint as all your services will be instantiated right away, however your page will be loaded faster as you no longer have to resolve those dependencies.

Then as some dependencies cannot be resolved in such predictable way or because some services are not needed on each page, i.e. it is making the memory footprint needlessly bigger than needed, you could have lazy services, which are being resolved at runtime instead of being "dumped".

Jameskmonger commented 8 years ago

Ah, okay. So what you mean is almost a compilation process where 'static' (which is a very loaded term in a programming context) dependencies are resolved on compilation so they do not have to be resolved at runtime?

theofidry commented 8 years ago

exactly

remojansen commented 8 years ago

I'm closing this issue because I don't think this will be possible anymore due to contextual bindings. Contextual binding allows us to declare constraints that are resolved at run-time like the following:

kernel.bind<IWeapon>("IWeapon").to(Katana).when(request => { 
    return request.parentRequests.target.name.equal("katana"); 
})

This makes quite complex (maybe even impossible) to pre-calculate and dumb all the injections that take place in an application.

theofidry commented 8 years ago

@remojansen but we have the same issues in say PHP for example. This can be solved by having pre-calculable dependencies. Then contextual ones are still solved at run-time.

Also note that this happens when you rely on autowiring:

kernel.bind(new inversify.Binding("FooInterface", Foo, BindingScope. Singleton));
var foobar = kernel.resolve("FooBarInterface");

In other words you are binding your services (ex Foo) to interfaces and classes and then just ask for an interface. But what happens when you have multiple implementations of the same interface? Your current solution is contextual binding.

However, you could solve this problem differently by explicitly resolving your dependencies. Instead of having:

foo1: bound to "FooInteface"
foo2: bound to "FooInterface" when asked by "foobar"
bar: bound to "BarInterface"

foobar: ask for "FooInterface" and "BarInterface"

You could give your services names and resolves those dependencies via names:

foo1: bound to "@foo1" (still implements FooInterface)
foo2: bound to "@foo2" (still implements FooInterface)
bar: bound to "@bar" (still implements BarInterface)

foobar: ask for "@foo2" and "@bar" (still typehint FooInterface and BarInterface so could change foo2 for foo1 without changing anything)

In another words, you are making all your dependencies predictable. Note that changes only the way you declare your bindings (rather the convention used, does not change anything under the hood per say), but nothing to your service declarations.

Also note that in PHP when dealing with such cases, there is also the case where we do not want the service to be loaded to the IoC Container systematically, hence just requiring the service to be loaded at run time (and will be only if needed). Which are just lazy services and requires to be specified as such, so it's just a configuration thing and nothing special.

remojansen commented 8 years ago

I understand, basically the tool that resolved the dependencies would not be able to use contextual bindings but developers could get around it using named and tagged bindings. I have added this to the a new page in the wiki so it is documented.