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

More advanced scope support #218

Closed
xenoterracide opened this Issue May 21, 2016 · 15 comments

Comments

6 participants
@xenoterracide

xenoterracide commented May 21, 2016

Coming from java (Spring/CDI) and what I'm not seeing from the documentation is 3 things regarding scopes.

  1. making custom scopes (useful for things like session scope)
    1. scopes will need create and destroy hooks for every object within there scope.
    2. Obviously need a way to define exactly when a scope is created/destroyed
  2. The ability to use custom annotations (decorators?) to apply afformtioned scope, so instead of @Injectable you could have @Singleton and @Session
  3. Maybe implement a few more scopes (could be out of scope of this project. could be a "sub project like Spring MVC")
    1. @Request (feasible?)
    2. @Session
    3. @Singleton (existing but would be nice not to have to do said kernel thing)
@remojansen

This comment has been minimized.

Show comment
Hide comment
@remojansen

remojansen May 21, 2016

Member

Hi, thanks a lot for your feedback.

  1. About number one, if you could help us to understand better the requirements and explain us how the API should look that could be a good starting point. I have plans to investigate support for other kinds of scope like webworker scope because in IoC containers like Ninject we have thread scope... so I totally understand why you are asking for this feature.
  2. This should be covered by inversify-binding-decorators.
import { injectable, Kernel } from "inversify";
import makeFluentProvideDecoratorfrom "inversify-binding-decorators";

var kernel = new Kernel();
let provide = makeFluentProvideDecorator(kernel);

let TYPE = {
    IWeapon : "IWeapon"
};

interface IWeapon {}

let provideSingleton = function(identifier) {
    return provide(identifier)
              .inSingletonScope()
              .done();
};

@provideSingleton(TYPE.IWeapon)
class Shuriken implements IWeapon {
    public hit() {
        return "hit!";
    }
}

let shuriken = kernel.get<IWeapon>(TYPE.IWeapon);
Member

remojansen commented May 21, 2016

Hi, thanks a lot for your feedback.

  1. About number one, if you could help us to understand better the requirements and explain us how the API should look that could be a good starting point. I have plans to investigate support for other kinds of scope like webworker scope because in IoC containers like Ninject we have thread scope... so I totally understand why you are asking for this feature.
  2. This should be covered by inversify-binding-decorators.
import { injectable, Kernel } from "inversify";
import makeFluentProvideDecoratorfrom "inversify-binding-decorators";

var kernel = new Kernel();
let provide = makeFluentProvideDecorator(kernel);

let TYPE = {
    IWeapon : "IWeapon"
};

interface IWeapon {}

let provideSingleton = function(identifier) {
    return provide(identifier)
              .inSingletonScope()
              .done();
};

@provideSingleton(TYPE.IWeapon)
class Shuriken implements IWeapon {
    public hit() {
        return "hit!";
    }
}

let shuriken = kernel.get<IWeapon>(TYPE.IWeapon);
@remojansen

This comment has been minimized.

Show comment
Hide comment
@remojansen

remojansen May 21, 2016

Member

About being able to use @singleton without doing:

let provideSingleton = function(identifier) {
    return provide(identifier)
              .inSingletonScope()
              .done();
};

It is not possible right now and it would require a significant change in the architecture of the library so it is less likely that it will happen. It is not a big deal because you can copy paste the above code snippet and use it as @provideSingleton(serviceIdentifier).

I can imagine request and session scope becoming available at some point. I will try to think about possibles ways to implement this...

Member

remojansen commented May 21, 2016

About being able to use @singleton without doing:

let provideSingleton = function(identifier) {
    return provide(identifier)
              .inSingletonScope()
              .done();
};

It is not possible right now and it would require a significant change in the architecture of the library so it is less likely that it will happen. It is not a big deal because you can copy paste the above code snippet and use it as @provideSingleton(serviceIdentifier).

I can imagine request and session scope becoming available at some point. I will try to think about possibles ways to implement this...

@xenoterracide

This comment has been minimized.

Show comment
Hide comment
@xenoterracide

xenoterracide May 22, 2016

Some context. I'm not 100% sure that I'm quite yet qualified. Currently I'm looking at replacing AngularJS as my framework because I find it has become overly prescriptive and I'd like to use Material Design, but moving to Angular 2 means that its Material Design impl won't work, and it doesn't seem polished to use Polymer (there's some toe stepping with binding instead of being able to inject into components). One thing needed to compose a great framework is a session scope. In Spring we actually implemented a TransactionScope and then got them to add it to Spring Tx.

I haven't actually worked with TS/InversifyJS yet, and glancing at the architecture docs, so pseudo code. From what I can tell custom scopes need a conversation id. My suggestions are based largely on Springs implementation, which was designed a long time ago, so maybe a new impl could be better designed http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-custom and the impl of transaction scope

interface Scope {
   void onDestroy( Function<Object> callback );
   void onCreate( Function<Object> callback);
   String currentConversationId();
   Object get( String name );
   void remove( name )

}

kernel.registerScope( "myScope", new MyScope() );
kernel.getScope(MyScope).onDestroy( function (obj) { log("destroying" + obj )} )

hopefully this is helpful



xenoterracide commented May 22, 2016

Some context. I'm not 100% sure that I'm quite yet qualified. Currently I'm looking at replacing AngularJS as my framework because I find it has become overly prescriptive and I'd like to use Material Design, but moving to Angular 2 means that its Material Design impl won't work, and it doesn't seem polished to use Polymer (there's some toe stepping with binding instead of being able to inject into components). One thing needed to compose a great framework is a session scope. In Spring we actually implemented a TransactionScope and then got them to add it to Spring Tx.

I haven't actually worked with TS/InversifyJS yet, and glancing at the architecture docs, so pseudo code. From what I can tell custom scopes need a conversation id. My suggestions are based largely on Springs implementation, which was designed a long time ago, so maybe a new impl could be better designed http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-custom and the impl of transaction scope

interface Scope {
   void onDestroy( Function<Object> callback );
   void onCreate( Function<Object> callback);
   String currentConversationId();
   Object get( String name );
   void remove( name )

}

kernel.registerScope( "myScope", new MyScope() );
kernel.getScope(MyScope).onDestroy( function (obj) { log("destroying" + obj )} )

hopefully this is helpful



@remojansen

This comment has been minimized.

Show comment
Hide comment
@remojansen

remojansen May 22, 2016

Member

Thanks a lot for those links It will help me a lot during analysis 👍

Member

remojansen commented May 22, 2016

Thanks a lot for those links It will help me a lot during analysis 👍

@remojansen remojansen added this to the 2.1.0-beta.1 milestone May 24, 2016

@tenowg

This comment has been minimized.

Show comment
Hide comment
@tenowg

tenowg Jun 14, 2016

Member

would be great if we could get something similar to:

using(IScope scope = kernel.newScope("request")) {
    // Anything marked with .inScope() at the binding would be resolved to this scope
    // Anything no marked would resolve as normal
}

I understand that is from .NET syntax, but I think the idea is there.

this would also be a good time to think of using a IDisposable.

This format would be for those that are using the kernel directly, as in my case with my MVC project.

Member

tenowg commented Jun 14, 2016

would be great if we could get something similar to:

using(IScope scope = kernel.newScope("request")) {
    // Anything marked with .inScope() at the binding would be resolved to this scope
    // Anything no marked would resolve as normal
}

I understand that is from .NET syntax, but I think the idea is there.

this would also be a good time to think of using a IDisposable.

This format would be for those that are using the kernel directly, as in my case with my MVC project.

@remojansen remojansen modified the milestones: 2.2.0, 2.1.0 Jun 28, 2016

@ktersius

This comment has been minimized.

Show comment
Hide comment
@ktersius

ktersius Jul 12, 2016

I'm not sure if this belongs on this issue or not.

I find that I need to be able to resolve session information let's say ISession. This session is only valid for a specific call and contains things like user information for current request.

It would be great if one could create a child kernel/scope where I could bind my ISession to and then resolve my aggregate root with. Any subsequent resolves, via a factory for instance, that might get triggered from within this aggregate root, lets say via a promise that get's resolved later on will then use this child kernel and resolve the correct ISession.

Not sure if this can be done already?

I'm not sure if this belongs on this issue or not.

I find that I need to be able to resolve session information let's say ISession. This session is only valid for a specific call and contains things like user information for current request.

It would be great if one could create a child kernel/scope where I could bind my ISession to and then resolve my aggregate root with. Any subsequent resolves, via a factory for instance, that might get triggered from within this aggregate root, lets say via a promise that get's resolved later on will then use this child kernel and resolve the correct ISession.

Not sure if this can be done already?

@remojansen

This comment has been minimized.

Show comment
Hide comment
@remojansen

remojansen Jul 13, 2016

Member

@ktersius at the moment this is not possible because we don't support child kernels or custom scopes kike session or request scope yet. But both features are in our road map. Child kernel us 100% duable and you can be sure we will implement it custom scopes is a complex feature and need more analysis before I can't promise anything just yet. I will use this issue to keep you updated.

Member

remojansen commented Jul 13, 2016

@ktersius at the moment this is not possible because we don't support child kernels or custom scopes kike session or request scope yet. But both features are in our road map. Child kernel us 100% duable and you can be sure we will implement it custom scopes is a complex feature and need more analysis before I can't promise anything just yet. I will use this issue to keep you updated.

@remojansen remojansen modified the milestones: 4.0.0, 2.2.0, 5.0.0, 4.1.0 Aug 3, 2016

@remojansen remojansen modified the milestones: Side Projects, 3.1.0, 4.0.0 Nov 29, 2016

@antoniusostermann

This comment has been minimized.

Show comment
Hide comment
@antoniusostermann

antoniusostermann Jan 21, 2017

Contributor

Just found this. Because I think this fits to my gitter comment, posting it here:


Hello, does anyone know of a best practice to inject "context" or "scope" into a class? For example, given I have a server handling requests, many services need a specific request object to work properly. One, for example, grabs the parameters out of a given request. How should I offer the currentRequest dependency? don't really like to implement a setRequest() method on each of these classes... Is it, for example, possible to register an asynchronous "scope" to the container, and inject the currentRequest as the default request into my classes? So sth. like this:

/* My composition root */
server.onRequest(request => {
  // request is now the currently handled request, so set it as context object
  container.bindContext<Request>("currentRequest").to(request);

  // get params of current request
  let params = container.get<ParameterService>("parameterService").getParams(); 
});

// Purpose of this class is to get parameters out of specific request, so it strongly depends on a given request instance
@injectable()
class ParameterService {
  constructor(@injectContext("currentRequest") request: Request) {
    this.request = request;
  }

  getParams():  Parameter[]; // Fetches Parameter based on given request

That way, I do not need to implement setters in each of my classes, which have to be called to enable the class to work properly. I consider this an anti pattern and strongly believe strict dependencies belong into the constructor.
If I'd use a factory instead to create ParameterService objects, all other classes which depend on ParameterService could not inject this service without being a factory on their own. This would end in a factory mess, where all factories have unnecessary dependencies (they need a specific request instance only to pass it to ParameterService

But using currentRequest only as the default injection, I could still test my class easily.
The problem with this is that since container is a global object and the currentRequest is a callback parameter handled asynchronously, setting this context won't be easy.

Does anyone have an idea how to achieve this with inversify, or comes up with a best practive to solve such problems? Thanks!! :)

Contributor

antoniusostermann commented Jan 21, 2017

Just found this. Because I think this fits to my gitter comment, posting it here:


Hello, does anyone know of a best practice to inject "context" or "scope" into a class? For example, given I have a server handling requests, many services need a specific request object to work properly. One, for example, grabs the parameters out of a given request. How should I offer the currentRequest dependency? don't really like to implement a setRequest() method on each of these classes... Is it, for example, possible to register an asynchronous "scope" to the container, and inject the currentRequest as the default request into my classes? So sth. like this:

/* My composition root */
server.onRequest(request => {
  // request is now the currently handled request, so set it as context object
  container.bindContext<Request>("currentRequest").to(request);

  // get params of current request
  let params = container.get<ParameterService>("parameterService").getParams(); 
});

// Purpose of this class is to get parameters out of specific request, so it strongly depends on a given request instance
@injectable()
class ParameterService {
  constructor(@injectContext("currentRequest") request: Request) {
    this.request = request;
  }

  getParams():  Parameter[]; // Fetches Parameter based on given request

That way, I do not need to implement setters in each of my classes, which have to be called to enable the class to work properly. I consider this an anti pattern and strongly believe strict dependencies belong into the constructor.
If I'd use a factory instead to create ParameterService objects, all other classes which depend on ParameterService could not inject this service without being a factory on their own. This would end in a factory mess, where all factories have unnecessary dependencies (they need a specific request instance only to pass it to ParameterService

But using currentRequest only as the default injection, I could still test my class easily.
The problem with this is that since container is a global object and the currentRequest is a callback parameter handled asynchronously, setting this context won't be easy.

Does anyone have an idea how to achieve this with inversify, or comes up with a best practive to solve such problems? Thanks!! :)

@ktersius

This comment has been minimized.

Show comment
Hide comment
@ktersius

ktersius Jan 24, 2017

@antoniusostermann

I've since solved my original problem

private getChildContainer()
 {
let childKernel = new inversify.Container();; //Create a child kernel so that any resolved instances of IContext resolve the correct context.
childKernel.parent = this.container;
childKernel.bind<IContext>("IContext").toConstantValue(this.context); //For any class that needs access to context information.
return childKernel;
}

You should be able to easily pass the current request/context and then from there just make sure to use the child kernel for all resolving. This way you will get the correct context/request when resolving further down the line.

ktersius commented Jan 24, 2017

@antoniusostermann

I've since solved my original problem

private getChildContainer()
 {
let childKernel = new inversify.Container();; //Create a child kernel so that any resolved instances of IContext resolve the correct context.
childKernel.parent = this.container;
childKernel.bind<IContext>("IContext").toConstantValue(this.context); //For any class that needs access to context information.
return childKernel;
}

You should be able to easily pass the current request/context and then from there just make sure to use the child kernel for all resolving. This way you will get the correct context/request when resolving further down the line.

@antoniusostermann

This comment has been minimized.

Show comment
Hide comment
@antoniusostermann

antoniusostermann Jan 24, 2017

Contributor

@ktersius Thanks so much for your answer. But how to "make sure to use the child kernel for all resolving" if you are just using @inject in your classes?

Contributor

antoniusostermann commented Jan 24, 2017

@ktersius Thanks so much for your answer. But how to "make sure to use the child kernel for all resolving" if you are just using @inject in your classes?

@ktersius

This comment has been minimized.

Show comment
Hide comment
@ktersius

ktersius Jan 24, 2017

@antoniusostermann Your entry point should just use the childContainer after you create it. There will always be some root somewhere where you work with the container it-self to get the whole process started.

/* My composition root */
server.onRequest(request => {
  // request is now the currently handled request, so set it as context object
   let childContainer = getChildContainer(container, request);
  
  // get params of current request
  let params = childContainer.get<ParameterService>("parameterService").getParams(); 
});

ktersius commented Jan 24, 2017

@antoniusostermann Your entry point should just use the childContainer after you create it. There will always be some root somewhere where you work with the container it-self to get the whole process started.

/* My composition root */
server.onRequest(request => {
  // request is now the currently handled request, so set it as context object
   let childContainer = getChildContainer(container, request);
  
  // get params of current request
  let params = childContainer.get<ParameterService>("parameterService").getParams(); 
});
@remojansen

This comment has been minimized.

Show comment
Hide comment
@remojansen

remojansen Nov 4, 2017

Member

I'm closing this issue because we added support for inRequestScope. No plans for inSessionScope for now but I will think about it. About decorators for scopes it is currently possible via inversify-binding-decorators.

Member

remojansen commented Nov 4, 2017

I'm closing this issue because we added support for inRequestScope. No plans for inSessionScope for now but I will think about it. About decorators for scopes it is currently possible via inversify-binding-decorators.

@remojansen remojansen closed this Nov 4, 2017

@remojansen remojansen moved this from TODO to DONE in Status Nov 4, 2017

@guscastro

This comment has been minimized.

Show comment
Hide comment
@guscastro

guscastro Mar 5, 2018

Hi @remojansen ,

Do you have inSessionScope in your sight by any chance? This is a feature our team would be particularly interested in.

Thanks,
Gus

Hi @remojansen ,

Do you have inSessionScope in your sight by any chance? This is a feature our team would be particularly interested in.

Thanks,
Gus

@antoniusostermann

This comment has been minimized.

Show comment
Hide comment
@antoniusostermann

antoniusostermann Mar 8, 2018

Contributor

Hey @guscastro , with inversify-components (https://github.com/webcomputing/inversify-components) you are able to create your own scoped dependencies, but it's implemented the way @ktersius describes it here in #218 (comment). It might be helpful for you, although it also changes the way you declare dependencies.

Contributor

antoniusostermann commented Mar 8, 2018

Hey @guscastro , with inversify-components (https://github.com/webcomputing/inversify-components) you are able to create your own scoped dependencies, but it's implemented the way @ktersius describes it here in #218 (comment). It might be helpful for you, although it also changes the way you declare dependencies.

@guscastro

This comment has been minimized.

Show comment
Hide comment
@guscastro

guscastro Mar 8, 2018

Thanks, @antoniusostermann . I ended up using the workaround described as well. Unfortunately, we have a very large application built on top of inversify and changing declarations is likely more work than implementing the feature on inversify.

Thanks, @antoniusostermann . I ended up using the workaround described as well. Unfortunately, we have a very large application built on top of inversify and changing declarations is likely more work than implementing the feature on inversify.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment