Skip to content

Commit 2274253

Browse files
Improve saga restriction documentation (#7176)
* Update Saga with note on persister specific restrictions to saga name length * Update index.md * Create index_limitationnote_core_[1,).partial.md * Update and rename index_limitationnote_core_[1,).partial.md to index_limitationnote_core_[1,].partial.md * Update index.md * Update index.md * Update index.md * Update index_limitationnote_core_[1,].partial.md * Update index.md * Delete nservicebus/sagas/index_limitationnote_core_[1,].partial.md * Update index.md * Added inline conditional so that the snippet is rendered only for version 8 and above. Version 7 does not show it on the docs * Update index.md * Update index_disable-shared-state-check_core_[8,].partial.md * Remove partial * Version * Sigh * Revert "Remove partial" This reverts commit 8e81464. * Fix --------- Co-authored-by: Daniel Marbach <[email protected]> Co-authored-by: Daniel Marbach <[email protected]>
1 parent f4462b4 commit 2274253

File tree

2 files changed

+21
-33
lines changed

2 files changed

+21
-33
lines changed

nservicebus/sagas/index.md

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Sagas
33
summary: Maintain statefulness in distributed systems with the saga pattern and NServiceBus' event-driven architecture with built-in fault-tolerance and scalability.
44
component: Core
5-
reviewed: 2024-09-22
5+
reviewed: 2025-06-24
66
redirects:
77
- nservicebus/sagas-in-nservicebus
88
related:
@@ -39,10 +39,11 @@ snippet: simple-saga-data
3939

4040
### Avoid sharing types between sagas
4141

42-
Saga data types should not be shared across different sagas. Sharing types can result in persisters physically sharing the same storage structure which should be avoided.
42+
Saga data types should not be shared across different sagas. Sharing types can result in persisters physically sharing the same storage structure, which should be avoided.
4343

44-
> [!WARNING]
45-
> Sharing property types should also be avoided. Depending on the persister implementation, sharing property types can result in storage structure being shared between endpoints.
44+
Sharing complex property types should also be avoided. Depending on the persister implementation, sharing property types can result in the storage structure being shared between endpoints.
45+
46+
NServiceBus will perform a check at startup to ensure that saga data types are not shared across sagas. An exception will be thrown at startup if any shared root types are found. Complex types in properties that are shared between sagas are not included in this check.
4647

4748
partial: disable-shared-state-check
4849

@@ -55,31 +56,23 @@ The important part of a long-running process is its behavior. Just like regular
5556

5657
## Starting a saga
5758

58-
Since a saga manages the state of a long-running process, under which conditions should a new saga be created? Sagas are, in essence, a message driven state machine. The trigger to start this state machine is the arrival of one or more specified message types. In the previous example, a new saga is started every time a message of type `StartOrder` arrives. This is declared by adding `IAmStartedByMessages<StartOrder>` to the saga.
59+
Since a saga manages the state of a long-running process, under what conditions should a new saga be created? Sagas are, in essence, a message-driven state machine. The trigger to start this state machine is the arrival of one or more specified message types. In the previous example, a new saga is started every time a message of type `StartOrder` arrives. This is declared by adding `IAmStartedByMessages<StartOrder>` to the saga.
5960

6061
> [!NOTE]
6162
> `IHandleMessages<StartOrder>` is redundant since `IAmStartedByMessages<StartOrder>` already implies that.
6263
63-
This interface tells NServiceBus that the saga not only handles `StartOrder`, but that when that type of message arrives, a new instance of this saga should be created to handle it, if there isn't already an existing saga that correlates to the message. As a convenience, in NServiceBus version 6 and above, the message will set its mapped correlation property on the created saga data. In essence the semantics of `IAmStartedByMessages` is:
64-
65-
> Create a new instance if an existing one can't be found
66-
67-
68-
> [!NOTE]
69-
> NServiceBus requires each saga to have at least one message that is able to start it.
70-
64+
This interface tells NServiceBus that the saga not only handles `StartOrder`, but also that when a message of that type arrives, a new instance of this saga should be created to handle it, if there isn't already an existing saga that correlates to the message. As a convenience, in NServiceBus version 6 and above, the message will set its mapped correlation property on the created saga data. In essence, the semantics of `IAmStartedByMessages` is:
7165

72-
### Dealing with out of order delivery
66+
> Create a new instance of the saga if an existing instance cannot be found
7367
74-
> [!NOTE]
75-
> Always assume that messages can be delivered out of order, e.g. due to error recovery, network latency, or concurrent message processing.
68+
### Dealing with out-of-order delivery
7669

77-
Sagas not designed to handle the arrival of messages out of order can result in some messages being discarded. In the previous example, this could happen if a `CompleteOrder` message is received before the `StartOrder` message has had a chance to create the saga.
70+
Messages can be delivered out of order, e.g. due to error recovery, network latency, or concurrent message processing, and sagas must be designed to handle the arrival of out-of-order messages. Sagas not designed to handle the arrival of messages out of order can result in some messages being discarded. In the previous example, this could happen if a `CompleteOrder` message is received before the `StartOrder` message has had a chance to create the saga.
7871

7972
To ensure messages are not discarded when they arrive out of order:
8073

81-
- Implement multiple `IAmStartedBy<T>` interfaces for any message type that assumes the saga instance should already exist
82-
- Override the saga not found behavior and throw an exception using `IHandleSagaNotFound` and rely on NServiceBus recoverability capability to retry messages and resolve out of order issues.
74+
- Implement multiple `IAmStartedByMessages<T>` interfaces for any message type that assumes the saga instance should already exist
75+
- Override the saga not found behavior and throw an exception using `IHandleSagaNotFound` and rely on NServiceBus recoverability capability to retry messages to resolve out-of-order issues.
8376

8477
#### Multiple message types starting a saga
8578

@@ -91,7 +84,7 @@ When messages arrive in reverse order, the handler for the `CompleteOrder` messa
9184

9285
#### Relying on recoverability
9386

94-
In most scenarios, an acceptable solution to deal with out of order message delivery is to throw an exception when the saga instance does not exist. The message will be automatically retried, which may resolve the issue, or it will end up in the error queue, where it can be manually retried.
87+
In most scenarios, an acceptable solution to deal with out-of-order message delivery is to throw an exception when the saga instance does not exist. The message will be automatically retried, which may resolve the issue; otherwise, it will be placed in the error queue, where it can be manually retried.
9588

9689
To override the default saga not found behavior [implement `IHandleSagaNotFound` and throw an exception](saga-not-found.md).
9790

@@ -104,24 +97,23 @@ Correlation is needed in order to find existing saga instances based on data on
10497
10598
## Discarding messages when saga is not found
10699

107-
If a saga handles a message, but no related saga instance is found, then that message is discarded by default. Typically that happens when the saga has been already completed when the messages arrives and discarding the message is correct. If a different behavior is expected for specific scenarios, the default behavior [can be modified](saga-not-found.md).
100+
If a saga handles a message but no related saga instance is found, the message is discarded by default. Typically, this happens when the saga has already been completed by the time a message arrives and discarding the message is correct. If a different behavior is expected for specific scenarios, the default behavior [can be modified](saga-not-found.md).
108101

109102
## Ending a saga
110103

111-
When a saga instance is no longer needed it can be completed using the `MarkAsComplete()` API. This tells the saga infrastructure that the instance is no longer needed and can be cleaned up.
104+
When a saga instance is no longer needed, it can be completed using the `MarkAsComplete()` API. This tells the saga infrastructure that the instance is no longer needed and can be cleaned up.
112105

113-
> [!NOTE]
114-
> Instance cleanup is implemented differently by the various saga persisters and is not guaranteed to be immediate.
106+
Instance cleanup is implemented differently by the various saga persisters and is not guaranteed to be immediate.
115107

116108
### Outstanding timeouts
117109

118110
Outstanding timeouts requested by the saga instance will be discarded when they expire without triggering the [`IHandleSagaNotFound` API](saga-not-found.md)
119111

120-
### Messages arriving after saga has been completed
112+
### Messages arriving after a saga has been completed
121113

122114
Messages that [are allowed to start a new saga instance](#starting-a-saga) will cause a new instance with the same correlation id to be created.
123115

124-
Messages handled by the saga(`IHandleMessages<T>`), arriving after the saga has completed, will be passed to the [`IHandleSagaNotFound` API](saga-not-found.md).
116+
Messages handled by the saga (`IHandleMessages<T>`) that arrive after the saga has completed will be passed to the [`IHandleSagaNotFound` API](saga-not-found.md).
125117

126118
### Consistency considerations
127119

@@ -147,12 +139,14 @@ snippet: saga-with-reply
147139

148140
This is one of the methods on the saga base class that would be very difficult to implement without tying the saga code to low-level parts of the NServiceBus infrastructure.
149141

150-
## Configuring saga persistence
142+
## Saga persistence
151143

152144
Make sure to configure appropriate [saga persistence](/persistence/).
153145

154146
snippet: saga-configure
155147

148+
The choice of persistence can impact the design of saga data, for e.g. the length of the name of the saga class, virtual properties in saga etc. While NServiceBus persister tries to abstract things away, sometimes the limitations of the specific implementations can have an impact.
149+
156150
## Sagas and automatic subscriptions
157151

158152
The auto subscription feature applies to sagas as well as the regular message handlers.
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
2-
NServiceBus will perform a check at startup to ensure that saga data types are not shared across sagas. An exception will be thrown at startup if any shared root types are found.
3-
4-
> [!NOTE]
5-
> Types used in properties that are shared between sagas are not included. Depending on the persister, sharing types between saga root types can result in shared storage schema which should be avoided.
6-
71
The startup check can be disabled by turning off the best practice validation:
82

93
snippet: disable-shared-state-validation

0 commit comments

Comments
 (0)