I have a lot of objects in my program that are created at runtime with at least 1 parameter. There are many nested classes where a new data is evaluated, which is necessary for next inner instances.
At first, let me briefly describe the structure, and then I’ll provide you with the simplified example.
Structure description.
- An user sends string request ->
- Program determines
IRequestContext
andICommand
-> ICommand.Execute(IRequestContext)
is called ->- The execution creates
IStep[]
andIPlan(IStep[])
via factory and sends it to scheduler -> - At a certain time scheduler launches
IPlan
with theIRequestContext
. IPlan
executes step by step by callingIObservable<Report> IStep.Make(IRequestContext)
->- An implementations of
IStep
contains buffer instances to share result of their execution in scope of current plan. It’s not returned but it’s necessary to produce finalReport
, but not only for this purpose. Buffer can’t have interface cause it’s a storage of data after all. Report
containsenum Result
andIMessageBuilder
.IMesageBuilder
has only 1 methodBuild()
.
Details
Each class has a lot of dependencies by itself such as interfaces for work with database, localization, communication API. This dependencies are registred at the composition root. They are not problem in this case.
An instance of IRequestContext
is created at the runtime for each user request. IStep
, IMessageBuilder
and all sorts of buffers depend on it. Therefore I can’t just register SomeMessageBuilder
, because the necessary data for a builder is being created during the step execution. And different requests can’t share the same instance of message builder or buffer.
For each implementation of IMessageBuilder
there will be different count and types of parameters. That is why I can’t change interface to accept this entities like IMessageBuilder.Build(Entity1, Entity2, ...)
. Therefore I have to create and register factories for each implementation of IMessageBuilder
. As a result each IStep
implementation has dependency from a IFactory<T1,T2,...,IMessageBuilder>
.
Each IPlan
implementation has it’s own factory which creates unique combination of steps. Furthermore I use implementation of composite pattern for steps such as CompositeStep(IStep[] step) : IStep
. Therefore I have to register factory for each implementation of IStep
, and I have to register factory for each CompositeStep
which consists of different steps.
Simplified example
Please keep in mind that I have dozen of different IPlan
implementations which can use same steps.
Some interfaces and class for clarification.
public interface IPlan<in TContext,out TReport>{
IObservable<TReport> Execute(TContext context);
bool IsReadyToGoNext(TReport report);
}
public interface IStep<in TContext,out TReport>{
IObservable<TReport> Make(TContext context);
}
public interface IFactory<in T1, out TObject>
{
TObject Create(T1 param);
}
public interface IMessageBuilder{
SendMessage Build();
}
public class CreateMessageBuilder : LocalizedMessageBuilder, IMessageBuilder
{
public CreateMessageBuilder(long chatId, CultureInfo info
, ILocalizationProvider localizationProvider, int minSymbols, int maxSymbols)
{...}
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>
{
private readonly Container container;
private readonly IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory;
private readonly IFactory<CreateMessageBuilder, CreateSirenaStep.Buffer> bufferFactory;
public CreateSirenaPlanFactory(Container container
, IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory
, IFactory<CreateMessageBuilder,CreateSirenaStep.Buffer> bufferFactory)
{
this.container = container;
this.messageBuilderFactory = messageBuilderFactory;
this.bufferFactory = bufferFactory;
}
public IPlan Create(IRequestContext context)
{
CreateMessageBuilder messageBuilder = messageBuilderFactory.Create(context);
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([
container.GetInstance<CheckAbilityToCreateSirenaStep>(),
container.GetInstance<ValidateTitleCreateSirenaStep>()
]);
IStep<IRequestContext,CommandStep.Report>[] steps = [
container.GetInstance<GetUserCreateSirenaStep>(),
validation,
container.GetInstance<RequestDBToCreateSirenaStep>(),
];
return new CommandPlan(CreateSirenaCommand.NAME,steps);
}
}
Question
How to bind this? I’m not eager to create factory for each IStep
implementation that depends on 1 and more parameters, that being produced during a runtime. And I don’t know how to bind the buffer in this situation at all since SimpleInjector doesn’t have context.
P.S.
When I worked with Unity engine and with Zenject/Extenject there were much more options to divide contexts of a bindings and to create conditional bindings for an instances from certain factory. It’s a pity that SimpleInjector has a lack of such tools.
Because for each plan instance I could create context. And there would be 1 buffer instance created for each context. Furthermore I don’t need to create factory for each instance which need paramteres, because I could just bind native factory with parameters. It was a huge wonder for me when I found out that documentation doesn’t provide information about context, only about scopes. But I can’t create buffer instance in scope, because the scope will be closed just when Create
method will finish it work, and the instance would be disposed as I understood from the documentation. Perhaps I can use Flowing scope, however I still don’t understand what is the way to resolve instance with parameter(s).
5