Create a routing slip activity
A routing slip activity is a class that implements a processing step that can be added to a routing slip. An activity can be either a compensating activity or a non-compensating activity.
Create a compensating activity
Section titled “Create a compensating activity”To create an activity, create a class that implements the IActivity<TArguments, TLog> interface for activities that support compensation or
IExecuteActivity<TArguments> for those that don’t need compensation.
public class DownloadImageActivity : IActivity<DownloadImageArguments, DownloadImageLog>{ Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> context); Task<CompensationResult> Compensate(CompensateContext<DownloadImageLog> context);}The IActivity<TArguments, TLog> interface has two generic arguments. The first specifies the activity’s argument type and the second specifies the activity’s
log type. In the example above, DownloadImageArguments is the argument type and DownloadImageLog is the log type. The type parameters may be an interface,
class, or record type. Where the type is a class or a record, the proper accessors should be specified (i.e. { get; set; } or { get; init; }).
Create a non-compensating activity
Section titled “Create a non-compensating activity”public class DownloadImageActivity : IExecuteActivity<DownloadImageArguments>{ Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> context);}Implement the activity
Section titled “Implement the activity”| Verb | Description |
|---|---|
| Execute | The primary action that the activity performs as part of the workflow. |
| Compensate | The action the activity must take to undo or reverse its effects if any subsequent activities fail. |
Implement the Execute method
Section titled “Implement the Execute method”Both IActivityand IExecuteActivity require you to implement the Execute method. Execute is called while the routing slip is executing activities.
When Execute is called, the ExecuteContext argument contains the activity arguments, the routing slip’s TrackingNumber, and methods to complete or fault
the activity. The actual routing slip message, as well as any details of the underlying infrastructure, are excluded to prevent coupling between the activity
and the implementation. An example Execute method is shown below.
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
return execution.Completed<DownloadImageLog>(new {ImageSavePath = imageSavePath});}Execution Results
Section titled “Execution Results”After an activity finishes processing, it returns an ExecutionResult to the host. If the activity completes successfully, it can choose to store compensation
data in an activity log, which is passed to the Completed method on the ExecuteContext argument. If no compensation data is needed, the activity log is
optional. In addition to compensation data, the activity can also add or modify variables stored in the routing slip for use by subsequent activities.
| Result | Description |
|---|---|
| Complete | Indicates the activity completed successfully. |
| Fault | Indicates the activity failed. |
| Terminate | Indicates the routing slip should stop, but the process is considered complete. |
Completing
Section titled “Completing”Complete
Section titled “Complete”async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
// success with no compensation log return execution.Completed();}Complete with compensation log
Section titled “Complete with compensation log”async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
return execution.Completed<DownloadImageLog>(new {ImageSavePath = imageSavePath});}In the example above, the activity specifies the DownloadImageLog interface and initializes the log using an anonymous object. The object is then passed to the Completed method for storage in the routing slip before sending the routing slip to the next activity.
Revise itinerary
Section titled “Revise itinerary”An activity has complete control over the routing slip and can revise the itinerary to include additional activities.
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
return execution.ReviseItinerary(builder => { // add activity at the beginning of the current itinerary builder.AddActivity("Deviation", new Uri($"exchange:{optionalAddress}"));
// maintain the existing activities builder.AddActivitiesFromSourceItinerary();
// add activity at the end of the current itinerary builder.AddActivity("Deviation", new Uri($"exchange:{optionalAddress}")); });}Faulting
Section titled “Faulting”By default, if an activity throws an exception, it will be faulted and a RoutingSlipFaulted event will be published (unless a subscription changes the
rules). An activity can also return Faulted rather than throwing an exception.
Throwing an exception
Section titled “Throwing an exception”async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
// will throw an exception var result = 100 / 0;
return execution.Completed();}In the example above, the activity will throw an exception which will result in a Fault which will include data about the exception.
Explicitly
Section titled “Explicitly”async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ DownloadImageArguments args = execution.Arguments; string imageSavePath = Path.Combine(args.WorkPath, execution.TrackingNumber.ToString());
await _httpClient.GetAndSave(args.ImageUri, imageSavePath);
return execution.Faulted();}In the example above, the activity can look at the data and then explicitly return a Faulted result.
Terminating
Section titled “Terminating”In some situations, it may make sense to terminate the routing slip without executing any of the subsequent activities in the itinerary. This might be due to a business rule, in which the routing slip shouldn’t be faulted, but needs to end immediately.
To terminate a routing slip, call Terminate as shown.
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ // regular termination return execution.Terminate();}An optional reason can also be specified.
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution){ // terminate and include additional variables in the event return execution.Terminate(new { Reason = "Not a good time, dude."});}Implement the Compensate method
Section titled “Implement the Compensate method”Only IActivity requires you to implement the Compensate method. Compensate is called when a subsequent activity has faulted so that the activity can undo
or reverse its effects.
When an activity fails, the Compensate method is called for previously executed activities in the routing slip that stored compensation data. If an activity does not store any compensation data, the Compensate method is never called.
return context.Completed(new LogModel { SomeData = "abc"})The compensation method for the example above is shown below.
Task<CompensationResult> Compensate(CompensateContext<DownloadImageLog> compensation){ DownloadImageLog log = compensation.Log; File.Delete(log.ImageSavePath);
return compensation.Compensated();}Using the activity log data, the activity compensates by removing the downloaded image from the work directory. Once the activity has successfully compensated for the previous execution, it returns a CompensationResult by calling the Compensated method. If the compensation cannot be completed (due to logic issues or exceptions) and this results in a failure, the Failed method should be used, optionally providing an Exception.