In this article, we will look at the foundational pipeline block types that are used to construct pipelines within the Commerce Engine plugins and custom solutions.
Asynchronous vs Synchronous Pipeline Blocks
From XC 10.0, the PipelineBlock class has been split into AsyncPipelineBlock and SyncPipelineBlock classes for improved performance and stability, with the primary implementation difference being whether to call the pipeline block asynchronously or not during pipeline execution.
A notable change to the AsyncPipelineBlock is that it uses the RunAsync method instead of the Run method to enforce that the return type is a Task<TOutput>
and as an added reminder that the pipeline block is in fact asynchronously executed.
How Async and Sync Pipeline Blocks Work in the Same Pipeline
The initial questions I had when seeing the pipeline split were “how does this affect registering pipeline blocks to pipelines?” and “how does this affect the way in which pipelines are executed?”. The good news here is that is doesn’t affect either from an implementation standpoint.
From the registration perspective, both async and sync pipelines are registered as per XC 9.X registration mechanisms.
.AddPipeline<IMyPipeline, MyPipeline>(pipeline => pipeline .Add<MySyncPipelineBlock>() .Add<MyAsyncPipelineBlock>() .Add<MyAsyncConditionalPipelineBlock>() .Add<MyAsyncPolicyTriggerConditionalPipelineBlock>() .Add<MyConditionalPipelineBlock>() .Add<MyAsyncConditionalPipelineBlock>() )
From the async/sync execution perspective, async pipeline blocks are awaited before continuing on to the following pipeline block, which ensures the output of a pipeline block is the input of the following pipeline block.
Specialised Pipeline Blocks
You may have seen a few types of enhanced pipeline blocks in XC 9.X+, being ConditionalPipelineBlock and PolicyTriggerConditionalPipelineBlock, which may have been better suited to your custom implementations over adding conditional statements to the begining of your Run/RunAsync method.
Conditional Pipeline Blocks
As indicated by their names, the ConditionalPipelineBlock and AsyncConditionalPipelineBlock classes will be conditionally executed. This is achieved by defining the BlockCondition predicate to be evaluated to determine if the Run/RunAsync method should be called.
Where the evaluated BlockCondition returns false, an alternate method called ContinueTask is executed instead of Run/RunAsync and is intended to simply return the arg
or null
.
Returning arg
from ContinueTask considers the pipeline block as optional whereas returning null
may consider the pipeline block as mandatory depending on how this output is handled in subsequent pipeline blocks or the pipeline itself.
Warning: Using the ContinueTask method to run alternate code logic may be a sign of a bad code smell, such as breaking the separation of concerns rule. Having 2 separate pipeline blocks with inverted predicates is the recommended approach.
public class MyConditionalPipelineBlock : ConditionalPipelineBlock<MyArgument, MyArgument, CommercePipelineExecutionContext> { public MyConditionalPipelineBlock() { BlockCondition = obj => ((CommercePipelineExecutionContext)obj).CommerceContext.HasPolicy<MyCustomPolicy>(); } public override MyArgument Run(MyArgument arg, CommercePipelineExecutionContext context) { Condition.Requires(arg, nameof(arg)).IsNotNull(); // My code logic return arg; } public override MyArgument ContinueTask(MyArgument arg, CommercePipelineExecutionContext context) { return arg; } }
Policy Trigger Conditional Pipeline Blocks
Another more specialised form of conditional pipeline blocks are the PolicyTriggerConditionalPipelineBlock and AsyncPolicyTriggerConditionalPipelineBlock. These blocks actually have somewhat inverted logic to their name as you will need to specify the name of a policy in the ShouldNotRunPolicyTrigger property which will ensure the pipeline is not executed.
When the policy name is present in the request header’s PolicyKeys value the pipeline block will execute the ContinueTask method over the Run/RunAsync method (See Conditional Pipeline Blocks for more details).
A full set of existing policy keys and their usages can be found in Commerce Developer Reference: Policy keys. It is recommended that the Commerce Engine policy keys be avoided when creating custom policy trigger conditional pipeline blocks.
The BlockCondition predicate is already implemented to manage the conditional logic as mentioned above and the ContinueTask method will return the arg
as policy trigger conditional pipeline blocks should be considered optional and not mandatory.
public class MyPolicyTriggerConditionalPipelineBlock : PolicyTriggerConditionalPipelineBlock<MyArgument, MyArgument, CommercePipelineExecutionContext> { public override string ShouldNotRunPolicyTrigger => "IgnoreMyPipelineBlock"; public override MyArgument Run(MyArgument arg, CommercePipelineExecutionContext context) { Condition.Requires(arg, nameof(arg)).IsNotNull(); // My code logic return arg; } }