Durable Hello Sequence¶
Overview¶
This recipe shows the canonical Durable Functions orchestration chain in Python v2 using
df.Blueprint() and app.register_functions(bp).
The HTTP starter launches an orchestrator that calls one activity three times in order,
returning a deterministic list of greetings.
The pattern is intentionally small, but it teaches the most important Durable concept:
the orchestrator is replayed from history and must only coordinate durable tasks.
Every activity call is scheduled with yield context.call_activity(...) so execution can
checkpoint and replay safely.
When to Use¶
- You need ordered multi-step workflows where each step depends on the previous result.
- You want a starter template for Python durable orchestration with minimal ceremony.
- You need deterministic, replay-safe flow control without adding external state stores.
Architecture¶
+--------+ POST /api/start-sequence +------------------------+
| Client | -------------------------------------> | HTTP starter function |
+---+----+ +-----------+------------+
| |
| 202 + status URLs | client.start_new()
v v
+------------------------+ +--------------------------+
| Durable Orchestration | | hello_sequence_orch |
| Instance | | for city in [3 items]: |
+-----------+------------+ | yield call_activity |
| +------------+-------------+
| activity call |
v v
+------------------+ +----------------------+
| say_hello | | Final result list |
| "Hello <city>!" | | [Hello Tokyo!, ...] |
+------------------+ +----------------------+
Prerequisites¶
- Python 3.10+
- Azure Functions Core Tools v4
- Durable Functions extension bundles enabled through
host.json azure-functionsandazure-functions-durablefromrequirements.txt
Project Structure¶
examples/durable/durable_hello_sequence/
|- function_app.py
|- host.json
|- local.settings.json.example
|- requirements.txt
`- README.md
Implementation¶
The file defines a durable blueprint and registers it on the app.
The starter endpoint creates a new orchestration instance and returns management URLs.
@bp.route(route="start-sequence", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)
@bp.durable_client_input(client_name="client")
async def start_sequence(req: func.HttpRequest, client: df.DurableOrchestrationClient) -> func.HttpResponse:
instance_id = await client.start_new("hello_sequence_orchestrator")
return client.create_check_status_response(req, instance_id)
The orchestrator chains activities in sequence.
Each yield context.call_activity(...) records an event in durable history.
On replay, the runtime rehydrates previously completed outputs and continues deterministically.
@bp.orchestration_trigger(context_name="context")
def hello_sequence_orchestrator(context: df.DurableOrchestrationContext):
cities = ["Tokyo", "Seattle", "London"]
results = []
for city in cities:
greeting = yield context.call_activity("say_hello", city)
results.append(greeting)
return results
The activity remains pure and side-effect scoped.
@bp.activity_trigger(input_name="payload")
def say_hello(payload: str) -> str:
return f"Hello {payload}!"
Run Locally¶
Expected Output¶
POST /api/start-sequence -> 202 Accepted
Status query eventually returns:
{
"runtimeStatus": "Completed",
"output": [
"Hello Tokyo!",
"Hello Seattle!",
"Hello London!"
]
}
Production Considerations¶
- Scaling: activity functions scale out independently while orchestrator replays remain lightweight.
- Retries: add
call_activity_with_retrywhen greeting logic calls external systems. - Idempotency: keep activity side effects idempotent because retries or replays can happen.
- Observability: log
instance_id, city, and activity latency for each step. - Security: switch starter auth from
ANONYMOUStoFUNCTIONor managed front-door auth.