Skip to content

About

Twelve API design patterns on Azure, measured. Idempotency keys four ways: three that pass the usual test, one that is correct.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

api-design-patterns-azure

Twelve API design patterns on Azure, with idempotency keys implemented four times: three versions that pass a sequential retry test and fail in production, and one that holds. Companion repo for the Cloud Perspectives post "Twelve API Design Patterns, and the One Where Passing Tests Prove Nothing" (draft).

Where each pattern lives

What is deployed

Resource Role
API Management BasicV2, API patterns 09 gateway, 07 rate limit per subscription, 04 problem+json for 429, 11 backend circuit breaker
Function App, Flex Consumption, .NET 8 isolated the API: patterns 01-06, 08, 10-12
Storage account (identity only) tables users, orders, idempotency, ledger, reports, webhooks, settings; queues report-jobs, webhook-deliveries (+ poison)
Application Insights traces tagged PAYMENT, WEBHOOK-SEND, WEBHOOK-INBOX, REPORT, BALANCE

Patterns and where to find them

# Pattern Code Try
01 RESTful resources Functions/Resources/UsersApi.cs GET/PATCH/DELETE /api/v2/users/{id}
02 API versioning Functions/Versioning/UsersV1Api.cs GET /api/v1/users/{id}: Deprecation, Sunset, Link: rel="successor-version", 410 after sunset
03 Pagination (cursor) Functions/Pagination/OrdersApi.cs GET /api/v2/users/42/orders?limit=20, follow next
04 Consistent errors Core/Http/Problem.cs, Functions/Errors/ProblemMiddleware.cs, infra/apim/policy.xml any error; types in docs/problems.md
05 Idempotency keys Core/Idempotency/PaymentHandlers.cs, Functions/Idempotency/PaymentsApi.cs POST /api/payments?impl=atomic with Idempotency-Key
06 Conditional requests Functions/Resources/UsersApi.cs If-None-Match 304, If-Match 412, none 428
07 Rate limiting infra/apim/policy.xml 70 calls/min through APIM: 429 + Retry-After
08 Async request-reply Functions/AsyncReply/ReportsApi.cs POST /api/reports 202, poll, 303
09 API gateway infra/apim/apim.bicep, policy.xml subscription key, key injection, X-Correlation-Id
10 Webhooks Core/Webhooks/WebhookSignature.cs, Functions/Webhooks/Webhooks.cs Standard Webhooks signing, queue retries, inbox dedup
11 Circuit breaker Core/Resilience/CircuitBreaker.cs, Functions/Resilience/BalanceApi.cs; APIM backend rule PUT /api/dependency/mode {"mode":"failing"}, then GET .../balance
12 API aggregation Functions/Aggregation/SummaryApi.cs GET /api/v2/users/42/summary: partial, unavailable

The idempotency matrix

POST /api/payments?impl= selects one of four handlers. Same store, same provider, different semantics.

impl What it gets wrong
racy check-then-act: concurrent retries both charge; random provider charge id
memory result kept in process memory: after a restart the key exists and the replay does not
endpoint-scoped key scoped to the route: a retry through /orders/{id}/pay charges again
atomic nothing: atomic claim (AddEntity), durable result (UpdateEntity + ETag), key passed to the provider, lease takeover, payload fingerprint

Failure modes

Order ids starting with lost- make the simulated provider charge and then drop its first answer (the timeout case).

Run it

Prerequisites: .NET 8 SDK, Azure Developer CLI, Azure CLI, Azure Functions Core Tools. Works from Windows PowerShell 5.1.

dotnet test                                 # Core tests, no Azure needed

azd env new api-design-patterns
azd env set AZURE_LOCATION swedencentral
azd env set APIM_PUBLISHER_EMAIL you@example.com
azd up                                      # postdeploy: APIM named value + seed data

./scripts/race.ps1                          # idempotency matrix against Azure
./scripts/race.ps1 -ViaGateway -Parallel 10 # same, through APIM (rate limit applies)
./scripts/demo.ps1                          # all twelve patterns

APIM BasicV2 bills by the hour. azd down --purge when you are done.

Local

Copy src/ApiPatterns.Functions/local.settings.sample.json to local.settings.json, start Azurite, func start in that folder, then POST http://localhost:7071/api/ops/seed.

Verified on Azure (swedencentral, 2026-10-01)

  • race.ps1: the full idempotency matrix as predicted; the racy burst charged 16 times for 20 identical requests, all answered 201.
  • demo.ps1: all twelve steps. The APIM 429 comes back as problem+json with Retry-After. 70 simultaneous calls against 60/min let 69 through: APIM rate-limit counters are approximate under a burst.
  • Deploy note: Function routes must not start with admin (reserved by the host). The function still lists, every call returns an empty 404. Seed and recycle live under ops/.
  • Not exercised yet: the APIM backend circuit breaker rule (deployed, never tripped by the demo).
  • Webhook delivery defaults to https://$WEBSITE_HOSTNAME/api/webhooks/inbox. Set WEBHOOK_TARGET_URL to deliver elsewhere.

Layout

src/ApiPatterns.Core        framework-free patterns: problem details, idempotency handlers, breaker, webhook signing
src/ApiPatterns.Functions   Azure Functions app (HTTP + queue triggers), Table Storage stores
tests/ApiPatterns.Core.Tests xUnit: the failure matrix, breaker states, signature checks
infra/                      Bicep (subscription scope) + APIM module and policy.xml
scripts/                    postdeploy, demo, race (Windows PowerShell 5.1 compatible)
images/                     diagrams (SVG + PNG); images/src regenerates them from the Azure icon set
docs/problems.md            the problem type catalogue

Diagrams: cd images/src, set AZURE_ICONS to the Azure icon folder, run python map.py, python failure_modes.py and python matrix.py, then python render.py ../api-patterns-azure-map.svg ../idempotency-failure-modes.svg ../idempotency-test-matrix.svg (needs Playwright).

License

MIT

About

Twelve API design patterns on Azure, measured. Idempotency keys four ways: three that pass the usual test, one that is correct.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages