Non-Functional Specifications: Performance, Security, Scalability
How to write effective Non-Functional Requirements (NFR) for Golang projects — measurable performance targets, security specs, and scalability requirements for SDD.
Non-Functional Specifications: Performance, Security, Scalability
There is one part of the spec that gets skipped most often: Non-Functional Requirements (NFRs). Developers focus on “what the system should do” (functional) but routinely forget to define “how the system should behave” (non-functional).
The result? Features that “work correctly” yet time out after 10 seconds. APIs that are functional but have no rate limiting, leaving them wide open to abuse. Services that run smoothly at 100 req/s and collapse at 1000. This article covers how to write a non functional specification golang that is genuinely useful — specific, measurable, and usable as an acceptance criterion for testing.
09.1 NFR vs Functional Requirements
The line between the two is easy to blur, so it helps to see them side by side. The comparison below pairs each functional statement with its non-functional counterpart to make the distinction concrete.
1Functional: "System accepts POST /orders and creates an order"
2Non-functional: "System responds to POST /orders in < 500ms at p95"
3
4Functional: "System validates the JWT token"
5Non-functional: "System rejects 100% of requests without a valid JWT, with < 5ms latency overhead"
6
7Functional: "System reduces stock when an order is created"
8Non-functional: "Stock reduction must be atomic — no partial update even if the server restarts"The takeaway: functional requirements describe behavior, while NFRs describe the quality of that behavior. Both matter, but poor NFRs (or none at all) are a more common cause of production incidents than functional bugs.
09.2 Seven NFR Categories for Golang APIs
A good NFR section is exhaustive by category, not by accident. The seven categories below form the checklist every Golang API should be measured against so nothing important is quietly omitted.
Category 1: Performance — latency and throughput targets. Category 2: Availability — uptime, RTO, RPO, graceful degradation. Category 3: Security — auth, input validation, rate limiting, data protection. Category 4: Scalability — horizontal scaling, DB connections, auto-scaling. Category 5: Observability — metrics, logging, tracing, alerting. Category 6: Data Integrity — atomicity, idempotency, audit trail. Category 7: Testability — coverage targets, race-condition testing.
The takeaway: walking these seven categories one at a time turns “did we think about non-functional concerns?” from a vague worry into a concrete, coverable list.
09.3 Performance NFR Example
Performance is the category most easily hand-waved with words like “fast.” The example below replaces adjectives with numbers, splitting latency and throughput into individually testable targets.
1## Performance NFR — Create Order Endpoint
2
3### Latency
4- NFR-P1: Response time < 200ms at p50 under normal load
5- NFR-P2: Response time < 500ms at p95 under normal load
6- NFR-P3: Response time < 2000ms at p99 during peak load (flash sale)
7- NFR-P4: Hard timeout: no request exceeds 10 seconds
8
9### Throughput
10- NFR-P5: Must handle 200 req/s sustained
11- NFR-P6: Burst capacity: 500 req/s for a maximum of 30 secondsNotice that every target names a percentile and a load condition — “p95 under normal load,” not just “fast.” That precision is what lets a benchmark or load test later prove the NFR was met or violated.
09.4 Security NFR Checklist
Security NFRs are the ones most often assumed rather than written, which is exactly why they fail. The checklist below turns “make it secure” into concrete, verifiable boxes across authentication, input handling, and data protection.
1## Security Checklist — All Golang API Endpoints
2
3### Authentication
4- [ ] JWT validation: signature, expiry, issuer
5- [ ] Token not stored in logs
6
7### Authorization
8- [ ] Users can only access their own resources
9- [ ] Admin role check before admin operations
10
11### Input Validation
12- [ ] Max request size enforced (default 1MB)
13- [ ] UUID format validated before DB queries
14- [ ] JSON schema validation before business logic
15
16### Rate Limiting
17- [ ] Per-user: 100 req/min for normal writes, 10 req/min for sensitive ops
18- [ ] Per-IP: 1000 req/min (DDoS protection)
19- [ ] Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
20
21### Error Handling
22- [ ] Error messages don't leak internal information
23- [ ] 5xx errors expose only a generic message to the client
24- [ ] Internal error details only in logs, never in the response
25
26### Sensitive Data
27- [ ] Never-log fields: password, credit_card, cvv, otp
28- [ ] PII masked in logs: email, phone, address, name
29- [ ] HTTPS enforced in productionThe takeaway: a checklist converts security from tribal knowledge into an auditable list — each unchecked box is a specific, known exposure rather than a vague unease that “we should probably harden this.”
09.5 Implementing NFRs in Go Code
NFRs are only real if they show up in code. The first example wires per-user and per-IP rate limiting (NFR-S8, NFR-S9) into an Echo middleware, complete with the rate-limit response header the checklist demands.
1func (rl *RateLimiter) Middleware(next echo.HandlerFunc) echo.HandlerFunc {
2 return func(c echo.Context) error {
3 if !rl.checkUserLimit(c) {
4 c.Response().Header().Set("X-RateLimit-Limit", "100") // NFR-S10
5 return c.JSON(http.StatusTooManyRequests, map[string]string{
6 "error_code": "RATE_LIMIT_EXCEEDED",
7 "message": "rate limit exceeded, please try again later",
8 })
9 }
10 return next(c)
11 }
12}The rate limiter defends against abuse, but it does nothing about a single request that hangs forever. The middleware below enforces the hard-timeout NFR (NFR-P4) by attaching a deadline to the request context.
1func TimeoutMiddleware(timeout time.Duration) echo.MiddlewareFunc {
2 return func(next echo.HandlerFunc) echo.HandlerFunc {
3 return func(c echo.Context) error {
4 // NFR-P4: no request exceeds the timeout
5 ctx, cancel := context.WithTimeout(c.Request().Context(), timeout)
6 defer cancel()
7 c.SetRequest(c.Request().WithContext(ctx))
8 // Handle timeout via ctx.Done()...
9 return next(c)
10 }
11 }
12}Enforcement means little without visibility, so the final piece is a metric. The counter below satisfies the observability NFR (NFR-O2) by exposing order creations to Prometheus, labelled by status.
1var OrdersCreatedTotal = prometheus.NewCounterVec(
2 prometheus.CounterOpts{Name: "orders_created_total"},
3 []string{"status"},
4)Taken together, these three snippets show the pattern: each NFR maps to a specific, small piece of code — a middleware, a context deadline, a metric — and each carries the NFR ID in a comment so the requirement and its implementation stay linked.
09.6 NFR Performance Verification with Benchmark Tests
A performance NFR without a test is a wish. The Go test below turns NFR-P2 into an executable assertion, measuring p95 latency over a thousand iterations and failing the build if it exceeds the budget.
1// Verify NFR-P2: p95 < 500ms
2func TestNFR_CancelOrder_MaxLatency(t *testing.T) {
3 const iterations = 1000
4 const p95Limit = 500 * time.Millisecond
5
6 durations := collectDurations(t, iterations)
7 sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] })
8
9 p95 := durations[int(float64(iterations)*0.95)]
10 if p95 > p95Limit {
11 t.Errorf("NFR-P2 violated: p95 latency %v exceeds limit %v", p95, p95Limit)
12 }
13}The takeaway: encoding the NFR as a failing test makes the requirement self-policing — a future change that regresses p95 latency breaks CI immediately, instead of being discovered by a user during a flash sale.
09.7 NFR Checklist Integration in CI/CD
If NFR verification lives only on someone’s laptop, it will eventually be skipped. The workflow below runs performance, security, and coverage NFR checks on every push so the guarantees are enforced automatically.
1# .github/workflows/nfr-check.yml
2jobs:
3 performance-nfr:
4 steps:
5 - run: go test -bench=. -benchtime=10s ./internal/usecase/...
6 - run: go test -run TestNFR_ -timeout 120s ./...
7
8 security-nfr:
9 steps:
10 - uses: securego/gosec@master
11
12 coverage-nfr:
13 steps:
14 - run: |
15 go test -coverprofile=coverage.out ./internal/usecase/...
16 # Fail if coverage < 80% (NFR-T1)
17 go tool cover -func=coverage.out | grep total | awk '{if ($3 < "80.0%") exit 1}'The takeaway: moving NFR verification into CI is what makes the requirements binding — performance regressions, security findings, and coverage drops all become merge-blocking failures rather than good intentions.
09.8 NFR for Microservice Communication
When services depend on one another, the contract between them needs its own NFRs — latency budgets, retry rules, and fallback behavior. The spec below defines the SLA the Order Service expects from the Product Service.
1## NFR for Order Service → Product Service Communication
2
3### Latency SLA
4- NFR-MS1: Expect Product Service to respond in < 100ms
5- NFR-MS2: On timeout, retry 2x with exponential backoff (100ms, 200ms)
6- NFR-MS3: After 3 consecutive timeouts, circuit breaker opens for 30 seconds
7
8### Availability SLA
9- NFR-MS4: Order Service must work even when Product Service is down (if a valid cache exists)
10- NFR-MS5: Product data cache TTL: 5 minutesThe takeaway: inter-service NFRs turn a hidden runtime coupling into an explicit, testable contract — the retry counts, timeouts, and circuit-breaker thresholds are decided in the spec rather than improvised in an incident.
09.9 Anti-Patterns to Avoid
Most bad NFRs fail in the same recognizable ways. The two comparisons below contrast a useless NFR with its measurable rewrite so the fix is unmistakable.
1Anti-pattern 1 — Unmeasurable NFR
2❌ "System must be fast and reliable"
3✅ "Response time < 500ms at p95, uptime 99.9%"The first anti-pattern fails because it names no number to test against; the second fails in the opposite direction, setting a target no system can realistically meet, as shown next.
1Anti-pattern 2 — Over-aspirational without basis
2❌ "Response time < 10ms for all operations"
3✅ "< 50ms for cached reads, < 300ms for DB reads"Beyond those two lies the most insidious anti-pattern of all: an NFR with no verification method. Such an NFR will eventually be violated — quite possibly without anyone noticing — which is why every NFR you write must carry a concrete way to check it.
09.10 NFR for AI-Powered Features
AI features carry a unique risk profile: streaming latency, provider outages, runaway token costs, and content quality. The spec below captures those concerns for an AI product-description generator.
1## NFR for AI Product Description Generator
2
3- NFR-AI1: Streaming response must start in < 2 seconds (Time to First Token)
4- NFR-AI2: Complete response within < 30 seconds
5- NFR-AI4: If the LLM API is down → 503; DON'T return placeholder text as "AI generated"
6- NFR-AI8: Rate limit per user: 10 AI requests per hour
7- NFR-AI9: Monthly budget cap: alert at $500, hard stop at $1000
8- NFR-AI10: All output filtered for inappropriate contentThe takeaway: AI features need NFRs that classic endpoints don’t — a cost ceiling, a first-token latency target, and an explicit “fail loudly, never fake it” rule — because their failure modes are financial and reputational, not just slow.
09.11 NFR Evolution: From MVP to Scale
The right NFR for launch is usually the wrong NFR at scale. The plan below shows the same endpoint’s targets tightening as traffic grows, so the team doesn’t over-engineer on day one or under-provision on day one thousand.
1### Phase 1: MVP (0–10K orders/day)
2Performance: p95 < 1000ms, Rate limit: 50 orders/user/hour, Availability: 99.5%
3
4### Phase 2: Growth (10K–100K orders/day)
5Performance: p95 < 500ms, Rate limit: 20 orders/user/hour, Availability: 99.9%
6
7### Phase 3: Scale (100K+ orders/day)
8Performance: p95 < 200ms, Rate limit: adaptive, Availability: 99.95%The takeaway: NFRs are phase-bound, not permanent. Writing them as a staged plan prevents both premature optimization at MVP and painful surprises when growth arrives.
09.12 NFR Prompting Strategy
Claude can draft strong NFRs, but only if you feed it real system context. The prompt below supplies the platform, load profile, and infrastructure so the numbers it proposes are grounded rather than generic.
1I'm building a CREATE ORDER endpoint for Santekno Shop.
2
3System context:
4- B2C e-commerce platform, Indonesia
5- Peak: flash sale, 10x normal load
6- Normal load: 100 req/s platform-wide
7- Infrastructure: Kubernetes on AWS, RDS PostgreSQL, ElastiCache Redis
8
9Help me define specific, measurable NFRs for:
101. Performance (latency and throughput)
112. Availability (uptime, RTO, RPO)
123. Security (auth, rate limiting, data protection)
134. Scalability (horizontal scaling, DB connections)
145. Observability (metrics, logging, alerting thresholds)
15
16For each NFR:
17- Give specific numbers (not "fast" but "< 500ms")
18- Explain the rationale behind the number
19- Show how to measure/verify it
20- Avoid NFRs that can't be tested or verifiedThe takeaway: the quality of AI-generated NFRs is bounded by the context you provide — hand Claude the load profile and infrastructure, and it returns numbers you can defend; withhold them, and it returns platitudes.
09.13 Communicating NFR to Non-Technical Stakeholders
NFRs also need a translation for product and business audiences who don’t speak in percentiles. The summary below restates the same targets in plain language a stakeholder can actually reason about.
1## NFR Summary for the Product Team
2
3**Speed:**
4"The checkout API responds in under half a second for 95% of requests.
5During flash sales it may be slightly slower, but never more than 2 seconds."
6
7**Reliability:**
8"This service is available 99.9% of the time — under 9 hours of downtime per year.
9If issues occur, the system recovers within 5 minutes."
10
11**Security:**
12"Each customer can only access their own orders.
13There's spam protection: at most 20 orders per hour per user."The takeaway: translating NFRs into business language earns the buy-in that funds them — a stakeholder who understands “under 9 hours of downtime a year” will support the infrastructure that “99.9% uptime” quietly requires.
09.14 NFR Template for Santekno Shop Projects
Consistency across features comes from a shared starting point. The reusable template below gives every new spec the same NFR skeleton, so no category is forgotten from one feature to the next.
1## Non-Functional Requirements Template
2
3### Performance
4- [ ] p50 latency target: [X]ms at [Y] concurrent users
5- [ ] p95 latency target: [X]ms at [Y] concurrent users
6- [ ] Throughput target: [X] req/s sustained
7- [ ] Hard timeout: [X] seconds
8- [ ] Verification: [load test script or benchmark test]
9
10### Security
11- [ ] Authentication: [mechanism and requirement]
12- [ ] Authorization: [who can access this resource]
13- [ ] Rate limiting: [per-user and per-IP]
14- [ ] Sensitive data handling: [list fields and treatment]
15
16### Observability
17- [ ] Required metrics: [list metric names]
18- [ ] Log level rules: [INFO/WARN/ERROR for which conditions]
19- [ ] Alert thresholds: [alert conditions and severity]
20- [ ] Tracing: [yes/no, granularity]
21
22### Data Integrity
23- [ ] Transaction requirement: [single/distributed]
24- [ ] Idempotency: [yes/no, mechanism]
25- [ ] Audit trail: [yes/no, retention period]The takeaway: a template makes thoroughness the default — each feature inherits the same categories, so completeness stops depending on whether the author happened to remember observability that day.
09.15 Capacity Planning with NFR
NFRs aren’t just acceptance criteria — they are inputs to infrastructure sizing. The prompt below turns throughput and connection limits into concrete capacity questions Claude can help answer.
1Based on the following NFRs, help me do capacity planning for the Order Service:
2
3NFRs:
4- 200 req/s sustained throughput
5- 500 req/s burst capacity
6- p95 < 500ms
7- Max 10 DB connections per instance
8
9Questions to answer:
101. Minimum instances needed to handle this load?
112. PostgreSQL connection pool size?
123. Memory per instance?
134. Storage IOPS required?
14
15Assumptions:
16- Each request: 1 DB read + 2 DB writes (average)
17- DB read = 5ms, DB write = 10ms
18- App processing time (non-DB) = 50msThe takeaway: measurable NFRs double as capacity math — throughput and per-instance connection caps translate directly into instance counts and pool sizes, so the same numbers that gate tests also size the cluster.
09.16 Data Integrity NFRs
Data correctness is a non-functional concern that silently underpins every feature. The spec below states the atomicity, idempotency, and audit guarantees the system must never violate.
1## Data Integrity NFR
2
3- NFR-D1: All write operations must run inside a database transaction
4- NFR-D2: Idempotency: the same operation can be repeated safely
5- NFR-D3: Optimistic locking for concurrent updates
6- NFR-D4: Audit trail: every order status change must be recorded in order_history
7- NFR-D5: Soft delete: no physical delete of business dataThe takeaway: data-integrity NFRs are the invisible floor beneath every feature — they rarely appear in a demo, but their absence is what turns a small bug into corrupted records and an unrecoverable incident.
09.17 NFRs for Background Jobs
Background jobs have a different NFR profile than HTTP endpoints — timing, batch behavior, and idempotency dominate over latency. The spec below captures those concerns for a cleanup job.
1## NFR — Cleanup Expired Carts Job
2
3- NFR-J1: Must complete within 5 minutes per run
4- NFR-J3: DB queries must not lock tables — use batches with small transactions
5- NFR-J4: Batch size: max 500 records per batch
6- NFR-J5: Pause between batches: 100ms to prevent DB overload
7- NFR-J6: Idempotency: re-running must be safe (no double deletes)
8- NFR-J10: Alert if the job fails 3 consecutive timesThe takeaway: jobs need NFRs written for how they actually fail — locking the table, overrunning their window, or double-processing on a retry — none of which a latency percentile would ever catch.
09.18 Tips & Gotchas
Writing good NFRs is as much about judgment as rigor. The tips and gotchas below capture the habits that keep NFRs honest and the mistakes that make them counterproductive.
💡 Tip 1: Every NFR must have a clear verification method — “< 500ms” without a test is useless.
💡 Tip 2: Start with the most business-critical NFRs — security always, then user-visible performance, then data integrity.
💡 Tip 3: Review NFRs quarterly as load grows — what works for 100 users may fail at 100,000.
💡 Tip 4: Use NFRs to drive architecture decisions — “service must be stateless (NFR-SC1)” is what keeps session state out of the application layer.
⚠️ Gotcha 1: Over-tight NFRs can be premature optimization — “< 10ms for all operations” at MVP stage wastes engineering effort better spent elsewhere.
⚠️ Gotcha 2: Security NFRs require domain knowledge — “system must be secure” is useless without understanding your specific threat model.
⚠️ Gotcha 3: NFRs can conflict with the infrastructure budget — “99.999% uptime” demands an expensive multi-region setup.
⚠️ Gotcha 4: Observability NFRs are often skipped — yet they matter most in production, because without good metrics and alerting you can’t detect problems before users complain.
The through-line: NFRs are a balancing act — specific enough to test, ambitious enough to matter, but never so aggressive that they burn effort or budget the current phase can’t justify.
09.19 NFR Integration in the Full Spec
The most effective NFRs don’t live in a separate document nobody opens — they sit inside the feature spec, right beside the functional acceptance criteria. The excerpt below shows NFRs woven directly into a spec, each with its verification.
1# specs/order/create-order.md v2.0
2
3## Acceptance Criteria (Functional)
4[AC1–AC14 as before]
5
6## Acceptance Criteria (Non-Functional)
7
8### Performance
9- NFR1: p95 < 500ms at 200 concurrent users
10 Verification: scripts/load-test-create-order.js
11
12### Security
13- NFR2: 20 order creations/user/hour rate limit
14 Verification: test/integration/rate_limit_test.go
15
16### Reliability
17- NFR3: Stock decrement must be atomic
18 Verification: TestCreateOrder_ConcurrentRequests_NoOversellThe takeaway: co-locating NFRs with functional ACs guarantees developers actually read them during implementation, and pairing each with a verification path makes “was the NFR met?” a test result rather than an opinion.
09.20 Summary
Non-Functional Requirements are not “nice to have” — they are the difference between a system that “works” and one that is genuinely production-worthy.
Seven NFR categories: Performance, Availability, Security, Scalability, Observability, Data Integrity, and Testability.
Good NFRs must be: Specific (concrete numbers), Measurable (a verification method exists), Achievable (realistic given constraints), Relevant (appropriate to the business context), and Time-bound (valid for the current phase).
Integration with the feature spec: NFRs are most effective inside the feature spec, not as a separate document — that’s what ensures they’re read during implementation.
Evolution over time: NFRs for MVP differ from those needed at scale. Schedule regular reviews and plan NFR evolution as the business grows.
In the next article we enter Part 3 of the series — from spec to code. First up: Plan Mode in Claude Code — how to use the planning phase to produce a solid implementation plan before writing the first line of code.