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

Internally generate symbols/identifiers; optional-identifiers-happy-path? #534

Closed
atrauzzi opened this issue Apr 15, 2017 · 5 comments
Closed

Comments

@atrauzzi
Copy link

atrauzzi commented Apr 15, 2017

I'm interested in knowing if there could be a way where symbol/identifier management isn't mandatory when using inversify for certain types of injections.
I feel like I mostly understand why they exist and what purpose they serve (some injections don't correspond to a distinct type).

Some point-form thoughts

  • Isn't this something reflect-metadata should be helping with?
  • inversify must already maintain its own internal table of symbols for types, it's just that right now, the requirement is that it be populated from outside the container
  • I suspect this will only work with things where the types are distinct and metadata is available - which means classes. But without implying that this is specific to my use case, that's the only time that this kind of arrangement would be nice to have (see next point).
  • My trusty reference for dynamically-typed-with-oo-sprinkled-in, Laravel, actually might have some worthwhile inspiration.

The idea is that you have a scenario where the IoC becomes minimum-touch. It's a convention where the container only receives queries for instances by their static type and - barring any overrides - uses reflection to do the rest. Instead of it being mandatory to painstakingly describe the connections between types, you simply have to ensure your constructor parameters are clearly defined. With Laravel, you don't even have to inform the container of a type prior to looking it up! Actually, as far as I can tell, internally I think the Laravel DI system only uses strings to track types.

In the case of inversify and TypeScript/ECMAScript, the equivalent here would be to internally generate symbols/identifiers whenever the container sees a new type. Then, subsequent "naked" lookups of the type would result in that same - already established - symbol being internally rediscovered and used.

What this might eliminate

(please correct me if I'm wrong here!)

  • The need to put @injectable on classes
  • The need to explicitly declare constructor parameter bindings
  • The need to bind static types when their constructors only require unambiguous reflectable types

I'd effectively be able to say context.get(MyStaticType) and get back an instance, having done no other preparation in advance.

What this would not eliminate

  • The concept of symbols/identifiers
  • Requirement to use symbols/identifiers when declaring and injecting constants, factories and other ambiguous types

Why?

Mainly to reduce boilerplate, code maintenance, and the margin of cognitive overhead that comes with them. As I've been putting together my first non-contrived protoculture solution, I've discovered that because I'm using inversify, implementers are forced into managing their own symbols. Even though they may not be adding any ambiguous types to the container.
My first instinct was to try and hide this by managing the symbols/identifiers myself in some of my framework classes. But then my mind started pushing that responsibility down...down...down.
Which eventually got me to thinking about whether there could be a way to avoid these requirements entirely provided a specific set of rules were observed. And of course, with Laravel serving as an example, I'm sure there could be a way to have inversify offer the same. This is a total guess - but I don't think it would be too disruptive either?

This idea would go nicely with what's already been done here: #505 - in fact, when selectively designed for it, a request for a type could effectively cause its entire dependency graph to self-wire.
And of course nothing I'm suggesting here implies that the existing mechanisms to bind types should be eliminated. So caveats in the dependency graph can still be accommodated and self wiring would just continue beyond and around it.

More than happy to go into an extended discussion about this if needed. But I feel like it should be possible and would take this already great (I'm not going anywhere!) container and push it to yet-even-higher-heights.

🖋️

@atrauzzi atrauzzi changed the title Internally manage symbols/identifiers; optional-identifiers-happy-path? Internally generate symbols/identifiers; optional-identifiers-happy-path? Apr 15, 2017
@remojansen
Copy link
Member

Hi @atrauzzi, thanks for thinking about ways to improve inversify and sharing them. I really appreciate it.

I've been reading the links from laravel and I saw:

There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve these objects using reflection.

This is almost the same in Inversify. When we use classes (not interfaces), the only required annotation is @injectable(), the main problem is that we don't have fully-featured reflection so we can't explore a DLL and automatically generate bindings.

When you use @injectable() it will generate some metadata if the class is declared. The problem is, the class is declared only if it is imported at least one time.

We would need something like:

import "./entities/someInjectable1";
import "./entities/someInjectable1";
import "./entities/someInjectable1";
import "./entities/someInjectable1";
import "./entities/someInjectable1";

This is how the @provide() decorator works.

It would be much nicer if all the classes where loaded automatically but this is not possible due to the lack of fully featured reflection in JS. In Node.js we can write a function that reads a directory in the file system and loads every module but in browsers things are much more complicated.

Also, my understanding is that in the future the --emitDecoratorMetadata flag from TS will be able to generate metadata for interfaces. At the moment all interfaces are serialized as Object. The Object type is too ambiguous this is why we need @inject annotations.

I originally though about using naming or path conventions (that is how version 1.0 of inversify worked) but I don't think that is a good idea because I believe that the main features of InversifyJS is being a universal library that work in browsers and node, it is agnostic of frameworks and architectures. I also tried to allow InversifyJS so people can build frameworks in top of it and create great developer experiences. We could improve the Inversify experience but that would mean being less flexible.

I'm open to examine examples if you want to try ideas but I see this more as a side-project library specific for a concrete architecture and framework.

@atrauzzi
Copy link
Author

Yeah, definitely appreciate the differences in the JS space because it doesn't offer an autoloader pass the same way as PHP (if a class isn't found, you can plug a loader in as a last ditch to discover it before failing).

That all said, my imagination may be getting the better of me here... I still think these things might be possible.

If we think through how people will do their bindings, would it not be safe to assume that every class they'll want will have to have been imported at least once because they'll be pulling in that reference (to the constructor/static/class-type); prior to inversify's first attempt to work with it? Not sure if that's the most effective way to express what I'm thinking, but the order of everything actually works out.

The TS interface thing sounds like it can't come soon enough but with an object, you could generate some kind of fingerprint from the signature of the interface-object-meta-thingy. It's all duck-typed after all ;)

The name and path conventions I totally agree on. Inversify should be zero-convention when it comes to physical project structure. I'll take care of that with protoculture ;)
The convention was more about having an option of automatic self-wiring provided an implementer stuck to using reflectable types, which despite the interface issue may still be possible (the fingerprinting). And that's all in the logical space in my mind.

I'd have to spend some while looking at the code for inversify to understand where these opportunities are to pre-populate the metadata and what the right things to do are. But I'm slightly curious because even the little bit of metadata being generated now may be enough.

@remojansen
Copy link
Member

Hi @atrauzzi I think things are likely to head this way.

My vision about this right now is that I want to wait until there is a clear vision of where TypeScript is heading. If they are going to implement reflection for interfaces, then there are no need for custom transforms. If they are not going to do it, then using transform could be an option but I see the transforms as something that would live outside of the core repo.

You can see how people is already testing some of this stuff.

@atrauzzi
Copy link
Author

Yeah, overall I agree. From protoculture's side, I'll just continue "passing along" the code debt for managing those pointers. Then once the winner of all this is determined, most of these concerns will turn into noops.

Just something we all have to be aware of in TypeScript land until the dust settles 😄

Feel free to close this or leave it as a bookmark?

@remojansen
Copy link
Member

Thanks for understanding my point of view. Let's hope the future is more clear soon 👍

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