-
Notifications
You must be signed in to change notification settings - Fork 16
Add .NET SDK support to temporal-developer skill #39
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
24 commits
Select commit
Hold shift + click to select a range
5243c4a
Add .NET reference files for temporal-developer skill
donald-pinckney 6f5e7dc
Fix .NET alignment issues from self-review
donald-pinckney d338213
Fix .NET correctness issues from verification pass
donald-pinckney d80bd35
Update supported language references to include .NET
donald-pinckney 0114238
Edits to advanced features
donald-pinckney 2ee9c02
edits to determinism protection, and move the .editorconfig section
donald-pinckney 3709a85
missed one
donald-pinckney b41ecc6
edit determinism.md
donald-pinckney 7ae7afb
edit error-handling.md
donald-pinckney 4f19f25
edit gotchas.md
donald-pinckney c51d32b
edit patterns.md
donald-pinckney 3aa4ce7
edit versioning.md
donald-pinckney 2a38c83
edit observability.md
donald-pinckney 93388e5
fix metrics
donald-pinckney 54ce199
self-review round 1
donald-pinckney b4c958e
minor correctness fixed
donald-pinckney 9be5c85
Merge branch 'main' into add-dotnet
donald-pinckney 476f53b
Update references/dotnet/patterns.md
donald-pinckney 58bbf52
address comments, clarify reference to earlier code snippet
donald-pinckney 467fd27
clarify that operations are forbidden IN WORKFLOWS
donald-pinckney 014eb91
cleanup workflow cancellation handling example
donald-pinckney 76aa90a
add task token retrieval comment
donald-pinckney 744cf8d
update .net requirements
donald-pinckney 58f8972
Fix propagation of workflow cancellation
donald-pinckney 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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| # .NET SDK Advanced Features | ||
|
|
||
| ## Schedules | ||
|
|
||
| Create recurring workflow executions. | ||
|
|
||
| ```csharp | ||
| using Temporalio.Client.Schedules; | ||
|
|
||
| var scheduleId = "daily-report"; | ||
| await client.CreateScheduleAsync( | ||
| scheduleId, | ||
| new Schedule( | ||
| Action: ScheduleActionStartWorkflow.Create( | ||
| (DailyReportWorkflow wf) => wf.RunAsync(), | ||
| new(id: "daily-report", taskQueue: "reports")), | ||
| Spec: new ScheduleSpec | ||
| { | ||
| Intervals = new List<ScheduleIntervalSpec> | ||
| { | ||
| new(Every: TimeSpan.FromDays(1)), | ||
| }, | ||
| })); | ||
|
|
||
| // Manage schedules | ||
| var handle = client.GetScheduleHandle(scheduleId); | ||
| await handle.PauseAsync("Maintenance window"); | ||
| await handle.UnpauseAsync(); | ||
| await handle.TriggerAsync(); // Run immediately | ||
| await handle.DeleteAsync(); | ||
| ``` | ||
|
|
||
| ## Async Activity Completion | ||
|
|
||
| For activities that complete asynchronously (e.g., human tasks, external callbacks). | ||
| If you configure a `HeartbeatTimeout` on this activity, the external completer is responsible for sending heartbeats via the async handle. | ||
| If you do NOT set a `HeartbeatTimeout`, no heartbeats are required. | ||
|
|
||
| **Note:** If the external system that completes the asynchronous action can reliably be trusted to do the task and Signal back with the result, and it doesn't need to Heartbeat or receive Cancellation, then consider using **signals** instead. | ||
|
|
||
| ```csharp | ||
| using Temporalio.Activities; | ||
| using Temporalio.Client; | ||
|
|
||
| [Activity] | ||
| public async Task RequestApprovalAsync(string requestId) | ||
| { | ||
| var taskToken = ActivityExecutionContext.Current.Info.TaskToken; | ||
|
|
||
| // Store task token for later completion (e.g., in database) | ||
| await StoreTaskTokenAsync(requestId, taskToken); | ||
|
|
||
| // Mark this activity as waiting for external completion | ||
| throw new CompleteAsyncException(); | ||
| } | ||
|
|
||
| // Later, complete the activity from another process | ||
| public async Task CompleteApprovalAsync(string requestId, bool approved) | ||
| { | ||
| var client = await TemporalClient.ConnectAsync(new("localhost:7233")); | ||
| // Retrieve the task token from external storage (e.g., database) | ||
| var taskToken = await GetTaskTokenAsync(requestId); | ||
|
|
||
| var handle = client.GetAsyncActivityHandle(taskToken); | ||
|
|
||
| // Optional: if a HeartbeatTimeout was set, you can periodically: | ||
| // await handle.HeartbeatAsync(progressDetails); | ||
|
|
||
| if (approved) | ||
| await handle.CompleteAsync("approved"); | ||
| else | ||
| // You can also fail or report cancellation via the handle | ||
| await handle.FailAsync(new ApplicationFailureException("Rejected")); | ||
| } | ||
| ``` | ||
|
|
||
| ## Worker Tuning | ||
|
|
||
| Configure worker performance settings. | ||
|
|
||
| ```csharp | ||
| var worker = new TemporalWorker( | ||
| client, | ||
| new TemporalWorkerOptions("my-task-queue") | ||
| { | ||
| // Workflow task concurrency | ||
| MaxConcurrentWorkflowTasks = 100, | ||
| // Activity task concurrency | ||
| MaxConcurrentActivities = 100, | ||
| // Graceful shutdown timeout | ||
| GracefulShutdownTimeout = TimeSpan.FromSeconds(30), | ||
| } | ||
| .AddWorkflow<MyWorkflow>() | ||
| .AddAllActivities(new MyActivities())); | ||
| ``` | ||
|
|
||
| ## Workflow Init Attribute | ||
|
|
||
| Use `[WorkflowInit]` on a constructor to run initialization code when a workflow is first created. | ||
|
|
||
| **Purpose:** Execute some setup code before signal/update happens or run is invoked. | ||
|
|
||
| ```csharp | ||
| [Workflow] | ||
| public class MyWorkflow | ||
| { | ||
| private readonly string _initialValue; | ||
| private readonly List<string> _items = new(); | ||
|
|
||
| [WorkflowInit] | ||
| public MyWorkflow(string initialValue) | ||
| { | ||
| _initialValue = initialValue; | ||
| } | ||
|
|
||
| [WorkflowRun] | ||
| public async Task<string> RunAsync(string initialValue) | ||
| { | ||
| // _initialValue and _items are already initialized | ||
| return _initialValue; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Constructor and `[WorkflowRun]` method must have the same parameters with the same types. You cannot make blocking calls (activities, sleeps, etc.) from the constructor. | ||
|
|
||
| ## Workflow Failure Exception Types | ||
|
|
||
| Control which exceptions cause workflow failures vs workflow task retries. | ||
|
|
||
| **Default behavior:** Only `ApplicationFailureException` fails a workflow. All other exceptions retry the workflow task forever (treated as bugs to fix with a code deployment). | ||
|
donald-pinckney marked this conversation as resolved.
|
||
|
|
||
| **Tip for testing:** Set `WorkflowFailureExceptionTypes` to include `Exception` so any unhandled exception fails the workflow immediately rather than retrying the workflow task forever. This surfaces bugs faster. | ||
|
|
||
| ### Worker-Level Configuration | ||
|
|
||
| ```csharp | ||
| var worker = new TemporalWorker( | ||
| client, | ||
| new TemporalWorkerOptions("my-task-queue") | ||
| { | ||
| // These exception types will fail the workflow execution (not just the task) | ||
| WorkflowFailureExceptionTypes = new[] { typeof(ArgumentException), typeof(InvalidOperationException) }, | ||
| } | ||
| .AddWorkflow<MyWorkflow>() | ||
| .AddAllActivities(new MyActivities())); | ||
| ``` | ||
|
|
||
| ## Dependency Injection | ||
|
|
||
| The .NET SDK supports dependency injection via the `Temporalio.Extensions.Hosting` package, which integrates with .NET's generic host. | ||
|
|
||
| ### Worker as Generic Host | ||
|
|
||
| ```csharp | ||
| using Temporalio.Extensions.Hosting; | ||
|
|
||
| public class Program | ||
| { | ||
| public static async Task Main(string[] args) | ||
| { | ||
| var host = Host.CreateDefaultBuilder(args) | ||
| .ConfigureServices(ctx => | ||
| ctx. | ||
| AddScoped<IOrderRepository, OrderRepository>(). | ||
| AddHostedTemporalWorker( | ||
| clientTargetHost: "localhost:7233", | ||
| clientNamespace: "default", | ||
| taskQueue: "my-task-queue"). | ||
| AddScopedActivities<MyActivities>(). | ||
| AddWorkflow<MyWorkflow>()) | ||
| .Build(); | ||
| await host.RunAsync(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Activity Dependency Injection | ||
|
|
||
| As shown in the host setup above, activities can be registered with `AddScopedActivities<T>()`, `AddSingletonActivities<T>()`, or `AddTransientActivities<T>()`. Activities registered this way are created via DI, allowing constructor injection: | ||
|
|
||
| ```csharp | ||
| public class MyActivities | ||
| { | ||
| private readonly ILogger<MyActivities> _logger; | ||
| private readonly IOrderRepository _repository; | ||
|
|
||
| public MyActivities(ILogger<MyActivities> logger, IOrderRepository repository) | ||
| { | ||
| _logger = logger; | ||
| _repository = repository; | ||
| } | ||
|
|
||
| [Activity] | ||
| public async Task<Order> GetOrderAsync(string orderId) | ||
| { | ||
| _logger.LogInformation("Fetching order {OrderId}", orderId); | ||
| return await _repository.GetAsync(orderId); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Note:** Dependency injection is NOT available in workflows — workflows must be self-contained for determinism. | ||
Oops, something went wrong.
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.