-
Notifications
You must be signed in to change notification settings - Fork 6
New overload for sub-saga invocation #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| using System.Linq.Expressions; | ||
| using Dapr.Actors; | ||
| using Dapr.Actors; | ||
|
|
||
| namespace Sagaway.Callback.Router; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace Sagaway.Hosts.DaprActorHost; | ||
|
|
||
| /// <summary> | ||
| /// Represents the options for configuring a sub-saga invocation call. | ||
| /// </summary> | ||
| public record CallSubSagaOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets the name of the callback method in the main saga that should be invoked | ||
| /// once the sub-saga completes its operation. | ||
| /// </summary> | ||
| public required string CallbackMethodName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets any additional metadata specific to the sub-saga invocation, | ||
| /// which will be included in the Dapr binding call context. | ||
| /// </summary> | ||
| public string CustomSagawayMetadata { get; init; } = string.Empty; | ||
|
|
||
| /// <summary> | ||
| /// Gets the custom binding name to use for the Dapr binding operation. | ||
| /// If not specified, the default binding name is used. | ||
| /// </summary> | ||
| public string UseBindingName { get; init; } = string.Empty; | ||
|
|
||
| /// <summary> | ||
| /// Gets a dictionary of additional metadata to include in the binding context. | ||
| /// These values will override any defaults if the same keys are present. | ||
| /// </summary> | ||
| // ReSharper disable once CollectionNeverUpdated.Global | ||
| public Dictionary<string, string>? CustomBindingMetadata { get; init; } = []; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -529,6 +529,107 @@ public IDictionary<string, string> CaptureCallbackContext(string callbackMethodN | |
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Invokes a method on a sub-saga actor using an expression to capture the method call. | ||
| /// Extracts the method name and parameters from the provided expression and sends them via Dapr binding. | ||
| /// Includes support for custom callback contexts, metadata, and binding options. | ||
| /// </summary> | ||
| /// <typeparam name="TSubSaga">The type of the interface of the sub-saga actor.</typeparam> | ||
| /// <param name="methodExpression">An expression that represents the method to invoke on the sub-saga actor.</param> | ||
| /// <param name="actorTypeName">The type name of the actor as registered in the Dapr actor runtime.</param> | ||
| /// <param name="newActorId">The unique identifier of the sub-saga actor.</param> | ||
| /// <param name="options"> | ||
| /// A set of additional options for the sub-saga call, including: | ||
| /// <list type="bullet"> | ||
| /// <item><description>CallbackMethodName: The method to invoke in the main saga after the sub-saga completes.</description></item> | ||
| /// <item><description>CustomSagawayMetadata: Metadata to include in the Dapr binding call context.</description></item> | ||
| /// <item><description>CustomBindingMetadata: A dictionary of additional binding metadata, which will override default values if keys overlap.</description></item> | ||
| /// <item><description>UseBindingName: Specifies an alternate binding name, defaulting to the callback binding name if not set.</description></item> | ||
| /// </list> | ||
| /// </param> | ||
| /// <returns>A task representing the asynchronous operation of invoking the sub-saga actor.</returns> | ||
| /// <example> | ||
| /// Example usage: | ||
| /// <code> | ||
| /// await CallSubSagaAsync<ISubSaga>( | ||
| /// saga => saga.ExecuteAsync(param1, param2), | ||
| /// "SubSagaActor", | ||
| /// "SubSaga123", | ||
| /// new CallSubSagaOptions( | ||
| /// CallbackMethodName: "MainSagaCallback", | ||
| /// CustomSagawayMetadata: "SomeMetadata", | ||
| /// CustomBindingMetadata: new Dictionary<string, string> | ||
| /// { | ||
| /// { "key1", "value1" }, | ||
| /// { "key2", "value2" } | ||
| /// }, | ||
| /// UseBindingName: "customBindingName")); | ||
| /// </code> | ||
| /// </example> | ||
| protected async Task CallSubSagaAsync<TSubSaga>(Expression<Func<TSubSaga, Task>> methodExpression, string actorTypeName, | ||
| string newActorId, CallSubSagaOptions options) | ||
| where TSubSaga : ISagawayActor | ||
| { | ||
| _logger.LogInformation("Starting sub-saga with actor id {NewActorId} using method {CallbackMethodName}", newActorId, options.CallbackMethodName); | ||
|
|
||
| // Use the method name of StartSubSagaWithContextAsync to handle the sub-saga dispatch | ||
| var callbackContext = CaptureCallbackContext(options.CallbackMethodName); | ||
|
|
||
| // Extract method name and parameters from the expression | ||
| var methodCall = (MethodCallExpression)methodExpression.Body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. methodExpression.Body always have type MethodCallExpression? |
||
| var methodName = methodCall.Method.Name; | ||
| var arguments = methodCall.Arguments.Select(a => Expression.Lambda(a).Compile().DynamicInvoke()).ToArray(); | ||
|
|
||
| _logger.LogDebug("Extracted method {MethodName} with arguments for sub-saga", methodName); | ||
|
|
||
| // Prepare the SubSagaInvocationContext object | ||
| var invocationContext = new SubSagaInvocationContext | ||
| { | ||
| MethodName = methodName, // The target method to invoke in the sub-saga | ||
| CallbackContext = callbackContext, | ||
| ParametersJson = JsonSerializer.Serialize(arguments, GetJsonSerializerOptions()) | ||
| }; | ||
|
|
||
| var invokeDispatcherParameters = new Dictionary<string, string>() | ||
| { | ||
| ["x-sagaway-dapr-callback-method-name"] = nameof(ProcessASubSagaCallAsync), | ||
| ["x-sagaway-dapr-actor-id"] = newActorId, | ||
| ["x-sagaway-dapr-actor-type"] = actorTypeName, | ||
| ["x-sagaway-dapr-message-dispatch-time"] = DateTime.UtcNow.ToString("o"), // ISO 8601 format | ||
| ["x-sagaway-dapr-custom-metadata"] = options.CustomSagawayMetadata, | ||
| }; | ||
|
|
||
| LogDebugContext("Sub Saga call context", invokeDispatcherParameters); | ||
|
|
||
| if (options.CustomBindingMetadata is not null) | ||
| { | ||
| foreach (var (key, value) in options.CustomBindingMetadata) | ||
| { | ||
| invokeDispatcherParameters[key] = value; | ||
| } | ||
| } | ||
|
|
||
| _logger.LogInformation("Dispatching sub-saga invocation for method {MethodName}", methodName); | ||
|
|
||
| // Create a new DaprClient for the sub-saga invocation, so it will not use the preconfigured HttpClient with the default headers | ||
| var daprClientBuilder = new DaprClientBuilder(); | ||
| var subSagaDaprClient = daprClientBuilder.Build(); // No custom headers for sub-saga | ||
|
|
||
| var bindingName = string.IsNullOrWhiteSpace(options.UseBindingName) | ||
| ? GetCallbackBindingName() | ||
| : options.UseBindingName; | ||
|
|
||
| // Dispatch the sub-saga invocation with a single parameter (invocationContext) | ||
| await subSagaDaprClient.InvokeBindingAsync( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add try-catch and log for error or throw? |
||
| bindingName, | ||
| "create", // Binding operation | ||
| invocationContext, | ||
| invokeDispatcherParameters | ||
| ); | ||
|
|
||
| _logger.LogInformation("Sub-saga invocation dispatched successfully for method {MethodName}", methodName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Call or start a sub-saga by invoking a method on a sub-saga actor using an expression to capture the method call. | ||
| /// The method extracts the function name and parameters from the provided expression, and sends them via Dapr binding, | ||
|
|
@@ -548,7 +649,8 @@ public IDictionary<string, string> CaptureCallbackContext(string callbackMethodN | |
| /// </code> | ||
| /// This will invoke the "DoSomethingAsync" method on the sub-saga and allow for a callback to the "MainSagaCallback" method on completion. | ||
| /// </example> | ||
| protected async Task CallSubSagaAsync<TSubSaga>(Expression<Func<TSubSaga, Task>> methodExpression, string actorTypeName, | ||
| [Obsolete("Use overload with options instead")] | ||
| protected async Task CallSubSagaAsync<TSubSaga>(Expression<Func<TSubSaga, Task>> methodExpression, string actorTypeName, | ||
| string newActorId, string callbackMethodName = "", string customMetadata = "") | ||
| where TSubSaga : ISagawayActor | ||
| { | ||
|
|
||
1 change: 0 additions & 1 deletion
1
Sagaway.IntegrationTests/Sagaway.IntegrationTests.OrchestrationService/Actors/Argument.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...tegrationTests/Sagaway.IntegrationTests.TestSubSagaCommunicationService/IMainSagaActor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...ntegrationTests/Sagaway.IntegrationTests.TestSubSagaCommunicationService/ISubSagaActor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.