Multiple Operation Implementations¶
All the examples of creating Operations show a single implementation of the Operation’s marker interface. In other words, if you
create Operation MyOperation with the marker interface IMyOperation as shown in the code below, then there is only one
implementation of IMyOperation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using KnightMoves.Pipelines;
using KnightMoves.Pipelines.Interfaces;
namespace MyApplication.Operations
{
// Marker Interface
public interface IMyOperation : IPipelineOperation<MyApplicationContext> { }
public class MyOperation : BasePipelineOperation<MyApplicationContext>, IMyOperation
{
public override void Execute(MyApplicationContext context)
{
// Logic goes here
}
}
}
|
However, what if you want to create another implementation of IMyOperation to give you the ability to switch
implementations? In other words, suppose you utilized the Strategy Pattern
by creating another implementation of IMyOperation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using KnightMoves.Pipelines;
using KnightMoves.Pipelines.Interfaces;
namespace MyApplication.Operations
{
// Marker Interface
public interface IMyOperation : IPipelineOperation<MyApplicationContext> { }
public class MyAlternativeOperation : BasePipelineOperation<MyApplicationContext>, IMyOperation
{
public override void Execute(MyApplicationContext context)
{
// Logic goes here
}
}
}
|
The Pipelines framework is designed to automatically scan the assemblies, pick up all implementations of
IPipelineOperation<MyApplicationContext>, and then inject them into your Pipeline Coordinator
for you. If you then add your operation to the pipeline with multiple implementations as shown in the code
below, then it will default to the last one if finds, which is unpredictable.
_pipelineCoordinator
.Execute<IMyOperation>() // Don't know which IMyOperation implementation will execute
This can be resolved in one of two ways.
Provide the selected implementation when during DI registration
Use a factory registration function that executes logic to determine which implementation to use
Each of these approaches is described below.
Providing the Selected Implementation During DI Registration¶
To resolve the selected implementation of your operations when the application is bootstrapped, you will need to create
a collection of System.Type to define the implementations you want the Pipeline Coordinator to use and feed it
into the registration extension method as shown in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using KnightMoves.Pipelines.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
var implementations = new List<Type>
{
typeof(MyAlternativeOperation)
};
services.AddPipelineCoordinator<MyPipelineCoordinator, MyApplicationContext>
(
typeof(Startup).Assembly,
implementations
);
...
}
...
}
|
In the code example above, the Pipeline Coordinator will use MyAlternativeOperation in the pipeline when calling
the Execute<IMyOperation>() method. This will be the permanent implementation of the operation unless you change the
implementations collection, rebuild, and redeploy your application.
If you need to switch implementations at runtime, then you can use the factory approach described below.
Using a Factory Registration Function¶
If you need to switch implementations at runtime (useful for things like code toggles, feature flags, etc.), then you can use a factory function to determine which implementation to use. For this, you will need to create a factory function that returns an instance of the OperationConfig class as shown in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using KnightMoves.Pipelines.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient(sp => {
// Logic here to determine which implementation to use based on
// config, environment, sp.GetRequiredService<T>(), etc.
var useAlternative = true;
return new OperationConfig
{
ForcedImplementations = useAlternative ?
[ typeof(MyAlternativeOperation) ] :
[ typeof(MyOperation) ]
};
});
// OperationConfig will be used to resolve the selected implementations at runtime
services.AddPipelineCoordinator<MyPipelineCoordinator, MyApplicationContext>(typeof(Startup).Assembly);
...
}
...
}
|
With the setup above, the factory function will be called each time an instance of the Pipeline Coordinator is created.
This will allow you to execute logic to determine which implementation of IMyOperation to use at runtime.