System Patterns
Design patterns and architectural principles implemented throughout the Grade My Investments platform.
sequenceDiagram
participant U as User
participant AF as Azure Functions
participant DB as MySQL
participant RE as Report Engine
participant FMP as FMP API
participant BS as Blob Storage
participant NS as Notification
U->>AF: POST generate report
AF->>DB: create Job row
AF-->>U: 202 Accepted
RE->>DB: poll for pending jobs
DB-->>RE: job ready
RE->>FMP: fetch financial data
FMP-->>RE: stock data
RE->>BS: upload Excel report
RE->>DB: update job status
DB->>NS: trigger email - SMS
NS-->>U: job complete notification
U->>AF: GET job folder
AF-->>U: SAS download URLCore Architectural Patterns
Layered Architecture
Clean separation between presentation, business, and data layers
- Presentation (Blazor WASM, MAUI)
- Services (Business Logic)
- Data Access (Repository)
- Database (MySQL)
Microservices Architecture
15 independent console applications for different business concerns
- Report Engine & Report Generator Alpha
- Claude Analysis & Report Comparison
- Email Sender & SMS Sender
- Billing Engine & Schedules Engine
- Download Builder & File Purge
- Health Monitor
Foundational Design Patterns
Implementation: Throughout entire system
- Azure Functions Program.cs: Lines 22-94
- Client Web Program.cs: Lines 48-102
- Admin Web Program.cs: Lines 47-134
- Report Generator Program.cs: Lines 177-227
Benefits: Loose coupling, testability, maintainability
Implementation: Data access abstraction layer
- RepositoriesInterfaces.cs: 45 interfaces
- Repositories.cs: Concrete implementations
- Entity Framework integration
Benefits: Data access abstraction, unit testing support
Implementation: Custom token bucket algorithm
- ReportGeneratorAlpha/RateLimiter.cs
- 3,000 calls per minute limit
- Semaphore-based concurrency control
Benefits: API quota protection, system stability
Implementation: Azure Functions HTTP triggers
- AzureFunctions/Functions.cs
- Standard HTTP verbs (GET, POST, PUT, DELETE)
- Route patterns with parameters
Benefits: Standardized API interface, scalability
Implementation: HTTP Client Factory
- Client Web: IHttpClientFactory
- Admin Web: AddHttpClient()
- MAUI: Named HTTP clients
Benefits: Resource management, configuration reuse
Implementation: Configuration and setup
- MAUI: MauiApp.CreateBuilder()
- Blazor WASM: WebAssemblyHostBuilder
- Service configuration chains
Benefits: Fluent configuration, step-by-step construction
Implementation: Server-side pagination with skip/take
- Client Web: Portfolio, Trade History, Reports
- Admin Web: User management, System logs
- Repository pattern: Skip().Take() methods
- Blazor components: Paginated data display
Benefits: Performance optimization, improved user experience, memory efficiency
Implementation: First-In-First-Out message processing queues
- EmailSender Console App: Queue-based email processing
- SMS Sender Service: Sequential message delivery
- Report Engine Alpha: Report generation queue management
- MySQL Database tables used for queueing mechanism
Benefits: Message ordering guarantee, system reliability, load distribution, fault tolerance
Implementation: Machine learning model training and prediction pipelines
- PyEasy Console: MLForecastingService.cs
- Report Generator Alpha: MLForecastingService.cs
- SSA forecasting with fallback to linear regression
- Multiclass classification for sentiment analysis
Benefits: Predictive analytics, data-driven insights, confidence intervals, anomaly detection
Enterprise Patterns
Unit of Work
Implementation: Entity Framework DbContext
- GmiDbContext manages transactions
- SaveChangesAsync() commits as single unit
- 61 DbSets for entity management
Service Layer
Implementation: Business logic orchestration
- ServicesInterfaces.cs: 50+ interfaces
- Services.cs: Concrete implementations
- Repository orchestration with validation
Options Pattern
Implementation: Configuration management
- IConfiguration throughout system
- appsettings.json configuration
- Environment-specific settings
Strategy Pattern
Implementation: Authentication strategies
- Google OAuth for Client Web
- Azure AD for Admin Web
- JWT Bearer for API authentication
Command Pattern
Implementation: Azure Functions
- Each Function = Command
- Encapsulated request processing
- Standardized error handling
Adapter Pattern
Implementation: External API integration
- FmpApiClient for Financial Modeling Prep
- BlobStorageService for Azure Storage
- REST client services for HTTP APIs
CQRS Pattern (Command Query Responsibility Segregation)
Grade My Investments applies the CQRS pattern in the Admin Web application where read operations (queries) and write operations (commands) are handled through distinct service methods with purpose-built interfaces. This separation enables optimized query paths for search/filtering while keeping mutation logic clean and auditable.
Query Side (Read-Optimized)
Activity Log Search
Interface: IActivityLogEntryService
GetByAccountIdAsync(accountId, limit)- filtered by accountGetByAccountIdAndTypeAsync(accountId, type, limit)- filtered by type (PageView, LinkClick, Operation)- Indexed on
AccountID,ActivityDateTime, andActivityType
Email Queue Search
Interface: IMessagingService
GetEmailsWithFilterAsync(status, fromDate, toDate, searchText, pageSize, pageNumber)- Supports pagination, date range filtering, status filtering, and text search
- Used by Admin Web to browse sent, pending, and failed emails
SMS Queue Search
Interface: IMessagingService
GetSmsWithFilterAsync(status, fromDate, toDate, searchText, pageSize, pageNumber)- Same filtering capabilities as email search
- Used by Admin Web to monitor SMS delivery and troubleshoot failures
Command Side (Write-Optimized)
Activity Logging Commands
Interface: IActivityLogEntryService
LogPageViewAsync(accountId, pageName)LogLinkClickAsync(accountId, linkName, url)LogOperationAsync(accountId, operationName, detail)- All fire-and-forget - never blocks user operations
Email/SMS Enqueue Commands
Interface: IMessagingService
EnqueueEmailToSendAsync(to, cc, bcc, subject, body)EnqueueSmsToSendAsync(subject, body, toNumber)MarkEmailAsSentAsync(id)/MarkSmsAsSentAsync(id)RecordEmailSendFailureAsync(id, error)/RecordSmsSendFailureAsync(id, error)
Why CQRS Here?
- Writes are high-volume, fire-and-forget (every click/page view)
- Reads need complex filtering, pagination, and date ranges
- Admin Web queries never interfere with client write paths
- Database indexes optimized separately for read vs write patterns
Microsoft Cloud Design Patterns
Grade My Investments implements numerous patterns from the Microsoft Azure Cloud Design Patterns catalog. These proven patterns address common challenges in distributed cloud applications including reliability, scalability, and security.
Queue-Based Load Leveling
Implementation: Database-backed queues with background consumers
emailstosendtable queues outbound emails for the EmailSender servicesmstosendtable queues outbound SMS for the SmsSender servicedownloadstable queues ZIP build requests for the DownloadBuilder service- Decouples user-facing API from slow external calls (SendGrid, Twilio, blob storage)
Competing Consumers
Implementation: Horizontally scalable background services
- EmailSender, SmsSender, and DownloadBuilder are stateless consumers
- Multiple instances can run concurrently, each claiming pending items
- ReportEngine supports
ProcessorCount * threadsPerCoreconcurrent reports - Configurable batch sizes allow tuning throughput per instance
Retry Pattern
Implementation: Configurable retry with failure tracking and exponential backoff
- Email and SMS senders retry up to 3 times before marking as permanently failed
RetryCountfield onEmailToSendandSmsToSendtracks attemptsRecordEmailSendFailureAsync()increments retry count and preserves error detailsMarkEmailAsFailedAsync()terminates retries after max attempts- FMP API calls use exponential backoff (1s, 2s, 4s) on 429 rate limits and 5xx server errors
- FMP proxy and client both implement retry — resilient at every layer
- Stripe payment recording retries with
FailedPaymentReconciliationfallback
Bulkhead Pattern
Implementation: Rate limiting and resource isolation
RateLimitingMiddlewareenforces per-account/per-IP token bucket limits- Authenticated users: 100 requests/minute; anonymous: 30 requests/minute
- FMP API rate limiter isolates external API call budget from user requests
- Prevents any single user from exhausting system resources
Health Endpoint Monitoring
Implementation: Dedicated health monitor service + API health endpoints
- HealthMonitor systemd service runs every 60 seconds, collecting disk/memory/CPU, database connectivity, external API reachability (FMP, Anthropic, SendGrid, Blob Storage)
- Monitors all 11 background service statuses via
systemctl is-active - Tracks queue depths, stale jobs, log file sizes, and system uptime
- Persists health checks to
systemhealthcheckstable with Warning/Critical/Healthy status - Admin Web dashboard displays real-time health banner with warning details
- Dedicated System Health page with auto-refresh, history, and color-coded metrics
Valet Key Pattern
Implementation: SAS tokens for Azure Blob Storage
GenerateSasUrl()creates time-limited, read-only SAS tokens- Download ZIP files served via SAS URLs (1-hour expiry)
- Clients access blob storage directly without proxying through the API
- Reduces API server load and bandwidth for large file downloads
Gateway Aggregation
Implementation: Azure Functions as unified API gateway
- Single API endpoint aggregates all backend services
- Middleware pipeline: Authentication, Rate Limiting, Security Headers, CORS
- FMP proxy endpoint hides third-party API key from clients
- Allowlisted FMP paths prevent abuse of proxied external API
Backends for Frontends (BFF)
Implementation: RestClient service layer per frontend
Gmi.RestClientprovides typed service interfaces for all frontends- Client Web, Admin Web, and Mobile each register the same services via DI
- Abstracts HTTP communication, token management, and error handling
AddGmiRestClient()extension method configures per-app service sets
Saga Pattern
Implementation: Multi-step transactions with compensating rollback
CreateReportJobWithDataAsync()orchestrates: job creation, symbol list association, notification settings, coupon application- Wrapped in
Database.BeginTransactionAsync()with rollback on any step failure - Credit card default-swap uses transaction to prevent race conditions
- Admin notifications fire after commit (fire-and-forget, no rollback needed)
Sidecar Pattern
Implementation: 11 independent background services as systemd units
- ReportEngine — orchestrates multi-threaded report generation jobs (spawns ReportGeneratorAlpha child processes)
- ClaudeAnalysis — AI-powered report analysis via Anthropic API
- ReportComparison — period-over-period report comparison
- EmailSender — processes email queue via SendGrid
- SmsSender — processes SMS queue via Twilio
- BillingEngine — handles recurring billing cycles
- SchedulesEngine — executes scheduled report generation
- DownloadBuilder — builds ZIP archives from folders
- FilePurge — cleans up soft-deleted files from blob storage
- HealthMonitor — system health monitoring and alerting
Cache-Aside Pattern
Implementation: In-memory response and token caches with explicit TTL
FmpProxyFunctionscaches upstream FMP responses for 5 minutes, emittingX-Cache: HIT/MISSheadersGoogleTokenValidatorandEntraIdTokenValidatorcache validated tokens to avoid re-hitting Google and Microsoft identity endpoints on every request- Stale entry sweep triggers when cache size exceeds 1,000 entries
- Shields upstream APIs from duplicate load while preserving correctness via short TTLs
Claim Check Pattern
Implementation: Metadata in MySQL, large payloads in Azure Blob Storage
Fileentity storesBlobContainer+BlobPathreferences; binary content lives in Azure Blob StorageDownloadentity stores blob path for generated ZIP archives; SAS URL returned on retrieval- Keeps DB rows small and scan-friendly while letting clients stream large artifacts directly from blob
- MAUI uploads stream to blob first, then the claim (AccountEmailAddressID / FileID) is persisted in MySQL
Asynchronous Request-Reply
Implementation: Client submits, polls status, retrieves result
- Downloads: client creates a
downloadsrow, DownloadBuilder builds the ZIP asynchronously, client polls untilStatus = Ready, then fetches the SAS URL - Report generation: client creates a
Job, ReportEngine processes it, email/SMS notifications fire on completion - Claude analysis:
ClaudeAnalysisJobcreated, ClaudeAnalysis service processes via Anthropic API, client polls for results - Avoids long-lived HTTP connections and frees the Functions host from blocking on multi-minute work
Publisher-Subscriber (Pub/Sub)
Implementation: Stripe webhooks as durable event subscription
- Stripe publishes
charge.succeeded,payment_method.attached,invoice.payment_failed, etc. StripeWebhookFunctionssubscribes via HTTPS endpoint with signature verificationStripeWebhookEventtable records every received event for idempotent processing — duplicates are detected and skipped- Decouples payment outcome from user-facing request path; replay-safe for Stripe's at-least-once delivery
External Configuration Store
Implementation: Azure Key Vault with managed-identity access
- Connection strings, Stripe / SendGrid / Twilio / FMP API keys all stored as Key Vault secrets
- App Services resolve secrets at runtime via
@Microsoft.KeyVault(VaultName=...;SecretName=...)references — no secrets in source or app settings - System-assigned managed identities on each workload grant least-privilege
get/listaccess - Secret rotation is a Key Vault operation — no redeploy required
Federated Identity
Implementation: Dual identity providers with a unified account model
- Client Web & MAUI authenticate through Google OAuth; tokens validated by
GoogleTokenValidator - Admin Web authenticates through Microsoft Entra ID; tokens cryptographically validated by
EntraIdTokenValidator(issuer, tenant, appid, signature) - Both flows resolve to a local
AccountorAdminUserrecord, letting the rest of the system stay identity-provider-agnostic - No password storage — authentication is delegated to the identity provider
Static Content Hosting
Implementation: Blazor WebAssembly deployed as static files
- Client Web, Admin Web, and Technology Web are Blazor WASM single-page apps — compiled to
.wasm/.dll/.jsassets - Served from Azure App Service (Windows) with
WEBSITE_RUN_FROM_PACKAGE=1— no server-side rendering per request - All dynamic data comes from the Azure Functions API; the web tier is pure static delivery
- Trivial to scale out — every instance serves the identical read-only asset set
Ambassador Pattern
Implementation: FMP proxy as outbound egress ambassador
FmpProxyFunctionsis the only path through which clients reach the Financial Modeling Prep API- Injects the FMP API key server-side, preventing key leakage to browsers/mobile clients
- Enforces an allowlist of FMP paths so the proxy can't be abused to call arbitrary endpoints
- Adds caching, logging, and rate-limiting on behalf of every caller — one place to add cross-cutting policies for the upstream service
Scheduler Agent Supervisor
Implementation: SchedulesEngine schedules, ReportEngine executes, HealthMonitor supervises
- Scheduler:
SchedulesEngineevaluatesReportGenerationSchedulecron expressions and creates pendingJobrows at the right time - Agents:
ReportEngine,ReportGeneratorAlpha,ClaudeAnalysis,DownloadBuilderpick up pending work and perform long-running operations - Supervisor:
HealthMonitorchecks stale-job counters, queue depths, andsystemctl is-activefor each agent, raisingSystemHealthCheckentries on anomalies - Missed schedules and crashed agents surface in the Admin dashboard health banner
Deployment Stamps
Implementation: Dev and Prod as fully isolated Pulumi stacks
Gmi.Pulumi.Infrastructure.DevandGmi.Pulumi.Infrastructure.Prodeach provision a complete stamp: RG, VNet, subnets, MySQL Flexible Server, Blob Storage, Key Vault, App Service Plan(s), Function App, 3 Web Apps, Report VM, NSGs- No shared infrastructure between stamps — dev changes can never affect prod traffic
- Resource naming convention (
gmiapp-{env}-*) makes the stamp boundary explicit across every resource - Same code path deploys to each stamp — scales to additional stamps (regional, tenant-specific) without refactoring
12-Factor App Methodology
Grade My Investments adheres to the 12-Factor App methodology, a set of best practices for building modern, cloud-native software-as-a-service applications. Originally published by Heroku engineers, these factors ensure applications are portable, scalable, and maintainable in cloud environments.
ICodebase
One codebase tracked in revision control, many deploys
- Single Azure DevOps Git repository
- Trunk-based development with master and develop branches
- Same codebase deploys to dev and production environments
IIDependencies
Explicitly declare and isolate dependencies
- All NuGet packages declared via
PackageReferencein.csprojfiles - No system-wide package dependencies assumed
dotnet restoreresolves all dependencies from package feed
IIIConfig
Store config in the environment
- Azure Key Vault for secrets (connection strings, API keys)
- Environment variables override
appsettings.json - Pipeline variable groups inject environment-specific config at deploy time
IVBacking Services
Treat backing services as attached resources
- MySQL via connection string (swappable between local and Azure)
- Azure Blob Storage via connection string
- Stripe, SendGrid, Twilio, FMP API all configured via injected settings
VBuild, Release, Run
Strictly separate build and run stages
- Build: Azure Pipelines compiles all projects, runs 695 unit and integration tests
- Release: Publishes artifacts with environment-specific config
- Run: Deploys to Azure App Services, VMs, and Azure Functions
VIProcesses
Execute the app as one or more stateless processes
- Azure Functions are stateless HTTP handlers
- Console workers are stateless queue consumers
- All state persisted to MySQL or Azure Blob Storage
VIIPort Binding
Export services via port binding
- Blazor apps self-host via ASP.NET Core Kestrel
- Azure Functions expose HTTP endpoints via runtime
- No external web server dependency (no IIS required)
VIIIConcurrency
Scale out via the process model
- 11 independent background services scalable as separate processes
- Azure Functions auto-scale on demand
- ReportEngine uses
ProcessorCount * threadsPerCoreconcurrency
IXDisposability
Maximize robustness with fast startup and graceful shutdown
CancellationTokenpropagated through all console apps- Ctrl+C / SIGTERM handled for graceful shutdown
- Scoped DI services ensure proper resource cleanup via
IDisposable
XDev/Prod Parity
Keep development, staging, and production as similar as possible
- Pulumi IaC provisions identical resource types in dev and prod
appsettings.Development.jsonoverrides only connection strings- Same Docker-compatible .NET runtime in both environments
XILogs
Treat logs as event streams
ILogger<T>injected across all services (450+ usages)- Structured logging with named placeholders (
{AccountId},{JobId}) - Serilog integration available for advanced sinks
XIIAdmin Processes
Run admin/management tasks as one-off processes
- BillingEngine CLI:
billing-report,charge,charge-account - Console apps support
run-oncemode for single execution - SQL migration scripts in
migrations/folder for schema changes
Domain-Driven Design (DDD)
Grade My Investments' architecture is organized around Domain-Driven Design principles as described by Eric Evans. The system is decomposed into distinct bounded contexts, each with its own entities, services, and repositories. A ubiquitous language drawn from the financial services and report generation domain ensures that code reads like business requirements.
Bounded Contexts
Account, AccountEmailAddress, AccountSmsNumber
Invoice, Charge, Payment, BillingRun, ServicePricing
Job, JobSymbolList, JobCharge, SymbolList, ReportGenerationSchedule
Folder, File, Download, FileDownload
EmailToSend, SmsToSend, AdminNotificationEmailAddress
CreditCard, StripeWebhookEvent, CouponCode, CouponUsage
ClaudeAnalysisJob, ClaudeAnalysisRequest, ReportComparisonJob
PositionReport, PositionReportListItem, SymbolListTemplate
SystemHealthCheck, CriticalErrorLog, ActivityLogEntry, AuditLog
DDD Building Blocks
Entities
Objects with unique identity persisted across the system lifecycle.
- 50 domain entities in
Gmi.ClassLibrary/Models.cs - Each has a
[Key]identity field (e.g.,AccountID,JobID) - Examples:
Account,Job,Invoice,Folder,CreditCard
Aggregates
Clusters of entities treated as a unit for data changes, accessed through an aggregate root.
- Account Aggregate: Account (root) + EmailAddresses, SmsNumbers, CreditCards, AccountFolders
- Job Aggregate: Job (root) + JobSymbolLists, JobCharges, JobNotificationSettings
- Billing Aggregate: Invoice (root) + InvoiceCharges, Charges
- Folder Aggregate: Folder (root) + Files, ChildFolders, Downloads
Value Objects
Immutable objects defined by their attributes, not identity.
AccountEmailValidationToken— temporary, identity-lessServicePricing— immutable business rules for pricingStorageProperties— computed folder/file metadata
Domain Services
Operations that don't belong to a single entity, encapsulating cross-entity business logic.
BillingService.ProcessMonthlyBillingAsync()— orchestrates charges, payments, invoicesJobService.CreateReportJobWithDataAsync()— coordinates job + symbol lists + notifications + couponsPaymentService.ProcessStripePaymentAsync()— payment with reconciliation
Domain Events
Notifications that something meaningful happened in the domain.
- Account created →
SendNewAccountNotificationAsync() - Report generated →
SendReportGeneratedNotificationAsync() - Payment failed →
FailedPaymentReconciliationentity persisted - Stripe webhook →
StripeWebhookEventcaptured as external event
Ubiquitous Language
Code uses the same terminology as the business domain.
- Billing: Invoice, Charge, Payment, BillingRun, ServicePricing
- Jobs: Job, JobStatus, SymbolList, PositionReport, ReportGenerationSchedule
- Storage: Folder, File, Download, FileDownload
- Methods:
ProcessMonthlyBillingAsync,ApplyCouponCodeToJobAsync,RequestFolderDownloadAsync
Repository per Aggregate
Grade My Investments implements 46 repository interfaces in RepositoriesInterfaces.cs,
each aligned to an aggregate boundary. Repositories encapsulate data access for their aggregate root and
its children, ensuring that all persistence operations go through a well-defined contract.
Modern Development Patterns
Async/Await Pattern
Throughout entire codebase for non-blocking operations
- • All service methods return Task<T>
- • Database operations with async
- • HTTP calls with cancellation tokens
- • File operations with async I/O
Extension Methods
Fluent API extensions for configuration
- • ServiceCollectionExtensions.cs
- • AddGmiRestClient()
- • Complex service registration logic
- • Fluent configuration patterns
Observer Pattern
Event handling in MAUI mobile applications
- • XAML event binding
- • Button click handlers
- • Navigation events
- • Page lifecycle events
SEO Page Generation Patterns
The free public ticker grade pages (/grade/{TICKER}) introduced a family of patterns for generating thousands of crawlable static pages from live data on read-only cloud hosting — several born directly from production incidents during the feature's first day.
- Static Site Generation (Prerender Pipeline): Razor components render to plain HTML at build/generation time; a hybrid rewrite layer serves static pages and the WASM app from one URL space.
- Last-Known-Good Snapshot Fallback: every ticker caches its generated grade, stats, and summary — an upstream FMP failure serves the previous snapshot instead of breaking the page or the run.
- Change-Detection Gating: Claude summaries regenerate only when a ticker's grade actually changes — cost-aware AI invocation keyed on business state, not schedules.
- Artifact Composition on Read-Only Hosting: generation runs where the data lives (report VM), output lands in blob storage, and the deploy pipeline composes it into the immutable run-from-package site.
- Deterministic Rendering: unchanged data produces byte-identical output — stable ordering, no timestamps in markup — so re-runs and diffs stay meaningful.
- Orphaned-Job Reclamation: the single-runner job queue requeues anything stuck in Running at service startup, surviving deploys and crashes mid-run.
- Chunked Checkpointing with Live Progress: the ~2,500-ticker batch saves in chunks of 50 with live counters, so operators see progress mid-run and interruptions lose minutes, not hours.
- Progressive Enhancement + Viewport-Lazy Assets: pages are fully functional with JavaScript disabled; search, group filters, signed-in detection, and the 1MB Mermaid engine (scroll-into-view) layer on top.
Application-Level Patterns
Beyond foundational and cloud design patterns, Grade My Investments implements several application-level patterns that solve recurring cross-cutting concerns across the platform.
Real-time Notification Bar
Implementation: Admin notification bar component that polls /admin/queue-counts every 60 seconds
- Shows system health status with color-coded indicators (critical=red, warning=yellow)
- Displays action queue counts for support requests, feature requests, bug reports, and feedback
- Persistent top bar in admin layout provides at-a-glance operational awareness
Announcement & Maintenance Banners
Implementation: Shared Blazor components injected into layouts
- AnnouncementBanner and MaintenanceBanner poll for active items on page load
- Announcements support severity levels and date-range visibility windows
- Maintenance windows display scheduled downtime with countdown details
- Dismissible banners with session-scoped persistence
Collapsible Navigation Groups
Implementation: Expandable/collapsible sidebar navigation sections
- Both admin and client nav menus use grouped navigation with CSS transitions
- Groups collapse/expand on click with smooth animation
- Active group auto-expands based on current route
- Reduces visual clutter as the page count grows
Database-Backed Content Management
Implementation: CRUD pattern for all content entities
- Blog, Knowledge Base, Changelog, and Announcements all stored in MySQL
- Admin CRUD pages for each content type with publish/draft workflow
- Slug-based URL routing for SEO-friendly public pages
- View count tracking for blog posts
Multi-Channel Customer Feedback Pipeline
Implementation: 4 feedback channels following a consistent pattern
- Support requests, feature requests, bug reports, and platform feedback
- All follow: submit → admin notification email → admin review → user notification
- Each has dedicated admin management pages with status workflows
- Consistent REST API pattern across all 4 channels
Email Preference Upsert
Implementation: CreateOrUpdate pattern for per-account email preferences
- Unique constraint on AccountID ensures one preference record per account
- PUT endpoint creates if not exists, updates if exists (upsert)
- Granular opt-in/opt-out toggles for each notification category
Account Data Export
Implementation: GDPR-style data export via aggregated JSON download
- Single API endpoint aggregates data from multiple tables (account, jobs, files, billing, activity)
- JSON response downloaded via JavaScript interop blob conversion
- Supports data portability and privacy compliance requirements
URL Redirect Pattern
Implementation: Multi-route Blazor page for backward-compatible URLs
- BlogRedirects page catches old static blog URLs and redirects to new slug-based routes
- Multiple
@pagedirectives on a single component map old paths to new destinations - Preserves SEO value and prevents broken bookmarks during URL migration
Pattern Summary
Pattern Benefits
- Maintainability: Clean separation of concerns
- Testability: Dependency injection enables mocking
- Scalability: Microservices and async patterns
- Reusability: Interface-based design
- Flexibility: Strategy and factory patterns
- Predictive Intelligence: ML.NET integration for forecasting
Implementation Quality
Best Practices
- Consistent dependency injection across all layers
- Interface segregation for testability
- Async/await for all I/O operations
- Configuration pattern for all settings
- Repository pattern for data access abstraction