Architectural Requirements Demo 4 - COS301-SE-2026/Mealchemy GitHub Wiki
Architectural Requirements
Table of Contents
- Architectural Patterns
- Design Patterns
- Architectural Constraints
- Architectural Diagram
- Mapping Quality Requirements to Architectural Decisions
Architectural Patterns
Mealchemy follows a layered 4-tier architecture combined with a client-server communication model. The system is separated into presentation, access, services, persistence, and recommendation-processing responsibilities. This separation of concerns improves maintainability, modularity, scalability, and independent development across frontend, backend, database, and recommendation components.
Client-Server Architecture
Mealchemy follows a client-server architecture. The mobile frontend acts as the client and is responsible for presenting the user interface and capturing user interactions. The backend server is responsible for processing requests, enforcing business rules, managing authentication, coordinating recommendation processing, and accessing stored application data.
The frontend does not communicate directly with the database. The API sits between the client (presentation layer) and the services layer, providing a single point of entry for requests. All requests and data operations are routed through the backend to ensure security, validation, and consistent application behaviour.
Layered 4-Tier Architecture
The backend follows a layered structure where each layer has a specific responsibility:
- Controller Layer: Receives requests from the frontend and returns responses.
- Service Layer: Contains business logic, validation, and application rules.
- Repository Layer: Handles data access and persistence operations.
- Entity/Model Layer: Represents persistent application data.
Mealchemy also applies MVC style separation within this layered architecture. Controllers manage incoming HTTP requests, models/entities represent application data, and services coordinate business logic before data is accessed through repositories.
This structure improves modularity and maintainability by separating request handling, business logic, and database access responsibilities.
Recommendation Service Architecture
Mealchemy includes a modular recommendation service responsible for heuristic-based recommendation processing. The recommendation service supports:
- Preference filtering
- Pantry matching
- Recipe ranking
The recommendation service operates independently from the main backend persistence layer and does not directly access the database. The backend acts as a middle man between the client and the recommendation service, as the client never directly accesses the recommendation service. Instead, it communicates with the backend through API requests and the backend forwards the necessary context and preference data to the recommendation engine where the necessary logic is performed and the results are returned to the backend. The backend remains responsible for data persistence, validation, and ownership checks.
Separating recommendation processing from the core backend improves modularity and allows recommendation functionality to evolve independently while remaining integrated with the main application services.
Repository Pattern
Mealchemy uses the Repository Pattern to separate business logic from persistence operations. Services interact with repositories rather than directly querying the database.
Repositories are responsible for retrieving, storing, and updating persistent application data such as users, recipes, pantry ingredients, shopping lists, and preferences.
This pattern improves maintainability, testability, and separation of concerns by isolating database access logic from application services.
Client-Side State Management
The frontend follows a feauture first MVVM (Model, View, ViewModel) architecture, where each feature module contains its own Model, View (screens and widgets), ViewModel (providers), and repository with a shared service layer handleing all the outgoing HTTP calls to the backend.
The frontend follows a provider-based state management pattern using Riverpod. Application state is managed through providers rather than directly within UI components.
This approach keeps the frontend modular and improves separation between presentation logic, business logic, and data retrieval. It also supports dependency injection and allows frontend components to switch between mock repositories and backend repositories during development and testing.
Design Patterns
Mealchemy uses selected object-oriented design patterns to improve modularity, maintainability, extensibility, and separation of concerns across the frontend, backend, and recommendation systems.
The following design patterns have been implemented:
Adapter Pattern
Purpose
The Adapter Pattern converts the interface of an external or platform-specific component into the interface the application expects. It isolates the rest of the application from external API structures, platform differences, and protocol differences, so the underlying component can be replaced or mocked without changing its users.
Mealchemy uses the Adapter Pattern in three places.
Use 1: External Nutrition Data
Mealchemy Participants
- Target interface:
NutritionDataProvider(findExternalIngredient(name),getExternalIngredientDetails(sourceId)) - Adapter:
NutritionDataAdapter - Adaptee:
UsdaApiClient, with its raw response typesUsdaSearchResponseandUsdaFoodDetail - Client:
IngredientCatalogueServiceUse in Mealchemy
Mealchemy uses USDA FoodData Central as an external source of ingredient and nutrition data. UsdaApiClient performs the HTTP calls and contains no business logic. NutritionDataAdapter translates USDA responses into Mealchemy's own ExternalIngredientItemResponse, mapping USDA nutrient codes to calories, protein, carbohydrates, fat, fibre and sodium, and recording USDA as the data source. Failures are converted into a NutritionProviderException.
IngredientCatalogueService depends only on the NutritionDataProvider interface, so USDA field names never reach the rest of the application. This reduces coupling to the third-party system and allows the provider to be replaced or mocked in tests.
Use 2: Photo and Video
Mealchemy Participants
- Target interface: media interface
- Adapter: media adapter class
- Adaptees: the platform photo and video selection and capture services on Android and iOS, and the object storage service that receives uploaded files
- Client: the recipe creation and editing features Use in Mealchemy
Recipes can carry a photo and a video. The application depends on a single media interface instead of using device plugins or the storage service directly. The adapter converts platform-specific behaviour into Mealchemy's own request and result types: selecting or capturing a file, validating its type and size, obtaining a short-lived signed upload URL from the backend, uploading the file directly to object storage, and returning the stored URL.
Platform differences and permission handling stay inside the adapter, and tests replace it with a fake that needs no device or storage service.
Use 3: Voice
Mealchemy Participants
- Target interface: voice interface (speak, pause, replay, stop, set speaking speed, start and stop listening, check availability)
- Adapter: voice adapter class
- Adaptees: the platform text-to-speech and speech-recognition services on Android and iOS
- Client: the Hands-Free Cooking feature Use in Mealchemy
Hands-Free Cooking reads recipe steps aloud, changes the speaking speed, and listens for commands such as "next", "back" and "repeat". The feature depends only on the voice interface. The adapter translates its calls into the correct Android or iOS calls, handles microphone permission, and reports whether speech recognition is available on the device.
Platform differences therefore stay in one class, and tests replace it with a fake that needs no device or microphone.
Decorator Pattern
Purpose
The Decorator Pattern attaches additional behaviour to an object dynamically while keeping the same interface, so the additional behaviour can be added without changing the object or the code that uses it.
Mealchemy Participants
- Component interface: the feature repository contract
- Concrete component: the API repository, which calls the backend
- Decorator: the cache-aware repository, which wraps the API repository and implements the same contract
- Collaborators: Network Status (connectivity and reachability), the user-scoped Local Cache (Drift / SQLite), and the image cache
Use in Mealchemy
Offline support is added to repositories without changing the screens, providers or API repositories that use them. The cache-aware repository is remote-first: it always tries the backend and falls back to the cache only when a request fails because the device is offline. Complete, successful responses are written to the cache transactionally, and a partly failed fetch never overwrites good cached data.
Write operations always go to the backend and are never converted into local edits, and mutation requests are blocked centrally while offline. Cached data is read-only and scoped to the signed-in user.
Because the decorator has the same interface as the component it wraps, dependency injection can supply either the plain API repository or the cache-aware one, and tests can use fakes for both.
Mediator Pattern
Purpose
The Mediator Pattern defines an object that encapsulates how a set of objects interact. Objects communicate through the mediator instead of referring to each other directly, which reduces coupling between them.
Mealchemy Participants
- Mediator:
NotificationService - Colleagues (publishers):
VaultInvitationService,VaultMemberService,VaultFolderService,VaultFolderRecipeServiceandRecipeEditLockService - Colleagues (delivery):
NotificationRepository(stored inbox and activity feed) andSimpMessagingTemplate(live push over STOMP WebSocket) - Messages:
NotificationEvent(stored notifications) andVaultLiveEvent(live-only recipe lock events)
Use in Mealchemy
Several services change the state of a shared vault: invitations, role changes, member removal, folder and recipe changes, and recipe edit locks. Each change must reach the affected users as a stored notification and as a live update for anyone who is connected.
Instead of each service saving notifications and pushing messages itself, they call NotificationService (publish or publishLiveEvent). The mediator decides who receives the event, saves one Notification row per recipient, and pushes the result to each recipient's private queue (/user/queue/notifications and /user/queue/vault-events). Publishing services therefore have no dependency on the persistence or WebSocket infrastructure.
The mediator publishes through Spring application events and delivers after the calling transaction commits, so users are never notified of a change that was rolled back. A failed push to one recipient is logged and does not affect other recipients or the stored notification. Recipe lock events are live-only and are not stored.
Architectural Constraints
Cost
- All technologies, services, and hosting platforms must remain within free tier limits.
- No paid APIs or services may be introduced.
Platform Support
- The system must run on both iOS and Android.
Security
- No passwords, secrets, or API keys may be hardcoded in source files.
- All credentials are injected at runtime via environment variables.
Database
- PostgreSQL is accessed exclusively by the Spring Boot backend.
- No other service writes to the database directly.
- Flyway migration files are immutable once committed and run, all schema changes require a new versioned migration file
- [However during development with Docker, the database volume can we wiped upon editing existing migration files - this can no longer be done as the database has been deployed]
Backend
- The backend holds no server-side session state, all authentication state lives in the JWT token
- The recommendation engine communicates exclusively through the Spring Boot REST API and has no database connection of its own
Layer Boundaries
- Controllers may only call services, services may only call repositories or dedicated external service clients, repositories may only interact with the database, no layer may skip or bypass an adjacent layer.
API Design
- All backend endpoints must follow RESTful conventions, no business logic may be exposed through query parameters or non-standard HTTP methods.
Testing
- All three services must maintain a minimum of 80% code coverage, no code may be merged without passing the full CI pipeline.
Architectural Diagram
View Architectural Diagram (PDF)
Note: the diagram above shows the high-level architecture only. See the Detailed Design diagram (Frontend Component Architecture and Backend Layer Architecture) at View Detailed Architectural Diagram (PDF), which is not represented in the mermaid diagram above.
Mapping Quality Requirements to Architectural Decisions
| QR | Quality Requirement | Quantified Requirement | Architectural Decision | Why it Satisfies the Requirement |
|---|---|---|---|---|
| QR1 | Performance | At a 10-user baseline and 20-user peak for 5 minutes each, p95 latency <= 2 seconds, error rate < 1%, and peak throughput >= 1.8x baseline | Cloud Run and Neon deployed in regional proximity; bounded database connection pool; connections released when service methods complete; stateless request handling | Regional proximity reduces database network delay, early connection release limits pool contention, and stateless request handling allows concurrent reads to be distributed across instances |
| QR2 | Scalability | At 100 concurrent users for 5 minutes, >= 99% successful responses, no service crash, and no manual recovery; latency recorded as an observation | Stateless backend; Cloud Run automatic scaling and managed restart; deployment capped at three instances for cost control | Any instance can serve any request, Cloud Run can add capacity and replace unhealthy instances automatically, and the explicit instance cap explains why latency degrades at high load |
| QR3 | Security | 100% of authorization and ownership checks pass; 0 verified secrets; 0 high or critical container findings; 0 unresolved medium-or-higher API findings | JWT validation with deny-by-default rules; ownership checks in the service layer; Secret Manager; Gitleaks; Trivy; input validation, security headers, and limited API exposure | Protected routes reject missing or invalid credentials, ownership rules apply below the controller, secrets stay outside source code, and automated scans block serious repository, image, and API findings |
| QR4 | Reliability and Availability | >= 99.9% availability over 30 days; 20 of 20 controlled engine-failure responses within 3 seconds with 0 unhandled HTTP 500 responses; 4 of 4 offline collections readable; 0 offline writes and cross-user reads | Health checks, managed restart, and external monitoring; backend timeout and defined engine fallback; viewer-scoped SQLite cache and repository fallback; offline mutation interceptor and composite cache keys | Monitoring measures service availability, timeout and fallback contain engine failures, cached repositories preserve supported reads, and viewer-scoped keys with blocked mutations protect offline data |
| QR5 | Maintainability | Backend and frontend coverage >= 80%; 0 accessibility-guideline failures on named priority screens | Controller-service-repository layering; dependency injection; JaCoCo and lcov coverage gates in CI; accessible Flutter components and guideline tests | Isolated dependencies keep application logic testable, CI prevents coverage from falling below the threshold, and guideline tests detect accessibility regressions in priority workflows |
Updated 30 September 2026