Skip to content

ogu83/Northwind.MigrationLab

Repository files navigation

Northwind.MigrationLab

A compact, recordable .NET 10 sample that shows what a practical legacy migration looks like — without pretending the entire legacy system disappears overnight.

Built as a companion to the video ".NET 10 for real projects: what I would actually adopt in a legacy migration".


What this is

Most migration demos start from a clean greenfield. This one starts from something boring: a small order-management module with hidden config, a blocking import, inconsistent errors, and static database helpers.

The sample walks through four concrete migration seams — the same four covered in the video — and shows each one as a before/after pair you can read side by side in the code.

# Before (samples/legacy-snapshot) After (src/)
1 IConfiguration injected directly, magic string keys Typed DatabaseOptions / ImportOptions, validated at startup
2 Ad-hoc JSON errors, raw exception messages leaked AddProblemDetails + AddValidation + /openapi/v1.yaml
3 Blocking import runs on the request thread Channel<ImportJob> + BackgroundService, HTTP returns 202 immediately
4 Raw SQL via static StaticDbHelper, DataTable returned as JSON EF Core write path + explicit raw SQL read path, side by side

Prerequisites

Requirement Notes
.NET 10 SDK 10.0.102 or later
SQL Server (local instance) Windows Authentication — no password needed
dotnet ef tools dotnet tool install --global dotnet-ef

The app connects to Server=. with Windows Authentication. If your instance name differs, update appsettings.json before running.


Quick start

# 1. Clone
git clone <repo-url>
cd NET10LegacyMigration/Northwind.MigrationLab

# 2. Create the database
dotnet ef database update \
  --project src/Northwind.Infrastructure \
  --startup-project src/Northwind.Api

# 3. Run
dotnet run --project src/Northwind.Api

The API starts on https://localhost:5001 (or whatever port is shown in the terminal).


Endpoints

Method Route What it does
GET /api/orders Paginated order list (EF Core path)
GET /api/orders/{id} Single order by GUID
POST /api/orders Create order — validated, returns 201 + Location
POST /api/import/orders Enqueue import job — returns 202 Accepted immediately
GET /healthz SQL Server health check as JSON
GET /openapi/v1.yaml Generated OpenAPI 3.x document

Try the demo checkpoints

Demo 2 — validation + ProblemDetails

# Bad request → structured 400
curl -X POST https://localhost:5001/api/orders \
  -H "Content-Type: application/json" \
  -d '{"customerId": "", "items": []}'

# Good request → 201 + Location header
curl -X POST https://localhost:5001/api/orders \
  -H "Content-Type: application/json" \
  -d '{"customerId": "CUST-1", "items": []}'

Demo 3 — async import

# Returns 202 immediately; watch the terminal for worker logs
curl -X POST https://localhost:5001/api/import/orders

Project structure

src/
  Northwind.Api/              Minimal API — routes, Program.cs, middleware
  Northwind.Application/      Use cases, interfaces, DTOs (no EF, no HTTP)
  Northwind.Domain/           Entities and enums (pure .NET BCL only)
  Northwind.Infrastructure/   EF Core, workers, raw SQL queries, typed options

tests/
  Northwind.UnitTests/        Domain + Application + ImportChannel (no DB, no HTTP)
  Northwind.IntegrationTests/ WebApplicationFactory against a real SQL Server test DB

samples/
  legacy-snapshot/            The "before" code — MVC controller, static helper,
                              synchronous import. Reference only, not in solution.

Running tests

# All tests (requires local SQL Server for integration tests)
dotnet test Northwind.MigrationLab.slnx

# Unit tests only (no SQL Server required — same as CI)
dotnet test Northwind.MigrationLab.slnx --filter "Category!=Integration"

Integration tests spin up a fresh SQL Server database per test class (named NorthwindMigrationLab_Test_<guid>) and drop it when done.


Commands

# Build
dotnet build Northwind.MigrationLab.slnx

# Run API
dotnet run --project src/Northwind.Api

# Apply migrations
dotnet ef database update \
  --project src/Northwind.Infrastructure \
  --startup-project src/Northwind.Api

# Add a new migration
dotnet ef migrations add <MigrationName> \
  --project src/Northwind.Infrastructure \
  --startup-project src/Northwind.Api \
  --output-dir Migrations

# Format check
dotnet format --verify-no-changes

CI

GitHub Actions runs on every push and pull request to main.

  • Builds the solution in Release configuration
  • Runs unit tests only (no SQL Server in CI)
  • Uploads TRX test results as a build artifact

Integration tests are tagged [Trait("Category", "Integration")] and excluded from the CI filter.


What is deliberately left out

These topics are deferred — each one is a good follow-up video on its own:

  • Authentication and authorization
  • EF Core query tuning (indexes, projections, pagination)
  • OpenTelemetry and structured logging
  • CI/CD deployment pipeline
  • WCF or legacy SOAP service behind the adapter seam
  • AI-assisted reporting on top of the clean API surface

Tech stack

Layer Choice
Runtime .NET 10
API ASP.NET Core Minimal API
Validation Microsoft.Extensions.Validation (.NET 10 built-in)
API docs Microsoft.AspNetCore.OpenApi
ORM EF Core 10 with SQL Server
Raw reads DbConnection + DbCommand (no extra package)
Import queue System.Threading.Channels (no broker)
Health checks AspNetCore.HealthChecks.SqlServer
Tests xUnit + Microsoft.AspNetCore.Mvc.Testing

About

A compact, recordable .NET 10 sample that shows what a practical legacy migration looks like — without pretending the entire legacy system disappears overnight.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages