Search code examples
dependency-injectionspring4d

How to delegate Container1.Resolve<T> to Container2.Resolve<T>, if Container1 cannot resolve T?


Using Spring4D, I would like to build a container that delegates service resolution to another container, if it cannot resolve a service -- something along these lines:

function TContainer.Resolve<T>: T;
begin
  if not TryResolve<T>(Result) then
    Result := OtherContainer.Resolve<T>;
end;

Is this possible?


Solution

  • There are so called subdependency resolvers (future versions will just call them type resolver) inside a container that handle specific types or type patterns (like being able to resolve TArray<T> or IList<T> where T is something being registered).

    You can implement your own that checks if a type is not inside of the container you attached this resolver to and then delegate the resolve chain for this type to another container.

    Here is some example code how to achieve that (without freeing objects)

    uses
      Spring,
      Spring.Container,
      Spring.Container.Core,
      System.SysUtils;
    
    type
      TFoo = class
    
      end;
    
      TBar = class
      private
        fFoo: TFoo;
      public
        constructor Create(const foo: TFoo);
        property Foo: TFoo read fFoo;
      end;
    
      TSubContainerResolver = class(TInterfacedObject, ISubDependencyResolver)
      private
        fContainer: TContainer;
        fSubContainer: TContainer;
      public
        constructor Create(const container, subContainer: TContainer);
    
        function CanResolve(const context: ICreationContext;
          const dependency: TDependencyModel; const argument: TValue): Boolean;
        function Resolve(const context: ICreationContext;
          const dependency: TDependencyModel; const argument: TValue): TValue;
      end;
    
    { TBar }
    
    constructor TBar.Create(const foo: TFoo);
    begin
      fFoo := foo;
    end;
    
    { TSubContainerResolver }
    
    constructor TSubContainerResolver.Create(const container, subContainer: TContainer);
    begin
      fContainer := container;
      fSubContainer := subContainer;
    end;
    
    function TSubContainerResolver.CanResolve(const context: ICreationContext;
      const dependency: TDependencyModel; const argument: TValue): Boolean;
    begin
      Result := not fContainer.Kernel.Registry.HasService(dependency.TypeInfo)
        and fSubContainer.Kernel.Resolver.CanResolve(context, dependency, argument);
    end;
    
    function TSubContainerResolver.Resolve(const context: ICreationContext;
      const dependency: TDependencyModel; const argument: TValue): TValue;
    begin
      Result := fSubContainer.Kernel.Resolver.Resolve(context, dependency, argument);
    end;
    
    procedure ScenarioOne;
    var
      c1, c2: TContainer;
      b: TBar;
    begin
      c1 := TContainer.Create;
      c2 := TContainer.Create;
      c1.Kernel.Resolver.AddSubResolver(TSubContainerResolver.Create(c1, c2));
    
      // dependency in subcontainer
      c1.RegisterType<TBar>;
      c1.Build;
      c2.RegisterType<TFoo>;
      c2.Build;
    
      b := c1.Resolve<TBar>;
      Assert(Assigned(b.fFoo));
    end;
    
    procedure ScenarioTwo; 
    var
      c1, c2: TContainer;
      b: TBar;
    begin
      c1 := TContainer.Create;
      c2 := TContainer.Create;
    
      c1.Kernel.Resolver.AddSubResolver(TSubContainerResolver.Create(c1, c2));
      c2.Kernel.Resolver.AddSubResolver(TSubContainerResolver.Create(c2, c1));
    
      // type in subcontainer but dependency in parent container
      c1.RegisterType<TFoo>;
      c1.Build;
      c2.RegisterType<TBar>;
      c2.Build;
    
      b := c1.Resolve<TBar>;
      Assert(Assigned(b.fFoo));
    end;
    
    begin
      ScenarioOne;
      ScenarioTwo;
    end.