Migration Service
The Voltimax.MigrationService is a .NET Worker Service that orchestrates database migrations, Elasticsearch index setup, Keycloak identity configuration, and data seeding on application startup. It runs as part of the .NET Aspire AppHost and must complete before the Platform API or Azure Functions start.
Overview
The migration service is a short-lived background service. It performs all migration steps sequentially, then shuts itself down. In the Aspire orchestration, downstream services like the Platform API use WaitFor(migrationService) to ensure infrastructure is ready before accepting requests.
Execution Pipeline
The Worker class extends BackgroundService and runs the following steps in order:
| Step | Component | Description |
|---|---|---|
| 1 | DatabaseMigrator | Applies pending EF Core migrations to PostgreSQL |
| 2 | ElasticsearchMigrator | Creates/updates index templates, ILM policies, and data streams |
| 3 | KeycloakMigrator | Ensures realm, clients, roles, and (dev) users exist |
| 4 | DatabaseSeeder | Seeds initial data within a transaction |
| 5 | DevLocationSeeder | Creates a development gateway configuration (Development only) |
After all steps complete, the service calls hostApplicationLifetime.StopApplication() to terminate gracefully.
All steps emit OpenTelemetry activities under the Migrations activity source for tracing.
Database Migration
DatabaseMigrator uses Entity Framework Core to apply pending migrations against the PlatformDbContext (PostgreSQL). It uses an execution strategy to handle transient failures:
- Lists applied and pending migrations with structured logging
- Calls
Database.MigrateAsync()withinCreateExecutionStrategy()
INFO
Migrations are generated in the Voltimax.Platform.Data project. To add a new migration, run:
dotnet ef migrations add <MigrationName> --project src/Voltimax.Platform.DataElasticsearch Migration
The service delegates to ElasticsearchMigrator from Voltimax.Iot.Metrics. This creates or updates the ILM lifecycle policy, component templates, index template, and data stream. See Index Migrator for details.
Keycloak Migration
The Keycloak migration ensures the identity provider is correctly configured. It uses the Keycloak Admin API via the Keycloak.AuthServices.Sdk.Kiota client to provision the realm, OIDC clients, roles, and authorization settings.
All Keycloak operations are idempotent - running the migration multiple times is safe. For the full breakdown of each step, resources, policies, and permissions, see the Security Provisioning documentation.
Clients
The migration creates four OIDC clients in the voltimax realm:
| Client ID | Type | Purpose |
|---|---|---|
iot-platform-api | Confidential, service account | Platform API resource server with Authorization Services |
iot-platform-scalar | Public (PKCE) | Scalar API documentation UI |
iot-frontend | Public (PKCE) | Vue.js frontend application |
iot-platform-mcp | Public (PKCE) | MCP server for AI tooling |
Public clients use PKCE (S256 code challenge) and include protocol mappers for realm roles and audience claims.
Roles & Authorization
The migration provisions client roles (platform-admin, platform-operator, platform-viewer, gateway-device) and the full authorization model (resources, scopes, policies, and scope permissions) on the iot-platform-api client via Keycloak's bulk import endpoint.
See Authorization Model for how these are structured.
Dev User
In the Development environment, the migration creates a user with credentials dev / devpass and assigns the platform-admin role. This allows immediate API access during local development.
Database Seeding
DatabaseSeeder runs within a transaction using the execution strategy pattern. It is currently a placeholder for initial data seeding - add seed logic inside the ExecuteAsync callback.
Dev Location Seeding
In the Development environment, DevLocationSeeder creates a gateway configuration for local testing. It:
- Checks if gateway
123083100762already exists (skip if so) - Creates a
GatewayConfigurationwith a physical location (Groningen, NL) - Configures multiple asset devices:
- Siemens PAC2200 meters (grid, solar)
- CNTE STAR-H battery system
- Eastron SDM630 meters (solar carport, DC chargers)
- Peblar AC chargers (7 units)
Aspire Integration
In the AppHost, the migration service is wired with all required infrastructure:
var migrationService = builder
.AddProject<Voltimax_MigrationService>("migrations")
.WithReference(infrastructure.Postgres)
.WithReference(infrastructure.PlatformDb)
.WithReference(infrastructure.StorageContainers.Configuration)
.WithReference(infrastructure.StorageContainers.Workflows)
.WithElasticsearch(infrastructure.Elasticsearch, secrets)
.WaitFor(infrastructure.Postgres);Downstream services wait for the migration to complete:
var platformFunctions = builder
.AddAzureFunctionsProject<Voltimax_Platform_Functions>("platform-functions")
.WaitFor(migrationService);Project Dependencies
| Dependency | Purpose |
|---|---|
Voltimax.Platform.Repositories | Repository access for seeding |
Voltimax.Platform.Data | EF Core PlatformDbContext and migrations |
Voltimax.Iot.Metrics | Elasticsearch migration (ElasticsearchMigrator) |
Voltimax.Iot.ServiceDefaults | Aspire service defaults (OpenTelemetry, health checks) |
Voltimax.EnergyAdvisor | Energy advisor service registration |
Keycloak.AuthServices.Sdk.Kiota | Keycloak Admin API client |
Duende.AccessTokenManagement | Client credentials token management |
Configuration
Keycloak
Keycloak admin credentials are configured in appsettings.json and typically overridden via user secrets or environment variables:
{
"Keycloak": {
"AuthServerUrl": "https://keycloak.example.com/",
"Realm": "voltimax",
"Admin": {
"Realm": "master",
"ClientId": "admin-cli"
}
}
}WARNING
The MigrationService connects to the master realm with admin credentials to perform provisioning. It ensures the voltimax realm exists and configures all clients and authorization settings. See Provisioning for the environment variable mapping.
Elasticsearch
Elasticsearch connection settings and index namespace are provided via Aspire resource references. See Data Storage for configuration details.
PostgreSQL
The PostgreSQL connection string is provided via the Aspire PlatformDb resource reference.