-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Asynchronous Popularity Report Export #32
Description
feat: Asynchronous Popularity Report Export
Description
Implement a resilient, asynchronous report generation system. This feature offloads heavy data aggregation from the API to a background Worker Service (MusicAlbums.Worker), using the Transactional Outbox pattern — via MassTransit's built-in Outbox over Azure Service Bus — to guarantee that every export request is eventually processed, even during infrastructure outages.
Proposed Changes
-
Database (PostgreSQL/Dapper):
- Add a
Reportstable to track status (Pending,Processing,Completed,Failed) and store the Blob URL. - Add an
OutboxMessagestable to storeExportRequestedevents (managed by MassTransit's Outbox).
- Add a
-
API (
MusicAlbums.Api):- Add
ReportsControllerwith two endpoints:POST /reports/popularity— accepts the request, writes toReportsandOutboxMessagesin a single Dapper transaction, returns202 Acceptedwith areportIdandstatus: "Pending".GET /reports/{reportId}— returns the current status anddownloadUrlonce the report is ready.
- Add
-
Worker Service (
MusicAlbums.Worker):- New .NET Worker Service project hosted as a second Azure Container App in the same environment.
- Uses MassTransit with Azure Service Bus as the transport.
- MassTransit's built-in Outbox polls
OutboxMessages, publishesExportRequestedto thereport-requestsqueue, and marks messages as processed — no custom relay logic needed. ExportRequestedConsumerreceives the message, runs the popularity aggregation query, uploads the resulting CSV to Azure Blob Storage, and updates theReportsrecord toCompletedwith thedownloadUrl.
-
Infrastructure (Azure/Bicep):
- Provision an Azure Service Bus namespace and queue (
report-requests). - Provision an Azure Blob Storage container (
reports). - Add a second Azure Container App for
MusicAlbums.Workerwithin the existing Container Apps environment.
- Provision an Azure Service Bus namespace and queue (
Architecture Flow
POST /reports/popularity
→ Insert Report (Pending) + OutboxMessage in one DB transaction
→ Return 202 Accepted { reportId, status: "Pending" }
MassTransit Outbox (Worker)
→ Polls OutboxMessages table
→ Publishes ExportRequested → Azure Service Bus (report-requests queue)
ExportRequestedConsumer (Worker)
→ Runs popularity aggregation query (PostgreSQL/Dapper)
→ Uploads CSV → Azure Blob Storage
→ Updates Report → { status: "Completed", downloadUrl }
GET /reports/{reportId}
→ Returns { reportId, status, downloadUrl? }
BDD Scenarios
Scenario 1: Successfully initiating a report
Given a valid authenticated user
When I send a POST request to /reports/popularity
Then the API should return 202 Accepted
And the response body should contain a unique reportId and status: "Pending"
Scenario 2: Ensuring reliability via Outbox
Given the Azure Service Bus is temporarily unavailable
When I request a popularity report
Then the API should still return 202 Accepted
And the request must be persisted in the OutboxMessages table to be relayed once connectivity is restored
Scenario 3: Polling for report status
Given a report has been initiated and a reportId was returned
When I send a GET request to /reports/{reportId}
Then the API should return the current status of the report
And if the report is completed, the downloadUrl field should contain a valid link to the file in Blob Storage
Scenario 4: Completing the export
Given a report request has been picked up by the ExportRequestedConsumer
When the data aggregation and Blob upload are finished
Then the record in the Reports table should be updated to status: "Completed"
And the downloadUrl field should contain a valid link to the file in Blob Storage
Metadata
Metadata
Assignees
Labels
Projects
Status