Documentation Home

Operation Dependencies

As your pipeline grows and you add more steps to the logic in the form of Operations and Async Operations you may encounter situations where new business requirements must be implemented.

Implementing new requirements in the form of new Operations will require you to place the execution of the newly developed Operation somewhere in the pipeline sequence. Sometimes the implementation of new requirements will require the moving of existing Operations up or down in the pipeline order. While this re-ordering of Operations is very easy when using this framework, it gives rise to the following errors.

  • Moving an existing Operation up in the pipeline may cause you to place it above another Operation that is supposed to be executed before it

  • Moving an existing Operation down in the pipeline may cause you to place it below another Operation that needs to be executed after it

  • A new Operation can be added to the pipeline above another Operation that needs to be executed before it

When one Operation expects another Operation to have been executed before it, then this constitutes an Operation-to-Operation dependency. Over time, it becomes difficult to ascertain how far up or down in the pipeline is safe to execute an Operation. When an Operation expects multiple Operations to have been executed before it, then it gets even more difficult to manage the order of Operations in the pipeline.

Operation Dependency Resolution

When creating an Operation, you must ascertain what other Operation(s) if any must have been executed before it. If one or more Operations must execute before the current Operation, then you should mark those dependencies by adding the Operation marker interface types to the Operation’s Dependencies collection in its constructor.

The code below illustrates this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using KnightMoves.Pipelines;
using KnightMoves.Pipelines.Interfaces;

namespace MyApplication.Operations
{
    // Marker Interface
    public interface IFilterCustomersOperations : IPipelineOperation<MyApplicationContext> { }

    public class FilterCustomersOperation : BasePipelineOperation<MyApplicationContext>, IFilterCustomersOperations
    {
        public override void Execute(MyApplicationContext context)
        {
            // In order for this to work, some other Operation must have been executed
            // to fetch the Customers and plant them in the Context.Customers collection.
            // If the Customers collection has not been initialized and/or populated, then
            // this code will break

            context.EmailCampaignCustomers = context.Customers.Where(c => c.Region == Regions.Midwest);
        }
    }
}

In order to safely place this Operation somewhere in the pipeline, you can mark its dependencies like so.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using KnightMoves.Pipelines;
using KnightMoves.Pipelines.Interfaces;

namespace MyApplication.Operations
{
    // Marker Interface
    public interface IFilterCustomersOperations : IPipelineOperation<MyApplicationContext> { }

    public class FilterCustomersOperation : BasePipelineOperation<MyApplicationContext>, IFilterCustomersOperations
    {
        public FilterCustomersOperation()
        {
            // Dependency added here
            Dependencies.Add(typeof(IFetchCustomersOperationAsync));
        }

        public override void Execute(MyApplicationContext context)
        {
            context.EmailCampaignCustomers = context.Customers.Where(c => c.Region == Regions.Midwest);
        }
    }
}

Here you are notifying the Pipeline Coordinator that this Operation cannot be placed above IFetchCustomersOperationAsync in the pipeline order. If this Operation is moved above IFetchCustomersOperationAsync, then the Pipeline Coordinator will throw an OperationDependencyNotExecutedException at runtime.

The unit test for the class that uses the Pipeline Coordinator should throw this exception or it will be thrown the very first time you run the application. In this way, you are guaranteed the safety of being notified that an Operation-to-Operation dependency was not satisfied and you will have to resolve the dependency by moving one of the Operations up or down to ensure dependent Operations are executed first.