Skip to content

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:

StepComponentDescription
1DatabaseMigratorApplies pending EF Core migrations to PostgreSQL
2ElasticsearchMigratorCreates/updates index templates, ILM policies, and data streams
3KeycloakMigratorEnsures realm, clients, roles, and (dev) users exist
4DatabaseSeederSeeds initial data within a transaction
5DevLocationSeederCreates 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:

  1. Lists applied and pending migrations with structured logging
  2. Calls Database.MigrateAsync() within CreateExecutionStrategy()

INFO

Migrations are generated in the Voltimax.Platform.Data project. To add a new migration, run:

bash
dotnet ef migrations add <MigrationName> --project src/Voltimax.Platform.Data

Elasticsearch 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 IDTypePurpose
iot-platform-apiConfidential, service accountPlatform API resource server with Authorization Services
iot-platform-scalarPublic (PKCE)Scalar API documentation UI
iot-frontendPublic (PKCE)Vue.js frontend application
iot-platform-mcpPublic (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:

  1. Checks if gateway 123083100762 already exists (skip if so)
  2. Creates a GatewayConfiguration with a physical location (Groningen, NL)
  3. 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:

csharp
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:

csharp
var platformFunctions = builder
    .AddAzureFunctionsProject<Voltimax_Platform_Functions>("platform-functions")
    .WaitFor(migrationService);

Project Dependencies

DependencyPurpose
Voltimax.Platform.RepositoriesRepository access for seeding
Voltimax.Platform.DataEF Core PlatformDbContext and migrations
Voltimax.Iot.MetricsElasticsearch migration (ElasticsearchMigrator)
Voltimax.Iot.ServiceDefaultsAspire service defaults (OpenTelemetry, health checks)
Voltimax.EnergyAdvisorEnergy advisor service registration
Keycloak.AuthServices.Sdk.KiotaKeycloak Admin API client
Duende.AccessTokenManagementClient credentials token management

Configuration

Keycloak

Keycloak admin credentials are configured in appsettings.json and typically overridden via user secrets or environment variables:

json
{
  "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.