Requirements to Technical Specs
Product asks arrive as user stories; engineers ship technical specs - API shapes, data models, failure modes, and rollout plans. Good translation prevents "build the button" without knowing billing rules, idempotency, or Rust service boundaries.
Recipe
Quick-reference recipe card - copy-paste ready.
# Tech spec: <feature>
## Problem / user outcome
## Scope (in) / Non-goals (out)
## API sketch (Axum routes + serde types)
## Data model changes (sqlx migrations expand-contract)
## Failure modes & retries
## Observability (tracing spans, metrics)
## Rollout (flag, canary) & rollback
## Acceptance tests (Given/When/Then)
## Open questions & spikesWhen to reach for this:
- Story points exceed one sprint
- Cross-crate or schema impact
- PM asks for date before engineering understands shape
- Compliance or security touch
Working Example
# Tech spec: Export invoices as PDF
## Problem
Finance users need PDF invoices in bulk export (up to 500/request).
## Non-goals
- Email delivery (separate epic)
- Custom branding per tenant (phase 2)
## API
`POST /v1/invoices/export` → 202 + `job_id`
`GET /v1/jobs/{id}` → status + S3 URL when ready
## Data
Read `invoices` + `line_items`; no schema change phase 1.
## Implementation (Rust)
- Axum route enqueues Tokio task or separate worker binary
- Worker uses `printpdf` or headless pipeline; uploads via `aws-sdk-s3`
- `Idempotency-Key` header dedupes retries
## Failure modes
- S3 down → retry with backoff; DLQ after 5 attempts
- PDF > 50MB → fail job with clear error code
## Acceptance
- Given 10 invoices, When export job completes, Then ZIP URL valid 24h
- p95 job time < 60s for 100 invoices in staging load testuse serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct ExportRequest {
pub invoice_ids: Vec<String>,
#[serde(default = "default_max")]
pub max_count: usize,
}
fn default_max() -> usize { 500 }
#[derive(Debug, Serialize)]
pub struct JobStatus {
pub job_id: String,
pub state: String, // pending | running | done | failed
pub download_url: Option<String>,
}What this demonstrates:
- User outcome stated without prescribing UI-only solutions
- Async job pattern chosen explicitly for Rust stack
- Failure modes and acceptance criteria are testable
- Serde models align PM and engineering early
Deep Dive
How It Works
- Discovery questions - Who, frequency, compliance, peak volume, failure tolerance.
- Vertical slice - Thin end-to-end path before polishing edge cases.
- Contract first - Serde types before deep implementation.
- Migration discipline - Expand-contract called out in spec, not surprise PR.
- Review with PM - Walk acceptance tests; adjust scope before sprint commit.
Spec Quality Checklist
| Section | Missing = risk |
|---|---|
| Non-goals | Scope creep mid-sprint |
| Rollback | Incident without mitigation |
| Observability | Blind production launch |
| Idempotency | Duplicate charges/exports |
Gotchas
- Spec after code started - Rework and blame. Fix: No sprint commit without reviewed spec for medium+ work.
- Vague acceptance - "Fast" or "scalable." Fix: Numbers: p95, max rows, error codes.
- Ignoring worker tier - API spec without queue story. Fix: Worker section mandatory for async work.
- Hidden compliance - PII in logs discovered late. Fix: Data classification in spec header.
- One giant spec - Never ships. Fix: Phase 1 non-goals explicit; phase 2 ticket stub.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| RFC for architecture | Cross-team debate | Simple CRUD story |
| Story mapping session | New product area | Single API endpoint |
| Prototype spike | High uncertainty UX | Regulated billing without spec |
| OpenAPI-only | External API consumers | Internal worker flows omitted |
FAQs
How long should specs take?
Half day to two days for medium features; spike if longer than three days writing.
Who approves specs?
Tech lead + PM sign acceptance; security for PII/payment.
Spec for bug fixes?
Lightweight: repro, root cause hypothesis, test plan - skip full template.
Axum vs worker in spec?
State which binary owns each path and shared crate boundaries.
How handle changing requirements?
Amend spec version; re-estimate; do not silently absorb scope.
Specs and Jira?
Link spec PR or doc in epic; acceptance tests copied to ticket.
CLI features in spec?
Include clap UX, exit codes, and distribution channel (cargo-dist).
When involve design?
Before API freeze when UX drives pagination, filters, or export limits.
Open questions limit?
If >3 blockers, time-box spikes before sprint commitment.
Spec storage?
docs/specs/ in repo - versioned with code.
Related
- Estimation & Risk - size after spec
- Roadmap Contributions - feed planning
- Running Design Reviews - large changes
- Axum Routing - HTTP patterns
- Serde - contract modeling
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.