simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 155 forks source link

Resolving instances with parameters in a certain context #1002

Closed HedgehogNSK closed 1 month ago

HedgehogNSK commented 1 month ago

My case

This question is a continuation of my question on StackOverflow

I'm trying to find a way to resolve instances which depends on runtime made instances. I modified example for this topic in comparsion with the original question

Core interfaces of the case

 // There are IFactory interfaces with different quantity of parameters
public interface IFactory<in T1, out TObject>
{
  TObject Create(T1 param);
}
public interface IPlan<in TContext,out TReport>{
  IObservable<TReport> Execute(TContext context);
}
public interface IStep<in TContext,out TReport>{
  IObservable<TReport> Make(TContext context);
}
public interface IMessageBuilder{
  SendMessage Build();
}

Base classes:

public abstract class CommandStep : IStep<IRequestContext,CommandStep.Report>
{
  public abstract IObservable<Report> Make(IRequestContext context);
  public enum Result {...}
  public record class Report(Result Result, IMessageBuilder? MessageBuilder = null){...}
}

public abstract class CompositeStep<TContext,T> : IStep<TContext,T>
{
  public CompositeStep(params IStep<TContext,T>[] steps){...}
  public virtual IObservable<T> Make(TContext context){...}
}
public class CommandPlan : IPlan<IRequestContext, CommandStep.Report>
{
  public readonly string commandName;
  public CommandPlan(string commandName, IEnumerable<IStep<IRequestContext, CommandStep.Report>> steps)
   : base(steps)
  {
    this.commandName = commandName;
  }
  protected override bool IsReadyToGoNext(CommandStep.Report report) { ... }
  public record Report(CommandPlan Plan, IRequestContext Context, CommandStep.Report StepReport);
}

public class CreateMessageBuilder : LocalizedMessageBuilder, IMessageBuilder
{
  public CreateMessageBuilder(long chatId, CultureInfo info
  , ILocalizationProvider localizationProvider, int minSymbols, int maxSymbols)
  : base(chatId, info,localizationProvider)
  {...}
  public void SetUser(UserRepresentation representation){...};
  public void SetSirena(SirenRepresentation sirena){...};
  public void IsUserAllowedToCreateSirena(bool isAllowed){...};
  public void IsTitleValid(bool isValid){...};
  public override SendMessage Build(){...};
}

Example of plan factory:

public class CreateSirenaPlanFactory : IFactory<IRequestContext, CommandPlan>
{
  public CreateSirenaPlanFactory(...){...}
  public CommandPlan Create(IRequestContext context)
  {
   //`CreateMessageBuilder` and `Buffer` has to be created for each plan instance as transient. 
    CreateMessageBuilder messageBuilder = messageBuilderFactory.Create(context);

    // `Buffer` is a data storage class for the data that are being created during the plan execution.
    //  I can't pass it in the `IStep.Make` method, because shared data is different for each plan and I can't break interface
    // Content of buffer and count of such buffers differs from plan to plan
    CreateSirenaStep.Buffer buffer = bufferFactory.Create(messageBuilder);

    //Same buffer instance has to be shared between group of steps below
    //But for each new request a new instance of plan and of buffer has to be created
    var validation = new CompositeCommandStep([
      //TiedStep1
      //TiedStep2
    ]);

    List<CommandStep> steps = [/*Step1*/];

     if(Condition(context))
      steps.Add(/*Step2*/);

    steps.AddRange([
      validation,
      /*Step3*/
    ]);
    return new CommandPlan(CreateSirenaCommand.NAME, steps);
  }

  public bool Condition(IRequestContext context){...}
}

Dependency Injection Code Smell

@dotnetjunkie shared reference to the article The article spotlights important concerns. But the solution that it provides can't cover complex systems.

  1. Example with ICommand changing to ICommandHandler<TCommand> is actually confusing. After considering the example it became obvious that TCommand is not a Command, it is a data structure. And ICommandHandler is still ICommand with changed name. And all example is about changing ICommand interface to ICommand<T>. At least, it is working solution. In my case it is ICommand<IRequestContext>.
  2. Example with apdater in case where dependency is a property of other class is useful. But if dependency is a runtime parameter of data model and this dependency can't be set as a parameter for ICommand.Execute then it would be nested in a container or wrapper. And that container can be registered.
    public class Container<T> where T: class
    {
    public T? Value {get;set;]
    }

    And this kind of solution is a workaround incarnate. Becasue it's a same extra layer of abstraction as well as a factory. This solution doesn't provide understanding how to solve contextual resolving. When an application has to resolve a transient adapter for a set of an instances.

public interface  IConatiner{} //or any other IService
public class A(IService service){}
public class B(IService service){}
public class Set(A a, B b){}

public class CompositionRoot
{
   public Register(Container container)
   {
      container.Register<IConatiner, Container>(LifeStyle.Transient); // or any other IService
      container.Register<A>(LifeStyle.Transient);
      container.Register<B>(LifeStyle.Transient);
      container.Register<Set>(LifeStyle.Transient);
   }
}

In this example each resolving creates a new Implementation of IContainer. But what should we do if it's necessary to create 1 instance per 1 set, so A and B instances shares the same IContainer instance in the context of Set instance?

For instance, in Extenject we can create subcontainer that would let bind IContainer as a Singleton which is cached and visible only inside current context. So each time when DI has to resolve the some dependency it starts search from subcontainer and if it can't find necessary dependency it goes to a parent container, then to a parent of parent container and so on, until it reaches a root container.

The expecting registration in this case is something like:

public Register(Container container){
   var subContainer = container.CreateSubcontainer();
   subContainer.Register<IContainer, Container>(LifeStyle.Singleton);
   container.Register<A>(LifeStyle.Transient);
   container.Register<B>(LifeStyle.Transient);
   container.Register<Set>(LifeStyle.Transient);
}
  1. The article doesn't give a clue how to register collection wich depends on runtime values. I can't figure out how to register collection of steps, where set of steps is floating, avoiding using factory with service locator.

The Simple Injector documentation provides an example of registrating Func Factory.

// using System;
// using SimpleInjector;
// using SimpleInjector.Advanced;
public static void RegisterFuncFactory<TService, TImpl>(
    this Container container, Lifestyle lifestyle = null)
    where TService : class
    where TImpl : class, TService
{
    lifestyle = lifestyle ?? container.Options.DefaultLifestyle;
    var producer = lifestyle.CreateProducer<TService, TImpl>(container);
    container.RegisterInstance<Func<TService>>(producer.GetInstance);
}

And as for me CreateProducer looks like a native factory that uses service locator which create producers instead of runtime entites: Producer<TService, Impl>IFactory.Create(Container)

dotnetjunkie commented 1 month ago

I read your question (and that on Stack Overflow) a few times, but I still have a hard time figuring out what the intention is of your application design. I therefore won't (and can't) comment on your design and won't suggest any alternative designs.

That said, considering your current design, Simple Injector might not the best pick, but keep in mind that you'll find that many DI containers in the .NET space to be limited when it comes to mixing runtime data with design time dependencies. There are some DI Containers who have got some features in place for this, such as Ninject, which allows providing runtime data to its IKernel.Get method (the equivalent method of Simple Injector's GetInstance). Microsoft's MS.DI library contains a ActivatorUtilities.CreateInstance method that also allows supplying runtime dependencies. Most of the time, however, you can only control runtime data to the root object being resolved, not into nested objects and it won't work when trying to wrap instances in decorators.

If you want to stick with Simple Injector, the only thing I can consider is changing interfaces such that the components can be initialized after creation. For instance

public interface IInitializableComponent<T>
{
    void Initialize(T data);
}

public interface IFactory<TData, TComponent>
    where TComponent : IInitializableComponent<TData>
{
    TComponent Create(TData data);
}

This change allows the factory implementation to become the following:

public class SimpleInjectorFactory<TData, TComponent> : IFactory<TData, TComponent>
    where TComponent : IInitializableComponent<TData>
{
    private readonly SimpleInjector.Container container;

    public SimpleInjectorFactory(Container container) => this.container = container;

    public TComponent Create(TData data)
    {
        var component = this.container.GetInstance<TComponent>();
        component.Initialize(data);
        return component;
    }
}

I hope this helps

HedgehogNSK commented 1 month ago

Thank you, Steven. I really appreciate your time and your intention to help.

If you want to stick with Simple Injector, the only thing I can consider is changing interfaces such that the components can be

Yes, I would like to stick with Simple Injector. I could use Ninject because it has same interface as a Extenject. But I'm trying to extend my experience by working with different popular libraries. And my application is some sort of research.

The only problem with this approach that I had already started to make a factories similar to what you suggest. But when I started to create factory for each step, and each other entity which has a parameter(s), I felt that something is wrong. I have a lot of commands, which has it's own plan with several steps, so I decided that it's probably code smell. That's why I came with the question. And the article that you provided convincied me that I was right. But unfortunately, I still don't see better way.

I was registring just general services before DI implementation into PlanFactory. Perhaps I could still leave it this way:

 public class CreateSirenaPlanFactory : IFactory<IRequestContext, CommandPlan>
{
  private readonly IGetUserOperationAsync getUserOperationAsync;
  private readonly ICreateSirenaOperationAsync createSirenAsync;
  private readonly ILocalizationProvider localizationProvider;

  public CreateSirenaPlanFactory(IGetUserOperationAsync getUserOperationAsync
  , ICreateSirenaOperationAsync createSirenAsync
  , ILocalizationProvider localizationProvider)
  {
    this.getUserOperationAsync = getUserOperationAsync;
    this.createSirenAsync = createSirenAsync;
    this.localizationProvider = localizationProvider;
  }

  public CommandPlan Create(IRequestContext context)
  {
    Container<IRequestContext> contextContainer = new(context); //Right now context is passed as a parameter.
    CreateMessageBuilder messageBuilder = new(context.GetChat().Id
    , context.GetCultureInfo(), localizationProvider
    , ValidateTitleCreateSirenaStep.TITLE_MIN_LENGHT
    , ValidateTitleCreateSirenaStep.TITLE_MAX_LENGHT);
    CreateSirenaStep.Buffer buffer = new(messageBuilder);
    CommandStep[] steps = [
      new GetUserCreateSirenaStep(contextContainer,buffer, getUserOperationAsync),
      new CheckAbilityToCreateSirenaStep(contextContainer, buffer),
      new ValidateTitleCreateSirenaStep(contextContainer,buffer),
      new RequestDBToCreateSirenaStep(contextContainer,buffer,createSirenAsync)
    ];
    return new(steps, contextContainer);
  }
}

As I mentioned in question, there is a factory example in the Simple Injector documentation that creates a registration, not an entity. I think I'll try go this way.

But the most hard part of the question for me is how to register collection which depends on some condition. If you'd provide me some example, or at least a direction that would be great:

T var1 = new T(...);
T var2 = new T(...);
List<T> list = [var1 ];
if(condition)
 list.Add(var2 ;

I read your question (and that on Stack Overflow) a few times, but I still have a hard time figuring out what the intention is of your application design.

My application is a Telegram bot. It handles commands from user, that they send via messenger interface. And the bot reacts on this commands, then it creates a response (IMessageBuilder instance) as a result of command processing . I'm not sure if you really have to dive in architecture. But if you wish to, I can answer on all of your question in details. :) Link to github project. It's open source.

The Plan is a pattern of my own that is some sort of reduced State Machine pattern but it has only 1 direction: it goes forward, or it waits interupts on current step. And I made it with intention to handle Observable nature of application.

By the way, as I see the State Machine pattern in its classical view would have same problems with Simple Injector integration. Because each state creates new state inside itself and each state pass a parameters through a contructor of the next state.

dotnetjunkie commented 1 month ago

But the most hard part of the question for me is how to register collection which depends on some condition. If you'd provide me some example, or at least a direction that would be great:

This completely depends on the type condition. If the condition is based on runtime state, you can't make the registration conditional (e.g. using RegisterConditional). Simple Injector has all kinds of optimizations and expects object graphs to be static in nature, i.e. it creates expression trees and emits IL resembling the structure of a certain object graph, and the next time you request that object graph, you'll get exactly the same structure. If you use runtime data, the first call will determine the structure of the graph.

If you tell me more about the condition, the type of collections/objects you have and how these collections are used (point at some examples in your repo), I'll be happy to try come up with a solution that works with Simple Injector.

HedgehogNSK commented 1 month ago

Thank you. Now I see that it's impossible to register collection with a runtime parameter. Although it has to be possible to create func factory that can produce collection by parameter. Right now I don't have real case where I have to select actual steps for collection. It's more like research question. But I have a lot of commands in the bot, so I bet the case will appear soon. Let's skip this step.

I have 2 more questions, the answers to which would help me get rid off service locator in a plan factories. I distribute Buffer properties into separate Container<T> for each nested property. And I rewrite the planFactory

    public override void Install()
    { 
       //Message builder depends on parameter, so I still need a way to bind it.
       //I left it this way
      Container.Register<IFactory<IRequestContext, CreateMessageBuilder>, CreateMessageBuilder.Factory>();

      //Properties that were nested in buffer right now a separate containers as it was suggested in the article
      Container.Register<NullableContainer<CreateMessageBuilder>>();
      Container.Register<NullableContainer<string>>();
      Container.Register<NullableContainer<UserRepresentation>>();
      ...
    }

CreateSirenaPlanFactory right now looks like this:

public class CreateSirenaPlanFactory : IFactory<IRequestContext,CommandPlan>
{
  private readonly IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory;
  private readonly NullableContainer<CreateMessageBuilder> messageBuilderContainer;
  private readonly CheckAbilityToCreateSirenaStep checkAbilityStep;
  private readonly ValidateTitleCommandStep validateTitleStep;
  private readonly GetUserCommandStep getUserCommandStep;
  private readonly RequestDBToCommandStep dbRequestStep;

  public CreateSirenaPlanFactory(
   IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory
  , NullableContainer<CreateMessageBuilder> messageBuilderContainer
  , CheckAbilityToCreateSirenaStep checkAbilityStep
  , ValidateTitleCommandStep validateTitleStep
  , GetUserCommandStep getUserCommandStep
  , RequestDBToCommandStep dbRequestStep)

  {
    this.messageBuilderFactory = messageBuilderFactory;
    this.messageBuilderContainer = messageBuilderContainer;
    this.checkAbilityStep = checkAbilityStep;
    this.validateTitleStep = validateTitleStep;
    this.getUserCommandStep = getUserCommandStep;
    this.dbRequestStep = dbRequestStep;
  }

  public CommandPlan Create(IRequestContext context)
  {
    CreateMessageBuilder messageBuilder = messageBuilderFactory.Create(context);
    messageBuilderContainer.Set(messageBuilder);

    var validation = new CompositeCommandStep([
      checkAbilityStep,
      validateTitleStep
    ]);

    IObservableStep<IRequestContext,CommandStep.Report>[] steps = [
      getUserCommandStep,
      validation,
      dbRequestStep,
    ];
    return new(CreateSirenaCommand.NAME,steps);
  }
}
  1. The steps inside context/scope of same plan factory has to share reference to the same containers. But for each plan factory container has to be transient. Probably it's most important question for me or I can't rid of service locator from factory. Because as containers are registered right now, they would be transient for each step. And If I register them as a Singleton then the containers will be shared between different plans. How register containers to reach the goal?
  2. Is it possible to register collection which you can see in the Create method? I have an issue with a CompositeStep registration which contains 2 other steps in this case.
dotnetjunkie commented 1 month ago

If components or their data needs to be shared, this is typically what you'd use the Scoped lifestyle for. But perhaps the default scoping model doesn't work for you. Please read this to learn more about both the default (ambient) scoping model and the alternative (flowing) scoping model.

Is it possible to register collection which you can see in the Create method? I have an issue with a CompositeStep registration which contains 2 other steps in this case.

I think I understand what you want. You want to construct an object graph similar to the following:

new CreateSirenaPlanFactory(
    messageBuilderFactory: new CreateMessageBuilder.Factory(),
    messageBuilderContainer: new NullableContainer<CreateMessageBuilder>(),
    step: new CompositeCommandStep<IRequestContext, CommandStep.Report>(
        new CheckAbilityToCreateSirenaStep(),
        new ValidateTitleCommandStep()),
    observableStep: new CompositeObservableStep<IRequestContext,CommandStep.Report>(
        new GetUserCommandStep(),
        new CheckAbilityToCreateSirenaStep(),
        new ValidateTitleCommandStep(),
        new RequestDBToCommandStep()));

You can do this by making the following registrations:

// Auto-Register all non-generic factory implementations such as CreateSirenaPlanFactory
container.Register(typeof(IFactory<,>), AppDomain.CurrentDomain.GetAssemblies());

// Map the open-generic IStep<,> to the open-generic CompositeCommandStep<,>. This
// means that every time IStep is resolved, it gets injected with CompositeCommandStep.
// CompositeCommandStep<TContext, T> shoul depend on IEnumerable<IStep<TContext, T>>
container.Register(typeof(IStep<,>), typeof(CompositeCommandStep<,>));

// Auto register all non-generic step implementations and register them as collections.
// This allows them to be resolved as IEnumerable<TStep<TContext, T>> (or any collection type)
// These collections will be injected into CompositeCommandStep<,>.
container.Collection.Register(typeof(IStep<,>), AppDomain.CurrentDomain.GetAssemblies());

// Register the composite for IObservableStep
container.Register(typeof(IObservableStep<,>), typeof(CompositeObservableStep<,>));

// Register observable steps as collection.
container.Collection.Register(typeof(IObservableStep<,>), AppDomain.CurrentDomain.GetAssemblies());

Perhaps this is not exactly what you need, but might hopefully give you some ideas. The previous registrations completely rely on the use of Auto-Registration to register many (closed) types at once. A very powerfull feature which can save you tons of code in your Composition Root.

HedgehogNSK commented 1 month ago

Thank you.

Flowing scopes

I had read the documentation even before I asked the question on Stack Overflow. I also shared a concern there that flowing scope is disposable. Therefore :

  1. if I create steps in a flowing scope then I have to dispose scope before return a created object from a factory. So what will happen with the objects created in scope? I expect that diposable objects would be disposed, and nothing would happen with non-disposable objects.
  2. if I create it inside factory, it means that it will contains a service locator. And as you answered many times before to me and to a lot of other questions on Stack Overflow and here as well that Service Locator is anti-pattern. So I wonder what exactly class or method has to contains create new scope? Because an example provided in the article shows a ServiceDecorator that uses service locator. So I'm confused.

Clarification

I think I understand what you want. You want to construct an object graph similar to the following:

It's my mistake. IObservableStep actually is IStep. I've tried to simplify names for easier understanding and forgot to change in 1 place. Excuse me please for confusing you. What I'm trying to do is:

public abstract class CompositeStep<TContext,T> : IStep<TContext,T>
{
  public CompositeStep(params IStep<TContext,T>[] steps){...}
  public virtual IObservable<T> Make(TContext context){...}
}
CompositeCommandStep : CompositeStep<IRequestContext, CommandStep.Report>
 {/*...*/}

new CreateSirenaPlanFactory(
    messageBuilderFactory: new CreateMessageBuilder.Factory(),
    messageBuilderContainer: new NullableContainer<CreateMessageBuilder>(),
    steps: [
        new GetUserCommandStep(messageBuilderContainer /*, ...*/),
        new CompositeCommandStep<IRequestContext, CommandStep.Report>(
            new CheckAbilityToCreateSirenaStep(messageBuilderContainer /*, ...*/),
            new ValidateTitleCommandStep(messageBuilderContainer /*, ...*/)
        ), 
        new RequestDBToCommandStep(messageBuilderContainer /*, ...*/)
    ]
);

CompositeStep consists of different steps for different plans

If a composite step is present inside a plan then it could be a combination of different types and count of steps. It's just an implementation of composite pattern. That's why I can't bind it like this:

container.Register(typeof(IStep<,>), typeof(CompositeCommandStep<,>));

Almost each plan has composite step. Here is another example of CompositeStep usage.

public class FindSirenaPlanFactory : IFactory<IRequestContext,CommandPlan>
{
  private readonly Container container;
  public FindSirenaPlanFactory(Container container) => this.container = container;

  public CommandPlan Create(IRequestContext context)
  {
    IStep<IRequestContext, CommandStep.Report>[] steps = [
      container.GetInstance<ValidateSearchParamFindSirenaStep>(),
      container.GetInstance<RequestFindSirenaStep>()
    ];
    CompositeCommandStep compositeStep = new CompositeCommandStep(steps);

    return new(FindSirenaCommand.NAME,[compositeStep]);
  }
}
dotnetjunkie commented 1 month ago

So what will happen with the objects created in scope? I expect that diposable objects would be disposed, and nothing would happen with non-disposable objects.

Yes, this assumption is correct. The scope will dispose all disposable Scoped instances that it created, and nothing will happen to non-disposable objects. Scopes just ensure Scoped instances are reused and ensures deterministic disposal of disposable objects.

if I create it inside factory, it means that it will contains a service locator.

Not exactly. Mark Seemann explains here that:

A DI container encapsulated in a Composition Root is not a Service Locator - it's an infrastructure component. It becomes a Service Locator if used incorrectly: when application code (as opposed to infrastructure code) actively queries a service in order to be provided with required dependencies, then it has become a Service Locator.

In other words:

Service Locator is ultimately not identified by the mechanics of its API, but by the role it plays.

This argument by Mark is reflected in our book (chapter 5) and I also explain this in my Stack Overflow answers whenever relevant.

What this simply means is that, as long as you define your factory implementation inside the Composition Root, you can use as much functionality of your favorite DI Container as you want, without it being a Service Locator. But when the factory implementation is implemented in application code, it will inevitably apply the Service Locator anti-pattern.

If a composite step is present inside a plan, then it could be a combination of different types and count of steps. It's just an implementation of composite pattern. That's why I can't bind it like this

Perhaps not in your case, but in general Simple Injector has good support for handling the composite design pattern, especially because it separates collections from one-to-one mappings. e.g.:

container.Collection.Register<ILogger>(
    typeof(FileLogger),
    typeof(SqlLogger),
    typeof(EventLogLogger));

container.Register<ILogger, CompositeLogger>();

This results in the following object graph:

ILogger logger = new CompositeLogger(new ILogger[]
{
    new FileLogger(),
    new SqlLogger(),
    new EventLogLogger()
});

// SomeComponent depends on ILogger
var component = new SomeComponent(logger);

But whether this works in your case is hard for me to say.

But here is another wild idea: have you considered removing the notion of a DI Container completely from the equation? Instead, you can apply Pure DI. This can be especially useful if your open source project is a reusable library or framework. In those cases, you should also read this and this.

HedgehogNSK commented 1 month ago

Thank you for your time. I really appreciate this. I suppose that I have everything to make a choice for my application. Now I better see the limitation of Simple Injector. I already read some of those articles, but I will read them again.

I just want to summarize some aspects and please correct me if I'm wrong:

  1. Using factories with DI Container as a parameter is reasonable only if the factory registred in composition root and it isn't created via constructor outside of a composition root.
  2. Simple Injector right now doesn't have build-in approach to handle situation:
    • when an entity instance has to be shared between other entities but only inside one context, and oustide context it have to be transient. As in my case Container shared only between several Step inside context of 1 Plan. Except flowing scopes, which Idon't know how to apply to my factories and which brokes some benifiets of ambient scopes.
    • when it's necessary to register collection where CompositeEntity(IEnumerable<IEntity>): IEntity combined with simple IEntity instances in one collection:
      public IEnumerable<IEntity> Create()
      {
      IEntity[] entities = new [
      new Entity(),
      new CompositeEntity(
      new Entity(), 
      new Entity(), 
      )
      ];
      return entities;
      }

I don't know how, but I have strong feeling that it's possible to create a condition for RegisterConditional or a method RegisterInScope that could cache instance for a certain class. Maybe later I would spend some time to solve this riddle. :) Zenject has not only singleton and transient lifestyle of registration, but it also has cached lifestyle. Maybe this is a key for a solution.

dotnetjunkie commented 1 month ago
  1. Using factories with DI Container as a parameter is reasonable only if the factory registred in composition root and it isn't created via constructor outside of a composition root.

Perhaps it's just a mis-wording, but the important part is that the source code of the factory implementation is defined inside the Composition Root. The factory's abstraction can of course still be defined in application code (and the factory would be of little use in case its abstraction was also defined inside the composition root).

  1. Simple Injector right now doesn't have build-in approach to handle situation:

That's a fair assessment and you could even remove the "right now" wording. In your specific case, you will have to fall back to very specific lambda registrations where you construct part of the object graphs (or collections) by hand, e.g.:

container.Register<IEnumerable<IEntity>>(() => new IEntity[]
{
    new Entity(),
    new CompositeEntity(
      new Entity(), 
      new Entity(), 
     )
});

Zenject has not only singleton and transient lifestyle of registration, but it also has cached lifestyle. Maybe this is a key for a solution.

I'm unfamiliar with Zenject, so I'm unable to comment what the difference is between Zenject's cached lifestyle and Simple Injector's Lifestyle.Scoped and ScopedLifestyle.Flowing.

HedgehogNSK commented 1 month ago

Could you please extend the explanation:

Perhaps it's just a mis-wording, but the important part is that the source code of the factory implementation is defined inside the Composition Root.

I'm not sure if I understood this part correct "defined inside". Does it means registered?

public interface IFactory<TParam,T>
{
  T Create(TParam param);
}
public class Factory: IFactory<string, Entity>
{
  public readonly Container container;
  public Factory(Container container)
  {
    this.container = container;
  }
  public Entity Create(string param){
    Entity entity = container.GetInstance();
    entity.Initialize(param);
  }
}

//Somewhere in Composition Root
public void Install(Cotainer container)
{
  container.RegisterSingleton<IFactory<string,Entity>, Factory>();
}

Does this sample of code relates to "defined inside"?

dotnetjunkie commented 1 month ago

I'm not sure if I understood this part correct "defined inside". Does it means registered?

No, it does not. To understand, we first need to go back to what the Composition Root is. This is a concept that was coined by Mark Seemann in Dependency Injection in .NET and later extended in the second edition (which I co-authored). You might not have access to one of those editions, but we published a freely available except of the section on the Composition Root online. The important part of the excerpt that I want to paraphrase is the following:

Don’t be misled to think that the Composition Root is part of your User Interface Layer. Even if you place the Composition Root in the same assembly as your User Interface Layer [...] the Composition Root isn’t part of that layer. Assemblies are a deployment artifact—you split code into multiple assemblies to allow code to be deployed separately. An architectural layer, on the other hand, is a logical artifact—you can group multiple logical artifacts in a single deployment artifact. Even though the assembly that both holds the Composition Root and the User Interface Layer depends on all other modules in the system, the User Interface Layer doesn’t.

While the excerpt doesn't mention this explicitly, you could view the Composition Root as a layer, i.e. a logical artifact that holds a bunch of code. The Composition Root is in the startup path of your application and it depends on all other layers in your application.

So when I talk about the factory implementation being defined in the Composition Root, I mean that you placed the class in such place that you consider it to be part of that layer. For instance:

namespace MyApp
{
    // Program is in the start-up assembly
    // Program can be considered to be part of the Composition Root "layer".
    static class Program
    {
        // Main is the entry point of the application
        static void Main(string[] args)
        {
            var container = new Container();

            // This is where the factory is "registered". This is different from the place
            // where it is "defined." Scroll down to see where it is "defined."
            // Registration is ALWAYS done inside the Composition Root, but you have
            // to be careful not to scatter your Composition Root (CR) across application
            // layers. For instance, don't create an MyApp.DAL.DALBootstrapper class
            // that depends on the Container to register stuff. In that case the CR is no
            // longer a "unique location."
            container.Register(typeof(IFactory<,>), typeof(SimpleInjectorFactory<,>));

            container.Register<IOrderRepository, SqlOrderRepository>();
            container.Register<ILogger, FileLogger>(Lifestyle.Singleton);
            container.Register<CancelOrderHandler>();

            var handler = container.GetInstance<CancelOrderHandler>();

            var orderId = Guid.Parse(args[0]);
            var command = new CancelOrder { OrderId = orderId };

            handler.Handle(command);
        }
    }

    // This implementation is placed (i.e. 'defined') "close to" the Program class
    // (even though its abstraction might not), which is why we can consider it
    // to be part of the Composition Root. What "close" means, is up to you, but
    // I would typically say all in the single start-up assembly and -in case your
    // startup assembly also contains UI stuff, which is typically the case when
    // working on web applications- commonly placed in a single folder.
    public sealed class SimpleInjectorFactory<TService, TParam>
        : IFactory<TService, TParam>
    {
        // Because this implementation is part of the Composition Root, you are
        // free to depend on the Container without risking applying the Service
        // Locator anti-pattern.
        public SimpleInjectorFactory(SimpleInjector.Container container) ...

        ...
    }
}

So again, "defined" means where you put the code. If you place the Factory class inside the startup assembly, you can consider it to be part of the Composition Root, but if you place the Factory class inside your Domain Layer or Data Access Layer, I would consider it to be defined outside the Composition Root.

I hope this helps.

HedgehogNSK commented 1 month ago

I think I get it. I'm not sure if I can handle a book in English. I had been read Simple Injector documentation for a few days before asked the question and it was hard time. :) But I will difinetly read those articles that you shared with me. I don't have anymore questions. Thank you. 🙏

dotnetjunkie commented 1 month ago

You're more than welcome.

I have know idea what your native language is, but if it helps, the book is available as well in Chinese, Italian, Polish, Russian, and Japanese. You can find a link to site of the publishers of those languages in the about section of my blog.

But please don't be urged buying my book just for the sake of thanking me. I make very little from especially those translated versions. But do consider getting a hand on a copy if you think it helps you in improving your software skills. This, for me, is the main reason of engaging online with other developers. I simply enjoy helping others.