ワンクリックで
service-architecture
Request lifecycle, Tower layer ordering, and scaling choices in the backend-service `service` template
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Request lifecycle, Tower layer ordering, and scaling choices in the backend-service `service` template
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Choosing and profiling the global allocator (jemalloc, mimalloc, system) in the backend-service `service` template
Observability wiring for the backend-service `service` template: metrique wide-event metrics, tracing logs, and optional dial9 runtime profiling
Setting up automated crate releases with release-plz and OIDC trusted publishing
Standard GitHub Actions CI structure for Rust projects using ci-battery-pack
Cross-platform binary builds for GitHub Releases with cargo-binstall support
Propagating and formatting errors in Rust applications with anyhow
| name | service-architecture |
| description | Request lifecycle, Tower layer ordering, and scaling choices in the backend-service `service` template |
How a request flows through this service, why the middleware stack is ordered the way it is, and how to scale it. The stack lives in src/routes.rs::router.
The service template uses axum: its extractors and Router make handlers and middleware cheap to write, at a small per-request cost from cloning state and running extractors. Drop to raw hyper (the hyper-util examples) only when you need what axum hides: custom connection lifecycle, protocol upgrades, or the last allocation off a hot path.
Layers wrap the router, so the last one added is the outermost and a request flows through them top down. The order is deliberate. Several layers are optional features, so a given service may not have all of them; the list shows the full stack with every feature enabled:
x-request-id or mint one, and echo it back. It runs first so everything inside has a correlation id.Anything that can produce a status (rate limit, panic, timeout, body limit) sits inside telemetry_middleware so the status is recorded, while request-id and tracing sit outside it so the id and span exist when it runs. /health is mounted on a separate router with none of this, so probes are not metered or rate-limited.
The service template ships a single global token bucket (GlobalKeyExtractor). To limit per client instead:
GlobalKeyExtractor for PeerIpKeyExtractor.into_make_service_with_connect_info::<SocketAddr>() so the extractor can read the peer address from request extensions (tests then need a SocketAddr injected).governor_conf.limiter().retain_recent() task. The keyed store grows one entry per key and is never auto-evicted, so without this it leaks memory under a wide key space.No new dependency or feature is needed: PeerIpKeyExtractor ships with the tower_governor the rate-limit feature already pulls, so this is a code change.
Behind a load balancer the peer IP is the balancer, so key off a trusted forwarded-for header instead of the socket address.
The service template deliberately leaves these out (workload-specific, easy to misuse). Add each with cargo bp add backend-service -F <feature>, then wire it as below.
cargo bp add backend-service -F cache, which adds moka): front the store's reads with its future::Cache. A cache changes the service's consistency contract; its failure mode is silent stale reads. Size the TTL against an explicit staleness budget.cargo bp add backend-service -F load-shed, which adds the tower layers): use ConcurrencyLimitLayer paired with LoadShedLayer, do not hand-roll a counter check. The pair caps concurrent requests and sheds the overflow with a 503; a bare concurrency limit without LoadShedLayer queues the overflow without bound. Use the always-on IN_FLIGHT metric only to size the cap from observed concurrency, not as the shedding mechanism. Place it inside the recorder so the 503 is counted, but outside the rate limit and timeout.telemetry_middleware, or that status stops being recorded.telemetry_middleware; it reads the id they set.DefaultBodyLimit innermost so oversized bodies are rejected before buffering.classify_operation (src/middleware.rs) or it records as Other./health stays on the bypass router: no metrics, no rate limit.cargo bp add backend-service -F <feature>, not a plain cargo add, so dependency versions stay the ones the pack pins.telemetry skill.