Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
drusellers committed May 8, 2024
1 parent e5b2bed commit a0fd4b4
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 17 deletions.
71 changes: 54 additions & 17 deletions doc/content/3.documentation/1.concepts/7.routing-slip.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ In addition to the basic routing slip pattern, MassTransit also supports [compen

In MassTransit Courier, an *Activity* refers to a processing step that can be added to a routing slip.

To create an activity, create a class that implements the *IActivity* interface.
### Compensating

To create an activity, create a class that implements the `IActivity` interface for activities that need to have compensation actions or `IExecuteActivity` for those that don't.

```csharp
public class DownloadImageActivity :
Expand All @@ -23,29 +25,40 @@ public class DownloadImageActivity :
}
```

The *IActivity* interface is generic with two arguments. The first parameter specifies the activity’s argument type and the second parameter specifies the activity’s log type. In the example shown above, *DownloadImageArguments* is the argument type and *DownloadImageLog* is the log type. Both parameters may be interface, class or record types. Where the type is a class or a record, the proper accessors should be specified (i.e. `{ get; set; }` or `{ get; init; }`).
The `IActivity` interface is generic with two arguments. The first parameter specifies the activity’s argument type and the second parameter specifies the activity’s log type. In the example shown 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; }`).

#### Execute Activities

An *Execute Activity* is an activity that only executes and does not support compensation. As such, the declaration of a log type is not required.
### Non-Compensating

```csharp
public class ValidateImageActivity :
IExecuteActivity<ValidateImageArguments>
public class DownloadImageActivity :
IExecuteActivity<DownloadImageArguments>
{
Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> context);
}
```

### Implementing
An *Execute Activity* is an activity that only executes and does not support compensation. As such, the declaration of a log type is not required.

An activity must implement two interface methods, *Execute* and *Compensate*. The *Execute* method is called while the routing slip is executing activities and the *Compensate* method is called when a routing slip faults and needs to be compensated.
## Implementing

| Verb | Description |
|:--------|:-----|
| Execute | The action the activity should take |
| Compensate | The action the activity should take if downstream Activities `Fault` |

### Execute

Both the `IActivity`and `IExecuteActivity` require you to implement the *Execute* method.

The *Execute* method is called while the routing slip is executing activities


### Compensate

Only the `IActivity` requires you to implement the *Compensate* method.

The *Compensate* method is called when a routing slip faults and needs to be compensated.

When the *Execute* method is called, an *execution* argument is passed containing the activity arguments, the routing slip *TrackingNumber*, and methods to mark the activity as completed or faulted. The actual routing slip message, as well as any details of the underlying infrastructure, are excluded from the *execution* argument to prevent coupling between the activity and the implementation. An example *Execute* method is shown below.

```csharp
Expand All @@ -60,20 +73,20 @@ async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execu
return execution.Completed<DownloadImageLog>(new {ImageSavePath = imageSavePath});
}
```
### Completing

Once activity processing is complete, the activity returns an *ExecutionResult* to the host. If the activity executes successfully, the activity can elect to store compensation data in an activity log which is passed to the *Completed* method on the *execution* argument. If the activity chooses not to store any compensation data, the activity log argument is not required. In addition to compensation data, the activity can add or modify variables stored in the routing slip for use by subsequent activities.
## Execution Results

#### Execution Results
Once activity processing is complete, the activity returns an *ExecutionResult* to the host. If the activity executes successfully, the activity can elect to store compensation data in an activity log which is passed to the *Completed* method on the *execution* argument. If the activity chooses not to store any compensation data, the activity log argument is not required. In addition to compensation data, the activity can add or modify variables stored in the routing slip for use by subsequent activities.

| Result | Description |
|:--|:--|
| Complete | Returning `Complete` indicates a successful activity |
| Fault | Returning `Faulted` indicates an unsuccessful activity |
| Terminate | Returning `Terminate` indicates the routing slip should stop, successfully. |

### Completing

**Complete (Success)**
**Complete**

```csharp
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution)
Expand All @@ -89,7 +102,7 @@ async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execu
}
```

**Complete (Success) - With Compensation Log**
**Complete - With Compensation Log**

```csharp
async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execution)
Expand All @@ -103,9 +116,33 @@ async Task<ExecutionResult> Execute(ExecuteContext<DownloadImageArguments> execu
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

```csharp
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

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.
Expand Down Expand Up @@ -197,9 +234,9 @@ Task<CompensationResult> Compensate(CompensateContext<DownloadImageLog> compensa

Using the activity log data, the activity compensates by removing the downloaded image from the work directory. Once the activity has compensated the previous execution, it returns a *CompensationResult* by calling the *Compensated* method. If the compensating actions could not be performed (either via logic or an exception) and the inability to compensate results in a failure state, the *Failed* method can be used instead, optionally specifying an *Exception*.

## Using a Routing Slip
## Building a Routing Slip

A routing slip specifies a sequence of processing steps called *activities* that are combined into a single transaction. As each activity completes, the routing slip is forwarded to the next activity in the itinerary. When all activities have completed, the routing slip is completed and the transaction is complete.
A routing slip specifies a sequence of processing steps called *activities* that are combined into a single itinerary. As each activity completes, the routing slip is forwarded to the next activity in the itinerary. When all activities have completed, the routing slip is completed and the transaction is complete.

A key advantage to using a routing slip is it allows the activities to vary for each transaction. Depending upon the requirements for each transaction, which may differ based on things like payment methods, billing or shipping address, or customer preference ratings, the routing slip builder can selectively add activities to the routing slip. This dynamic behavior is in contrast to a more explicit behavior defined by a state machine or sequential workflow that is statically defined (either through the use of code, a DSL, or something like Windows Workflow).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
title: Routing Slips
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: Monitor via Saga
---

# How to Monitor a Routing Slip with a Saga

Because Routing Slips carry their state on the wire, it's not possible to query the state of a currently executing routing slip. Use this pattern when your solution requires tracking the progress of a Routing Slip that's in-flight.

## Create a Normal Routing Slip

```csharp
var builder = new RoutingSlipBuilder(NewId.NextGuid());

// add your activities as normal
builder.AddActivity("DownloadImage", new Uri("rabbitmq://localhost/execute_downloadimage"),
new
{
ImageUri = new Uri("http://images.google.com/someImage.jpg")
});
builder.AddActivity("FilterImage", new Uri("rabbitmq://localhost/execute_filterimage"));
builder.AddVariable("WorkPath", @"\dfs\work");

var routingSlip = builder.Build();
```

## Build the Saga

```csharp
public class MonitorRoutingSlip :
MassTransitStateMachine<MonitorState>
{
public MonitorRoutingSlip()
{
InstanceState(x => x.CurrentState);
}
}
```

## Add Subscription to Routing Slip

```csharp
var builder = new RoutingSlipBuilder(NewId.NextGuid());

// ... add activities and variables as normal
// ⭐️ KEY ITEM
builder.AddSubscription(new Uri("<the saga queue>"), RoutingSlipEvents.All);

var routingSlip = builder.Build();
```

0 comments on commit a0fd4b4

Please sign in to comment.