You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .github/copilot-instructions.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
## Overview
4
4
5
-
REST API for managing football players built with ASP.NET Core 10. Implements CRUD operations with a layered architecture, EF Core + SQLite persistence, FluentValidation, AutoMapper, and in-memory caching. Part of a cross-language comparison study (Go, Java, Python, Rust, TypeScript). Primarily a learning and reference project — clarity and educational value take precedence over brevity.
5
+
REST API for managing football players built with ASP.NET Core 10. Implements CRUD operations with a layered architecture, EF Core persistence (SQLite by default, PostgreSQL opt-in via `DATABASE_PROVIDER`), FluentValidation, AutoMapper, and in-memory caching. Part of a cross-language comparison study (Go, Java, Python, Rust, TypeScript). Primarily a learning and reference project — clarity and educational value take precedence over brevity.
6
6
7
7
## Tech Stack
8
8
@@ -11,7 +11,7 @@ REST API for managing football players built with ASP.NET Core 10. Implements CR
-Migration namespace constants in `ProviderSpecificMigrationsAssembly` — renaming breaks runtime provider filtering for one or both providers
185
185
- CD pipeline tag format (`vX.Y.Z-stadium`) or the stadium name sequence — names are assigned sequentially A→Z from the list in `CHANGELOG.md`; the next name is always the next unused letter
186
186
187
187
### Creating Issues
@@ -207,7 +207,9 @@ This project uses Spec-Driven Development (SDD): discuss in Plan mode first, cre
207
207
208
208
**Add an endpoint**: Add DTO in `Models/` → update `PlayerMappingProfile` in `Mappings/` → add repository method(s) in `Repositories/` → add service method in `Services/` → add controller action in `Controllers/` → add/update validator rule set in `Validators/` → add tests in `test/.../Unit/` → run pre-commit checks.
209
209
210
-
**Modify schema**: Update `Player` entity → update DTOs → update AutoMapper profile → update `HasData()` seed data in `OnModelCreating` if needed → run `dotnet ef migrations add <Name>` → update tests → run `dotnet test`.
210
+
**Modify schema**: Update `Player` entity → update DTOs → update AutoMapper profile → update `HasData()` seed data in `OnModelCreating` if needed → run `dotnet ef migrations add <Name>` twice (once for each provider: output goes to `Migrations/` for SQLite, `Migrations/Npgsql/` for PostgreSQL) → update tests → run `dotnet test`.
211
+
212
+
**Switch database provider**: Set `DATABASE_PROVIDER=postgres` (plus `DATABASE_URL`) to use PostgreSQL, or leave unset for SQLite (default). `ProviderSpecificMigrationsAssembly` filters migration discovery to the active provider's namespace at runtime — no code changes needed to switch.
- Proposing changes to core architecture or dependencies
217
219
- Historical context for past decisions is needed
218
220
219
-
ADRs are in `adr/` (0001–0012). Each file is self-contained.
221
+
ADRs are in `adr/` (0001–0014). Each file is self-contained.
220
222
221
223
**After completing work**: Suggest a branch name (e.g. `feat/add-player-search`) and a commit message following Conventional Commits including co-author line:
Copy file name to clipboardExpand all lines: CHANGELOG.md
+20Lines changed: 20 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,10 +44,30 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc
44
44
45
45
### Added
46
46
47
+
-`DATABASE_PROVIDER` environment variable (`sqlite` default, `postgres` opt-in) to select the database engine at startup (issue #249).
48
+
- PostgreSQL 17 support via `Npgsql.EntityFrameworkCore.PostgreSQL` 10.0.1; migrations in `Migrations/Npgsql/` use proper PostgreSQL column types (`uuid`, `boolean`, `timestamp with time zone`).
49
+
-`ProviderSpecificMigrationsAssembly` that filters the EF Core migration set to the active provider's namespace, ensuring `MigrateAsync()` applies the correct migrations for both SQLite and PostgreSQL.
50
+
-`postgres` Docker Compose profile and service (`postgres:17-alpine`), started only when `DATABASE_PROVIDER=postgres` is set; the API service uses `depends_on` with `required: false` so SQLite mode incurs no dependency on the postgres service.
51
+
-`.env.example` documenting `DATABASE_PROVIDER`, `DATABASE_URL`, and `POSTGRES_PASSWORD`.
52
+
-`.env` added to `.gitignore`.
53
+
- ADR-0014 (`adr/0014-configurable-database-provider.md`) documenting the decision; supersedes ADR-0003.
54
+
47
55
### Changed
48
56
57
+
-`AddDbContextPoolWithSqlite` renamed to `AddDbContextPool` in `ServiceCollectionExtensions`; now reads `DATABASE_PROVIDER` and wires either `UseSqlite` or `UseNpgsql` accordingly.
58
+
-`compose.yaml`: `api` service receives `DATABASE_PROVIDER` and `DATABASE_URL` environment variables; `postgres-data` named volume added.
59
+
-`scripts/entrypoint.sh`: SQLite file-presence check is skipped when `DATABASE_PROVIDER=postgres`.
60
+
- ADR-0003 status updated to "Superseded by ADR-0014".
61
+
-`README.md`: added Database section documenting SQLite and PostgreSQL modes.
62
+
49
63
### Fixed
50
64
65
+
- Populate `BuildTargetModel` in Npgsql seed migration designer files so Npgsql's SQL generator can resolve column types when applying `InsertData` operations.
66
+
- Suppress `PendingModelChangesWarning` for the postgres provider path — hand-crafted designer files cannot replicate Npgsql-injected runtime annotations (`Relational:MaxIdentifierLength`, `UseIdentityByDefaultColumn`), causing a false-positive that aborted `MigrateAsync()` at startup.
67
+
- Normalize `DATABASE_PROVIDER` to lowercase in `entrypoint.sh` via `tr` so `POSTGRES`, `Postgres`, etc. are handled consistently with `AddDbContextPool`.
68
+
- Trim and normalize `DATABASE_PROVIDER` in `AddDbContextPool` before the provider switch; add explicit `sqlite`/empty case; throw `InvalidOperationException` for unrecognized values so typos no longer silently fall through to SQLite.
69
+
- Move `Npgsql.EntityFrameworkCore.PostgreSQL` package from the "Development dependencies" `ItemGroup` to "Runtime dependencies".
The project used SQLite as its only database engine (ADR-0003). This meant all environments — local development, Docker Compose, and any deployment — ran the same SQLite file-based database. For the majority of contributors this is ideal: zero infrastructure required.
12
+
13
+
However, a fixed SQLite-only setup has limits:
14
+
15
+
- SQLite does not support concurrent writes, so multi-instance deployments are not possible.
16
+
- Developers who want to validate production-parity behaviour (e.g. PostgreSQL-specific query plans, type semantics, or constraint handling) have no path to do so without modifying the project.
17
+
- A fixed SQLite-in-dev / PostgreSQL-in-prod split (a common alternative) introduces a different problem: subtle behavioral differences between environments that are hard to catch locally.
18
+
19
+
The goal is to make the database engine fully configurable with a single environment variable, so the same stack is used consistently across every environment a developer chooses to run.
20
+
21
+
## Decision
22
+
23
+
We will introduce a `DATABASE_PROVIDER` environment variable that selects the database engine at startup:
24
+
25
+
-**`DATABASE_PROVIDER=sqlite`** (default): SQLite everywhere. Zero infrastructure required. Works on any machine without Docker. Clone and run.
26
+
-**`DATABASE_PROVIDER=postgres`**: PostgreSQL everywhere. Requires Docker. Opt-in for developers who want a server-based engine or full production parity.
27
+
28
+
The default is `sqlite` to keep the barrier to entry as low as possible.
29
+
30
+
### Provider selection at startup
31
+
32
+
`ServiceCollectionExtensions.AddDbContextPool` reads `DATABASE_PROVIDER` and wires the appropriate EF Core provider:
SQLite and PostgreSQL require different column type mappings (`TEXT`/`INTEGER` vs `uuid`/`boolean`/`timestamp with time zone`). A single migration set cannot satisfy both providers. To resolve this:
49
+
50
+
- SQLite migrations remain in `Migrations/` (namespace `...Migrations`).
51
+
- PostgreSQL migrations are placed in `Migrations/Npgsql/` (namespace `...Migrations.Npgsql`).
52
+
- A custom `ProviderSpecificMigrationsAssembly` (implementing EF Core's `IMigrationsAssembly`) filters the discovered migrations to only those in the active provider's namespace. It is registered via `options.ReplaceService<IMigrationsAssembly, ProviderSpecificMigrationsAssembly>()`.
53
+
54
+
`MigrateAsync()` at startup continues to work for both providers; each sees only its own migration set.
55
+
56
+
### Docker Compose profiles
57
+
58
+
The `postgres` service is declared under the `postgres` Compose profile so it only starts when explicitly requested:
59
+
60
+
```bash
61
+
# SQLite (default — no extra Docker service)
62
+
docker compose up
63
+
64
+
# PostgreSQL (opt-in)
65
+
DATABASE_PROVIDER=postgres docker compose --profile postgres up
66
+
```
67
+
68
+
## Consequences
69
+
70
+
### Positive
71
+
72
+
- Developers choose their engine once and use it consistently across all environments.
73
+
- SQLite remains the zero-friction default — clone, run, done.
74
+
- PostgreSQL is a first-class option for production-parity testing without requiring project-wide changes.
75
+
- The Compose profile approach prevents accidental PostgreSQL containers from starting in SQLite mode.
76
+
-`dotnet run` continues to work unchanged with SQLite.
77
+
78
+
### Negative
79
+
80
+
- Two parallel migration sets must be maintained. Adding a schema change requires migrations in both `Migrations/` and `Migrations/Npgsql/`.
81
+
-`ProviderSpecificMigrationsAssembly` uses an EF Core internal API (`MigrationsAssembly` from `Microsoft.EntityFrameworkCore.Migrations.Internal`), annotated with `#pragma warning disable EF1001`. This may require updates on major EF Core version upgrades.
82
+
- PostgreSQL support is not tested in CI (no Testcontainers integration yet — tracked in issue #353).
83
+
84
+
### Neutral
85
+
86
+
- The `DATABASE_URL` connection string format follows the Npgsql convention (`Host=...;Database=...;Username=...;Password=...`).
87
+
-`.env.example` documents all three new environment variables (`DATABASE_PROVIDER`, `DATABASE_URL`, `POSTGRES_PASSWORD`). `.env` is git-ignored.
0 commit comments