Non‐Functional Requirements Demo 2 - COS301-SE-2026/Mealchemy GitHub Wiki

3.1.5 Non-Functional Requirements

Non-functional requirements, or quality requirements, describe how the system performs rather than what it does. Each requirement below has a target, the design decision that delivers it, and the evidence available for it.

Five quality requirements are targeted: Performance, Scalability, Security, Reliability and Availability, and Maintainability.


QR1: Performance

# Requirement Target
QR1.1 Response time for user requests under normal load Under 2 seconds for 95% of requests
QR1.2 Concurrent users supported At least 500

How the architecture supports this

Application and database are in the same region. Cloud Run runs in europe-west3 (Frankfurt) and the Neon database in eu-central-1. Placing them together removes cross-region network latency from every database call. If the database were in a different region, every query would add tens of milliseconds before any work is done.

No session lookup on each request. Login state is carried in the JWT and verified by checking its signature. The server does not look up a session record to identify the caller, so authentication costs no database call.

Database connections are released early. Connections are returned to the pool when the service method finishes, not when the HTTP response is sent. Under load this matters because the pool is the scarcest resource, so holding connections longer than necessary is what causes requests to queue.

Work is not duplicated across services. The recommendation engine is a separate service, so recommendation processing does not compete for the same thread pool as ordinary API requests.

Also noted and adressed:

Two properties are known from the deployment configuration rather than from testing. The first is cold start. The service is configured to scale to zero when idle, so the first request after an idle period waits for a container to start. This is a deliberate trade to stay inside the free tier. The uptime monitor calling the health endpoint every 5 minutes keeps production warm during normal hours, so users are unlikely to meet a cold start in practice. The second is the connection pool size, which sets the point at which requests begin to queue rather than being served immediately.


QR2: Scalability

# Requirement Target
QR2.1 Workload increase supported Up to 200%
QR2.2 Performance loss under that increase No more than 10%
QR2.3 Architectural change required to handle the increase None

How the architecture supports this

The backend holds no state between requests. Everything needed to serve a request arrives with the request. There is no in-memory session, no user data cached on one instance, and no instance that owns a particular user. This is the property that makes horizontal scaling possible. Any instance can serve any request, so adding instances adds capacity without any coordination between them.

Cloud Run adds and removes instances automatically. Instance count follows request volume. No manual step, redeploy, or configuration change is needed when load rises, and instances are removed again when it falls.

The engine scales separately. Because recommendation work runs in its own service, heavy recommendation load scales that service without forcing the API to scale with it, and the reverse.

Also noted and adressed:

What can be stated with confidence is QR2.3. The architecture requires no change to handle more load, because scaling is horizontal, automatic, and bounded only by configuration values. Instances scale freely, but they all share one database. The connection pool is therefore the first thing that will saturated, before instance count becomes the constraint. Scaling past that point means increasing the pool and the database plan and not changing the application.


QR3: Security

# Requirement Target
QR3.1 All endpoints except register, login, and health require a valid token 100%
QR3.2 Missing, invalid, or expired token HTTP 401
QR3.3 Request for data the caller does not own HTTP 403
QR3.4 Passwords hashed before storage BCrypt cost 12 or higher
QR3.5 Network traffic encrypted TLS 1.2 or higher on every connection
QR3.6 No passwords or keys committed to the repository 0 findings per pull request
QR3.7 No container image with a known serious vulnerability 0 findings per pull request

Authorisation is based on ownership. Before returning or changing anything, the service checks that the signed-in user owns that data. A user who is correctly signed in still cannot read or edit another user's recipes, pantry, or vaults.

How the architecture supports this

Mechanism Where it lives
Token signature and expiry checked on every request JwtUtil, JwtAuthFilter
Everything requires authentication unless explicitly allowed SecurityConfig
Passwords hashed with BCrypt at cost 12 SecurityConfig
Ownership checked before reading or changing data Service layer, across 8 services
Errors returned without stack traces or internal detail GlobalExceptionHandler
Passwords and keys supplied at start-up, never stored in code Google Secret Manager
CI signs in to the cloud without a stored key Workload Identity Federation
Separate identities for deploying and for running 3 service accounts
Recommendation engine not reachable from the internet Deployed with --no-allow-unauthenticated
Every pull request scanned for committed secrets Gitleaks
Every pull request scanned for vulnerable images Trivy
Third-party build steps pinned to exact commit hashes All workflows

Ownership checks sit in the service layer, not the controller. A new endpoint added to a controller calls into the service to do its work, and the check runs there, so the new endpoint cannot accidentally skip it. If the check lived in the controller, every new endpoint would have to remember to repeat it.

The engine has no public address and no database access. Its Cloud Run service rejects unauthenticated calls, so it can only be reached by the backend's identity. Separately, its service account has no database permissions. Both would have to fail for the engine to reach user data directly.

Evidence

These figures are counts from the codebase and the test suite. They are reproducible by running the test suite and inspecting the pipeline output.

Measurement Value Target Verdict
Endpoints requiring authentication All except register, login, health 100% Met
Tests confirming unauthenticated access is refused 22 All pass Met
Tests confirming ownership is enforced 109 All pass Met
Ownership checks in the code 33, across 8 services Met
BCrypt cost factor 12 12 or higher Met
Encrypted connections 3 of 3 100% Met
Committed secrets found 0 0 Met
Serious image vulnerabilities found 0 0 Met

Ownership is confirmed at two levels. Service tests check the rule in isolation. Integration tests confirm that a blocked request reaches the client as an HTTP 403 after passing through the real security filters and a real database.


QR4: Reliability and Availability

# Requirement Target
QR4.1 Production API available 99.9% uptime
QR4.2 Scheduled maintenance 2 hours per month or less
QR4.3 Recovery by returning to a known-good version Under 5 minutes
QR4.4 A failed deployment does not interrupt service 0 seconds downtime
QR4.5 A failed database migration stops the deployment 0 partial deployments
QR4.6 Database operations complete fully or not at all 0 lost transactions

How the architecture supports this

Mechanism Effect
Every previous version is kept as a revision Returning to an earlier version is a traffic switch, not a rebuild
New version deployed with no traffic, then switched over A failed deployment leaves the old version serving
Database migration runs before anything is deployed A failed migration stops the process before any code goes live and deployed
Production deployments require reviewer approval No unreviewed change reaches production
Health checks in the container and in the application after deployemnt An unhealthy container receives no traffic
Health check after switching back A switch to a broken version is caught immediately
Database transactions on multi-step operations Nothing is left half-written
External uptime monitor every 5 minutes Independent record of availability

The recovery time target is met by design rather than by effort. Because every past revision still exists, returning to one is a routing change that takes seconds. Nothing is rebuilt, so recovery time does not depend on build duration.

Transaction safety is enforced at the service layer. Registration is the clearest case: it creates a user, a profile, a preferences record, and a vault as one unit. If any step fails, all four are undone, so a half-registered account cannot exist.

Evidence

Uptime is monitored from 29 July 2026, when monitoring was pointed at the current production system. The monitor is a keyword monitor. It does not only check that the server replied, it checks that the reply contains "status":"UP". A server that answers while unhealthy is recorded as down, which a plain connection check would miss.

Measurement Value Target Verdict
Monitoring since 29 July 2026
Monitor type Keyword, checks the reply says "UP" Met
Check interval 5 minutes
Uptime over the monitored period 99.57 99.9% Negligable difference of 0.33 from target. Met
Scheduled maintenance taken 0 hours 2 h per month or less Met
Deployments causing downtime 0 0 Met

The monitor is confirmed to detect real failures. A two minute outage was recorded and alerted on 29 July 2026.

Limitation. Returning to an earlier version restores the application but does not undo database changes. An earlier version can only run if it still works with the current database structure. Where a change removes or restricts a column, recovery means fixing forward instead, and the rollback workflow asks the operator to confirm this before proceeding.


QR5: Maintainability

# Requirement Target
QR5.1 Backend test coverage enforced in CI 80% or higher
QR5.2 Frontend test coverage enforced in CI 80% or higher
QR5.3 Every measured backend class has at least one covered line 1 line or more
QR5.4 A change on main reaches production without manual steps Under 2 hours
QR5.5 No merge on a failing build 100% gated
QR5.6 Controllers do not call repositories directly 0 violations
QR5.7 Database changes go through versioned migrations 100%

What coverage includes

Coverage measures application logic. It excludes code with no logic to test: the start-up class, configuration classes, data-transfer objects, database entities, repository interfaces, and generated files. Including these would inflate the figure without indicating anything about test quality.

Backend coverage is checked on three counts: instruction, branch, and method. A separate rule requires every measured class to have at least one covered line, so a class with no tests at all is noticed even with a good overall average.

How the architecture supports this

Mechanism Effect
Separate CI jobs per part of the system Changing one part does not rebuild the others
Controller, service, repository layering Logic can be tested without a web server or a database
Repository interfaces with real and mock implementations The app can be tested without a running backend
Coverage gates that fail the build Coverage cannot drop
Checkstyle, flutter analyze, Ruff, mypy Style and type errors are caught before review
Versioned migrations with the schema validated at start-up A mismatch fails immediately instead of corrupting data
Deployments filtered by changed path Documentation changes do not redeploy services

Evidence

Coverage figures come from Codecov, which receives a report from every CI run on main. The backend figure comes from JaCoCo, the frontend figure from lcov.

Measurement Value Target Verdict
Backend coverage 97% 80% or higher Met
Frontend coverage 88% 80% or higher Met
Combined coverage 89% 80% or higher Met
Database changes made outside migrations 0 0 Met

Deployment time is bounded by the pipeline rather than by a person. A merge to main triggers the build, the migration, and the deployment with no manual step in between, so the 2 hour target is set by pipeline duration and approval wait, not by developer availability.


Mapping of quality requirements to architectural decisions

QR1 - Perfroamcne

QR2 - Saclabaility

QR3 - Security

QR4 - Reliability and Avaialbility

QR5 - Maintainability

QR Requirement Decision Why it delivers the requirement
QR1 Response under 2 s Application and database in the same region Removes cross-region latency from every database call
QR1 Response under 2 s Login state carried in the token Authentication costs no database lookup
QR1 Response under 2 s Connections released when the service method ends The scarcest resource is held for the shortest time
QR1 Cold start accepted Scale to zero when idle Trades a start-up delay for zero cost while idle; the uptime monitor keeps production warm
QR2 200% more load Stateless backend Any instance serves any request, so instances can be added without coordination
QR2 No architectural change Limits held in configuration Instance and pool limits are free-tier settings; raising them changes a value, not the design
QR2 Independent scaling Engine as a separate service Recommendation load and API load scale separately
QR3 Authentication Signed tokens, deny by default Signature and expiry checked on every request; endpoints are closed unless opened
QR3 Authorisation Ownership checked in the service layer Placed below controllers, so a new endpoint cannot skip it
QR3 Credential protection Secrets supplied at start-up No credential in the image or the repository; CI authenticates without a stored key
QR3 Service isolation Separate identities per role The engine has no public address and no database permission
QR3 Supply chain Pin and scan Build steps pinned to exact hashes; images scanned every pull request
QR4 Recovery under 5 min Every revision retained Recovery is a traffic switch, so it does not depend on build time
QR4 No downtime on failure Deploy and switch-over separated The new version takes no traffic until it is confirmed healthy
QR4 Safe database changes Migration runs first A failed migration stops the deployment before any code goes live
QR4 99.9% availability Managed platform, external monitoring The platform replaces unhealthy containers; the monitor checks the reply says "UP"
QR5 80% coverage Layering with injected dependencies Logic is testable without a web server or database
QR5 Deploy under 2 h Automated pipeline No manual step between merge and production
QR5 Schema integrity Versioned migrations only Schema is validated at start-up, so a mismatch fails immediately

Last updated: 30 July 2026