Skip to main content

Devlog: 2026-03-17 — .NET Solution Scaffold

What Happened

Scaffolded the .NET 8 modular monolith solution structure at src/backend/, implementing ADR-004 (modular monolith). The API container is now running in the dev profile and responding through the Nginx proxy.

Research Conducted

Before building, reviewed:

  • .NET 8 modular monolith best practices (Clean Architecture, vertical slices, project organization)
  • .NET Aspire evaluation (decided against -- overlaps with existing Docker Compose setup, can adopt later)
  • EF Core 8 database-per-tenant patterns (scoped DbContext, connection string from JWT, no connection pooling)
  • Docker multi-stage builds for .NET (using SDK image for dev, runtime image for production later)
  • PowerSeller X project structure for ecosystem consistency patterns

Key Decisions

  • Minimal APIs over Controllers -- lighter weight, modern convention, sufficient for ~20 endpoints
  • Flat project structure -- one project per bounded context, not nested Clean Architecture layers
  • No .NET Aspire -- we already have working Docker Compose + Nginx proxy infrastructure
  • Framework Reference for ASP.NET Core in module projects -- allows modules to define their own Minimal API endpoints
  • PowerSeller.SaaS namespace -- aligns with ecosystem (PowerSeller.X, PowerSeller.MBSAccess)

Solution Structure

src/backend/
PowerSeller.SaaS.sln
Directory.Build.props
Dockerfile.dev
PowerSeller.SaaS.Api/ → Host (DI, middleware, Swagger, health)
PowerSeller.SaaS.SharedKernel/ → Abstractions (ITenantContext, IModule, Result)
PowerSeller.SaaS.Infrastructure/ → EF Core, multi-tenancy (TenantDbContext, TenantRegistry)
PowerSeller.SaaS.Modules.BestEx/ → BestEx module shell (status endpoint, empty Domain/Services/Endpoints)
tests/
PowerSeller.SaaS.Api.Tests/
PowerSeller.SaaS.Modules.BestEx.Tests/

What Was Built

FilePurpose
PowerSeller.SaaS.slnSolution with 4 src + 2 test projects
Directory.Build.propsShared settings: net8.0, nullable enable, warnings as errors
PowerSeller.SaaS.Api/Program.csMinimal API host with Swagger, CORS, health check, tenant middleware
PowerSeller.SaaS.Api/Middleware/TenantMiddleware.csExtracts tenant from JWT/header, resolves connection string
PowerSeller.SaaS.Api/Extensions/ServiceCollectionExtensions.csMulti-tenancy DI registration
PowerSeller.SaaS.SharedKernel/ITenantContext.csTenant context interface
PowerSeller.SaaS.SharedKernel/IModule.csModule registration interface
PowerSeller.SaaS.SharedKernel/Result.csResult pattern for domain operations
PowerSeller.SaaS.Infrastructure/Data/TenantDbContext.csEF Core context with per-tenant connection string
PowerSeller.SaaS.Infrastructure/Data/TenantRegistry.csMaps tenant IDs to connection strings
PowerSeller.SaaS.Infrastructure/Data/DesignTimeDbContextFactory.csEF Core migration tooling support
PowerSeller.SaaS.Infrastructure/MultiTenancy/TenantContext.csScoped tenant state
PowerSeller.SaaS.Modules.BestEx/BestExModule.csModule shell with /api/bestex/status endpoint
Dockerfile.devSDK-based dev image with dotnet watch and NuGet restore caching
docker-compose.ymlUpdated api service to use Dockerfile.dev

Verification

  • docker compose --profile dev up starts proxy + docs + api + cache (4 containers)
  • http://pssaas.powerseller.local/api/health → 200
  • http://pssaas.powerseller.local/api/bestex/status{"module":"BestEx","status":"ready"}
  • http://pssaas.powerseller.local/api/swagger/index.html → 200
  • BestEx module test passes (1 passed, 0 failed)

Issues Encountered

  • Directory.Build.props must be explicitly copied in the Dockerfile before dotnet restore (not just *.sln)
  • Class library projects need <FrameworkReference Include="Microsoft.AspNetCore.App" /> to use Minimal API types
  • WithOpenApi() requires a separate NuGet package -- removed for now, Swagger at host level is sufficient
  • Test adapter discovery fails when bin/obj directories are stale from different container runs -- clean first
  • Running dotnet test inside the API container OOMs -- run tests in a separate container instance

Technical Debt

  • pssaas-web container is in restart loop (no frontend code exists yet). Harmless but noisy in logs.
  • Test package versions are pinned to specific versions (2.9.2, 2.8.2, 17.11.1) rather than floating -- intentional for reproducibility.
  • The TenantMiddleware falls back to a "default" tenant when no tenant header/claim is present. This is for dev convenience; production must require explicit tenant identification.

What's Next

  1. Product Owner reviews BestEx spec (Draft status)
  2. After approval, begin BestEx implementation in PowerSeller.SaaS.Modules.BestEx