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

BindingToSyntax refactor discussion #1327

Open
tonyhallett opened this issue May 2, 2021 · 8 comments
Open

BindingToSyntax refactor discussion #1327

tonyhallett opened this issue May 2, 2021 · 8 comments

Comments

@tonyhallett
Copy link
Contributor

There is common code in each of the "to" methods. For instance

    public toConstantValue(value: T): interfaces.BindingWhenOnSyntax<T> {
        this._binding.type = BindingTypeEnum.ConstantValue;
        this._binding.cache = value;
        this._binding.dynamicValue = null;
        this._binding.implementationType = null;
        this._binding.scope = BindingScopeEnum.Singleton;
        return new BindingWhenOnSyntax<T>(this._binding);
    }

Common
We set the type.
We reset the properties that do not apply.
We set the scope to singleton ( this is common for some of these "to" methods )

Non custom
We set the applicable prroperty - for toConstantValue it is the cache

Common
Return new BindingWhenOnSyntax with the binding

Solution
`
public toConstantValue(value: T): interfaces.BindingWhenOnSyntax {
return this._setBinding(BindingTypeEnum.ConstantValue,true,"cache",value);
}

private _setBinding<TBindingStoreProperty extends keyof interfaces.Binding<T>>(
    bindingType:interfaces.BindingType,
    isSingleton:true,
    bindingStoreProperty:TBindingStoreProperty,
    bindingStoreValue:interfaces.Binding<T>[TBindingStoreProperty]
    ): interfaces.BindingWhenOnSyntax<T> {
        //reset
        const binding = this._binding;
        binding.implementationType = null;
        binding.cache = null;
        binding.dynamicValue = null;
        binding.factory = null;
        binding.provider = null;

        binding.type = bindingType;
        if(isSingleton){
            binding.scope = "Singleton";
        }
        binding[bindingStoreProperty] = bindingStoreValue;
        return new BindingWhenOnSyntax<T>(this._binding);
    }

`

@tonyhallett
Copy link
Contributor Author

@notaphplover What do you think ?

@notaphplover
Copy link
Member

Hi @tonyhallett, I think this is a good question and it's not easy to find a good answer. I'll do my best to give you my opinion.

I think this request is a proposal to make an abstraction to avoid duplicated code. I think it's good to remove duplicated code if we can make an abstracion: "those X pieces of code represents exactly the same, so we can write a common one and call it instead of having those X pieces".

But I wonder if this case is the same, it depends on multiple factors:

  • How do we want to model our processes?
  • How likely are our processes to change?
  • Given the process X with the subprocess X1 and the process Y with the process Y1, is X1 conceptually equivalent to Y1?

Sometimes we have duplicated code to represent different concepts. This image allways comes to my mind in these cases even if it's used to explain the Liskov substitution principle:

Liskov-1

So I think answering your question is equivalent to answering the next question:

Are the subtasks of building a ConstantValue binding and binding a Function binding (as an example) equivalent?

I think the answer is "Yes, but we got the wrong abstracion".

I realize binding is a wrapper to be used for anything. Resolution flows uses this class but the logic is too complex. Resolver has to know which is the right property to use to get a result. Why? The binding itself could take that responsability.

I think the duplicated code we are facing is a smell of a wrong abstraction. Your proposal solves the fact of having duplicated code, but we shouldn't give a method a hint of which bindingStoreProperty is the right one to be used, we should try to give Binding a method to provide the solution we need.

As an example, if we have a BaseBinding with common properties and methods, we could add two child types: InstanceBinding and CacheBinding CacheBinding could provide a getCached() method to provide the cached result and Binding could have an isCacheable method. This way the duplicated code would dissapear because the responsability of getting the cached result would be moved to CachedBinding.

@tonyhallett
Copy link
Contributor Author

tonyhallett commented May 2, 2021

I will respond in detail later but yes I agree that there should be a base binding as I mentioned here

I have coded a solution that appears to work, probably not the best as it was a proof of concept that is backwards compatible.
I have had to comment out lots of tests but not the tests that bind from the container/container modules and get from the container. These tests pass.

There is now a BindingBase abstract class.

Note that I was rapidly coding on top of my new scope code so it will look ever so slightly different. I have marked with asterisks the new scope code.

CacheBinding could provide a getCached() method to provide the cached result and Binding could have an isCacheable method.

With my code, binding has a single responsibility of providing the value when it is not coming from a cache. The "cache" is available on the binding as public resolveScope: interfaces.Scope<T>
Although not pertinent to this discussion please see the scope code below. RootRequestScope is faciliatated by a Context hierarchy similar to Request, managed in planAndResolve. This allows for #1143. If so inclined you can create your own custom scope.

export interface Scope<T>{
        get(binding:Binding<T>,request:Request):Promise<T>|T|null
        set(binding:interfaces.Binding<T>,request:Request,resolved:T|Promise<T>):T | Promise<T>
    }

class SingletonScope<T> implements interfaces.Scope<T>{
  get(binding:interfaces.Binding<T>,_: interfaces.Request): Promise<T> | T | null {
    if(binding.activated) {
      return binding.cache;
    }
    return null;
  }
  set(binding:interfaces.Binding<T>,_:interfaces.Request,resolved:T|Promise<T>):T | Promise<T> {
      binding.cache = resolved;
      binding.activated = true;

      if (isPromise(resolved)) {
          resolved = resolved.catch((ex) => {
              // allow binding to retry in future
              binding.cache = null;
              binding.activated = false;

              throw ex;
          });
      }
      return resolved;
  }

}

class RequestResolveScope<T> implements interfaces.Scope<T>{
  get(binding:interfaces.Binding<T>,request: interfaces.Request): Promise<T>|T|null {
    const store = this._getRequestStore(request)!;
    if(store.has(binding.id)){
      return store.get(binding.id)
    }
    return null;
  }
  set(binding:interfaces.Binding<T>,request:interfaces.Request,resolved:T|Promise<T>):T | Promise<T> {
      const store = this._getRequestStore(request)!;
      store.set(binding.id,resolved);
      return resolved;
  }
  _getRequestStore(request:interfaces.Request): interfaces.RequestScope {
    return request.parentContext.plan.rootRequest.requestScope;
  }
}

class TransientScope<T> implements interfaces.Scope<T>{
  get(): T|null {
      return null
  }
  set(_:interfaces.Binding<T>,__:interfaces.Request,resolved:T|Promise<T>):T | Promise<T> {
      return resolved;
  }

}

class RootRequestScope<T> implements interfaces.Scope<T>{
  get(binding:interfaces.Binding<T>,request: interfaces.Request): T | null {
      const store = this._getRootContextRootRequestStore(request.parentContext)!;
      if(store.has(binding.id)){
        return store.get(binding.id);
      }
      return null;
  }
  set(binding:interfaces.Binding<T>,request:interfaces.Request,resolved:T|Promise<T>):T | Promise<T> {
    const store = this._getRootContextRootRequestStore(request.parentContext)!;
    store.set(binding.id,resolved);
    return resolved;
  }
  _getRootContextRootRequestStore(context:interfaces.Context):interfaces.RequestScope {
    while(context.parentContext !== undefined){
      context = context.parentContext;
    }
    const rootContextRootRequest = context.plan.rootRequest;
    if(rootContextRootRequest.rootRequestScope === undefined){
      rootContextRootRequest.rootRequestScope = new Map<any, any>();
    }
    return rootContextRootRequest.rootRequestScope;
  }
}



const singletonScope = new SingletonScope<any>();
const transientScope = new TransientScope<any>();
const requestScope = new RequestResolveScope<any>();
const rootRequestScope = new RootRequestScope<any>();

export const scopeMap = new Map<interfaces.BindingScope,interfaces.Scope<any>>();
scopeMap.set("Singleton", singletonScope);
scopeMap.set("Request", requestScope);
scopeMap.set("Transient", transientScope);
scopeMap.set("RootRequest", rootRequestScope);

export function getResolveScope<T>(scope:interfaces.BindingScope): interfaces.Scope<T>{
  if(scopeMap.has(scope)){
    return scopeMap.get(scope)!;
  }
  throw new Error(UNSUPPORTED_SCOPE_ENUMERATION(scopeMap));
}

class BindingInSyntax<T> implements interfaces.BindingInSyntax<T> {

    private _binding: interfaces.Binding<T>;

    public constructor(binding: interfaces.Binding<T>) {
        this._binding = binding;
    }

    public inRequestScope(): interfaces.BindingWhenOnSyntax<T> {
        return this.setScope("Request")
    }

    public inSingletonScope(): interfaces.BindingWhenOnSyntax<T> {
        return this.setScope("Singleton")
    }

    public inTransientScope(): interfaces.BindingWhenOnSyntax<T> {
        return this.setScope("Transient")
    }

    public inRootRequestScope(): interfaces.BindingWhenOnSyntax<T> {
        return this.setScope("RootRequest")
    }

    public inCustomScope(scope: interfaces.Scope<T>): interfaces.BindingWhenOnSyntax<T> {
        this._binding.scope = "Custom";
        this._binding.resolveScope = scope;
        return new BindingWhenOnSyntax<T>(this._binding);
    }


    private setScope(scope:interfaces.BindingScope): interfaces.BindingWhenOnSyntax<T>{
        this._binding.setScope(scope);
        return new BindingWhenOnSyntax<T>(this._binding);
    }
}

Back to binding derivations !

abstract class BindingBase<T> implements interfaces.Binding<T> {

    public id: number;
    public moduleId: interfaces.ContainerModuleBase["id"];

    // Determines weather the bindings has been already activated
    // The activation action takes place when an instance is resolved
    // If the scope is singleton it only happens once
    public activated: boolean;

    // A runtime identifier because at runtime we don't have interfaces
    public serviceIdentifier: interfaces.ServiceIdentifier<T>;

    // The constructor of a class which must implement T
    public implementationType: interfaces.Newable<T> | T | null;

    // Cache used to allow singleton scope and BindingType.ConstantValue bindings
    public cache: T | null;

    // Cache used to allow BindingType.DynamicValue bindings
    public dynamicValue: interfaces.DynamicValue<T> | null;

    // The scope mode to be used
    public scope: interfaces.BindingScope | interfaces.CustomScope;

    // The scope
    public resolveScope: interfaces.Scope<T> // *** new scope code

    // The kind of binding
    public abstract type: interfaces.BindingType;

    // A factory method used in BindingType.Factory bindings
    public factory: interfaces.FactoryCreator<T> | null;

    // An async factory method used in BindingType.Provider bindings
    public provider: interfaces.ProviderCreator<T> | null;

    // A constraint used to limit the contexts in which this binding is applicable
    public constraint: (request: interfaces.Request) => boolean;

    // On activation handler (invoked just before an instance is added to cache and injected)
    public onActivation: interfaces.BindingActivation<T> | null;

    // On deactivation handler (invoked just before an instance is unbinded and removed from container)
    public onDeactivation: interfaces.BindingDeactivation<T> | null;

    public constructor(serviceIdentifier: interfaces.ServiceIdentifier<T>, scope: interfaces.BindingScope) {
        this.id = id();
        this.activated = false;
        this.serviceIdentifier = serviceIdentifier;
        this.setScope(scope); // *** new scope code
        this.constraint = (request: interfaces.Request) => true;
        this.implementationType = null;
        // perhaps these null setters can go.  With the reduced tests the first is necessary or there is 1 failed test
        this.cache = null;
        this.factory = null;
        this.provider = null;
        this.onActivation = null;
        this.onDeactivation = null;
        this.dynamicValue = null;
    }
  
    // *** new scope code
    public setScope(scope:interfaces.BindingScope): void {
        this.scope = scope;
        this.resolveScope = getResolveScope<T>(scope); 
    }
    abstract _getClone(serviceIdentifier:interfaces.ServiceIdentifier<T>):interfaces.Binding<T>
    abstract provideValue(context:interfaces.Context, binding:interfaces.Binding<T>, childRequest:interfaces.Request[]): T | Promise<T>
    public clone(): interfaces.Binding<T> {
        const clone = this._getClone(this.serviceIdentifier);
        clone.scope = this.scope;
        clone.resolveScope = this.resolveScope;
        clone.activated = (clone.scope === BindingScopeEnum.Singleton) ? this.activated : false;
        clone.implementationType = this.implementationType;
        clone.dynamicValue = this.dynamicValue;
        clone.type = this.type;
        clone.factory = this.factory;
        clone.provider = this.provider;
        clone.constraint = this.constraint;
        clone.onActivation = this.onActivation;
        clone.onDeactivation = this.onDeactivation;
        clone.cache = this.cache;
        return clone;
    }

}

class ConstructorBinding<T> extends BindingBase<T>{
    type = BindingTypeEnum.Constructor;
    constructor(serviceIdentifier: interfaces.ServiceIdentifier<T>, constructor:T){
        super(serviceIdentifier, "Singleton");
        this.implementationType = constructor;
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new ConstructorBinding<T>(serviceIdentifier, this.implementationType as T);
    }
    provideValue(context: interfaces.Context): T | Promise<T> {
        return this.implementationType as T;
    }

}



class ConstantValueBinding<T> extends BindingBase<T>{
    type = BindingTypeEnum.ConstantValue;
    constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>,
        readonly constantValue:T){
        super(serviceIdentifier, "Singleton");
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new ConstantValueBinding<T>(serviceIdentifier,this.constantValue);
    }
    provideValue(context: interfaces.Context): T | Promise<T> {
        return this.constantValue;
    }

}
type FactoryType = "toDynamicValue" | "toFactory" | "toProvider";
abstract class FactoryTypeBinding<T> extends BindingBase<T>{
    factoryType: FactoryType
    /* constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>){
        super(serviceIdentifier, "Singleton");
    } */
    abstract _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T>
    provideValue(context: interfaces.Context): T | Promise<T> {
        try {
            return this.factoryProvideValue(context);
        } catch (error) {
            if (isStackOverflowExeption(error)) {
                throw new Error(
                    ERROR_MSGS.CIRCULAR_DEPENDENCY_IN_FACTORY(this.factoryType, this.serviceIdentifier.toString())
                );
            } else {
                throw error;
            }
        }
    }

    abstract factoryProvideValue(context: interfaces.Context): T | Promise<T>

}

class FactoryBinding<T> extends FactoryTypeBinding<T>{
    type = BindingTypeEnum.Factory
    factoryType:FactoryType = "toFactory"
    constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>,
        readonly factory: interfaces.FactoryCreator<any>){
            super(serviceIdentifier, "Singleton")
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new FactoryBinding<T>(serviceIdentifier,this.factory!);
    }
    factoryProvideValue(context: interfaces.Context): T | Promise<T> {
        return this.factory(context) as unknown as T;
    }

}

class DynamicValueBinding<T> extends FactoryTypeBinding<T>{
    type = BindingTypeEnum.DynamicValue
    factoryType: FactoryType = "toDynamicValue";
    constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>,
        scope: interfaces.BindingScope,
        readonly dynamicValue:interfaces.DynamicValue<T>){
        super(serviceIdentifier,scope);
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new DynamicValueBinding<T>(serviceIdentifier,this.scope as any, this.dynamicValue!);
    }
    factoryProvideValue(context: interfaces.Context): T | Promise<T> {
        return this.dynamicValue!(context)
    }

}

class ProviderBinding<T> extends FactoryTypeBinding<T>{
    type = BindingTypeEnum.Provider
    factoryType: FactoryType = "toProvider"
    constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>,
        readonly provider: interfaces.ProviderCreator<any>){
        super(serviceIdentifier, "Singleton");
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new FactoryBinding<T>(serviceIdentifier,this.provider!);
    }
    factoryProvideValue(context: interfaces.Context): T | Promise<T> {
        return this.provider(context) as unknown as T;
    }

}

class InstanceBinding<T> extends BindingBase<T>{
    type = BindingTypeEnum.Instance
    constructor(
        serviceIdentifier: interfaces.ServiceIdentifier<T>,
        scope: interfaces.BindingScope,
        readonly implementationType: interfaces.Newable<T>){
        super(serviceIdentifier,scope);
    }
    _getClone(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.Binding<T> {
        return new InstanceBinding<T>(serviceIdentifier,this.scope as any,this.implementationType);
    }
    provideValue(context:interfaces.Context, binding:interfaces.Binding<T>, childRequests:interfaces.Request[]): T | Promise<T> {
        return resolveInstance(
            binding,
            binding.implementationType as unknown as interfaces.Newable<T>,
            childRequests,
            _resolveRequest
        );
    }

}



export { BindingBase, ConstructorBinding, DynamicValueBinding, ConstantValueBinding, FactoryBinding, ProviderBinding, InstanceBinding };


BindingToSyntax

class BindingToSyntax<T> implements interfaces.BindingToSyntax<T> {

    private _binding: interfaces.Binding<T>;

    public constructor(
        private readonly addRemoveBinding:(binding:interfaces.Binding<T>,add:boolean)=> void,
        private readonly scope:interfaces.BindingScope,
        private readonly serviceIdentifier:interfaces.ServiceIdentifier<T>
        ) {
    }

    public to(constructor: new (...args: any[]) => T): interfaces.BindingInWhenOnSyntax<T> {
        return this.addBinding(new InstanceBinding<T>(this.serviceIdentifier,this.scope,constructor));
    }

    public toSelf(): interfaces.BindingInWhenOnSyntax<T> {
        if (typeof this.serviceIdentifier !== "function") {
            throw new Error(`${ERROR_MSGS.INVALID_TO_SELF_VALUE}`);
        }
        return this.to(this.serviceIdentifier);
    }

    public toConstantValue(value: T): interfaces.BindingWhenOnSyntax<T> {
        return this.addBinding(new ConstantValueBinding<T>(this.serviceIdentifier,value));
    }

    public toDynamicValue(func: interfaces.DynamicValue<T>): interfaces.BindingInWhenOnSyntax<T> {
        return this.addBinding(new DynamicValueBinding<T>(this.serviceIdentifier,this.scope,func));
    }

    public toConstructor<T2>(constructor: interfaces.Newable<T2>): interfaces.BindingWhenOnSyntax<T> {
        return this.addBinding(new ConstructorBinding<T>(this.serviceIdentifier,constructor as unknown as T));
    }

    public toFactory<T2>(factory: interfaces.FactoryCreator<T2>): interfaces.BindingWhenOnSyntax<T> {
        return this.addBinding(new FactoryBinding<T>(this.serviceIdentifier,factory));
    }

    public toFunction(func: T): interfaces.BindingWhenOnSyntax<T> {
        // toFunction is an alias of toConstantValue
        if (typeof func !== "function") { throw new Error(ERROR_MSGS.INVALID_FUNCTION_BINDING); }
        return this.addBinding(new ConstantValueBinding<T>(this.serviceIdentifier,func));
    }

    public toAutoFactory<T2>(serviceIdentifier: interfaces.ServiceIdentifier<T2>): interfaces.BindingWhenOnSyntax<T> {
        return this.toFactory( (context) => {
            const autofactory = () => context.container.get<T2>(serviceIdentifier);
            return autofactory;
        });
    }

    public toProvider<T2>(provider: interfaces.ProviderCreator<T2>): interfaces.BindingWhenOnSyntax<T> {
        return this.addBinding(new ProviderBinding<T>(this.serviceIdentifier,provider));
    }

    public toService(service: string | symbol | interfaces.Newable<T> | interfaces.Abstract<T>): void {
        this.toDynamicValue(
            (context) => context.container.get<T>(service)
        );
    }

    private addBinding(binding:BindingBase<T>): BindingInWhenOnSyntax<T>{
        if(this._binding){
            this.addRemoveBinding(this._binding, false);
        }
        this._binding = binding;
        this.addRemoveBinding(this._binding, true);
        return new BindingInWhenOnSyntax<T>(this._binding);
    }
}

container changes.
Now that a binding is not created until the BindingToSyntax has been used there is no need for BindingTypeEnum.Invalid.


    private addRemoveBinding<T>(binding:interfaces.Binding<T>,add:boolean){
        if(add){
            this._bindingDictionary.add(binding.serviceIdentifier, binding);
        }else {
            this._bindingDictionary.removeByCondition(lookupBinding => lookupBinding === binding);
        }
    }
    public bind<T>(serviceIdentifier: interfaces.ServiceIdentifier<T>): interfaces.BindingToSyntax<T> {
        const scope = this.options.defaultScope || BindingScopeEnum.Transient;
        return new BindingToSyntax<T>(this.addRemoveBinding.bind(this),scope, serviceIdentifier);
    }
private bindModule<T>(serviceIdentifier: interfaces.ServiceIdentifier<T>,moduleId:number): interfaces.BindingToSyntax<T> {
        const scope = this.options.defaultScope || BindingScopeEnum.Transient;
        const addRemoveBindingModule = (binding:interfaces.Binding<T>,add:boolean) => {
            binding.moduleId = moduleId;
            this.addRemoveBinding(binding,add);
        }
        return new BindingToSyntax<T>(addRemoveBindingModule,scope, serviceIdentifier);
    }
    public rebindModule<T>(serviceIdentifier: interfaces.ServiceIdentifier<T>, moduleId:number): interfaces.BindingToSyntax<T> {
        this.unbind(serviceIdentifier);
        return this.bindModule(serviceIdentifier, moduleId);
    }

    private _getContainerModuleHelpersFactory() {

        const getBindFunction = (moduleId: interfaces.ContainerModuleBase["id"]) =>
            (serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
                const bindingToSyntax = this.bindModule(serviceIdentifier, moduleId);
                return bindingToSyntax;
            };
         const getRebindFunction = (moduleId: interfaces.ContainerModuleBase["id"]) =>
            (serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
                const bindingToSyntax = this.rebindModule(serviceIdentifier, moduleId);
                return bindingToSyntax;
            };
      

resolver - this has some new scope code

//*** passing around requestScope is gone.
function resolve<T>(context: interfaces.Context): T | Promise<T> | (T | Promise<T>)[] {
    return _resolveRequest(context.plan.rootRequest) as T | Promise<T> | (T | Promise<T>)[];
}

//*** passing around requestScope is gone.
const _resolveRequest = <T>(request: interfaces.Request): undefined | T | Promise<T> | (T | Promise<T>)[] => {

    request.parentContext.setCurrentRequest(request);

    const bindings = request.bindings;
    const childRequests = request.childRequests;

    const targetIsAnArray = request.target && request.target.isArray();

    const targetParentIsNotAnArray = !request.parentRequest ||
                                   !request.parentRequest.target ||
                                   !request.target ||
                                   !request.parentRequest.target.matchesArray(request.target.serviceIdentifier);

    if (targetIsAnArray && targetParentIsNotAnArray) {

        // Create an array instead of creating an instance
        return childRequests.map((childRequest: interfaces.Request) => {
            return _resolveRequest(childRequest) as T | Promise<T>
        });

    } else {

        if (request.target.isOptional() && bindings.length === 0) {
            return undefined;
        }

        const binding = bindings[0];

        return resolveBinding(request, binding);
    }

};

const resolveBinding = <T>(request: interfaces.Request,binding:interfaces.Binding<T>): T | Promise<T> => {
    const childRequests = request.childRequests;
    let result: Promise<T> | T;
    const fromScope = binding.resolveScope.get(binding,request); // *** new scope code
    if(fromScope !==null){
        return fromScope;
    }
    result = binding.provideValue(request.parentContext, binding, childRequests); // let the binding do the work

    if (isPromise(result)) {
        result = result.then((resolved) => _onActivation(request, binding, resolved));
    } else {
        result = _onActivation(request, binding, result);
    }

    result = binding.resolveScope.set(binding,request, result); // *** new scope code
    return result;
}

Much nicer

@tonyhallett tonyhallett mentioned this issue May 2, 2021
11 tasks
@tonyhallett
Copy link
Contributor Author

I will create a pull request for the Scope abstraction tomorrow.

@notaphplover
Copy link
Member

hi @tonyhallett sorry for the late reply.

I've had a look, the only problem I see is the TransientScope class:

class TransientScope<T> implements interfaces.Scope<T>{
  get(): T|null {
      return null
  }
  set(_:interfaces.Binding<T>,__:interfaces.Request,resolved:T|Promise<T>):T | Promise<T> {
      return resolved;
  }
}

get() returns null and set does not store anything. I think there's no need to add a TransientScope class. I would add a CacheableBinding child type who has a Scope. This way there is no need to add a Transient scope because a TransientBinding would be a direct child of BindingBase.

But sure, go ahead, I think your approach is really good and we can allways iterate

@tonyhallett
Copy link
Contributor Author

sorry for the late reply.

Not at all !

get() returns null and set does not store anything. I think there's no need to add a TransientScope class

We could have no TransientScope and have resolveScope null and have the resolver check for null but I think the resolver should be ignorant. I also like it because it is the definition of a transient scope and you can compare scopes side by side and see exactly what they mean.. It also keeps caching out of the Binding inheritance.

we can allways iterate

Cool, I will make the pull request/s tomorrow and if for any reason it comes up short we can try other avenues.

Two pull requests ? Scope first then binding inheritance ?

@notaphplover
Copy link
Member

sorry for the late reply.

Not at all !

get() returns null and set does not store anything. I think there's no need to add a TransientScope class

We could have no TransientScope and have resolveScope null and have the resolver check for null but I think the resolver should be ignorant. I also like it because it is the definition of a transient scope and you can compare scopes side by side and see exactly what they mean.. It also keeps caching out of the Binding inheritance.

we can allways iterate

Cool, I will make the pull request/s tomorrow and if for any reason it comes up short we can try other avenues.

All right :)

Two pull requests ? Scope first then binding inheritance ?

I think they those Scopes are related to bindings and iterations could be easier if we submit only one pull request, so I would suggest a single pull request

@tonyhallett
Copy link
Contributor Author

@notaphplover Will have something to show tomorrow.

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