Skip to content
SlimAhmadPublic

About

A Standardized Provider Abstraction Library (SPAL) for swapping SQL Server, PostgreSQL, Cosmos DB, and MongoDB storage backends via configuration only.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Omniport logo

Omniport

A Standardized Provider Abstraction Library (SPAL) for swapping SQL Server, PostgreSQL, Cosmos DB, and MongoDB storage backends via configuration only.


What is Omniport?

Omniport gives every microservice a single shared contract, IStorageBrokerProvider, that each database provider implements. A microservice keeps its own StorageBroker — it still owns its entity model, OnModelCreating, and migrations — but delegates all provider-specific configuration (which EF Core provider to use, connection strings, dialect quirks) to whichever IStorageBrokerProvider is injected.

The result: swapping a microservice's database backend is a config value change (Storage:Backend) plus a connection string — no recompilation of business logic.

This follows The Standard, Chapter 1.8 — Standardized Provider Abstraction Libraries (SPAL).

Packages

Package Purpose
Omniport The shared contract (IStorageBrokerProvider, TargetServerType). Every other package depends on it. Kept small and stable — a breaking change here is a breaking change everywhere.
Omniport.SqlServer SQL Server provider (Microsoft.EntityFrameworkCore.SqlServer).
Omniport.PostgreSql PostgreSQL provider (Npgsql.EntityFrameworkCore.PostgreSQL), plus a DateTimeOffsetTruncationInterceptor that handles the Postgres timestamptz precision mismatch against .NET's DateTimeOffset.
Omniport.Cosmos Cosmos DB provider (Microsoft.EntityFrameworkCore.Cosmos), with local-emulator support via CosmosStorageBrokerOptions.
Omniport.MongoDb MongoDB provider (MongoDB.EntityFrameworkCore), with local-instance support via MongoDbStorageBrokerOptions.

Only reference the providers a given microservice actually needs — there's no requirement to pull in all four if a service will only ever run against one or two backends.

Install

dotnet add package Omniport
dotnet add package Omniport.SqlServer     # or PostgreSql / Cosmos / MongoDb

Pin exact versions in consuming services — don't float:

<PackageReference Include="Omniport" Version="1.0.0" />
<PackageReference Include="Omniport.SqlServer" Version="1.0.0" />

Usage

1. StorageBroker — delegate provider config to Omniport

public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
    private readonly IStorageBrokerProvider storageBrokerProvider;

    public StorageBroker(IStorageBrokerProvider storageBrokerProvider)
    {
        this.storageBrokerProvider = storageBrokerProvider;

        // Cosmos and MongoDB are schemaless — no relational migration history.
        if (storageBrokerProvider is CosmosStorageBrokerProvider
            || storageBrokerProvider is MongoDbStorageBrokerProvider)
        {
            this.Database.EnsureCreated();
        }
        else
        {
            this.Database.Migrate();
        }
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
        this.storageBrokerProvider.Configure(optionsBuilder);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // this service's own entity configuration goes here first
        this.storageBrokerProvider.ConfigureModel(modelBuilder);
    }

    protected override void ConfigureConventions(
        ModelConfigurationBuilder configurationBuilder) =>
            this.storageBrokerProvider.ConfigureConventions(configurationBuilder);
}

2. Program.cs — pick a provider from config

string backend = builder.Configuration["Storage:Backend"]; // "SqlServer" | "Postgres" | "Cosmos" | "MongoDb"
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
string migrationsAssembly = $"YourService.{backend}";

builder.Services.AddSingleton<IStorageBrokerProvider>(_ => backend switch
{
    "SqlServer" => new SqlServerStorageBrokerProvider(connectionString, migrationsAssembly),
    "Postgres"  => new PostgreSqlStorageBrokerProvider(connectionString, migrationsAssembly),
    "Cosmos"    => new CosmosStorageBrokerProvider(new CosmosStorageBrokerOptions
    {
        ConnectionString = connectionString,
        DatabaseName = builder.Configuration["Storage:Cosmos:DatabaseName"],
        TargetServerType = builder.Configuration.GetValue<bool>("Storage:UseLocalEmulator")
            ? TargetServerType.Local
            : TargetServerType.Remote
    }),
    "MongoDb"   => new MongoDbStorageBrokerProvider(new MongoDbStorageBrokerOptions
    {
        ConnectionString = connectionString,
        DatabaseName = builder.Configuration["Storage:MongoDb:DatabaseName"],
        TargetServerType = builder.Configuration.GetValue<bool>("Storage:UseLocalEmulator")
            ? TargetServerType.Local
            : TargetServerType.Remote
    }),
    _ => throw new NotSupportedException($"Unsupported storage backend: {backend}")
});

builder.Services.AddDbContext<StorageBroker>();

3. appsettings.json

{
  "Storage": {
    "Backend": "SqlServer",
    "UseLocalEmulator": false,
    "Cosmos": { "DatabaseName": "YourServiceDB" },
    "MongoDb": { "DatabaseName": "YourServiceDB" }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=...;Database=...;"
  }
}

Switching backends is now: change Storage:Backend + ConnectionStrings:DefaultConnection. Nothing else in the microservice changes.

4. Migrations (SQL Server / PostgreSQL only)

Cosmos and MongoDB are schemaless — they don't support dotnet ef migrations. Provisioning happens via Database.EnsureCreated() at startup instead (see the StorageBroker snippet above).

For SQL Server and PostgreSQL, each relational provider project in the consuming microservice's own repo needs a design-time factory so dotnet ef can construct a StorageBroker without full app startup:

internal class SqlServerContextFactory : IDesignTimeDbContextFactory<StorageBroker>
{
    public StorageBroker CreateDbContext(string[] args)
    {
        string connectionString =
            Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING")
                ?? "Server=localhost;Database=YourServiceDB;Trusted_Connection=True;TrustServerCertificate=True";

        return new StorageBroker(
            new SqlServerStorageBrokerProvider(connectionString, "YourService.SqlServer"));
    }
}
dotnet ef migrations add InitialCreate --project YourService.SqlServer --context StorageBroker --output-dir Migrations
dotnet ef database update --project YourService.SqlServer --context StorageBroker

Give the same migration the same name across providers (InitialCreate, AddUsageLimitIndex, ...) even though they live in separate folders, so it's obvious they correspond to the same logical schema change. Run dotnet ef database update for every provider on every deploy, not just the currently-active one, so a future Storage:Backend flip doesn't hit a stale/uninitialized database.

5. Provider-specific notes

PostgreSQL — ConfigureConventions maps DateTimeOffset to timestamptz at microsecond precision, and DateTimeOffsetTruncationInterceptor truncates outgoing DateTimeOffset parameters to match, since Postgres timestamptz doesn't carry the full precision .NET's DateTimeOffset does.

Cosmos DB — entities need a partition key configured per-entity in the microservice's OnModelCreating, since only the microservice knows its entity shapes: modelBuilder.Entity<T>().HasPartitionKey(x => x.SomeProperty).

MongoDB — MongoDB.EntityFrameworkCore is newer and less feature-complete than the relational/Cosmos EF Core providers (no migrations, limited LINQ operator support, different owned-entity mapping). Confirm current capabilities against the official docs before relying on advanced query patterns. If it's too limited for a given service, that service can bypass DbContext and use MongoDB.Driver directly for the affected operations while still using MongoDbStorageBrokerProvider elsewhere — that's a microservice-level decision and doesn't change this library.

Additional MongoDB specifics to configure in the microservice's OnModelCreating:

// MongoDB has no native DateTimeOffset — store as UTC DateTime and convert.
modelBuilder.Entity<YourEntity>()
    .Property(e => e.CreatedAt)
    .HasConversion(v => v.UtcDateTime, v => new DateTimeOffset(v, TimeSpan.Zero));

// MongoDB documents use `_id` as the primary key field by convention.
modelBuilder.Entity<YourEntity>()
    .Property(e => e.Id)
    .HasElementName("_id");

6. External Mockability (Cloud-Foreign compliance)

For Acceptance Testing / Airplane-Mode runs, each provider supports a local target via TargetServerType.Local:

  • SQL Server / PostgreSQL — point the connection string at a Testcontainers-managed container.
  • Cosmos DB — run the Azure Cosmos DB Emulator and set TargetServerType.Local, which routes to CosmosStorageBrokerOptions.LocalEmulatorEndpoint / LocalEmulatorKey.
  • MongoDB — run the official mongo Docker image and set TargetServerType.Local, which routes to MongoDbStorageBrokerOptions.LocalConnectionString (mongodb://localhost:27017 by default).

A consuming service's acceptance test suite can drive provider selection from environment variables (a PROVIDER / CONNECTION_STRING pair read into a ClientBroker) so a CI matrix runs the whole suite once per backend without any code duplication — see the pattern description in this repo's build spec for the full example.

Versioning

Semver, strictly enforced:

  • PATCH (1.0.x) — bug fixes only, no interface changes.
  • MINOR (1.x.0) — new capability, fully backward compatible (e.g. an optional overload).
  • MAJOR (x.0.0) — any change to IStorageBrokerProvider's signature, or any change to a provider constructor's parameters. Every consuming microservice must bump to this deliberately after testing — never auto-float major versions.

Contributing

This repo follows The Standard. See .agents/skills/the-standard-* for the coding, testing, and contribution-practice rules enforced here.

About

A Standardized Provider Abstraction Library (SPAL) for swapping SQL Server, PostgreSQL, Cosmos DB, and MongoDB storage backends via configuration only.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages