Relocates ModuleOrchestrator, ServiceCollectionExtensions, and ApiPrefixConvention from SlpModularCms.Api into SlpModularCms.Core.Hosting so a new Master-less SlpModularCms.Api.Slave host project (ports 5285/7222) can share the same bootstrap code without duplicating it. This lets a developer run a master instance and a slave instance side by side locally to test the master/slave connection, without touching the existing master/slave protocol itself. Relocates the two orchestrator/convention test files from Modules.Identity.Tests to Core.Tests, dropping an incidental ProjectReference to SlpModularCms.Api that existed only for those tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.6 KiB
9.6 KiB
Requirements — Local Dev Master/Slave Setup
Intent Analysis
- User Request: Enable running two local instances of the CMS backend simultaneously — one with
SlpModularCms.Modules.Masterloaded (the existing full CMS, acting as master) and one without it (acting purely as a slave/target) — on two different ports, so the master↔slave connection can be tested end-to-end locally. - Request Type: New Feature (developer tooling / local run configuration)
- Scope Estimate: Multiple components — new host project (
SlpModularCms.Api.Slave), solution/build changes,appsettings/launchSettingsfor both instances, frontend dev-server configuration for pointing at either instance. - Complexity Estimate: Moderate — no new business logic; the master/slave protocol itself (
Modules.Master'sCmsInstanceService/SlaveApiClientandModules.Availability'sMasterController/MasterAvailabilityService) already exists and is unchanged. Complexity comes from correctly separating build/run configuration for two instances without duplicating orchestration logic.
Background (from codebase investigation)
SlpModularCms.Api'sProgram.csdoes not directly referenceModules.Master. Instead,Infrastructure/ModuleOrchestrator.csdiscovers modules at startup by scanning its own build output directory forSlpModularCms.Modules.*.dlland instantiating anyIModuleimplementation found — currently every module the API project references (Core,Availability,Identity,Master) always ends up in that output folder viaProjectReferences inSlpModularCms.Api.csproj.- The master/slave connection mechanism already exists and needs no changes:
- Slave side (present in every instance, since
Modules.Availabilityis always loaded):MasterController+MasterAvailabilityService+MasterApiKeyProtectoraccept and validate a per-instance API key pushed by a master. - Master side (only in
Modules.Master):CmsInstanceService+SlaveApiClient+ApiKeyProtectormanage registered CMS instances and talk to them. - The existing frontend "Add CMS Instance" dialog (from
master-cms-module) is the existing UI flow for registering a slave under a master.
- Slave side (present in every instance, since
- Frontend reads its backend URL from
VITE_API_BASE_URL(frontend/src/lib/config.ts), sourced from a Vite env file (.env.local,.env.example), evaluated once per dev server process. Vite dev server currently runs on a fixed port (5173,vite.config.ts). - Backend CORS allowed origins are configured per-instance under
Cors:AllowedOriginsinappsettings.Development.json/appsettings.local.json.
Decisions from Clarification
- Instance separation mechanism: A new host project,
SlpModularCms.Api.Slave, with its own.csprojreferencingSlpModularCms.Core,SlpModularCms.Modules.Identity, andSlpModularCms.Modules.Availability— but notSlpModularCms.Modules.Master. BecauseModuleOrchestratordiscovers modules purely by scanning its own build output, omitting theMasterproject reference is sufficient to produce a slave-only instance; no config-driven module toggle is needed for this feature. - Production plugin-loading model is explicitly out of scope: the user separately wants a future production model where the API is built once without module project references and modules are dropped in as standalone DLLs post-build. That is a distinct, larger architectural change (build/packaging/CI, not local dev tooling) and is not part of this feature. Capture it as a follow-up idea (see "Out of Scope" below).
- Database isolation: Master and slave instances use separate local databases (separate connection strings/database names), same local SQL Server.
- Ports: Master keeps existing ports (
http://localhost:5284,https://localhost:7221). Slave gets a new pair (http://localhost:5285,https://localhost:7222). - Launch method: Two
dotnet run --launch-profile <name>invocations (one per terminal/process) — no background helper script required. - Frontend: The frontend must also be runnable against the slave instance (its own dev server / env configuration), not just the master — so the slave's admin UI is separately reachable for manual testing.
- Establishing the connection: No scripted/seeded auto-registration. The user will manually register the slave under the master using the existing "Add CMS Instance" UI flow, specifically to exercise/validate that flow end-to-end.
Functional Requirements
FR-1: Slave-only backend host project
- Add
SlpModularCms.Api.Slave, a new ASP.NET Core Web API project insrc/, added to the solution. - Bootstraps the same way as
SlpModularCms.Api(core infrastructure, CORS, rate limiting, controllers, module discovery/orchestration, exception handling, Scalar in Development) — but its.csprojreferencesCore,Modules.Identity, andModules.Availabilityonly (noModules.Masterreference), soModuleOrchestratornever discovers/loads the Master module for this instance. - Avoid duplicating the bootstrap/orchestration code (
ModuleOrchestrator,ServiceCollectionExtensions,ApiPrefixConvention) betweenSlpModularCms.ApiandSlpModularCms.Api.Slave— extract shared pieces into a location both projects can reference without pulling inModules.Master(see NFR-1).
FR-2: Distinct local run configuration per instance
SlpModularCms.Api(master) keeps its existinglaunchSettings.jsonprofiles and ports (5284 / 7221).SlpModularCms.Api.Slavegets its ownlaunchSettings.jsonwith profiles on ports 5285 (HTTP) and 7222 (HTTPS).- Each project has its own
appsettings.json/appsettings.Development.json/appsettings.local.json(gitignored) with:- A distinct
ConnectionStrings:DefaultConnection(separate database name/catalog) so master and slave don't share data. Cors:AllowedOriginscovering whichever frontend origin(s) will point at that instance.- The slave instance does not need
MasterModuleconfiguration (that section is only meaningful whereModules.Masteris loaded).
- A distinct
- Both instances can be started independently via
dotnet run --launch-profile <name>from their respective project directories.
FR-3: Frontend configurable against either instance
- Add a way to run the existing frontend against the slave instance in addition to the master, without needing two separate frontend codebases:
- A second Vite env file (e.g.
.env.slave.local, gitignored like.env.local) settingVITE_API_BASE_URLto the slave's HTTPS URL (https://localhost:7222). - A second npm script (e.g.
dev:slave) that runs Vite with that env mode on a different dev-server port (e.g. 5174), so both frontend instances can run side by side if needed. - Update
.env.exampleand/orfrontend/README.mdto document both modes.
- A second Vite env file (e.g.
FR-4: Documentation for the local master/slave workflow
- Add a short runbook (e.g.
frontend/README.mdand/or a root-leveldocs/READMEsection, or a dedicated markdown file referenced from the main README) describing:- How to start the master backend + master frontend.
- How to start the slave backend (+ optionally slave frontend) on the alternate ports.
- How to use the master frontend's existing "Add CMS Instance" dialog to register the local slave (what URL/API key to use) and confirm the connection works (e.g. status shows as connected/healthy).
Non-Functional Requirements
NFR-1: No logic duplication between host projects
- The master and slave host projects must share bootstrap/orchestration code (module discovery, DI wiring for core infrastructure, controller conventions) rather than maintaining two copies that can drift. Exact extraction mechanism (e.g. a small shared class library, or a shared partial/extension file linked into both projects) is a Functional/Application Design decision, not fixed here.
NFR-2: Isolation
- Master and slave instances must not share a database, so testing against one cannot corrupt or interfere with the other's data.
NFR-3: Local-only scope
- No changes to the actual master/slave communication protocol, production deployment, or CI/CD pipeline. This is strictly a local development convenience.
NFR-4: Secrets hygiene
- Slave's
appsettings.local.json(and any new gitignored env files) must follow the existing pattern — never committed, followingdotnet-appsettingsconventions already used bySlpModularCms.Api.
Out of Scope
- Production drop-in module DLL loading: Building the API once without module project references and loading modules from an external "Modules" folder dropped in post-build/at-deploy. This is a distinct future initiative (build/packaging/CI concern) explicitly deferred by the user during clarification — to be captured as a new backlog/feature item when picked up, not designed as part of this feature.
- Automated/scripted slave registration: The user explicitly wants to exercise the existing "Add CMS Instance" UI flow manually; no seed data or dev-only registration endpoint will be added.
- Any change to
Modules.Master,Modules.Availability, or the master/slave communication protocol itself.
Summary
This feature adds a second, Master-less ASP.NET Core host project (SlpModularCms.Api.Slave) plus per-instance run configuration (ports, databases, CORS) and frontend tooling, so the developer can run a master CMS instance (full stack, port 5284/7221) and a slave-only instance (no Master module, port 5285/7222) side by side locally, and manually connect them via the existing "Add CMS Instance" UI flow to validate the master/slave integration end-to-end. The production "plugin-style" module loading idea raised during clarification is explicitly out of scope and noted for future consideration.