new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Temporal · Develop · all subjects

best-practices

226 notes in this subject, read out of this brain and free to use. This is page 1 of 4.

.NET SDK best practices topics

The .NET SDK documentation covers best practices in four main areas: error handling, testing, debugging, and converters and encryption for data handling.

.NET SDK official resources and community

Technical resources for the .NET SDK include: .NET API Documentation at https://dotnet.temporal.io/api/, .NET SDK Code Samples at https://github.com/temporalio/samples-dotnet, .NET SDK GitHub repository at https://github.com/temporalio/sdk-dotnet, and Temporal 101 in .NET Free Course at https://learn.temporal.io/courses/temporal_101/dotnet/. Community support is available through Temporal .NET Community Slack at https://temporalio.slack.com/archives/C012SHMPDDZ and .NET SDK Forum at https://community.temporal.io/tag/dotnet-sdk.

.NET SDK installation and quickstart

Detailed installation instructions for the .NET SDK are available in the Quickstart documentation. There is a walkthrough covering how to use Temporal primitives including Activities, Workflows, and Workers to build and run a Temporal application. Once local Temporal Service is set up, developers should start with Workflow basics, Activity basics, Activity execution start, and Worker process documentation.

.NET SDK documentation structure and key topics

The .NET SDK developer guide covers the following main areas: Workflows (including basics, child workflows, continue-as-new, cancellation, timeouts, message passing, schedules, timers, dynamic workflows, and versioning), Activities (including basics, execution, standalone activities, timeouts, asynchronous completion, dynamic activities, and benign exceptions), Workers (including worker processes and interceptors), Temporal Client, Temporal Nexus, Platform (observability and UI enrichment), and Best practices (error handling, testing, debugging, converters and encryption).

Getting and setting CurrentDetails in Workflow code

```csharp using Temporalio.Workflows; [Workflow] public class YourWorkflow { [WorkflowRun] public async Task<string> RunAsync(string input) { // Get the current details var currentDetails = Workflow.CurrentDetails; Workflow.Logger.LogInformation($"Current details: {currentDetails}"); // Set/update the current details Workflow.CurrentDetails = "Updated Workflow details with new status"; return "Workflow completed"; } } ``` This example shows how to read and update the current Workflow details dynamically during execution.

Creating a timer with Summary metadata

```csharp using Temporalio.Workflows; [Workflow] public class YourWorkflow { [WorkflowRun] public async Task<string> RunAsync(string input) { await Workflow.DelayWithOptionsAsync(new DelayOptions(TimeSpan.FromMinutes(5)) { Summary = "Waiting for payment confirmation" }); return "Timer completed"; } } ``` This example shows how to attach a Summary to a timer created with DelayWithOptionsAsync.

Workflow.CurrentDetails for dynamic UI enrichment

Inside a Workflow, you can get and set Workflow.CurrentDetails to display dynamic information that can be updated throughout the Workflow's lifetime. Unlike static summary and details set at start time, CurrentDetails can be modified during execution. It supports Markdown format (excluding images, HTML, and scripts) and can span multiple lines.

Starting a Workflow with static summary and details example

```csharp using Temporalio.Client; var client = await TemporalClient.ConnectAsync(new("localhost:7233")); var handle = await client.StartWorkflowAsync( (YourWorkflow wf) => wf.RunAsync("Workflow input"), new WorkflowOptions { Id = "your-Workflow-id", TaskQueue = "your-task-queue", StaticSummary = "Order processing for customer #12345", StaticDetails = "Processing premium order with expedited shipping" }); ``` This example demonstrates how to provide static summary and details when starting a Workflow to enrich its UI representation.

Timer Summary using DelayWithOptionsAsync

When creating a timer in a Workflow using Workflow.DelayWithOptionsAsync, you can set a Summary in DelayOptions. The Summary is a string limited to 200 bytes that provides context about the timer in the Temporal UI Timeline and Event History.

StaticSummary and StaticDetails in WorkflowOptions

When starting a Workflow using StartWorkflowAsync or ExecuteWorkflowAsync, you can provide StaticSummary and StaticDetails in WorkflowOptions. StaticSummary is a single-line description limited to 200 bytes that appears in the Workflow list view. StaticDetails can be multi-line and is limited to 20K bytes, appearing in the Workflow details view. Both support standard Markdown format excluding images, HTML, and scripts.

.NET SDK Workers documentation structure

The .NET SDK Workers documentation includes coverage of worker processes and interceptors as primary topics.

.NET SDK worker process documentation

The .NET SDK documentation provides guidance on implementing and running worker processes.

.NET SDK interceptors for workers

The .NET SDK documentation covers interceptors as a feature for workers, available at /develop/dotnet/workers/interceptors.

Workflow resilience and recovery

Temporal Workflows are resilient and can run for years even if underlying infrastructure fails. If an application crashes, Temporal automatically recreates the workflow's pre-failure state so execution can continue from where it left off.

Complete Worker code example in .NET SDK

var client = await TemporalClient.ConnectAsync(new("localhost:7233")); using var tokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (_, eventArgs) => { tokenSource.Cancel(); eventArgs.Cancel = true; }; var activities = new MyActivities(); using var worker = new TemporalWorker(client, new TemporalWorkerOptions("my-task-queue").AddActivity(activities.SayHello).AddWorkflow<SayHelloWorkflow>()); Console.WriteLine("Running worker"); try { await worker.ExecuteAsync(tokenSource.Token); } catch (OperationCanceledException) { Console.WriteLine("Worker cancelled"); }

Temporalio NuGet package installation

Install the Temporalio NuGet package in Workflow, Worker, and Client projects using: dotnet add [ProjectPath] package Temporalio. Alternatively, centralize the package using Directory.Packages.props and Directory.Build.props at the solution root.

Execute workflow from client in .NET SDK

Use client.ExecuteWorkflowAsync to start and wait for a workflow completion. Pass a lambda expression referencing the workflow method (e.g., (SayHelloWorkflow wf) => wf.RunAsync("Temporal")) and ExecuteWorkflowOptions with a unique id and task queue name matching the worker's task queue.

Project structure for Temporal .NET applications

A typical Temporal .NET application consists of three projects: a Workflow project (class library) containing workflow and activity definitions, a Worker project (console application) that runs the worker, and a Client project (console application) that starts workflow executions. The Worker and Client projects reference the Workflow project.

TemporalClient connection in .NET SDK

Connect to a Temporal Service using TemporalClient.ConnectAsync with connection options. Default: TemporalClient.ConnectAsync(new("localhost:7233")) connects to the default namespace at localhost on port 7233.

Workflow definition with .NET SDK

A Workflow in .NET is defined as a class decorated with the [Workflow] attribute containing a method decorated with the [WorkflowRun] attribute that returns Task<T>. The RunAsync method is marked as async and contains the workflow orchestration logic. Workflows orchestrate Activities and contain application logic. Example: [Workflow] public class SayHelloWorkflow { [WorkflowRun] public async Task<string> RunAsync(string name) { return await Workflow.ExecuteActivityAsync((MyActivities act) => act.SayHello(name), new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); } }

.NET SDK version requirement

The .NET SDK requires .NET 6.0 or later. Install the latest version of .NET from the official .NET download page.

Worker creation and setup in .NET SDK

Create a TemporalWorker by instantiating it with a TemporalClient and TemporalWorkerOptions specifying the task queue name. Register activities using .AddActivity() and workflows using .AddWorkflow<T>(). Execute the worker with await worker.ExecuteAsync(cancellationToken). A Worker polls a Task Queue for work, dequeues workflow or activity tasks, and executes them.

Complete client code example in .NET SDK

var client = await TemporalClient.ConnectAsync(new("localhost:7233")); var result = await client.ExecuteWorkflowAsync((SayHelloWorkflow wf) => wf.RunAsync("Temporal"), new(id: $"my-workflow-id-{Guid.NewGuid()}", taskQueue: "my-task-queue")); Console.WriteLine("Workflow result: {0}", result);

Register Worker interceptor example

Example of registering an interceptor on the Worker: using var worker = new TemporalWorker( client, new TemporalWorkerOptions("my-task-queue") { Interceptors = new IWorkerInterceptor[] { new SimpleWorkerInterceptor(), }, } .AddActivity(activities.SayHello) .AddWorkflow<SayHelloWorkflow>()); await worker.ExecuteAsync();

Implementing Worker Interceptor example

To implement Worker call interceptors, define a class implementing IWorkerInterceptor. It provides InterceptActivity(), InterceptWorkflow(), and InterceptNexusOperation() methods for Activity, Workflow, and Nexus interception. This example demonstrates measuring Schedule-To-Start and Schedule-To-Close latency: using Temporalio.Activities; using Temporalio.Worker; using Temporalio.Worker.Interceptors; public class SimpleWorkerInterceptor : IWorkerInterceptor { public ActivityInboundInterceptor InterceptActivity( ActivityInboundInterceptor nextInterceptor) => new ActivityMetricsInterceptor(nextInterceptor); public WorkflowInboundInterceptor InterceptWorkflow( WorkflowInboundInterceptor nextInterceptor) => nextInterceptor; public NexusOperationInboundInterceptor InterceptNexusOperation( NexusOperationInboundInterceptor nextInterceptor) => nextInterceptor; } public class ActivityMetricsInterceptor(ActivityInboundInterceptor next) : ActivityInboundInterceptor(next) { public override async Task<object?> ExecuteActivityAsync( ExecuteActivityInput input) { var info = ActivityExecutionContext.Current.Info; var started = DateTimeOffset.UtcNow; // Before the activity executes var scheduleToStart = started - info.CurrentAttemptScheduledTime; Console.WriteLine( $"Schedule-To-Start latency: {scheduleToStart}"); // Execute the activity var result = await base.ExecuteActivityAsync(input); // After the activity completes var scheduleToClose = DateTimeOffset.UtcNow - info.CurrentAttemptScheduledTime; Console.WriteLine( $"Schedule-To-Close latency: {scheduleToClose}"); return result; } }

Register interceptor on Client

To register interceptors on the Client, pass interceptor instances in the Interceptors property of TemporalClientConnectOptions. Client interceptors modify outbound calls such as starting and signaling Workflows. The Interceptors list can contain multiple interceptors. The default behavior for interceptors is to form a chain where a method implemented on an interceptor instance in the list can perform side effects and modify data before passing it on to the corresponding method on the next interceptor in the list.

Interceptor execution locations

Outbound Client interceptors run on the Client. Inbound and Outbound Workflow interceptors run on the Worker in the Workflow sandbox. Inbound and Outbound Activity interceptors run on the Worker in the Activity context.

Implementing Client Interceptor example

To implement Client call interceptors, define a class implementing IClientInterceptor. Implement InterceptClient() to return a ClientOutboundInterceptor, overriding the outbound Client calls you want to modify. IClientInterceptor.InterceptClient receives the next ClientOutboundInterceptor in the chain and returns the created interceptor. This example shows context propagation by setting a user ID in the outbound headers: using Google.Protobuf; using Temporalio.Api.Common.V1; using Temporalio.Client; using Temporalio.Client.Interceptors; public static class UserContext { private static readonly AsyncLocal<string?> CurrentUser = new(); public static string? UserId { get => CurrentUser.Value; set => CurrentUser.Value = value; } } public class ContextPropagationInterceptor : IClientInterceptor { public ClientOutboundInterceptor InterceptClient( ClientOutboundInterceptor nextInterceptor) => new ContextPropagationClientOutboundInterceptor(nextInterceptor); } public class ContextPropagationClientOutboundInterceptor( ClientOutboundInterceptor next) : ClientOutboundInterceptor(next) { public override Task<WorkflowHandle<TWorkflow, TResult>> StartWorkflowAsync<TWorkflow, TResult>(StartWorkflowInput input) { var headers = input.Headers ?? new Dictionary<string, Payload>(); headers["user-id"] = new Payload { Metadata = { ["encoding"] = ByteString.CopyFromUtf8("plain/text") }, Data = ByteString.CopyFromUtf8(UserContext.UserId), }; return base.StartWorkflowAsync<TWorkflow, TResult>(input with { Headers = headers }); } }

Register Client interceptor example

Example of registering an interceptor on the Client: using Temporalio.Extensions.OpenTelemetry; var interceptor = new TracingInterceptor(); var client = await TemporalClient.ConnectAsync(new() { TargetHost = "localhost:7233", Interceptors = [interceptor], });

Register interceptor on Worker

To register interceptors on the Worker only, pass interceptors in the Interceptors argument of TemporalWorkerOptions. Worker interceptors modify inbound and outbound Workflow and Activity calls.

Context propagator workflow: register, inject, extract, access

Context propagation works in four steps: (1) Register a context propagator on the Client via ContextPropagators in ClientOptions; (2) Inject - the SDK calls Inject (from context.Context) or InjectFromWorkflow (from workflow.Context) to serialize values into Temporal headers on outbound calls; (3) Extract - the SDK calls Extract (into context.Context) or ExtractToWorkflow (into workflow.Context) to deserialize headers back into the context on inbound calls; (4) Access - Workflow and Activity code reads values from the context as usual.

ContextPropagator interface definition

The ContextPropagator interface requires four methods: Inject(context.Context, HeaderWriter) error for Client/Activity side, Extract(context.Context, HeaderReader) (context.Context, error) for Client/Activity side, InjectFromWorkflow(workflow.Context, HeaderWriter) error for Workflow side, and ExtractToWorkflow(workflow.Context, HeaderReader) (workflow.Context, error) for Workflow side. There are two pairs of methods because Go uses context.Context in non-Workflow code and workflow.Context inside Workflows. You must implement all four methods for values to propagate across every boundary.

Registering context propagator on Client and setting context values

Register the context propagator on the Client by passing it to client.Dial in the ContextPropagators field of client.Options. Before starting a Workflow with ExecuteWorkflow, create a context using context.Background() and set the context value using context.WithValue with the propagator's context key and a Values struct containing the data to propagate.

Accessing propagated values in Workflow and Activity

In a Workflow, propagated values are available on the workflow.Context using ctx.Value(PropagateKey). When the Workflow starts an Activity using workflow.ExecuteActivity, the SDK automatically propagates the same values to the Activity context. In an Activity, access propagated values from the context.Context parameter using ctx.Value(PropagateKey). The values maintain type assertions (e.g., vals := val.(Values)) to access the actual Values struct.

Context propagation overview and use cases

Context propagation lets you pass custom key-value data from a Client to Workflows, and from Workflows to Activities and Child Workflows, without threading it through every function signature. Common use cases include propagating tracing IDs, tenant IDs, auth tokens, or other request-scoped metadata.

Multiple context propagators per Client

You can configure multiple context propagators on a single Client, each responsible for its own set of keys.

Custom context propagator example implementation

A context propagator implements the ContextPropagator interface with four methods. The propagator uses an unexported contextKey type for storing values, a Values struct with Key and Value string fields (JSON-serialized), and a constant HeaderKey for passing values through Temporal server headers. The Inject and InjectFromWorkflow methods extract the value from context, convert it to a payload using the default data converter, and write it to headers. The Extract method reads from headers, deserializes the payload back to a Values struct, and returns a new context with the value set. The example propagator carries a custom key-value pair from Client to Workflows and Activities.

Built-in tracing interceptor alternative to custom propagator

If you want to propagate tracing context, check if there is a built-in tracing interceptor for your library before building a custom context propagator.

Go SDK best practices topics

The Go SDK best practices guide covers five main areas: multithreading, error handling, debugging, testing, and data handling.

Go SDK code examples and sample applications

Several reference implementations are available: the Background Check application demonstrates a non-trivial Temporal Application implementation; the Hello World application template provides a quick-start development app; the Money Transfer application template demonstrates basic workflow definitions; the Subscription-style Workflow Definition showcases patterns for subscription-based business processes; and the eCommerce application example demonstrates a per-user shopping cart workflow with an API and web UI.

Go SDK technical resources and links

Key Go SDK resources include: Go API Documentation at https://pkg.go.dev/go.temporal.io/sdk, Go SDK Code Samples at https://github.com/temporalio/samples-go, Go SDK GitHub repository at https://github.com/temporalio/sdk-go, and Temporal 101 in Go Free Course at https://learn.temporal.io/courses/temporal_101/go/.

Go SDK documentation structure and topics

The Temporal Go SDK documentation covers the following major topic areas: Workflows (including basics, child workflows, continue-as-new, cancellation, timeouts, message passing, selectors, side effects, schedules, timers, dynamic workflows, versioning, and workflow streams), Activities (including basics, execution, standalone activities, timeouts, asynchronous completion, dynamic activities, and benign exceptions), Workers (including running workers, sessions, and serverless workers), Temporal Client (client connections and namespaces), Temporal Nexus (quickstart, feature guide, and standalone operations), Platform features (observability and enriching the UI), Best practices (multithreading, context propagation, error handling, debugging, testing, and data handling), and Integrations (Google ADK integration).

Go SDK community resources

The Temporal Go community can be found on the Temporal Go Community Slack at https://temporalio.slack.com/archives/CTDTU3J4T and the Go SDK Forum at https://community.temporal.io/tag/go-sdk.

Temporal SDK overview page covers comprehensive development topics

The Temporal SDK developer guides provide a comprehensive overview of the structures, primitives, and features used in Temporal Application development. Guides are available for multiple SDKs including .NET, Go, Java, PHP, Python, Ruby, Rust, and TypeScript.

Benign exceptions reduce noise in logs and metrics

Marking errors as benign helps reduce noise in logs, metrics, and OpenTelemetry traces, making it easier to identify real issues in your observability data.

Temporal Cloud connection environment variables for mTLS

Set TEMPORAL_ADDRESS=<your-namespace>.<your-account-id>.tmprl.cloud:7233, TEMPORAL_NAMESPACE=<your-namespace>.<your-account-id>, TEMPORAL_TLS_CLIENT_CERT_PATH='path/to/your/client.pem', and TEMPORAL_TLS_CLIENT_KEY_PATH='path/to/your/client.key' for mTLS authentication.

Temporal Cloud connection environment variables for API key

Set TEMPORAL_ADDRESS=<your-namespace>.<your-account-id>.tmprl.cloud:7233, TEMPORAL_NAMESPACE=<your-namespace>.<your-account-id>, and TEMPORAL_API_KEY=<your-api-key> for API key authentication.

ClientConfigProfile.load() works with local and Cloud

ClientConfigProfile.load() responds to environment variables and TOML configuration files, so the same code works against a local dev server and Temporal Cloud without changes.

Implement Payload encryption when working with sensitive data

When working with sensitive data, you should always implement Payload encryption in your Temporal applications.

Java SDK best practices overview

The Java SDK best practices documentation covers four main areas: error handling, testing, debugging, and converters and encryption. These topics provide guidance on implementing robust and maintainable Temporal applications.

Java SDK best practices sections

The best practices guide for the Java SDK is organized into the following sections: error handling, testing (via testing-suite), debugging, and data handling (converters and encryption).

Exception handling rule of thumb for Workflows, Updates, and Signal handlers

When deciding what to catch in a Workflow, Update, or Signal handler: (1) Error — never catch it; if you must run cleanup on any exit path, use a detached Cancellation Scope rather than a broad catch. (2) CanceledFailure — rethrow it, optionally after cleanup in a detached Cancellation Scope; don't swallow it because cancellation is cooperative and swallowing it lets the Workflow Execution finish as Completed instead of Canceled. (3) ActivityFailure, ChildWorkflowFailure, or ApplicationFailure that you recognize and can recover from — handle it. (4) Everything else — rethrow it; a plain RuntimeException that isn't recognized fails only the current Workflow Task which retries indefinitely rather than failing the Workflow Execution.

Problems with catching Throwable in Workflow code

A catch (Throwable t) or catch (Exception e) placed around Workflow logic causes three separate problems: (1) DestroyWorkflowThreadError and UnsupportedVersion are swallowed instead of reaching the SDK, which can stall Worker cache eviction and interfere with replay. (2) CanceledFailure is swallowed, so a canceled Workflow Execution reports Completed instead of Canceled. (3) Every other exception, including real bugs, disappears with only a log line instead of failing the Workflow Task or Workflow Execution, so there's no signal in the Event History that anything went wrong.

Activity.wrap() and Workflow.wrap() behavior

For checked exceptions: wrap() returns a CheckedExceptionWrapper around it, and the SDK unwraps it automatically while propagating the failure and attaches the original exception as the cause of the resulting ApplicationFailure. For RuntimeException: wrap() returns it unchanged, making calling wrap() on an unchecked exception a safe no-op. For Error: wrap() rethrows it directly instead of wrapping it. When wrap() returns a CheckedExceptionWrapper, the caller still sees the original exception type and message in the cause chain.

When to use wrap() on exceptions

Only checked exceptions need wrap(). Any unhandled exception an Activity or Workflow throws — checked or not — is already converted to an ApplicationFailure automatically when it crosses the Activity or Workflow boundary. wrap() exists to satisfy the Java compiler when you want to throw a checked exception from a method that doesn't declare it, not to make the exception propagate. After unwrapping a cause to inspect it, either rethrow the failure you caught or throw a new ApplicationFailure with the original exception set as its cause; there is no wrapper left to reapply.

Never catch Throwable or Error in Workflows and Activities

Workflow and Activity code should only ever catch Exception or a narrower type. Never catch Throwable or Error. The Java SDK uses subclasses of Error as internal control signals that must reach the SDK's own code uncaught. DestroyWorkflowThreadError interrupts a Workflow thread so the Worker can release it back to the pool. UnsupportedVersion is thrown by Workflow.getVersion() when replayed history was produced by code outside the version range and is designed to not be caught by application code.

Wrap checked exceptions instead of adding throws declarations

Activity and Workflow method signatures should not declare throws for checked exceptions. Instead, wrap a checked exception with Activity.wrap() inside an Activity, or Workflow.wrap() inside a Workflow, before rethrowing it. The wrap() method only does something to a checked exception: if e is a checked exception, wrap() returns a CheckedExceptionWrapper around it; if e already extends RuntimeException, wrap() returns it unchanged; if e extends Error, wrap() rethrows it directly.

Failure cause chain structure

An exception thrown from an Activity or Child Workflow arrives at the caller wrapped with context. A failure from an Activity called from a Child Workflow called from a parent Workflow looks like: WorkflowFailedException (thrown to the client) → ChildWorkflowFailure (the child Workflow Execution failed) → ActivityFailure (the Activity Execution failed) → ApplicationFailure (what your code actually threw). Each wrapper adds context: ActivityFailure carries the Activity Type and Activity Id, ChildWorkflowFailure carries the Workflow Type and Workflow Id. Calling getCause() on each layer moves toward what actually failed.

Reading ApplicationFailure details

When reading an ApplicationFailure: (1) Read getOriginalMessage(), not getMessage(). getMessage() returns a decorated string such as 'message=Invalid credit card number, type=ValidationError, nonRetryable=true' meant for logs, not parsing. getOriginalMessage() returns the exact text you threw. (2) Match on getType(), a stable String, not instanceof your original exception class. ApplicationFailure is final and the original exception object doesn't survive serialization. type defaults to the thrown exception's fully qualified class name unless you set it explicitly with ApplicationFailure.newFailure(message, type, ...).

Catch ActivityFailure and ChildWorkflowFailure, not ApplicationFailure directly

When handling Activity and Child Workflow failures, catch ActivityFailure (or ChildWorkflowFailure) around a call, not ApplicationFailure directly, because the Activity or Child Workflow boundary always wraps the underlying failure. Always check for CanceledFailure as the cause before handling anything else, and rethrow it unhandled. This ensures cancellation is not swallowed.

Give your agent this brain