Skip to content

Architecture

Overview

The cookbook is a documentation-focused repository that standardizes how Azure Functions Python v2 patterns are explained. The architecture is intentionally simple: recipe source documents define the contract, published docs curate the reader journey, and runnable examples demonstrate execution behavior.

Layer Model

The architecture has three layers with clear responsibilities:

  • recipes/: canonical implementation narratives and trigger-specific guidance.
  • docs/: reader-friendly pages that aggregate patterns and provide onboarding.
  • examples/: runnable projects that validate recipe claims in code.

This separation allows recipe depth to grow without making onboarding pages noisy.

Repository Structure Example

Use a structure that preserves one recipe-to-example mapping and keeps documentation discoverable.

from pathlib import Path

import azure.functions as func
from pydantic import BaseModel


class RepositoryLayout(BaseModel):
    root: str
    docs: list[str]
    recipes: list[str]
    examples: list[str]


layout = RepositoryLayout(
    root=str(Path(".")),
    docs=["index.md", "recipes.md", "architecture.md", "contributing.md"],
    recipes=[
        "http-api-basic.md",
        "http-api-openapi.md",
        "github-webhook.md",
        "queue-worker.md",
        "timer-job.md",
    ],
    examples=["http-api-basic/", "http-api-openapi/", "github-webhook/", "queue-worker/", "timer-job/"],
)

app = func.FunctionApp()
_ = app
print(layout.model_dump())

Function App Composition

A recipe should present code in composition units that can be split later with Blueprints, while still starting from a single FunctionApp entry point.

import azure.functions as func
from pydantic import BaseModel


class AppMetadata(BaseModel):
    service: str
    version: str


metadata = AppMetadata(service="cookbook-sample", version="1.0.0")
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)


@app.route(route="health", methods=["GET"])
def health(_: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse(metadata.model_dump_json(), mimetype="application/json", status_code=200)

Module Layout Example

A production recipe can scale from one file to multiple modules while preserving the v2 decorator model.

from dataclasses import dataclass

import azure.functions as func
from pydantic import BaseModel


class HttpContract(BaseModel):
    route: str
    methods: list[str]


@dataclass(slots=True)
class ModulePlan:
    entrypoint: str
    modules: list[str]
    contract: HttpContract


plan = ModulePlan(
    entrypoint="function_app.py",
    modules=["handlers/http.py", "handlers/queue.py", "handlers/timer.py"],
    contract=HttpContract(route="jobs", methods=["POST"]),
)

app = func.FunctionApp()
_ = (app, plan)

Trigger Isolation Pattern

Each trigger should have one focused function and one payload model. This keeps validation local and limits blast radius during changes.

import json

import azure.functions as func
from pydantic import BaseModel


class QueuePayload(BaseModel):
    task_id: str
    kind: str


app = func.FunctionApp()


@app.queue_trigger(arg_name="msg", queue_name="jobs", connection="AzureWebJobsStorage")
def process_job(msg: func.QueueMessage) -> None:
    payload = QueuePayload.model_validate(json.loads(msg.get_body().decode("utf-8")))
    print(payload.task_id, payload.kind)
import datetime

import azure.functions as func
from pydantic import BaseModel


class TimerPayload(BaseModel):
    name: str
    fired_at: str


app = func.FunctionApp()


@app.timer_trigger(schedule="0 */15 * * * *", arg_name="timer", run_on_startup=False, use_monitor=True)
def run_timer(timer: func.TimerRequest) -> None:
    _ = timer
    payload = TimerPayload(name="refresh-cache", fired_at=datetime.datetime.now(datetime.UTC).isoformat())
    print(payload.model_dump_json())

Operational Contracts

Recipe architecture should always expose operational assumptions in code examples:

  • Validation path: parse request payloads with explicit models.
  • Failure path: return deterministic status codes or raise for retry semantics.
  • Idempotency path: include a stable operation key for webhook and queue flows.
  • Observability path: include log fields that make retries and latency traceable.

Evolution Strategy

As recipes expand, keep compatibility by evolving contracts rather than replacing them:

  • Add fields as optional first, then enforce in a later version.
  • Keep existing route names stable unless migration guidance is documented.
  • Add new trigger recipes as additive pages to avoid breaking reader workflows.
  • Keep code examples executable and parseable with Python 3.10+ syntax.