Adds SlpModularCms.Api.Slave for local master/slave dev testing (Unit 1)
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>
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
# Application Design — Local Dev Master/Slave Setup
|
||||
|
||||
Consolidated design summary. See individual files for full detail: `components.md`, `component-methods.md`, `services.md`, `component-dependency.md`.
|
||||
|
||||
## Key Decision
|
||||
|
||||
The only real design decision in this feature (no new business logic exists) is **how to share host bootstrap code between the master and slave processes without duplication (NFR-1)**. Resolved as: move `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` out of `SlpModularCms.Api` and into the existing `SlpModularCms.Core` project (new `SlpModularCms.Core.Hosting` namespace). Both host projects already reference `Core`, so this achieves a single shared copy with no new project and no file-linking, at the cost of two new package references on `Core` (`Asp.Versioning.Mvc`, `Microsoft.AspNetCore.OpenApi`) that it doesn't currently have.
|
||||
|
||||
## Components (see components.md)
|
||||
1. `SlpModularCms.Core.Hosting` — relocated shared bootstrap code
|
||||
2. `SlpModularCms.Api` — existing master host, now sources bootstrap from `Core.Hosting`
|
||||
3. `SlpModularCms.Api.Slave` — new slave-only host, no `Modules.Master` reference
|
||||
|
||||
## Component Methods (see component-methods.md)
|
||||
All method signatures are unchanged from today's implementation — only their namespace/location moves. Module exclusion for the slave is achieved entirely through the absence of a `ProjectReference`, not new conditional code.
|
||||
|
||||
## Services (see services.md)
|
||||
No new domain services. Existing master-side (`CmsInstanceService`, `SlaveApiClient`) and slave-side (`MasterAvailabilityService`, `MasterApiKeyProtector`) services are reused unchanged. A sequence diagram documents the intended local verification flow (start both, use the existing Add CMS Instance dialog, confirm connected status).
|
||||
|
||||
## Component Dependencies (see component-dependency.md)
|
||||
Dependency matrix and data-flow diagram showing both host projects depending on `Core` (+ `Modules.Availability`/`Modules.Identity`), with only `SlpModularCms.Api` additionally depending on `Modules.Master`. Each host uses its own local database; the frontend can target either host via `VITE_API_BASE_URL`.
|
||||
|
||||
## Traceability to Requirements
|
||||
|
||||
| Requirement | Design Element |
|
||||
|---|---|
|
||||
| FR-1 (slave-only host project) | `SlpModularCms.Api.Slave` component |
|
||||
| NFR-1 (no duplication) | `Core.Hosting` relocation |
|
||||
| FR-2 (distinct run config) | component-dependency.md ports/DB mapping |
|
||||
| FR-3 (frontend against either instance) | component-dependency.md data-flow diagram |
|
||||
| NFR-2 (DB isolation) | Separate `SlpModularCmsLocal_Master`/`SlpModularCmsLocal_Slave` in data-flow diagram |
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Component Dependencies — Local Dev Master/Slave Setup
|
||||
|
||||
## Dependency Matrix
|
||||
|
||||
| Component | Depends On | Depended On By |
|
||||
|---|---|---|
|
||||
| `SlpModularCms.Core` (incl. new `Core.Hosting`) | — | `SlpModularCms.Api`, `SlpModularCms.Api.Slave`, `Modules.Master`, `Modules.Availability`, `Modules.Identity` |
|
||||
| `SlpModularCms.Modules.Master` | `Core` | `SlpModularCms.Api` only |
|
||||
| `SlpModularCms.Modules.Availability` | `Core` | `SlpModularCms.Api`, `SlpModularCms.Api.Slave` |
|
||||
| `SlpModularCms.Modules.Identity` | `Core` | `SlpModularCms.Api`, `SlpModularCms.Api.Slave` |
|
||||
| `SlpModularCms.Api` (master host) | `Core`, `Modules.Master`, `Modules.Availability`, `Modules.Identity` | — (top-level executable) |
|
||||
| `SlpModularCms.Api.Slave` (new, slave host) | `Core`, `Modules.Availability`, `Modules.Identity` | — (top-level executable) |
|
||||
| `frontend` | Backend HTTP API (either instance, via `VITE_API_BASE_URL`) | — |
|
||||
|
||||
## Communication Patterns
|
||||
|
||||
- **Build-time**: Standard MSBuild `ProjectReference` — no new communication mechanism. The exclusion of `Modules.Master` from `SlpModularCms.Api.Slave` is achieved purely by omitting that `ProjectReference`, which in turn keeps `Modules.Master.dll` out of the slave's build output, which in turn means `ModuleOrchestrator.DiscoverModules` (running inside the slave process) never finds it.
|
||||
- **Runtime — Master ↔ Slave**: Unchanged existing HTTP-based protocol between `Modules.Master`'s `SlaveApiClient` (master side) and `Modules.Availability`'s `MasterController` (slave side), authenticated via a per-instance API key. This feature does not touch that protocol — it only makes it possible to point one local instance at another.
|
||||
- **Runtime — Frontend ↔ Backend**: Standard HTTPS/CORS, `VITE_API_BASE_URL` selects which backend instance the frontend dev server talks to; `Cors:AllowedOrigins` in the target instance's `appsettings` must include the calling frontend's origin.
|
||||
|
||||
## Data Flow (Local Setup)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
FE["frontend (Vite dev server)"]
|
||||
ApiM["SlpModularCms.Api<br/>:5284 / :7221"]
|
||||
ApiS["SlpModularCms.Api.Slave<br/>:5285 / :7222"]
|
||||
DBM["Local DB: SlpModularCmsLocal_Master"]
|
||||
DBS["Local DB: SlpModularCmsLocal_Slave"]
|
||||
|
||||
FE -- "VITE_API_BASE_URL (master mode)" --> ApiM
|
||||
FE -- "VITE_API_BASE_URL (slave mode)" --> ApiS
|
||||
ApiM -- "master/slave protocol (API key)" --> ApiS
|
||||
ApiM --> DBM
|
||||
ApiS --> DBS
|
||||
|
||||
classDef fe fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000;
|
||||
classDef api fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000;
|
||||
classDef db fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
|
||||
class FE fe;
|
||||
class ApiM,ApiS api;
|
||||
class DBM,DBS db;
|
||||
```
|
||||
|
||||
Text alternative: The frontend dev server can point at either the master (5284/7221) or slave (5285/7222) backend depending on which env mode is active. The master backend talks to the slave over the existing master/slave protocol. Each backend uses its own local database, keeping master and slave data isolated.
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# Component Methods — Local Dev Master/Slave Setup
|
||||
|
||||
All method signatures below are **unchanged from the existing implementation** — this feature only relocates the containing classes to `SlpModularCms.Core.Hosting` (namespace change only, per Application Design Q1/Q2 resolution). No business-rule changes.
|
||||
|
||||
## `SlpModularCms.Core.Hosting.ModuleOrchestrator`
|
||||
|
||||
| Method | Signature | Purpose |
|
||||
|---|---|---|
|
||||
| Constructor | `ModuleOrchestrator(ILogger<ModuleOrchestrator> logger)` | Create orchestrator instance |
|
||||
| `DiscoverModules` | `void DiscoverModules()` | Scan the host's own build output directory for `SlpModularCms.Modules.*.dll`, load them, and instantiate every `IModule` implementation found |
|
||||
| `RegisterModuleServices` | `void RegisterModuleServices(IServiceCollection services)` | Call `RegisterServices` on each discovered module |
|
||||
| `UseModules` | `void UseModules(IApplicationBuilder app)` | Call `UseModule` on each discovered module to wire up its middleware/pipeline |
|
||||
|
||||
**Slave-relevant note**: Because `DiscoverModules` scans the calling assembly's own output directory, and `SlpModularCms.Api.Slave.csproj` has no reference to `Modules.Master`, that DLL will simply not be present for `DiscoverModules` to find — no code change needed here to achieve module exclusion.
|
||||
|
||||
## `SlpModularCms.Core.Hosting.ServiceCollectionExtensions` (static)
|
||||
|
||||
| Method | Signature | Purpose |
|
||||
|---|---|---|
|
||||
| `AddCoreInfrastructure` | `IServiceCollection AddCoreInfrastructure(this IServiceCollection services, IConfiguration configuration)` | Registers DB context, Identity, JWT auth, authorization policies, exception handling, API versioning, OpenAPI |
|
||||
| `AddCmsCors` | `IServiceCollection AddCmsCors(this IServiceCollection services, IConfiguration configuration)` | Registers CORS policy from `Cors:AllowedOrigins` config |
|
||||
| `AddCmsRateLimiting` | `IServiceCollection AddCmsRateLimiting(this IServiceCollection services, IConfiguration configuration)` | Registers login/refresh rate limiters from `RateLimiting:*` config |
|
||||
|
||||
**Slave-relevant note**: `AddCmsCors` reads `Cors:AllowedOrigins` from whichever `appsettings` the calling host loads — the slave's own `appsettings.local.json` will list the frontend origin(s) that should be allowed to call it (see FR-3/component-dependency.md).
|
||||
|
||||
## `SlpModularCms.Core.Hosting.ApiPrefixConvention`
|
||||
|
||||
| Method | Signature | Purpose |
|
||||
|---|---|---|
|
||||
| Constructor | `ApiPrefixConvention(string prefix)` | Store the route prefix (`"api/v1"`) to apply |
|
||||
| `Apply` | `void Apply(ApplicationModel application)` | Prepend the prefix to every discovered controller's route template |
|
||||
|
||||
## New/Changed Files Summary
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` | New location (moved from `SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs`) |
|
||||
| `SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` | New location (moved from `SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs`) |
|
||||
| `SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` | New location (moved from `SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs`) |
|
||||
| `SlpModularCms.Api/Program.cs` | Updated `using` statements to `SlpModularCms.Core.Hosting`; no logic change |
|
||||
| `SlpModularCms.Api.Slave/Program.cs` | New file, same structure as `SlpModularCms.Api/Program.cs` |
|
||||
| `SlpModularCms.Core.csproj` | Add `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references |
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Components — Local Dev Master/Slave Setup
|
||||
|
||||
## 1. `SlpModularCms.Core.Hosting` (new namespace within existing `SlpModularCms.Core` project)
|
||||
|
||||
**Purpose**: Single, shared home for the ASP.NET Core host bootstrap/orchestration code that both the master and slave API hosts use identically.
|
||||
|
||||
**Responsibilities**:
|
||||
- Discover and instantiate `IModule` implementations from the host's own build output directory (unchanged behavior).
|
||||
- Register core cross-cutting services (DB context, Identity, JWT auth, authorization policies, exception handling, API versioning, OpenAPI, CORS, rate limiting) shared by every instance regardless of which optional modules (e.g. `Modules.Master`) are present.
|
||||
- Apply the `api/v1` route prefix convention to all discovered controllers.
|
||||
|
||||
**Interfaces**: Unchanged public API — `ModuleOrchestrator` (class), `AddCoreInfrastructure`/`AddCmsCors`/`AddCmsRateLimiting` (extension methods on `IServiceCollection`), `ApiPrefixConvention` (implements `IApplicationModelConvention`). Only the namespace moves (`SlpModularCms.Api.Extensions`/`SlpModularCms.Api.Infrastructure` → `SlpModularCms.Core.Hosting`).
|
||||
|
||||
**Not responsible for**: Any module-specific business logic — modules remain fully self-contained (`Modules.Master`, `Modules.Availability`, `Modules.Identity`).
|
||||
|
||||
## 2. `SlpModularCms.Api` (existing project, master host)
|
||||
|
||||
**Purpose**: The main CMS instance — full stack, including the Master module, used for day-to-day development and as the "master" side of a local master/slave test.
|
||||
|
||||
**Responsibilities**: Unchanged from today. `Program.cs` now sources `ModuleOrchestrator`/`ServiceCollectionExtensions`/`ApiPrefixConvention` from `SlpModularCms.Core.Hosting` instead of its own local files.
|
||||
|
||||
**Interfaces**: No change — same controllers, same routes, same behavior. Its own copies of `ModuleOrchestrator.cs`, `Extensions/ServiceCollectionExtensions.cs`, `Infrastructure/ApiPrefixConvention.cs` are deleted (moved to Core).
|
||||
|
||||
**Project references**: `Core` (now including the hosting code), `Modules.Availability`, `Modules.Identity`, `Modules.Master` — unchanged set, just implicitly picks up `Core.Hosting` via the existing `Core` reference.
|
||||
|
||||
## 3. `SlpModularCms.Api.Slave` (new project)
|
||||
|
||||
**Purpose**: A minimal, Master-less host used purely as a local target for testing the master↔slave connection. Not intended for end-user CMS administration beyond what's needed to validate connectivity.
|
||||
|
||||
**Responsibilities**: Bootstraps identically to `SlpModularCms.Api` (via `Core.Hosting`), but its build output never contains `SlpModularCms.Modules.Master.dll`, so `ModuleOrchestrator.DiscoverModules` never loads the Master module for this instance.
|
||||
|
||||
**Interfaces**: Same shape as `SlpModularCms.Api` (`Program.cs` calling into `Core.Hosting`), minus anything Master-specific (there is none in `Program.cs` today — the exclusion works purely through the project-reference/build-output mechanism).
|
||||
|
||||
**Project references**: `Core`, `Modules.Availability`, `Modules.Identity`. Deliberately **no** reference to `Modules.Master`.
|
||||
|
||||
**Configuration**: Own `appsettings.json` / `appsettings.Development.json` / `appsettings.local.json` (gitignored) and own `launchSettings.json` (ports 5285 HTTP / 7222 HTTPS), per FR-2 in requirements.md.
|
||||
|
||||
## Component Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Core["SlpModularCms.Core<br/>(incl. new Core.Hosting)"]
|
||||
Master["SlpModularCms.Modules.Master"]
|
||||
Avail["SlpModularCms.Modules.Availability"]
|
||||
Ident["SlpModularCms.Modules.Identity"]
|
||||
ApiMaster["SlpModularCms.Api<br/>(master host)"]
|
||||
ApiSlave["SlpModularCms.Api.Slave<br/>(slave host, new)"]
|
||||
|
||||
ApiMaster --> Core
|
||||
ApiMaster --> Master
|
||||
ApiMaster --> Avail
|
||||
ApiMaster --> Ident
|
||||
ApiSlave --> Core
|
||||
ApiSlave --> Avail
|
||||
ApiSlave --> Ident
|
||||
Master --> Core
|
||||
Avail --> Core
|
||||
Ident --> Core
|
||||
|
||||
classDef core fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000;
|
||||
classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
|
||||
classDef host fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000;
|
||||
class Core core;
|
||||
class Master,Avail,Ident module;
|
||||
class ApiMaster,ApiSlave host;
|
||||
```
|
||||
|
||||
Text alternative: Two host projects (`SlpModularCms.Api` and the new `SlpModularCms.Api.Slave`) both depend on `Core` (which now includes the shared hosting/bootstrap code) and on `Modules.Availability`/`Modules.Identity`. Only `SlpModularCms.Api` additionally depends on `Modules.Master` — `SlpModularCms.Api.Slave` deliberately omits that reference. All three module projects depend on `Core`.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Services — Local Dev Master/Slave Setup
|
||||
|
||||
## No new domain services
|
||||
|
||||
This feature introduces no new business/domain service. All existing services are reused unchanged:
|
||||
|
||||
- **Master-side** (only present in `SlpModularCms.Api`, because only it references `Modules.Master`): `CmsInstanceService`, `SlaveApiClient`, `ApiKeyProtector` — manage registered CMS instances and communicate with them.
|
||||
- **Slave-side** (present in both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`, since both reference `Modules.Availability`): `MasterAvailabilityService`, `MasterApiKeyProtector` — accept and validate a master's per-instance API key.
|
||||
- **Shared cross-cutting**: `IAuthService`, `IInvitationService`, `ISetupService` (from `Core.Identity` via `AddCoreInfrastructure`) — identical in both hosts.
|
||||
|
||||
## Orchestration Service
|
||||
|
||||
`ModuleOrchestrator` (relocated to `SlpModularCms.Core.Hosting`, see components.md) is the only "orchestration" element in this feature, and its role is unchanged: at startup, discover whichever module DLLs are present next to the host executable and wire them into DI + the request pipeline. Its behavior naturally differs between the two hosts only because of what's present in each host's build output — not because of any new conditional logic.
|
||||
|
||||
## Interaction Sequence — Local Master/Slave Connection Test
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(246,224,94,0.4) Developer
|
||||
participant Dev as Developer
|
||||
end
|
||||
box rgba(99,179,237,0.4) Master Instance (5284/7221)
|
||||
participant MasterApi as SlpModularCms.Api
|
||||
participant CmsInstanceSvc as CmsInstanceService
|
||||
end
|
||||
box rgba(154,230,180,0.4) Slave Instance (5285/7222)
|
||||
participant SlaveApi as SlpModularCms.Api.Slave
|
||||
participant MasterCtrl as MasterController
|
||||
end
|
||||
|
||||
Dev->>MasterApi: dotnet run --launch-profile master
|
||||
Dev->>SlaveApi: dotnet run --launch-profile slave
|
||||
Dev->>MasterApi: Open frontend, use Add CMS Instance dialog with slave URL
|
||||
MasterApi->>CmsInstanceSvc: Register CMS instance (slave URL + generated API key)
|
||||
CmsInstanceSvc->>MasterCtrl: Push registration / poll status using API key
|
||||
MasterCtrl-->>CmsInstanceSvc: Accepted, status healthy
|
||||
CmsInstanceSvc-->>MasterApi: Instance connected
|
||||
MasterApi-->>Dev: CMS Instance shows as Connected
|
||||
```
|
||||
|
||||
Text alternative: The developer starts the master and slave backends on their respective ports, then uses the master frontend's existing Add CMS Instance dialog. The master's CmsInstanceService registers and communicates with the slave's MasterController using a per-instance API key, and the frontend reflects the resulting connected status.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Unit of Work Dependency Matrix — Local Dev Master/Slave Setup
|
||||
|
||||
| Unit | Depends On | Blocks |
|
||||
|---|---|---|
|
||||
| Unit 1 — Backend dual-instance hosting | Application Design (approved) | Unit 2 |
|
||||
| Unit 2 — Frontend dual-instance tooling & runbook | Unit 1 (needs a working, running slave backend) | Build and Test (final manual verification) |
|
||||
|
||||
## Rationale
|
||||
|
||||
- Unit 1 is the critical path: without `SlpModularCms.Api.Slave` actually building and running on its own ports/database, there is nothing for Unit 2's frontend config or runbook to point at or verify against.
|
||||
- Unit 2 has no reverse dependency — it does not require any change to Unit 1 once Unit 1 is complete.
|
||||
- No parallelization opportunity given the small scope and single-developer nature of this feature (per Q2 = A, sequential execution was explicitly confirmed).
|
||||
|
||||
## Update Strategy
|
||||
|
||||
- **Update Approach**: Sequential (Unit 1 then Unit 2)
|
||||
- **Critical Path**: Unit 1
|
||||
- **Coordination Points**: The slave backend's HTTPS URL (`https://localhost:7222`) is the one piece of information Unit 2 needs from Unit 1's output.
|
||||
- **Testing Checkpoints**: After Unit 1 — verify both backends build/start independently. After Unit 2 — verify the full manual master/slave connection flow via the runbook.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Unit of Work — Requirement Map — Local Dev Master/Slave Setup
|
||||
|
||||
No user stories exist for this feature (User Stories stage was skipped — pure developer tooling, no user-facing functionality). Functional and non-functional requirements from `requirements.md` are mapped directly to units instead.
|
||||
|
||||
| Requirement | Unit |
|
||||
|---|---|
|
||||
| FR-1: Slave-only backend host project | Unit 1 |
|
||||
| FR-2: Distinct local run configuration per instance | Unit 1 |
|
||||
| FR-3: Frontend configurable against either instance | Unit 2 |
|
||||
| FR-4: Documentation for the local master/slave workflow | Unit 2 |
|
||||
| NFR-1: No logic duplication between host projects | Unit 1 |
|
||||
| NFR-2: Isolation (separate databases) | Unit 1 |
|
||||
| NFR-3: Local-only scope | Unit 1 + Unit 2 (both — neither touches production/CI) |
|
||||
| NFR-4: Secrets hygiene | Unit 1 (backend appsettings.local.json) + Unit 2 (frontend .env.slave.local) |
|
||||
|
||||
All requirements are covered by exactly one or both units — no orphaned requirements.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Unit of Work — Local Dev Master/Slave Setup
|
||||
|
||||
## Unit 1 — Backend Dual-Instance Hosting
|
||||
|
||||
**Responsibility**: Make it possible to run a Master-less slave backend locally alongside the existing master backend, on separate ports and databases, without duplicating bootstrap code.
|
||||
|
||||
**Scope**:
|
||||
- Relocate `ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention` from `SlpModularCms.Api` into `SlpModularCms.Core` (new `SlpModularCms.Core.Hosting` namespace); add `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references to `Core`.
|
||||
- Update `SlpModularCms.Api/Program.cs` to use the relocated classes (no behavior change); delete the old files from `SlpModularCms.Api`.
|
||||
- Add new `SlpModularCms.Api.Slave` project: references `Core`, `Modules.Availability`, `Modules.Identity` (no `Modules.Master`); own `Program.cs` mirroring `SlpModularCms.Api`'s bootstrap.
|
||||
- Add `SlpModularCms.Api.Slave`'s `appsettings.json` / `appsettings.Development.json` / `appsettings.local.json` (gitignored) with its own `ConnectionStrings:DefaultConnection` (separate database) and `Cors:AllowedOrigins`.
|
||||
- Add `SlpModularCms.Api.Slave/Properties/launchSettings.json` with profiles on port 5285 (HTTP) / 7222 (HTTPS).
|
||||
- Add `SlpModularCms.Api.Slave.csproj` to the solution file.
|
||||
- No dedicated test project (Q3 = A) — mirrors `SlpModularCms.Api`'s current pattern.
|
||||
|
||||
**Depends On**: Application Design decisions (already approved).
|
||||
|
||||
**Completion Criteria**: Both `SlpModularCms.Api` (master) and `SlpModularCms.Api.Slave` build and start successfully via `dotnet run --launch-profile <name>`; existing test suites (`Modules.Master.Tests`, `Modules.Availability.Tests`, `Modules.Identity.Tests`, `Core.Tests`) still pass unchanged; the slave instance's Scalar/OpenAPI page loads and shows no Master-related endpoints.
|
||||
|
||||
## Unit 2 — Frontend Dual-Instance Tooling & Runbook
|
||||
|
||||
**Responsibility**: Let the existing frontend point at either the master or slave backend, and document the manual workflow for connecting them.
|
||||
|
||||
**Scope**:
|
||||
- Add `.env.slave.local` (gitignored, mirrors `.env.local`) with `VITE_API_BASE_URL=https://localhost:7222`.
|
||||
- Update `.env.example` to document both `.env.local` (master) and `.env.slave.local` (slave) modes.
|
||||
- Add an npm script `dev:slave` (e.g. `vite --mode slave --port 5174`) to `frontend/package.json`; introduce a `.env.slave` (non-local, checked-in) mode file if needed for Vite's mode-based env loading, or rely on `--mode slave` reading `.env.slave.local`.
|
||||
- Add a runbook section (in `frontend/README.md` and/or a new `docs/local-master-slave-setup.md` referenced from the root README) with step-by-step instructions: start master backend, start slave backend, start master frontend, (optionally) start slave frontend, use the existing "Add CMS Instance" dialog to register the local slave, confirm connected status.
|
||||
|
||||
**Depends On**: Unit 1 must be complete and verified working (Q2 = A) — the runbook's steps assume a working slave backend to point at and to actually exercise end-to-end.
|
||||
|
||||
**Completion Criteria**: Frontend can be started against either instance via the documented commands; the runbook has been manually followed once to confirm the "Add CMS Instance" flow successfully connects the local master to the local slave.
|
||||
|
||||
## Sequencing
|
||||
|
||||
Unit 1 → Unit 2 (strictly sequential; Unit 2's runbook and manual verification require Unit 1's working slave backend).
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Application Design — Clarification
|
||||
|
||||
## Contradiction: Q1 (file-linking) vs Q2 (move into Core)
|
||||
|
||||
You answered Q1 with **C** (MSBuild file-linking: keep the `.cs` files physically in one project, link them into the other so there's no new project) and Q2 with **B** (put the code directly into the existing `SlpModularCms.Core` project instead of a new project).
|
||||
|
||||
These two answers solve the same problem in different, mutually exclusive ways:
|
||||
- **C (file-linking)** implies the source files stay in `SlpModularCms.Api` (or wherever) and get *linked* (not copied) into `SlpModularCms.Api.Slave` — two projects compiling the same files, still no single shared assembly.
|
||||
- **B (move into Core)** means the files move into `SlpModularCms.Core`, which is *already* referenced by every project (both host projects, all modules) — no linking needed at all, because it becomes a normal shared dependency like everything else in Core.
|
||||
|
||||
Given your stated goal — "1 buildable project for production, no overhead for testing" — **B fully supersedes C**: moving the three classes (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) into `SlpModularCms.Core` gives you exactly one copy of the code, compiled once, already available to both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` via the existing `Core` reference — no new project, no file-linking, no extra overhead. File-linking would only be needed if you wanted to avoid touching `Core`, which contradicts choosing B.
|
||||
|
||||
Checked `SlpModularCms.Core.csproj`: it already has a `FrameworkReference` to `Microsoft.AspNetCore.App` (covers MVC, rate limiting, etc.) and already references `Microsoft.AspNetCore.Authentication.JwtBearer` and `Microsoft.AspNetCore.Identity.EntityFrameworkCore`. Moving the code in would require adding exactly two more package references to `Core`: `Asp.Versioning.Mvc` (for `AddApiVersioning`) and `Microsoft.AspNetCore.OpenApi` (for `AddOpenApi`) — both already used today, just currently referenced at the `Api` project level instead of `Core`.
|
||||
|
||||
### Clarification Question 1
|
||||
Confirm the resolution:
|
||||
|
||||
A) Yes — go with **B**: move `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` into `SlpModularCms.Core` (namespaces become `SlpModularCms.Core.Hosting.*` or similar). No new project, no file-linking. `Core` gains two package references (`Asp.Versioning.Mvc`, `Microsoft.AspNetCore.OpenApi`) it doesn't currently have. Both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` call the same code via their existing `Core` reference.
|
||||
B) No — actually use file-linking (**C**) instead, and leave `Core` untouched; the three classes stay physically in `SlpModularCms.Api` and get linked into `SlpModularCms.Api.Slave`'s `.csproj` via `<Compile Include="..." Link="..." />`.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Application Design Plan — Local Dev Master/Slave Setup
|
||||
|
||||
## Scope
|
||||
|
||||
This feature has no new business logic or data model — the only real "component design" decision is **how to share bootstrap/orchestration code** (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) between `SlpModularCms.Api` (master) and the new `SlpModularCms.Api.Slave` project, per NFR-1 (no duplication) from requirements.md.
|
||||
|
||||
Investigated: all three classes are `public`, live in `SlpModularCms.Api.Extensions`/`SlpModularCms.Api.Infrastructure`, and only depend on `SlpModularCms.Core.*` namespaces (`Core.Data`, `Core.Identity.*`, `Core.Availability`, `Core.Modules`) — none of them reference `Modules.Master`, `Modules.Identity`, or `Modules.Availability` directly. This means they can move to a shared project without dragging in the Master module.
|
||||
|
||||
## Design Plan
|
||||
|
||||
- [ ] Decide shared bootstrap extraction approach (Question 1)
|
||||
- [ ] Decide shared project name/location (Question 2)
|
||||
- [ ] Generate `components.md` — the two host projects + the new shared bootstrap component
|
||||
- [ ] Generate `component-methods.md` — public extension methods / orchestrator methods, signatures unchanged from today
|
||||
- [ ] Generate `services.md` — confirm no new domain services (reuses existing `Modules.Master`/`Modules.Availability`/`Modules.Identity` services untouched)
|
||||
- [ ] Generate `component-dependency.md` — dependency matrix for `SlpModularCms.Api`, `SlpModularCms.Api.Slave`, and the shared bootstrap project
|
||||
- [ ] Generate consolidated `application-design.md`
|
||||
|
||||
## Questions
|
||||
|
||||
### Question 1
|
||||
How should the shared bootstrap code (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) be shared between the master and slave host projects?
|
||||
|
||||
A) Move all three classes into a new shared class library project that both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference (clean separation, standard .NET pattern, one source of truth).
|
||||
B) Keep the classes in `SlpModularCms.Api` and have `SlpModularCms.Api.Slave` reference `SlpModularCms.Api` itself — rejected in requirements analysis because `SlpModularCms.Api.csproj` references `Modules.Master`, which would defeat the purpose of a Master-less slave; listed here only for completeness.
|
||||
C) Use MSBuild file-linking (`<Compile Include="..\SlpModularCms.Api\...\*.cs" Link="..." />`) to share the same `.cs` files across both projects without a new class library.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: C, because I want 1 buildable project for production. I dont want to create extra overhead for testing purposes
|
||||
|
||||
### Question 2
|
||||
What should the new shared project be named, and what should it depend on?
|
||||
|
||||
A) `SlpModularCms.Api.Hosting` — new class library referencing only `SlpModularCms.Core`; contains `ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention` (namespaces updated to `SlpModularCms.Api.Hosting.*`). Both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference it plus their own module project references (Master only for the master host).
|
||||
B) Add this code directly into the existing `SlpModularCms.Core` project instead of creating a new project.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: B
|
||||
@@ -0,0 +1,142 @@
|
||||
# Execution Plan — Local Dev Master/Slave Setup
|
||||
|
||||
## Detailed Analysis Summary
|
||||
|
||||
### Transformation Scope (Brownfield)
|
||||
- **Transformation Type**: Single new component (host project) + configuration-only changes elsewhere. No architectural transformation, no deployment-model change, no infrastructure/cloud change.
|
||||
- **Primary Changes**: New `SlpModularCms.Api.Slave` host project; extraction of shared bootstrap/orchestration code so it isn't duplicated between the master and slave hosts; per-instance `appsettings`/`launchSettings`; frontend dev-server config for a second instance; a short runbook.
|
||||
- **Related Components**: `SlpModularCms.Api` (reference/behavior parity, no functional change), `SlpModularCms.Modules.Availability` (already slave-capable, unchanged), `SlpModularCms.Modules.Master` (unchanged — simply excluded from the slave project), `frontend` (dev-server/env config only).
|
||||
|
||||
### Change Impact Assessment
|
||||
- **User-facing changes**: No — this is developer tooling, not a product feature.
|
||||
- **Structural changes**: Yes, minor — one new project (`SlpModularCms.Api.Slave`) and, to satisfy NFR-1 (no duplicated bootstrap code), a small new shared project/location for `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` that both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference.
|
||||
- **Data model changes**: No — no new entities, no new migrations.
|
||||
- **API changes**: No — no new endpoints or contract changes; existing Master/Availability endpoints are reused as-is.
|
||||
- **NFR impact**: Minor — addressed directly in requirements.md (NFR-1 no duplication, NFR-2 DB isolation, NFR-3 local-only scope, NFR-4 secrets hygiene). No new performance/security/scalability posture is introduced beyond what already exists.
|
||||
|
||||
### Component Relationships (Brownfield)
|
||||
- **Primary Component**: New `SlpModularCms.Api.Slave` project (backend)
|
||||
- **Shared Components**: New shared bootstrap location (exact form decided in Application Design) referenced by both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`
|
||||
- **Dependent Components**: `frontend` (new dev-server mode pointing at whichever instance)
|
||||
- **Supporting Components**: `frontend/README.md` / root docs (runbook)
|
||||
|
||||
| Component | Change Type | Change Reason | Change Priority |
|
||||
|---|---|---|---|
|
||||
| Shared bootstrap (new) | Minor (extraction, no behavior change) | Avoid duplicating `ModuleOrchestrator` etc. between hosts (NFR-1) | Critical — both hosts depend on it |
|
||||
| `SlpModularCms.Api` | Configuration-only (adjust to consume shared bootstrap) | Keep master behavior identical, just sourced from shared location | Critical — must not regress existing master behavior |
|
||||
| `SlpModularCms.Api.Slave` (new) | Major (new project) | Slave-only host, no `Modules.Master` reference | Critical — the actual deliverable |
|
||||
| `frontend` | Configuration-only | Point at either instance via env/script | Important |
|
||||
| Docs/runbook | New content | Explain manual connection workflow | Optional but requested (FR-4) |
|
||||
|
||||
### Risk Assessment
|
||||
- **Risk Level**: Low — isolated to local dev tooling; no production code paths, no data model, no API contract changes; existing master build output is unaffected once the shared bootstrap is extracted correctly.
|
||||
- **Rollback Complexity**: Easy — new project and config files only; deleting them reverts to the current state.
|
||||
- **Testing Complexity**: Simple — existing unit tests for `Modules.Master`/`Modules.Availability` are untouched; verification is mostly "does each instance start correctly and can the existing Add CMS Instance flow connect them."
|
||||
|
||||
## Workflow Visualization
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["User Request"])
|
||||
|
||||
subgraph INCEPTION["🔵 INCEPTION PHASE"]
|
||||
WD["Workspace Detection<br/><b>COMPLETED</b>"]
|
||||
RA["Requirements Analysis<br/><b>COMPLETED</b>"]
|
||||
US["User Stories<br/><b>SKIPPED</b>"]
|
||||
WP["Workflow Planning<br/><b>IN PROGRESS</b>"]
|
||||
AD["Application Design<br/><b>EXECUTE</b>"]
|
||||
UG["Units Generation<br/><b>EXECUTE</b>"]
|
||||
end
|
||||
|
||||
subgraph CONSTRUCTION["🟢 CONSTRUCTION PHASE"]
|
||||
FD["Functional Design<br/><b>SKIP (per unit)</b>"]
|
||||
NFRA["NFR Requirements<br/><b>SKIP (per unit)</b>"]
|
||||
NFRD["NFR Design<br/><b>SKIP (per unit)</b>"]
|
||||
ID["Infrastructure Design<br/><b>SKIP (per unit)</b>"]
|
||||
CG["Code Generation<br/><b>EXECUTE</b>"]
|
||||
BT["Build and Test<br/><b>EXECUTE</b>"]
|
||||
end
|
||||
|
||||
subgraph OPERATIONS["🟡 OPERATIONS PHASE"]
|
||||
OPS["Operations<br/><b>PLACEHOLDER</b>"]
|
||||
end
|
||||
|
||||
Start --> WD
|
||||
WD --> RA
|
||||
RA --> US
|
||||
US --> WP
|
||||
WP --> AD
|
||||
AD --> UG
|
||||
UG --> CG
|
||||
CG --> BT
|
||||
BT --> End(["Complete"])
|
||||
|
||||
style WD fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style RA fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style US fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style WP fill:#FFA726,stroke:#E65100,stroke-width:3px,color:#000
|
||||
style AD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
|
||||
style UG fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
|
||||
style FD fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style NFRA fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style NFRD fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style ID fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style CG fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style BT fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style Start fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
|
||||
style End fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
|
||||
|
||||
linkStyle default stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
Text alternative: Workspace Detection, Requirements Analysis are complete (green). User Stories is skipped (gray, dashed). Workflow Planning is in progress (orange). Application Design and Units Generation are planned to execute (orange, dashed border indicating conditional-but-selected). Per-unit Functional Design, NFR Requirements, NFR Design, and Infrastructure Design are all skipped (gray, dashed) for both units. Code Generation and Build and Test always execute (green). Operations remains a placeholder.
|
||||
|
||||
## Phases to Execute
|
||||
|
||||
### 🔵 INCEPTION PHASE
|
||||
- [x] Workspace Detection (COMPLETED)
|
||||
- [x] Requirements Analysis (COMPLETED)
|
||||
- [x] User Stories (SKIPPED)
|
||||
- **Rationale**: Pure developer tooling / local run configuration with no user-facing functionality, no personas, no acceptance-criteria needs — matches the explicit skip criteria in the workflow ("Developer tooling or build process improvements").
|
||||
- [x] Execution Plan (IN PROGRESS)
|
||||
- [ ] Application Design - **EXECUTE**
|
||||
- **Rationale**: A new component boundary is introduced (the shared bootstrap location consumed by both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`) and its responsibilities/dependencies need to be defined before code generation, even though no new business logic exists. Kept minimal — no service-layer business rules to design, just component boundaries.
|
||||
- [ ] Units Generation - **EXECUTE**
|
||||
- **Rationale**: The change spans two clearly separable concerns (backend dual-instance hosting vs. frontend dual-instance tooling), each independently completable and testable — decomposing into units keeps Code Generation focused.
|
||||
|
||||
### 🟢 CONSTRUCTION PHASE (per unit)
|
||||
- [ ] Functional Design - **SKIP** (both units)
|
||||
- **Rationale**: No new data models, schemas, or business logic — this is host/bootstrap wiring and configuration reuse of existing services.
|
||||
- [ ] NFR Requirements - **SKIP** (both units)
|
||||
- **Rationale**: NFRs are already fully captured in requirements.md (no duplication, DB isolation, local-only scope, secrets hygiene) and require no tech-stack selection or further elaboration.
|
||||
- [ ] NFR Design - **SKIP** (both units)
|
||||
- **Rationale**: Depends on NFR Requirements, which is skipped.
|
||||
- [ ] Infrastructure Design - **SKIP** (both units)
|
||||
- **Rationale**: No cloud/infrastructure resources involved — purely local processes and local SQL Server databases using existing patterns.
|
||||
- [ ] Code Generation - **EXECUTE (ALWAYS)**
|
||||
- **Rationale**: Implementation of the new project, config files, and frontend tooling.
|
||||
- [ ] Build and Test - **EXECUTE (ALWAYS)**
|
||||
- **Rationale**: Verify both instances build and start, existing test suites still pass, and the manual master↔slave connection flow works.
|
||||
|
||||
### 🟡 OPERATIONS PHASE
|
||||
- [ ] Operations - PLACEHOLDER
|
||||
- **Rationale**: Future deployment and monitoring workflows; not applicable to local dev tooling.
|
||||
|
||||
## Proposed Units (for Units Generation)
|
||||
|
||||
1. **Unit 1 — Backend dual-instance hosting**: Extract shared bootstrap (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) into a location both hosts reference; add `SlpModularCms.Api.Slave` project (no `Modules.Master` reference); add its `launchSettings.json`/`appsettings*.json`; update `SlpModularCms.Api`'s config for CORS/port clarity if needed; add both projects to the solution.
|
||||
2. **Unit 2 — Frontend dual-instance tooling & runbook**: Add `.env.slave.local`/`.env.example` updates, `dev:slave` npm script, and the runbook documenting how to start both instances and use the existing Add CMS Instance dialog to connect them.
|
||||
|
||||
## Package Change Sequence
|
||||
|
||||
Unit 1 must complete before Unit 2 can be meaningfully tested end-to-end (the frontend needs a running slave backend to point at), though Unit 2's config/doc changes could technically be authored in parallel. Sequential execution (Unit 1 then Unit 2) is recommended for a clean verification story.
|
||||
|
||||
## Estimated Timeline
|
||||
- **Total Phases**: Application Design, Units Generation, Code Generation (2 units), Build and Test
|
||||
- **Estimated Duration**: Small — a few hours of focused work; no research spikes needed since the master/slave protocol already exists and is unchanged.
|
||||
|
||||
## Success Criteria
|
||||
- **Primary Goal**: Developer can run a master instance (port 5284/7221, full stack) and a slave instance (port 5285/7222, no Master module) locally at the same time, on separate databases.
|
||||
- **Key Deliverables**: `SlpModularCms.Api.Slave` project, shared bootstrap extraction, per-instance config, frontend slave mode, runbook.
|
||||
- **Quality Gates**: Both backends build and start cleanly; existing test suites (`Modules.Master.Tests`, `Modules.Availability.Tests`, frontend tests) still pass unchanged; manual verification that the master frontend's "Add CMS Instance" dialog can register and see the local slave as connected.
|
||||
- **Integration Testing**: Manual — start both instances, register the slave from the master frontend, confirm connected/healthy status.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Unit of Work Plan — Local Dev Master/Slave Setup
|
||||
|
||||
## Plan
|
||||
|
||||
- [ ] Generate `unit-of-work.md` with unit definitions and responsibilities
|
||||
- [ ] Generate `unit-of-work-dependency.md` with dependency matrix between units
|
||||
- [ ] Generate `unit-of-work-story-map.md` mapping requirements (no user stories exist for this feature — User Stories stage was skipped) to units
|
||||
- [ ] Validate unit boundaries and dependencies
|
||||
|
||||
## Context
|
||||
|
||||
No user stories exist for this feature (skipped as pure dev tooling). Units are mapped directly from the Functional Requirements in `requirements.md` and the components in `application-design/`. `SlpModularCms.Api` has no dedicated test project today (it's thin bootstrap code, exercised indirectly via module test suites and manual verification) — the same pattern is expected to apply to `SlpModularCms.Api.Slave`.
|
||||
|
||||
## Questions
|
||||
|
||||
### Question 1
|
||||
Confirm the two-unit split proposed in the execution plan:
|
||||
|
||||
A) **Unit 1 — Backend dual-instance hosting**: relocate `ModuleOrchestrator`/`ServiceCollectionExtensions`/`ApiPrefixConvention` into `SlpModularCms.Core.Hosting`; add `SlpModularCms.Api.Slave` project (no `Modules.Master` reference); add both projects' `launchSettings.json`/`appsettings*.json`; add to solution. **Unit 2 — Frontend dual-instance tooling & runbook**: `.env.slave.local` support, `dev:slave` npm script, runbook documentation.
|
||||
B) Different split — describe after [Answer]: tag below.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
### Question 2 (Dependencies)
|
||||
Unit 2 (frontend) needs a running slave backend to be meaningfully tested against. Should Unit 2 still be generated even though Unit 1 must be functionally verified first?
|
||||
|
||||
A) Yes — generate Unit 1 fully (including a working build) before starting Unit 2's code, since Unit 2's manual verification depends on Unit 1 existing and running.
|
||||
B) Generate both units' code in parallel/independently, verify together at the end during Build and Test.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
### Question 3 (Technical Considerations)
|
||||
Does `SlpModularCms.Api.Slave` need its own dedicated test project (mirroring `SlpModularCms.Api`, which currently has none)?
|
||||
|
||||
A) No — no dedicated test project, consistent with `SlpModularCms.Api` today (thin bootstrap code covered indirectly by module test suites; this feature adds no new business logic to unit test).
|
||||
B) Yes — add a new `SlpModularCms.Api.Slave.Tests` project.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Requirements Clarification — Follow-up
|
||||
|
||||
Your Question 1 answer combines two different concerns, and I want to scope this feature correctly before writing requirements.
|
||||
|
||||
## Ambiguity 1: Scope of the module-loading change
|
||||
|
||||
You answered Q1 with: *"For testing purposes a separate project will suffice, but when building the production builds later I want to be able to build the base version of the API and add the modules as DLL-files. Can we make that happen here as well?"*
|
||||
|
||||
Today, `SlpModularCms.Modules.Master` is a `ProjectReference` in `SlpModularCms.Api.csproj`, so it's always compiled straight into the API's own build output — `ModuleOrchestrator` then discovers it there via DLL-scanning. A true "base API + drop in module DLLs later" production model would mean:
|
||||
- Removing the `ProjectReference` to `Modules.Master` (and, by the same logic, to `Modules.Identity`/`Modules.Availability`) from `SlpModularCms.Api.csproj`.
|
||||
- Introducing a separate "Modules" output/probing folder that the API loads DLLs from at startup (instead of its own bin folder), populated by copying built module DLLs into it (a packaging/deployment concern).
|
||||
- Deciding what happens to module-specific EF Core migrations, DI wiring, and versioning across independently-built modules.
|
||||
|
||||
This is a meaningfully bigger architectural change than "add a config flag to skip the Master module for a local slave test instance" — it touches build/packaging (`.csproj` structure, CI/CD artifacts) well beyond local dev tooling.
|
||||
|
||||
### Clarification Question 1
|
||||
How do you want to handle this?
|
||||
|
||||
A) Keep this feature scoped to local dev only: a separate `SlpModularCms.Api.Slave` test host project (no `Modules.Master` reference) for running master+slave locally. Capture the "base API + drop-in module DLLs for production" idea as a new tech-debt-backlog / future-feature item to design properly on its own later.
|
||||
B) Expand this feature to also implement the production drop-in-DLL loading model now (restructure `SlpModularCms.Api.csproj` to not directly reference module projects, add a module-probing folder loaded at runtime, update `ModuleOrchestrator` accordingly) — understanding this is a larger, riskier change touching the deployment model, not just local dev.
|
||||
C) Do the config-driven `Modules:Disabled` toggle from the original option A instead (one shared build, no separate project, no production refactor) — simplest for local dev, defers the production plugin model entirely.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Requirements Clarification Questions — Local Dev Master/Slave Setup
|
||||
|
||||
Context found while investigating the codebase: `SlpModularCms.Api` discovers modules by scanning its own build output folder for `SlpModularCms.Modules.*.dll` (`ModuleOrchestrator.DiscoverModules`) and loads every module DLL it finds — there is currently no config-driven way to load the API without `SlpModularCms.Modules.Master`. To run a "slave-only" instance locally, `ModuleOrchestrator` needs a small change: skip registering/activating a module if it's named in a new config setting (e.g. `Modules:Disabled`), so the same build output can serve both an instance with the Master module active and one without.
|
||||
|
||||
Please answer each question below by filling in your choice after the `[Answer]:` tag.
|
||||
|
||||
## Question 1
|
||||
How should the "master" vs "slave" instance be toggled?
|
||||
|
||||
A) Config-driven module disable list read from `appsettings`/`appsettings.local.json` (e.g. `"Modules": { "Disabled": ["Master"] }`), checked by `ModuleOrchestrator` before registering/activating a module. One shared build; two different config files/launch profiles select the mode.
|
||||
B) Separate ASP.NET Core launch profiles only, no code change — accepting that both profiles will always load the Master module since it's a project reference (this would NOT actually give you a Master-less slave).
|
||||
C) A separate host project (`SlpModularCms.Api.Slave`) with its own `.csproj` that simply omits the `SlpModularCms.Modules.Master` project reference.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: X, For testing purposes a separate project will suffice, but when building the production builds later I want to be able to build the base version of the API and add the modules as DLL-files. Can we make that happen here as well?
|
||||
|
||||
## Question 2
|
||||
Should the master and slave instance use separate local databases (so slave-side data like registrations/users doesn't collide with master-side data)?
|
||||
|
||||
A) Yes — separate connection strings/database names (e.g. `SlpModularCmsLocal_Master` and `SlpModularCmsLocal_Slave`), same SQL Server instance, via `appsettings.local.json` per profile.
|
||||
B) No — both instances share the exact same local database.
|
||||
C) Use SQLite per instance instead of SQL Server, to make the setup fully self-contained without a shared SQL Server.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Question 3
|
||||
Which ports should the two instances use?
|
||||
|
||||
A) Master keeps the existing ports (`http://localhost:5284`, `https://localhost:7221`); slave gets a new pair (`http://localhost:5285`, `https://localhost:7222`).
|
||||
B) Let me specify custom ports (describe after [Answer]: tag below).
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Question 4
|
||||
How do you want to start both instances locally?
|
||||
|
||||
A) Two named `dotnet run --launch-profile <name>` invocations (one per terminal) — profiles added to the existing `SlpModularCms.Api/Properties/launchSettings.json`.
|
||||
B) A helper script (e.g. PowerShell) that launches both in the background with one command.
|
||||
C) Both A and B — launch profiles for IDE debugging (e.g. Rider/VS "Multiple startup projects" or `dotnet run`), plus a convenience script for quick CLI use.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Question 5
|
||||
Should the frontend (React app) also be runnable against the slave instance, or is the slave purely a backend API target (no need to browse it via the CMS frontend)?
|
||||
|
||||
A) Backend-only — slave just needs to be reachable over HTTP for the master-to-slave connection (registration + polling/push); Scalar/OpenAPI at the slave's own URL is enough to poke it manually if needed.
|
||||
B) The frontend should also be runnable against the slave (e.g. via a `.env.slave` or a second Vite dev server config), so its admin UI is separately reachable too.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: B
|
||||
|
||||
## Question 6
|
||||
Once both instances are running, do you want a documented/scripted way to actually register the slave under the master (so the "connection" is set up end-to-end), or is wiring that up manually through the existing "Add CMS Instance" UI flow sufficient?
|
||||
|
||||
A) Manual is fine — document the steps (start both, open master frontend, use the existing Add CMS Instance dialog with the slave's local URL) in a short README/runbook.
|
||||
B) Provide a scripted/seeded way (e.g. a dev-only endpoint or seed data) to pre-register the slave automatically on startup, skipping the manual UI step.
|
||||
X) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A, because I want to test the functionality of the "Add CMS Instance" UI flow.
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# Requirements — Local Dev Master/Slave Setup
|
||||
|
||||
## Intent Analysis
|
||||
|
||||
- **User Request**: Enable running two local instances of the CMS backend simultaneously — one with `SlpModularCms.Modules.Master` loaded (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`/`launchSettings` for 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`'s `CmsInstanceService`/`SlaveApiClient` and `Modules.Availability`'s `MasterController`/`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`'s `Program.cs` does not directly reference `Modules.Master`. Instead, `Infrastructure/ModuleOrchestrator.cs` discovers modules at startup by scanning its own build output directory for `SlpModularCms.Modules.*.dll` and instantiating any `IModule` implementation found — currently every module the API project references (`Core`, `Availability`, `Identity`, `Master`) always ends up in that output folder via `ProjectReference`s in `SlpModularCms.Api.csproj`.
|
||||
- The master/slave connection mechanism already exists and needs no changes:
|
||||
- **Slave side** (present in every instance, since `Modules.Availability` is always loaded): `MasterController` + `MasterAvailabilityService` + `MasterApiKeyProtector` accept and validate a per-instance API key pushed by a master.
|
||||
- **Master side** (only in `Modules.Master`): `CmsInstanceService` + `SlaveApiClient` + `ApiKeyProtector` manage 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.
|
||||
- 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:AllowedOrigins` in `appsettings.Development.json` / `appsettings.local.json`.
|
||||
|
||||
## Decisions from Clarification
|
||||
|
||||
1. **Instance separation mechanism**: A new host project, `SlpModularCms.Api.Slave`, with its own `.csproj` referencing `SlpModularCms.Core`, `SlpModularCms.Modules.Identity`, and `SlpModularCms.Modules.Availability` — but **not** `SlpModularCms.Modules.Master`. Because `ModuleOrchestrator` discovers modules purely by scanning its own build output, omitting the `Master` project reference is sufficient to produce a slave-only instance; no config-driven module toggle is needed for this feature.
|
||||
2. **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).
|
||||
3. **Database isolation**: Master and slave instances use separate local databases (separate connection strings/database names), same local SQL Server.
|
||||
4. **Ports**: Master keeps existing ports (`http://localhost:5284`, `https://localhost:7221`). Slave gets a new pair (`http://localhost:5285`, `https://localhost:7222`).
|
||||
5. **Launch method**: Two `dotnet run --launch-profile <name>` invocations (one per terminal/process) — no background helper script required.
|
||||
6. **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.
|
||||
7. **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 in `src/`, 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 `.csproj` references `Core`, `Modules.Identity`, and `Modules.Availability` only (no `Modules.Master` reference), so `ModuleOrchestrator` never discovers/loads the Master module for this instance.
|
||||
- Avoid duplicating the bootstrap/orchestration code (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) between `SlpModularCms.Api` and `SlpModularCms.Api.Slave` — extract shared pieces into a location both projects can reference without pulling in `Modules.Master` (see NFR-1).
|
||||
|
||||
### FR-2: Distinct local run configuration per instance
|
||||
- `SlpModularCms.Api` (master) keeps its existing `launchSettings.json` profiles and ports (5284 / 7221).
|
||||
- `SlpModularCms.Api.Slave` gets its own `launchSettings.json` with 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:AllowedOrigins` covering whichever frontend origin(s) will point at that instance.
|
||||
- The slave instance does not need `MasterModule` configuration (that section is only meaningful where `Modules.Master` is loaded).
|
||||
- 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`) setting `VITE_API_BASE_URL` to 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.example` and/or `frontend/README.md` to document both modes.
|
||||
|
||||
### FR-4: Documentation for the local master/slave workflow
|
||||
- Add a short runbook (e.g. `frontend/README.md` and/or a root-level `docs`/`README` section, or a dedicated markdown file referenced from the main README) describing:
|
||||
1. How to start the master backend + master frontend.
|
||||
2. How to start the slave backend (+ optionally slave frontend) on the alternate ports.
|
||||
3. 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, following `dotnet-appsettings` conventions already used by `SlpModularCms.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.
|
||||
Reference in New Issue
Block a user