(quality attribute scenarios)
Why these three¶
After completing the domain analysis, functionalities elicitation, ASR identification, domain storytelling, ubiquitous language definition, and tactical DDD modeling, three quality attribute scenarios emerged as the ones that most heavily shape the architecture of the platform. They are written against the MVP scope defined in Stories: no gamification, no community/reviews, no AI assistant.
The first two target the Product Passport module, which owns scan resolution and response assembly: it orchestrates multiple unreliable external APIs, starts from the local Product Catalog, and assembles the Resolved Passport that defines the consumer's first impression. Getting the scanning flow wrong would compromise the entire platform.
The third targets the cross-cutting integration layer between bounded contexts. Because the platform handles GDPR Article 9 special-category health data and operates under EU regulations (ESPR), the architecture must guarantee that destructive operations like account deletion propagate correctly and verifiably across every service boundary. This is not just a compliance checkbox, it directly constrains how bounded contexts reference each other, how data is partitioned, and how events are designed.
QAS-1: Performance: Multi-API Orchestration on Scan¶
Realizes stories 1.1, 1.2, 3.1, 3.2, 3.3.
| Part | Description |
|---|---|
| Source | Consumer in a supermarket |
| Stimulus | Scans a GS1 Digital Link QR code on a product packaging |
| Artifact | Product Passport module: PassportAssemblyService, ProductCatalogEntry, ResolvedPassport, ACL adapters |
| Environment | Normal operation. Consumer on a 4G mobile connection. All external APIs are reachable but respond with variable latency (50ms–2s per API). Product Catalog data is locally available. |
| Response | The GS1 payload (GTIN, lot, expiry) is decoded on the consumer's phone before any network call (story 1.1). The system resolves the GTIN to its owning Brand through the Product Catalog (ADR-001) and fires parallel requests through ACL adapters. A ResolvedPassport is assembled progressively: the local ProductCatalogEntry renders first, while Journey and Eco are independent TabData slots that render as their sources respond. If the consumer has a Health Profile, the Verdict is computed from local catalog data and versioned policies; its goal-fit line renders at the top before any external section. |
| Response Measure | First meaningful content (verdict + allergen/dietary alerts + Info tab) rendered in ≤ 1 second. Full Product Passport (all three tabs loaded) rendered in ≤ 3 seconds, matching the "under 3 seconds" acceptance criterion of story 1.1. No tab blocks the rendering of any other tab. |
Architectural impact¶
This scenario forces several design decisions:
- GS1 Digital Link decoding is a client-side concern: GTIN, lot and expiry are extracted in the browser before any request, so the first network round-trip already targets the right product.
- The PassportOrchestrator must implement parallel, non-blocking calls to all external APIs, with independent timeouts per adapter.
- The transient
ResolvedPassportuses a progressive assembly model: each external section isPending | Loaded(data) | Failed(reason). The storedProductCatalogEntrycontains no response state. - The Info tab and the personal verdict must be servable from local data only (Product Catalog + Health Profile), guaranteeing that the health-critical answer never waits on a third party.
- ACL adapters need per-adapter timeout and retry policies, since each external API has different latency characteristics and rate limits.
- The frontend must support incremental rendering: the dashboard shell loads immediately, and tabs populate asynchronously as data arrives.
QAS-2: Availability: Graceful Degradation Under External API Failure¶
Realizes stories 1.5, 1.6.
| Part | Description |
|---|---|
| Source | One or more external API dependencies (CarbonCloud, TrusTrace, Open Food Facts) |
| Stimulus | An external API returns HTTP 503 or fails to respond within the configured timeout threshold during a consumer's scan |
| Artifact | Product Passport module: PassportAssemblyService, ResolvedPassport (TabData), ACL adapters, circuit breaker |
| Environment | Normal operation. One or more external APIs are degraded or fully down. The consumer may have an active Health Profile. Other platform services (Product Catalog, Personalization, Consumer Inventory) are healthy. |
| Response | The system marks the affected section as TabData.Failed(reasonCode) and the UI renders a friendly fallback message (story 1.6). Other sections render normally. The Verdict still executes using local Product Catalog data. A ScanRecord and raw ProductScanned measurement fact are recorded regardless of external failures; the raw event remains consumer-side and reaches Brand Analytics only through a minimum-group-size aggregate batch. The only full-screen alternative state is the deliberate "We don't know this product yet" screen for unknown GTINs (story 1.5). |
| Response Measure | Zero full-page failures caused by any single external API being unavailable. The Product Passport renders successfully with at least the verdict and the Info tab within ≤ 3 seconds, even if all external APIs are simultaneously down. All downstream side effects (ScanRecord persisted, domain events emitted, scan history logged) execute regardless of external API state. Circuit breaker opens after 3 consecutive failures per adapter and stays open for a configurable cooldown period, preventing unnecessary calls to a known-down service. |
Architectural impact¶
This scenario forces:
- Circuit breaker per ACL adapter: each external API is wrapped in its own circuit breaker with independent failure counting and cooldown. An open circuit immediately returns
TabData.Failedwithout waiting for a timeout. - Separation of the transient ResolvedPassport from the persistent ProductCatalogEntry and ScanRecord: the ScanRecord is committed independently of response assembly. Even if all external APIs fail, the scan fact is recorded and consumer-side measurement events are emitted.
- Allergen checking depends on local data, not external APIs: the
DigitalLabelContent(ingredients, allergen markers) is part of the Product Catalog, which is an internal bounded context. This guarantees that health-critical warnings are never blocked by a third-party outage. - Bulkhead isolation: a slow or failing external API must not consume shared resources (thread pools, connection pools) that would degrade calls to healthy APIs.
QAS-3: Security & Privacy, Right-to-Erasure Propagation Across Bounded Contexts¶
Realizes stories 2.5, 5.1, 5.2; aggregation constraints shared with 6.3.
| Part | Description |
|---|---|
| Source | Consumer exercising their GDPR right to erasure |
| Stimulus | Consumer submits a full account deletion request through the platform |
| Artifact | Identity & Consent (Account and consent ledger), plus every module holding consumer-identifiable data: Personalization (HealthProfile), Fridge, Product Passport (ScanRecords), and the consumer-side anonymization pipeline |
| Environment | Normal operation. The consumer has been active on the platform: they have a health profile with recorded consent, fridge items, and scan history. Some of their data has already been processed by the Anonymization Pipeline and exists in the Analytics Context as de-identified aggregates feeding the brand dashboard. |
| Response | The deletion is split into a synchronous core and an asynchronous sweep. Synchronously, the HealthProfile is deleted, account credentials are disabled and the consent ledger is retained only as legally required audit evidence. Identity & Consent then emits AccountDeleted; Fridge and scan history are purged, and ScanRecords nullify consumerRef while retaining an unlinkable visitor scan fact. Brand Analytics is unaffected because it received only minimum-group-size BrandMetricBatchPublished aggregates, never per-scan data. The system generates a deletion confirmation log with processing timestamps. |
| Response Measure | The synchronous core (health data, credentials, account disablement) completes before the user sees the confirmation — this is the "immediate" of story 2.5. The asynchronous sweep across all remaining bounded contexts completes within 24 hours (well within the GDPR 30-day regulatory window). Deletion is verifiable: a deletion receipt is generated listing every bounded context that processed the event, with timestamps. A subsequent Subject Access Request for the same consumer returns no identifiable data. No orphaned references remain, any foreign key or cached reference pointing to the deleted consumer is either nullified or removed. If any bounded context fails to process the deletion event, the event is retried with exponential backoff. After a configurable number of retries, the failure is escalated to the Platform Admin for manual resolution. |
Architectural impact¶
This scenario constrains the architecture in ways that affect every bounded context:
- The most sensitive data is deleted synchronously, in the owning context: GDPR Article 9 health data never waits on event delivery. The event-driven sweep handles references and derived copies, not the source of truth.
- Consumer references across all BCs use a lightweight
ConsumerRef(ID only), never embedded consumer data. This ensures deletion is a matter of removing or nullifying references, not hunting for denormalized copies of consumer data scattered across services. - The
AccountDeletedevent must be processed by every BC reliably. This requires a durable event bus with at-least-once delivery guarantees and dead-letter handling. Fire-and-forget is not acceptable for a legally mandated operation. - Each bounded context must implement a
handleAccountDeletedhandler that is tested and auditable. This is not optional infrastructure, it is a domain-level requirement. - Anonymized data and identifiable data follow different lifecycles: the Analytics Context is explicitly exempt from deletion because its pipeline already strips consumer identity and only emits aggregates above a minimum group size — the same wall that guarantees brands never see individual-level health data (story 5.2). This separation must be enforced at the pipeline level: if the anonymization is ever found to be reversible or a group falls below the minimum size, the exemption no longer holds.
- Deletion must be idempotent: if the event is delivered twice (at-least-once semantics), the second processing must be a no-op, not an error.
- The deletion receipt / audit log is itself a cross-cutting concern that must be persisted independently of the bounded contexts being purged, so it survives the deletion it documents.