Skip to content

feat(data): add configurable database provider via DATABASE_PROVIDER (#249)#490

Merged
nanotaboada merged 5 commits intomasterfrom
feat/249-configurable-database-provider
May 3, 2026
Merged

feat(data): add configurable database provider via DATABASE_PROVIDER (#249)#490
nanotaboada merged 5 commits intomasterfrom
feat/249-configurable-database-provider

Conversation

@nanotaboada
Copy link
Copy Markdown
Owner

@nanotaboada nanotaboada commented May 3, 2026

Summary

  • Introduces DATABASE_PROVIDER env var (sqlite default, postgres opt-in) to select the database engine at startup without code changes
  • Adds ProviderSpecificMigrationsAssembly to filter EF Core migration discovery to the active provider's namespace at runtime, with separate Migrations/Npgsql/ migrations using PostgreSQL-typed columns (uuid, boolean, timestamp with time zone)
  • Adds a postgres Docker Compose profile with depends_on: required: false so SQLite mode incurs no Docker dependency

Test plan

  • dotnet build --configuration Release — succeeds
  • dotnet test --settings .runsettings — 64/64 passed
  • dotnet csharpier --check . — clean
  • CodeRabbit review — 1 finding addressed (moved Npgsql.EntityFrameworkCore.PostgreSQL from Development to Runtime ItemGroup)

Closes #249

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Runtime-selectable database engine (sqlite default, Postgres opt‑in) with Docker profile and provider-aware startup behavior.
  • Documentation

    • README, CHANGELOG, ADRs, and GitHub guidance updated; added .env.example and documented env vars.
  • Chores

    • Added Postgres runtime dependency, .env ignored in git, Compose adds optional Postgres service/volume; entrypoint adapts initialization by provider.
  • Tests

    • Integration tests adjusted to support provider-specific migration handling.

…249)

Introduce DATABASE_PROVIDER env var to select the database engine at
startup: sqlite (default, zero-infrastructure) or postgres (opt-in,
requires Docker Compose postgres profile). Both providers use
MigrateAsync() at startup for schema management.

- Add Npgsql.EntityFrameworkCore.PostgreSQL 10.0.1 to Runtime deps
- Rename AddDbContextPoolWithSqlite -> AddDbContextPool; reads
  DATABASE_PROVIDER and wires UseNpgsql or UseSqlite accordingly
- Add ProviderSpecificMigrationsAssembly to filter EF Core migration
  discovery to the active provider's namespace at runtime
- Add Migrations/Npgsql/ with PostgreSQL-typed migrations (uuid,
  boolean, timestamp with time zone) on separate timestamps to avoid
  MigrationAttribute.Id key collisions in the assembly scan
- Add postgres Compose profile; api depends_on postgres with
  required: false so SQLite mode incurs no Docker dependency
- Skip SQLite file-presence check in entrypoint.sh for postgres mode
- Add .env.example; add .env to .gitignore
- Add ADR-0014 superseding ADR-0003; update README and CHANGELOG

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

@nanotaboada has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 36 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea2978e2-2c98-432c-985b-16255d621b0f

📥 Commits

Reviewing files that changed from the base of the PR and between f937601 and 9a513b4.

📒 Files selected for processing (2)
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs

Walkthrough

Adds a configurable database provider via DATABASE_PROVIDER (default sqlite, opt-in postgres), adds Npgsql runtime dependency, replaces the SQLite-only DbContext registration with provider-aware AddPlayerDbContext + ProviderSpecificMigrationsAssembly, updates Docker Compose and entrypoint for optional Postgres, and updates docs, ADR, tests, and Sonar exclusions.

Changes

Configurable Database Provider

Layer / File(s) Summary
Configuration & Environment
.env.example, .gitignore
Adds DATABASE_PROVIDER=sqlite, DATABASE_URL, POSTGRES_PASSWORD to .env.example; adds .env to .gitignore.
Documentation & Decision
adr/0003-use-sqlite-for-data-storage.md, adr/0014-configurable-database-provider.md, adr/README.md, README.md, CHANGELOG.md, .github/copilot-instructions.md
Adds ADR-0014 and marks ADR-0003 superseded; documents provider selection, provider-specific migrations workflow, Docker Compose usage, migration authoring guidance, and updates Copilot instructions, README and changelog.
Project Dependency
src/.../Dotnet.Samples.AspNetCore.WebApi.csproj
Adds runtime dependency Npgsql.EntityFrameworkCore.PostgreSQL version 10.0.1.
Core Implementation
src/.../Extensions/ServiceCollectionExtensions.cs, src/.../Program.cs
Replaces SQLite-only registration with AddPlayerDbContext which reads normalized DATABASE_PROVIDER, configures UseSqlite or UseNpgsql (with retry and warning suppression), enables dev logging, and replaces IMigrationsAssembly with ProviderSpecificMigrationsAssembly; Program.cs calls the new method.
Infrastructure & Startup
compose.yaml, scripts/entrypoint.sh
Wires DATABASE_PROVIDER/DATABASE_URL into api env; adds profile-gated postgres service (Postgres 17) with postgres-data volume and healthcheck; api depends_on postgres with required: false; entrypoint lowercases/normalizes DATABASE_PROVIDER and skips SQLite file checks when using Postgres.
Tests / Tooling
test/.../PlayerWebApplicationTests.cs, test/.../DatabaseFakes.cs, .sonarcloud.properties
Test host and DatabaseFakes register ProviderSpecificMigrationsAssembly for the test DbContext; SonarCloud exclusions extended to Npgsql migration files; integration test wiring updated accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant DC as "docker compose"
    participant Entrypoint as "entrypoint.sh"
    participant API as "API (App)"
    participant EF as "EF Core (Migrations)"
    participant PG as "Postgres DB"

    DC->>Entrypoint: start container (env: DATABASE_PROVIDER)
    Entrypoint->>Entrypoint: normalize DATABASE_PROVIDER (default sqlite)
    alt provider == postgres
        Entrypoint->>API: launch app (Postgres mode)
        API->>EF: configure UseNpgsql + ProviderSpecificMigrationsAssembly
        API->>PG: connect & run MigrateAsync()
    else provider == sqlite
        Entrypoint->>Entrypoint: ensure SQLite file/dir
        Entrypoint->>API: launch app (SQLite mode)
        API->>EF: configure UseSqlite + ProviderSpecificMigrationsAssembly
        API->>API: run MigrateAsync() against SQLite file
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Add Npgsql provider and package; DATABASE_PROVIDER controls provider selection (#249)
Replace AddDbContextPoolWithSqlite with provider-aware registration (#249)
Update Docker Compose with profile-gated Postgres and env wiring (#249)
.env.example docs, gitignore .env, ADR-0014, README and CHANGELOG updates (#249)
All existing tests pass (#249) Tests were modified to register ProviderSpecificMigrationsAssembly, but CI/test run results are not present in this diff.

Out-of-scope changes

No functional out-of-scope code changes were identified relative to the objectives in the linked issue.

Possibly related PRs

Suggested labels

dependencies, .NET

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding configurable database provider selection via DATABASE_PROVIDER environment variable, which is the core objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #249

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/249-configurable-database-provider
  • 🛠️ sync documentation: Commit on current branch
  • 🛠️ sync documentation: Create PR
  • 🛠️ enforce http error handling: Commit on current branch
  • 🛠️ enforce http error handling: Create PR
  • 🛠️ idiomatic review: Commit on current branch
  • 🛠️ idiomatic review: Create PR
  • 🛠️ verify api contract: Commit on current branch
  • 🛠️ verify api contract: Create PR

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 36 minutes and 36 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Around line 6-9: Replace the real-looking password in the example env entries
with an unambiguous, non-deployable placeholder: update the DATABASE_URL value
to use a placeholder password (e.g., <your-postgres-password> or CHANGE_ME)
inside the connection string and set POSTGRES_PASSWORD to the same placeholder;
adjust any explanatory comment if needed so both DATABASE_URL and
POSTGRES_PASSWORD clearly signal they must be replaced before deployment (refer
to the DATABASE_URL and POSTGRES_PASSWORD entries to locate the changes).

In @.github/copilot-instructions.md:
- Around line 210-213: Update the README-style guidance that still claims the
repo is SQLite-only: change the overview and the "Never modify" section so they
acknowledge both SQLite and PostgreSQL options and mention the provider
switching behavior (refer to the existing "Switch database provider" text and
the ProviderSpecificMigrationsAssembly concept). Keep the step-by-step "Modify
schema" instructions but remove any SQLite-only wording and add that migrations
must be generated for both providers (Migrations/ and Migrations/Npgsql/) when
schema changes are made; also clarify that setting DATABASE_PROVIDER=postgres
plus DATABASE_URL enables Postgres. Ensure references to "Never modify" no
longer assert SQLite-only constraints and instead warn about editing
provider-specific migrations or migration assembly filtering.

In `@scripts/entrypoint.sh`:
- Around line 13-16: The script uses DATABASE_PROVIDER without normalizing case,
so comparisons (the if branch that checks "$DATABASE_PROVIDER" = "postgres") can
mismatch values like POSTGRES; change the assignment of DATABASE_PROVIDER in
scripts/entrypoint.sh to normalize to lowercase (consistent with
AddDbContextPool) before any branching, then use that normalized
DATABASE_PROVIDER for the if check and any subsequent logic; ensure the variable
name DATABASE_PROVIDER and the branch that checks "postgres" are updated to use
the lowercased value.

In
`@src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs`:
- Around line 38-63: The switch currently treats any non-"postgres" value as
SQLite and lets empty DATABASE_URL through; update the logic around the provider
variable and the DATABASE_URL guard so mis-typed providers fail fast: trim and
normalize the provider (e.g., provider =
(Environment.GetEnvironmentVariable("DATABASE_PROVIDER") ?? "").Trim();) then
explicitly handle "postgres" with a strict DATABASE_URL check using
string.IsNullOrWhiteSpace before calling options.UseNpgsql(connectionString),
explicitly handle "sqlite" (or empty provider) to use UseSqlite, and for any
other non-empty provider throw an InvalidOperationException indicating an
unsupported DATABASE_PROVIDER value so typos (like "postgress") no longer
silently create a SQLite DB.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 807ab79e-2e2f-4d83-a180-bd931529bf5f

📥 Commits

Reviewing files that changed from the base of the PR and between 0672369 and aed5f3e.

⛔ Files ignored due to path filters (9)
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151000_InitialCreate.Designer.cs is excluded by !**/Migrations/**, !**/*.Designer.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151000_InitialCreate.cs is excluded by !**/Migrations/**
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151100_SeedStarting11.Designer.cs is excluded by !**/Migrations/**, !**/*.Designer.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151100_SeedStarting11.cs is excluded by !**/Migrations/**
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151200_SeedSubstitutes.Designer.cs is excluded by !**/Migrations/**, !**/*.Designer.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151200_SeedSubstitutes.cs is excluded by !**/Migrations/**
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/ProviderSpecificMigrationsAssembly.cs is excluded by !**/Migrations/**
  • src/Dotnet.Samples.AspNetCore.WebApi/packages.lock.json is excluded by !**/packages.lock.json
  • test/Dotnet.Samples.AspNetCore.WebApi.Tests/packages.lock.json is excluded by !**/packages.lock.json
📒 Files selected for processing (15)
  • .env.example
  • .github/copilot-instructions.md
  • .gitignore
  • CHANGELOG.md
  • README.md
  • adr/0003-use-sqlite-for-data-storage.md
  • adr/0014-configurable-database-provider.md
  • adr/README.md
  • compose.yaml
  • scripts/entrypoint.sh
  • src/Dotnet.Samples.AspNetCore.WebApi/Dotnet.Samples.AspNetCore.WebApi.csproj
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Program.cs
  • test/Dotnet.Samples.AspNetCore.WebApi.Tests/Integration/PlayerWebApplicationTests.cs
  • test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/DatabaseFakes.cs

Comment thread .env.example
Comment thread .github/copilot-instructions.md
Comment thread scripts/entrypoint.sh Outdated
Comment thread src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs Outdated
nanotaboada and others added 3 commits May 2, 2026 23:27
Add the six Migrations/Npgsql/ files to sonar.exclusions,
sonar.coverage.exclusions, and sonar.cpd.exclusions for the same
reasons the SQLite migration set is already excluded: EF Core-
generated files with intentionally repetitive InsertData calls
are not meaningful targets for duplication or coverage analysis.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…249)

Address post-merge review findings on the configurable provider
feature. No behavioral change for valid sqlite/postgres values.

- ServiceCollectionExtensions: trim and normalize DATABASE_PROVIDER
  before the switch; add explicit "sqlite"/"" case; throw
  InvalidOperationException for unrecognized values so typos like
  "postgress" no longer silently fall through to SQLite; replace
  null-coalescing guard on DATABASE_URL with IsNullOrWhiteSpace
- entrypoint.sh: normalize DATABASE_PROVIDER to lowercase via tr
  so POSTGRES, Postgres, etc. are handled consistently
- copilot-instructions.md: update Overview to mention both providers;
  replace SQLite-only "Never modify" constraint with a warning about
  ProviderSpecificMigrationsAssembly namespace constants

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ing (#249)

Two bugs caught by Docker Compose sanity check of the PostgreSQL path.

- Populate BuildTargetModel in Npgsql seed migration designer files
  so Npgsql's SQL generator can resolve column types when applying
  InsertData operations (SeedStarting11, SeedSubstitutes)
- Suppress PendingModelChangesWarning for the postgres provider path
  via ConfigureWarnings; hand-crafted designer files cannot replicate
  Npgsql-injected runtime annotations (Relational:MaxIdentifierLength,
  UseIdentityByDefaultColumn), causing a false-positive that aborted
  MigrateAsync() at startup; BuildTargetModel is still populated so
  InsertData SQL generation continues to work correctly

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@nanotaboada nanotaboada force-pushed the feat/249-configurable-database-provider branch from 45f4118 to f937601 Compare May 3, 2026 12:55
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs (1)

32-35: ⚡ Quick win

Prefer a feature-specific name over AddDbContextPool.

This reads like the EF Core framework method at the call site, but it now also resolves environment variables and swaps the migrations assembly. Renaming it to something like AddPlayerDbContext or AddDatabaseProvider would make the custom behavior explicit.

As per coding guidelines: src/**/Extensions/**/*.cs: "Follow the Add{Feature} naming convention (e.g. AddPlayerServices, AddRateLimiting)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs`
around lines 32 - 35, The method AddDbContextPool should be renamed to a
feature-specific name (e.g., AddPlayerDbContext or AddDatabaseProvider) to avoid
clashing with EF Core's AddDbContextPool and to reflect the custom behavior (env
var resolution and migrations assembly swap); update the method declaration name
and all callers to the new name, keep the existing signature (IServiceCollection
Add... (this IServiceCollection services, IWebHostEnvironment environment)), and
update any XML/documentation/comments to match the new Add{Feature} naming
convention (e.g., AddPlayerDbContext).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs`:
- Around line 45-58: In the "postgres" branch (inside
ServiceCollectionExtensions.cs) the call options.UseNpgsql(connectionString)
needs to be replaced with the overload that enables transient-fault retries:
call options.UseNpgsql(connectionString, npgsqlOptions =>
npgsqlOptions.EnableRetryOnFailure(/* optionally maxRetryCount, maxRetryDelay,
errorCodes */)); keep the existing ConfigureWarnings call unchanged; this
attaches EF Core/Npgsql retry behavior so the db.Database.MigrateAsync() startup
call can survive transient connection blips.

---

Nitpick comments:
In
`@src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs`:
- Around line 32-35: The method AddDbContextPool should be renamed to a
feature-specific name (e.g., AddPlayerDbContext or AddDatabaseProvider) to avoid
clashing with EF Core's AddDbContextPool and to reflect the custom behavior (env
var resolution and migrations assembly swap); update the method declaration name
and all callers to the new name, keep the existing signature (IServiceCollection
Add... (this IServiceCollection services, IWebHostEnvironment environment)), and
update any XML/documentation/comments to match the new Add{Feature} naming
convention (e.g., AddPlayerDbContext).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0de4c155-fa01-4e35-963c-50a2bae94c27

📥 Commits

Reviewing files that changed from the base of the PR and between 45f4118 and f937601.

⛔ Files ignored due to path filters (2)
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151100_SeedStarting11.Designer.cs is excluded by !**/Migrations/**, !**/*.Designer.cs
  • src/Dotnet.Samples.AspNetCore.WebApi/Migrations/Npgsql/20260409151200_SeedSubstitutes.Designer.cs is excluded by !**/Migrations/**, !**/*.Designer.cs
📒 Files selected for processing (2)
  • CHANGELOG.md
  • src/Dotnet.Samples.AspNetCore.WebApi/Extensions/ServiceCollectionExtensions.cs
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

- Rename AddDbContextPool -> AddPlayerDbContext to follow the Add{Feature}
  naming convention and avoid shadowing EF Core's own AddDbContextPool;
  update call site in Program.cs accordingly
- Add EnableRetryOnFailure() to the Npgsql path so MigrateAsync() and
  subsequent queries survive transient connection blips at startup

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@nanotaboada nanotaboada force-pushed the feat/249-configurable-database-provider branch from 6e252af to 9a513b4 Compare May 3, 2026 14:37
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 3, 2026

@nanotaboada
Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Reviews resumed.

@nanotaboada nanotaboada merged commit 22deb02 into master May 3, 2026
9 checks passed
@nanotaboada nanotaboada deleted the feat/249-configurable-database-provider branch May 3, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add PostgreSQL support as configurable database provider

1 participant