#[activity]Expand description
Marks an async method as a durable activity (external side effects).
Use #[activity] for operations that have external side effects and need
to be retried on failure. Activities are called from within workflows via
DurableContext::run().
Do NOT use #[activity] for state mutations — use #[workflow] instead.
§Usage
#[entity_impl]
#[state(OrderState)]
impl OrderProcessor {
/// Send confirmation email - external side effect
#[activity]
async fn send_email(&self, to: String, body: String) -> Result<(), ClusterError> {
email_service.send(&to, &body).await
}
/// Charge payment - external API call
#[activity]
async fn charge_payment(&self, card: Card, amount: u64) -> Result<PaymentId, ClusterError> {
payment_gateway.charge(&card, amount).await
}
/// Main workflow that uses activities
#[workflow]
async fn process(&self, ctx: &DurableContext, order: Order) -> Result<(), ClusterError> {
// Activities are called via ctx.run() for persistence and retry
ctx.run(|| self.charge_payment(order.card, order.total)).await?;
ctx.run(|| self.send_email(order.email, "Order confirmed!")).await?;
Ok(())
}
}§When to Use
- External API calls (payment, email, webhooks)
- Operations that may fail transiently and need retry
- Side effects that should only happen once per workflow execution
§Visibility
Activities are #[private] by default — they can only be called internally
or from workflows via DurableContext. They cannot be made #[public].
§Idempotency Key
Use #[activity(key = |req| ...)] to deduplicate:
#[activity(key = |to, _body| to.clone())]
async fn send_email(&self, to: String, body: String) -> Result<(), ClusterError> {
// Only sends once per recipient within the workflow
}