A user event collection and viewing system built with .NET 8, Angular, Azure Service Bus, and Azure Functions — following event-driven architecture and the BMAD methodology.
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
│ Angular 21 │──────▶│ ASP.NET Core │──────▶│ Azure Service│──────▶│Azure Function│
│ SPA │ HTTP │ Web API │publish│ Bus │trigger│ (Isolated) │
│ (port 4200) │◀──────│ (port 5001) │ │ Queue: │ │ │
└─────────────┘ └─────────────────┘ │ "events" │ └──────┬───────┘
│ query └──────────────┘ │ persist
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Azure SQL │◀──────────────────────────────│ EF Core 8 │
│ Database │ │ DbContext │
└──────────────┘ └──────────────┘
Flow:
- User submits an event via the Angular SPA form
- API validates the request and publishes a message to the Azure Service Bus queue
- API returns
202 Acceptedimmediately (asynchronous processing) - Azure Function triggers on the queue message and persists the event to Azure SQL
- User browses events via the Angular SPA table with filtering, sorting, and pagination
| Component | Technology | Version |
|---|---|---|
| Frontend | Angular (standalone components, signals) | 21.x |
| Styling | SCSS | — |
| Backend API | ASP.NET Core Web API | .NET 8.0 (LTS) |
| Event Processor | Azure Functions (isolated worker) | v4, .NET 8.0 |
| Message Broker | Azure Service Bus | Queue mode |
| Database | Azure SQL via Entity Framework Core | EF Core 8.x |
| Infrastructure | Bicep (IaC) | — |
| Methodology | BMAD Method | v6.x |
- .NET 8.0 SDK (or later — the projects target
net8.0) - Node.js 18.x or later (for Angular CLI)
- Azure Functions Core Tools v4
- SQL Server LocalDB (included with Visual Studio or installable separately)
- An Azure Service Bus namespace + queue (required for end-to-end event flow; there is no official local Service Bus emulator)
EventHub/
├── EventHub.slnx # .NET solution file
├── src/
│ ├── api/EventHub.Api/ # ASP.NET Core Web API
│ │ ├── Controllers/ # EventsController (POST, GET)
│ │ ├── Services/ # EventService, ServiceBusPublisher
│ │ ├── Data/ # EF Core DbContext + Migrations
│ │ ├── Middleware/ # ExceptionHandlingMiddleware
│ │ └── appsettings.json
│ ├── functions/EventHub.Functions/ # Azure Function (isolated worker)
│ │ ├── Functions/ # EventProcessorFunction (ServiceBusTrigger)
│ │ ├── Services/ # EventPersistenceService
│ │ ├── Data/ # DbContext
│ │ └── local.settings.json
│ ├── shared/EventHub.Shared/ # Shared library
│ │ ├── Models/ # Event entity, EventType enum
│ │ └── DTOs/ # Request/Response DTOs, PagedResult<T>
│ └── web/EventHub.Web/ # Angular 21 SPA
│ └── src/app/
│ ├── components/ # event-list, event-form, layout
│ ├── services/ # EventService (HttpClient)
│ ├── interceptors/ # errorInterceptor
│ ├── models/ # TypeScript interfaces & enums
│ └── environments/ # API URL configuration
├── infra/ # Bicep IaC templates
│ ├── main.bicep
│ └── parameters.dev.bicepparam
├── _bmad-output/ # BMAD methodology artifacts
│ └── planning-artifacts/ # PRD, Architecture, Epics
└── README.md
git clone <repository-url>
cd EventHubThe API and Functions use SQL Server LocalDB by default. No additional setup is needed if LocalDB is installed.
Connection string in src/api/EventHub.Api/appsettings.json:
Server=localhost;Database=EventHubDb;Trusted_Connection=True;TrustServerCertificate=True;
cd src/api/EventHub.Api
dotnet ef database updateUpdate the connection string in both:
src/api/EventHub.Api/appsettings.json→ServiceBus:ConnectionStringsrc/functions/EventHub.Functions/local.settings.json→ServiceBusConnection
Replace with your Azure Service Bus namespace connection string. The queue name is events.
cd src/api/EventHub.Api
dotnet runThe API starts at https://localhost:7090 (and http://localhost:5197).
Swagger UI is available at https://localhost:7090/swagger.
cd src/functions/EventHub.Functions
func startThe function listens on the events queue and processes messages into the database.
cd src/web/EventHub.Web
npm install
npx ng serveThe SPA is available at http://localhost:4200. It proxies API calls to https://localhost:5001/api.
dotnet test src/tests/EventHub.Backend.Tests/EventHub.Backend.Tests.csprojThe backend test suite covers:
EventServiceTests— API service layer (publishing, retrieval, filtering)ExceptionHandlingMiddlewareTests— global error middlewareEventProcessorFunctionTests— Azure Function trigger logicEventPersistenceServiceTests— database persistence serviceCreateEventRequestValidationTests— DTO validation rules (including whitespace rejection)DbContextModelConfigurationTests— EF Core model configuration
Creates a new event and publishes it for asynchronous processing.
Request body:
{
"userId": "user123",
"type": "Click",
"description": "User clicked the buy button"
}| Field | Type | Required | Constraints |
|---|---|---|---|
userId |
string | Yes | Max 200 characters |
type |
string (enum) | Yes | PageView, Click, or Purchase |
description |
string | Yes | Max 2000 characters |
Response 202 Accepted:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Event accepted for processing"
}Response 400 Bad Request (validation error):
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"UserId": ["The UserId field is required."]
}
}Retrieves a paginated list of events with optional filtering and sorting.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
type |
string | — | Filter by event type (PageView, Click, Purchase) |
userId |
string | — | Filter by user ID |
fromDate |
datetime | — | Filter events created after this date |
toDate |
datetime | — | Filter events created before this date |
sortBy |
string | createdAt |
Sort field: createdAt, type, userId |
sortDescending |
bool | true |
Sort direction |
page |
int | 1 |
Page number (1-based) |
pageSize |
int | 20 |
Items per page (max 100) |
Response 200 OK:
{
"items": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userId": "user123",
"type": "Click",
"description": "User clicked the buy button",
"createdAt": "2026-03-02T10:30:00Z"
}
],
"totalCount": 42,
"page": 1,
"pageSize": 20
}Decision: Azure SQL with EF Core (Code-First).
Rationale: The Event model is a single flat entity with relational query needs (filtering, sorting, pagination). SQL provides strong LINQ-based querying via EF Core, code-first migrations for reproducible schemas, and lower cost for this use case. Cosmos DB would be overkill — it excels at document-oriented or globally distributed data, neither of which applies here.
Decision: Queue (point-to-point) instead of Topic (pub/sub).
Rationale: Only one consumer (the Azure Function) processes events. A queue provides simpler configuration with the same reliability guarantees (dead-letter, retry). Topics add unnecessary complexity when there are no multiple subscribers.
Decision: The API publishes to Service Bus and returns immediately with 202 Accepted, rather than writing to the database synchronously.
Rationale: This decouples the API from the database, improves response times, and provides natural buffering under load. The trade-off is eventual consistency — an event may take a moment to appear in the list after creation. For this application, the delay is acceptable.
Decision: .NET isolated worker model.
Rationale: The in-process model is deprecated. Isolated worker provides process-level isolation, full .NET 8 API surface, and better long-term support. The small overhead of out-of-process communication is negligible for this workload.
Decision: All components are standalone with lazy-loaded routes.
Rationale: Angular's modern approach — standalone components are the recommended default. They simplify the dependency tree, enable per-route code splitting, and reduce boilerplate. The project has no legacy module dependencies.
Decision: Set MessageId to the event's GUID.
Rationale: MessageId improves traceability and provides the key required for broker-level duplicate detection when that capability is enabled on the queue.
Decision: Centralized error middleware (API) + HTTP interceptor (Angular) + dead-letter queue (Service Bus).
Rationale:
- API:
ExceptionHandlingMiddlewarecatches all unhandled exceptions and returns RFC 7807 ProblemDetails. Exception details are included only in Development. - Angular: A functional
HttpInterceptorFnlogs errors and re-throws for component-level handling. - Service Bus: Messages that fail processing are retried up to 10 times, then moved to the dead-letter queue for investigation.
Bicep templates in infra/ provision all Azure resources:
- App Service Plan + App Service (API hosting)
- Azure Service Bus namespace +
eventsqueue (dead-letter enabled) - Function App + Consumption plan + Storage Account
- Azure SQL Server + Database
- Application Insights
Deploy with:
az deployment group create \
--resource-group rg-eventhub-dev \
--template-file infra/main.bicep \
--parameters @infra/parameters.dev.bicepparam sqlAdminPassword='<your-strong-password>'This project follows the BMAD (Business, Marketing, Architecture, Development) methodology. Planning artifacts are in _bmad-output/planning-artifacts/:
- PRD (
prd.md) — Product Requirements Document - Architecture (
architecture.md) — Architecture Decision Document - Epics (
epics.md) — Epic breakdown with 4 epics, 15 user stories
This project is a test task submission. All rights reserved.