Activity Definition
This page discusses the following:
An Activity Definition is the code that gives rise to an Activity Task Execution. Below are examples of basic Activity Definitions across supported SDKs.
- Go
- Java
- PHP
- Python
- TypeScript
- .NET
- Rust
import (
"context"
"go.temporal.io/sdk/activity"
)
func YourSimpleActivity(ctx context.Context) error {
return nil
}
Activity Definition in Java (Interface)
@ActivityInterface
public interface GreetingActivities {
@ActivityMethod
String composeGreeting(String greeting, String language);
}
Activity Definition in Java (Implementation)
static class GreetingActivitiesImpl implements GreetingActivities {
@Override
public String composeGreeting(String greeting, String name) {
return greeting + " " + name + "!";
}
}
Activity Definition in PHP (Interface)
#[ActivityInterface]
interface GreetingActivities
{
public function composeGreeting(string $greeting, string $name): string;
}
Activity Definition in PHP (Implementation)
class GreetingActivitiesImpl implements GreetingActivities
{
public function composeGreeting(string $greeting, string $name): string
{
return $greeting . ' ' . $name;
}
}
from temporalio import activity
@activity.defn(name="your_activity")
async def your_activity(input: YourParams) -> str:
return f"{input.greeting}, {input.name}!"
Activity Definition in TypeScript
export async function greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
Activity Definition in C# and .NET
using Temporalio.Activities;
public class MyActivities
{
[Activity]
public string MyActivity(MyActivityParams input) =>
$"{input.Greeting}, {input.Name}!";
}
use temporalio_sdk::activities::{ActivityContext, ActivityError};
use temporalio_macros::activities;
pub struct GreetingActivities;
#[activities]
impl GreetingActivities {
#[activity]
pub async fn greet(_ctx: ActivityContext, name: String) -> Result<String, ActivityError> {
Ok(format!("Hello, {}!", name))
}
}
Idempotency
Temporal recommends that Activities be idempotent. Idempotence means that performing an operation multiple times has the same result as performing it once. In the context of Temporal, Activities should be designed to be safely executed multiple times without causing unexpected or undesired side effects. A few examples where idempotent operations are vital would be:
- Infrastructure-as-Code (IaC) tool - Conserving resources is important when you're provisioning infrastructure in the cloud. An IaC system that was not designed with idempotence in mind could lead to high costs if the function to provision a new server was accidentally invoked multiple times. An IaC tool that is designed with idempotence in mind ensures that multiple invocations of the tool doesn't lead to unintended instances being created.
- Payment processing system - A payment processing system must charge the customer only once for a given purchase. If the system was not designed to be idempotent, duplicate requests would result in extra charges and unhappy customers. A payment processing system that is designed to be idempotent ensures customers are not charged multiple times for the same transaction, preventing financial discrepancies.
By design, completed Activities will not re-execute as part of a Workflow Replay. However, Activities won’t record to the Event History until they return or produce an error. If an Activity fails to report to the server at all, it will be retried. Designing for idempotence, especially if you have a Global Namespace, will improve reusability and reliability.
An Activity is idempotent if multiple Activity Task Executions do not change the state of the system beyond the first Activity Task Execution.
The lack of idempotency might affect the correctness of your application but does not affect the Temporal Platform. In other words, lack of idempotency doesn't lead to a platform error.
In some cases, whether something is idempotent doesn't affect the correctness of an application. For example, if you have a monotonically incrementing counter, you might not care that retries increment the counter because you don't care about the actual value, only that the current value is greater than a previous value.
You should always make your business logic Activities idempotent in Temporal. Because Activities may be retried, these functions may be executed more than once. A non-idempotent Activity could adversely affect the state of the system.
Activities are an atomic unit of execution within Temporal. They are invoked and either complete successfully or not. Take this into consideration when you design your Activities.
For example, consider an Activity that has the following three steps:
- Perform a database lookup
- Make a call to a microservice with parameters retrieved from the database
- Write the result of the microservice call to the filesystem
Imagine that the first two steps succeed, but the third step fails due to a permissions issue. During retry, the entire Activity—and therefore each of the three steps—is executed again. To maintain idempotency, design your Activities to be more granular. In this case, you could have three Activities, one for each step. This way, only the step that failed will be executed again. However, you must balance this against the potential for a larger Event History, since there would now be three Activity Executions instead of one.
Idempotence for Activities is also important due to a particular edge case inherent in distributed computing. Consider a scenario in which a Worker polls the Temporal Service, accepts the Activity Task, and begins executing the Activity. The Activity function completes successfully, but the Worker crashes just before it notifies the Temporal Service. In this case, the Event History won’t reflect the successful completion of the Task, so the Activity will be retried. If the Activity is not idempotent, this could have negative consequences, such as duplicate charges in a payment processing scenario.
You can achieve idempotency in your application through the use of unique identifiers, known as idempotency keys, which are used to detect duplicate requests. These are enforced by the service you are calling from your Activity, not by the Activity itself.
For example, the APIs provided by most payment processors allow the client to include an idempotency key with the request. When the payment service receives a request, it checks a database to determine whether there has already been a request with this key. If so, the duplicate request is ignored and does not result in another charge. If not, then it writes a new record to the database with this key, allowing it to identify duplicate requests in the future.
In Temporal, the request to the payment service would be made from within an Activity. You can use a combination of the Workflow Run ID and the Activity ID as an idempotency key since this is guaranteed to be consistent across retry attempts but unique among Workflow Executions.
For more information about idempotency in Temporal, see the following post:
Idempotency and Durable Execution
Activity retry policy
The Activity retry mechanism gives applications the benefits of durable execution. For example, Temporal will keep track of the exponential backoff delay even if the Worker crashes. Since Temporal can’t tell when a Worker crashes, Workflows rely on the start_to_close timeout to know how long to wait before assuming that an Activity is inactive.
For an Activity with a Retry Policy that allows retries, Temporal guarantees that the Activity will be observed as completed exactly once. However, the Activity may be executed multiple times and may even partially complete more than once during this process. This could lead to a scenario where certain parts of the Activity are executed multiple times before a successful execution is completed.
You should typically not write retry logic manually within your Activity Definition. It lengthens the needed Activity timeout, prevents users from counting failure metrics, and makes it harder for users to debug in Temporal UI when something is wrong.
Activity Parameters
An Activity Definition can use function/method parameters as usual for your language. When called from a Workflow, the parameter values and return value are recorded in the Event History of the Workflow Execution.
Activity Type
An Activity Type is a name given to an Activity Definition. When starting an Activity, you can identify it by the name (Activity Type), or by a reference to its function/method/class (Activity Definition)
Best practices for defining Activities
Here are some best practices you can use when you are creating Activities for your Workflow:
- Activity arguments and return values must be serializable.
- Activities that perform writes should be idempotent.
- Activities have timeouts and retry policies. For Activities, your operation should either complete within a few minutes or it should heartbeat. This way it will be clear to the Workflow when the Activity is still making progress.
- You need to specify at least one timeout, typically the start_to_close timeout. Keep in mind that the shorter the timeout, the faster Temporal will detect a problem and retry. See the Activity retry policy section to learn more.