Repository navigation
Technology Requirements Demo 4
This section outlines the technologies, frameworks, tools, and platforms selected for the system, together with the justification for each choice. Each selection is mapped to the functional and non-functional requirements it supports, with maintainability, scalability, performance, and ease of development as the primary decision criteria.
Framework: Flutter (stable channel, Dart SDK >= 3.0.0)
- Justification : Supports iOS and Android from a single codebase, satisfying the requirement that the system be accessible on both platforms without maintaining two separate applications. A single codebase reduces the maintenance surface and keeps feature parity across platforms.
Target platforms: Android (Gradle/Kotlin), iOS (Xcode), macOS (Xcode) for development only .
State management: flutter_riverpod.
- Justification : Riverpod resolves providers through a container rather than through the widget tree, so state can be read and overridden without a BuildContext. This has three consequences that matter for this system. Providers can be overridden per test, so a widget test can inject a mock repository and run without a live backend, which is what allowed frontend work to proceed in parallel with backend endpoint delivery. Provider lookup errors are caught at compile time rather than surfacing as runtime exceptions when a provider is missing above a widget in the tree. Provider declarations are global and immutable, so the same provider can be used from multiple screens without lifting state or passing it down through constructors.
HTTP client and secure storage: Dio for all backend requests, and a secure-storage package for the authentication token.
- Justification : A single HTTP client with interceptors gives one place to attach the token, handle expiry, and block mutation requests while offline. The token is kept in the platform's secure storage rather than in ordinary preferences or the cache.
Offline cache: Drift (SQLite) for structured data and flutter_cache_manager for images, both scoped to the signed-in user and read-only.
- Justification : Lets users read previously loaded vaults, recipes, pantry items and shopping lists without a connection. Scoping the cache to the signed-in user prevents data from one account being shown to another on a shared device. The cache is applied through a cache-aware repository (see the Decorator Pattern in the Architectural Requirements).
Real-time client: STOMP over WebSocket, using stomp_dart_client.
- Justification : Receives live notifications and recipe lock events from the backend while the app is open (see Real-time notifications under Backend).
Voice: the platform text-to-speech and speech-recognition services, accessed through flutter_tts and speech_to_text behind a voice adapter.
- Justification : Both run on the device at no cost and without API keys, and audio does not have to be sent to a server when on-device recognition is available. The adapter keeps Android and iOS differences out of the Hands-Free Cooking feature.
Media: behind a media adapter, with files uploaded directly to object storage.
- Justification : Keeps camera, gallery and permission differences between Android and iOS in one place, and keeps file data out of the backend (see Object storage under Hosting).
Linting: flutter analyze with the flutter_lints ruleset.
- Justification : Enforces a consistent code style and catches common Dart and Flutter errors before they reach CI, reducing review overhead and defect rate.
Testing: flutter test with lcov, 80% line coverage enforced.
- Justification : Native Flutter testing tooling with coverage output that integrates with the project coverage gate. The threshold is enforced in CI.
Framework: Spring Boot 3.x on Java 21.
- Justification : Framework for building RESTful APIs, with an embedded server, a modular structure that matches the required separation of concerns, and a large ecosystem of maintained libraries. Java 21 is a long-term support release, so the runtime remains supported for the lifetime of the system.
Build tool: Maven Wrapper (mvnw), Apache Maven 3.9.15.
- Justification : The wrapper pins the Maven version per repository, so local builds, CI builds, and Docker builds all use the same toolchain and version.
Maven was selected over Gradle for three reasons. First, a pom.xml is declarative XML with a fixed structure, so the build is read rather than interpreted. A Gradle build file is a Groovy or Kotlin script, which means build logic is code that a contributor must understand before changing anything. Second, Maven is the default in Spring Boot documentation and in most published Spring examples, so debugging a build problem is easier.
The backend is a single Maven module, so Gradle's strengths in large multi-module builds and custom task graphs are not relevant at this scale. Gradle is still used in this system where the platform requires it: the Flutter Android target builds through Gradle, as generated by Flutter. The two toolchains build separate artefacts and do not interact.
Key dependencies:
- Spring Web: exposes the REST endpoints
- Spring Data JPA: handles database access through the repository pattern
- Spring Security: handles authentication and authorisation filtering
- Spring Boot Actuator: exposes the health endpoint used by Cloud Run
- Spring WebSocket with STOMP: delivers live notifications and recipe lock events.
- Bean Validation: checks incoming request bodies.
- Flyway and the PostgreSQL driver: manage and access the schema.
- JJWT: creates and validates JSON Web Tokens.
- springdoc-openapi: generates the API documentation.
- The Google Cloud Storage client: issues signed upload URLs and deletes stored media.
Authentication: JWT, stateless, 24-hour expiry
- Justification : Stateless tokens allow any container instance to serve any request without shared session storage, which is required for horizontal autoscaling. The 24-hour expiry bounds the exposure window of a leaked token. WebSocket connections authenticate with the same token when the STOMP connection is opened.
Password hashing: BCrypt, cost factor 12.
- Justification : An adaptive hashing function with a per-password salt. Cost factor 12 raises the computational cost of offline brute-force attacks while keeping login latency acceptable.
Real-time notifications: Spring WebSocket with STOMP over a raw WebSocket endpoint (/ws), an in-memory simple broker, and per-user queues (/user/queue/notifications and /user/queue/vault-events).
- Justification : Shared-vault activity such as invitations, role changes, recipe changes and edit-lock changes must reach members while the app is open, without repeated polling. A persistent connection lets the server push events immediately. Every notification is also stored in the database, so the inbox is complete for users who were offline, and recipe locks are enforced on the server, so a missed live event never affects correctness.
- Cost note : Open connections keep a backend instance running, which reduces scale-to-zero while users are connected.
Code style: Google Java Style, enforced by Checkstyle in CI
- Justification : Removes style discussion from code review and keeps the codebase uniform across contributors. Violations fail the build.
Testing: JUnit via Maven, JaCoCo with 80% instruction, branch, and method coverage enforced.
- Justification : Standard Java testing and coverage tooling. Enforcing branch and method coverage in addition to instruction coverage prevents coverage being satisfied by tests that execute code without testing its decision paths.
Language: Python 3.12.
- Justification : Provides mature data-processing libraries suited to implementing and tuning the heuristic scoring logic. Running the engine as a separate service allows it to be developed, tested, deployed, and scaled independently of the Java backend.
Linting: Ruff
- Justification: Fast linting and formatting in a single tool, which keeps the CI feedback quick and enforces a consistent Python style.
Interface: REST, called only by the Spring Boot backend.
- Justification : The engine is stateless and has no database connection. The backend sends it the data it needs and stores the results, which keeps ownership checks and persistence in one place.
Type checking: Mypy in strict mode
- Justification : The engine passes structured data through multiple scoring stages, where a type error can produce an incorrect recommendation rather than a crash. Strict typing catches these errors at check time instead of at runtime.
Testing: pytest with pytest-cov, 80% line coverage enforced.
- Justification : Supports table-driven tests over scoring inputs, which suits the rule-based nature of the engine. Coverage reporting identifies untested branches of the heuristic logic.
Database: PostgreSQL 16.
- Justification : A relational database. The domain model is highly relational (users, vaults, recipes, ingredients, inventory, preferences, notifications), so referential integrity and joins are enforced by the database rather than by application code.
Schema management: Flyway, versioned SQL migrations.
- Justification : Applies the same ordered set of migrations to local, staging, and production databases, so all environments are built on an identical schema. Migration history is version controlled and auditable.
ORM: Hibernate via Spring Data JPA, with schema validation only.
- Justification : Removes the need of hand-written CRUD code through the repository pattern and derived queries. Hibernate is configured to validate the schema rather than generate it, so Flyway remains the only source for schema changes.
Containerisation: Docker, multi-stage builds.
- Justification : Produces an identical runtime artefact for local, CI, and production execution. Multi-stage builds discard build-time dependencies, reducing image size and the number of packages exposed to vulnerability scanning.
Orchestration: Docker Compose.
- Justification : Runs the backend, engine, and database together locally with the same container boundaries used in deployment, so integration issues are found during development rather than after deployment.
CI/CD: GitHub Actions.
- Justification : Integrated with the repository, so the pipeline triggers on push and pull request without external configuration. Supports Workload Identity Federation for authenticating to Google Cloud without stored service account keys.
Coverage reporting: Codecov, reported separately for backend, engine, and frontend.
- Justification : Separate reporting prevents high coverage in one component from hiding low coverage in another, and makes the 80% threshold visible on every pull request.
Secret scanning: Gitleaks.
- Justification : Scans commit history and fails the build if a credential is detected. If a credential is detected, the corresponding keys can be rotated immediately minimizing security vulnerability.
Container vulnerability scanning: Trivy.
- Justification : Scans the backend and engine images for known Common Vulnerabilities and Exposures in operating system packages and application dependencies, so vulnerable base images are identified before deployment.
Compute: Google Cloud Run, region europe-west3.
- Justification : Serverless container platform that autoscales with request volume and scales to zero when idle. Scaling is horizontal and automatic, which supports the concurrent user requirement without manual capacity planning, and the scale-to-zero behaviour keeps idle cost at zero which helps ensure our free tier requirement is met.
Container registry: Google Artifact Registry.
- Justification : Located in the same cloud project as Cloud Run, which allows direct image deploys without external pull-rate limits. Image tags are retained for redeployment during rollback.
Object storage: Google Cloud Storage, accessed through V4 signed upload URLs. Bucket region: Europe-west-1 (Frankfurt).
- Justification : Cloud Run containers are stateless and scale to zero, so uploaded files cannot be kept on the container's filesystem. The backend authorises each upload, checks that the user owns the recipe, validates the file type and size, and issues a short-lived signed URL. The client then uploads directly to the bucket, so file data never passes through the API. Stored objects are deleted after the owning recipe is updated or deleted.
- Trade-offs : The returned photo URLs are permanent and readable by anyone who has the address, so media attached to private recipes is not access-controlled beyond the address being hard to guess. The always-free storage allowance applies to specific US regions, so the bucket region determines whether the free-tier requirement is met.
Secrets: Google Secret Manager.
- Justification : Stores credentials encrypted at rest with IAM-scoped access and access auditing. Secrets are injected into Cloud Run at runtime, so no credential is present in the image or the repository.
CI/CD authentication: Workload Identity Federation.
- Justification : The pipeline exchanges a short-lived GitHub token for a short-lived Google Cloud token. No long-lived service account key exists, so there is no key to rotate or leak.
Identity and access: Google Cloud IAM.
- Justification : Three scoped service accounts enforce component boundaries at the infrastructure level. The engine service account has no database access and only the backend service account can invoke the engine, so the boundary holds even if application code attempts to bypass it.
Database hosting: Neon, managed PostgreSQL, region eu-central-1.
- Justification : Provides managed PostgreSQL on a free tier that does not expire after a trial window, and supports separate staging and production branches within one project, which satisfies the environment parity requirement.
Landing page: Firebase Hosting.
- Justification : Free static hosting with CDN delivery (using a network of distributed servers to send website content to users quickly by serving it from the location closest to them) and HTTPS, which is sufficient for a fully static landing page.
Android distribution: Firebase App Distribution.
- Justification : Distributes signed APKs to a controlled tester list without requiring a Play Store release process.
Brand-guide hosting: GitHub Pages.
- Justification : Serves the brand style guide directly from the repository at no cost. The published guide and the versioned source stay in sync.
Nutrition data: USDA FoodData Central, accessed through an adapter (NutritionDataAdapter).
- Justification : A free, comprehensive source of ingredient and nutrient data that removes the need to maintain a nutrition database by hand. The adapter converts USDA responses into Mealchemy's own model and turns failures into a single exception type, so USDA outages, timeouts and rate limits degrade gracefully and the provider can be replaced or mocked. The API key is supplied through environment configuration and secrets, never in source code. Nutritional values are shown as estimates, with a disclaimer citing USDA FoodData Central.
The following alternatives were evaluated and not selected. Each entry states the reason the chosen technology was preferred.
| Alternative | Reason not selected |
|---|---|
| React Native | Meets the cross-platform requirement, but relies on the JavaScript for native access and has a more fragmented library ecosystem. Flutter renders its own widgets, which gives more consistent UI behaviour across the two platforms. |
| Native Android and native iOS | Best platform integration, but requires two codebases, two toolchains, and two test suites. Not viable for the team size and timeline. |
| setState only | Adequate for local widget state, but provides no mechanism for sharing state across screens, which the vault, inventory, and preference features require. |
| Provider | Simpler than Riverpod and by the same author, but resolves through BuildContext, so a missing provider can fail at runtime and test overrides require wrapping the widget tree. |
| BLoC | Enforces an explicit event-to-state flow that suits complex state machines, but adds extra code per feature regardless of how complex or simple it is. Our screens are largely CRUD screens which this approach overcomplicates. |
| GetX | Combines state, routing, and dependency injection in one package, but that coupling makes layers harder to test in isolation and works against the single API service boundary. |
| Alternative | Reason not selected |
|---|---|
| Node.js with Express | Faster to start, but weaker compile-time guarantees and no equivalent of Spring Data JPA for the relational model. Spring Boot's structure enforces the layered architecture the system requires. |
| Python (FastAPI or Django) for the entire backend | Would unify the language with the engine, but the team's Java experience is stronger and Spring Security provides authentication infrastructure that would otherwise be hand-written. |
| Gradle as the build tool | Faster on incremental and multi-module builds, but the backend is a single module and CI builds run in a cold container where that advantage does not apply. Maven's declarative POM is also readable without learning a build scripting language. Gradle is still used for the Flutter Android target, where the platform requires it. |
| Session-based authentication | Requires shared session storage across autoscaled instances, which conflicts with the stateless horizontal scaling model. |
| Firebase Authentication | Removes the need to implement authentication, but moves user identity into a managed service that would then need to be joined against the relational data, complicating ownership checks and data export. |
| Argon2id for password hashing | Stronger memory-hard properties, but higher memory usage per login on a container with a constrained memory allocation. BCrypt at cost factor 12 was judged sufficient for the threat model. |
| Alternative | Reason not selected |
|---|---|
| Implementing the heuristics inside the Java backend | Removes one service and one network hop, but couples recommendation logic to the API deployment cycle and gives up Python's data-processing libraries. Keeping it separate allows the engine to be redeployed and tuned independently. |
| A hosted LLM API for recipe generation | A per-call API cost conflicts with the zero-cost budget constraint. A deterministic engine is also testable and reproducible, which an LLM response is not. The heuristic approach gives a more repeatble outcome as it can be traced. |
| Alternative | Reason not selected |
|---|---|
| MySQL or MariaDB | Capable relational alternatives, but weaker support for the JSON and full-text features that later search functionality may require, and Flyway and JPA integration is equally good on PostgreSQL. |
| MongoDB | Document storage would simplify storing recipes as single objects, but the domain requires joins across users, inventory, and ingredients, and lacks enforced referential integrity. |
| Hibernate schema generation (ddl-auto) | Convenient in development, but generates schema changes implicitly and provides no migration history or rollback path against populated databases. |
| Alternative | Reason not selected |
|---|---|
| AWS free tier (EC2 or Elastic Beanstalk) | The compute free tier expires 12 months after account creation, which conflicts with the requirement for a system that remains deployable at no cost beyond that window. As well as limited free-tier usage which our system is thought to surpass. |
| Render or Railway | Viable free tiers, but free instances sleep and have less predictable cold-start behaviour. Neither offers the IAM-level service isolation used to enforce the engine and database boundary. |
| Google Kubernetes Engine | Provides more control over scheduling and networking, but requires cluster management effort disproportionate to a two-service system, and a cluster incurs cost even when idle. |
| Docker Hub as the registry | Subject to pull-rate limits on anonymous and free accounts, and adds a cross-provider hop during deployment. Artifact Registry sits in the same project as Cloud Run. |
| Storing secrets only in GitHub Actions secrets | Sufficient for build-time values, but does not provide runtime secret injection or access auditing for the deployed service. |
| Supabase for database hosting | Provides managed PostgreSQL with a free tier, but free projects pause after a period of inactivity, which could make the deployed system unavailable without warning. |
| SonarCloud or Coveralls for coverage | Comparable reporting. Codecov was selected for its per-component reporting and pull request annotations. |
updated 30 September 2026.
· Team PUL5E · COS 301 · University of Pretoria · 2026 ·
Team PUL5E · COS 301
Demo 4
-
Software Requirements Specifications (SRS)
-
Software Architecture Specifications (SAS)
Demo 3
-
Software Requirements Specifications (SRS)
-
Software Architecture Specifications (SAS)
Demo 2
-
Software Requirements Specifications (SRS)
-
Software Architecture Specifications (SAS)
Demo 1
- Docker Setup
- CI Setup
- ACT Setup
- pgAdmin Setup
- Security Setup
- Render Setup
- Design Spec Deployment
- Wiki Submodule
Updated: 13 September 2026