908 lines
23 KiB
Markdown
908 lines
23 KiB
Markdown
# Refactor Blueprint: GameService and Workspace
|
|
|
|
## Purpose
|
|
|
|
This document is the implementation blueprint for splitting two oversized classes without changing product behavior:
|
|
|
|
- `RpgRoller/Services/GameService.cs`
|
|
- `RpgRoller/Components/Pages/Workspace.razor.cs`
|
|
|
|
The goal is to reduce future churn, make responsibilities explicit, improve testability, and create stable seams for future features.
|
|
|
|
This is a planning artifact only. It does not authorize behavior changes. During implementation, preserve the existing public API contracts and current user-visible behavior unless a separate task explicitly requests functional changes.
|
|
|
|
## Primary Constraints
|
|
|
|
- Do not use partial classes as the main decomposition strategy.
|
|
- Prefer composition of small sealed classes with clear ownership.
|
|
- Keep `IGameService` stable during the refactor unless a separate change requests an API redesign.
|
|
- Preserve current endpoint contracts and `WorkspaceQueryService` behavior.
|
|
- Preserve current workspace UX and screen flow.
|
|
- Do not revert unrelated local changes already present in the repo.
|
|
- Keep changes incremental and validation-heavy.
|
|
|
|
## Desired End State
|
|
|
|
### Backend
|
|
|
|
- `GameService` becomes a thin façade/coordinator.
|
|
- Stateful concerns are centralized in a shared state owner instead of spread across one giant class.
|
|
- Domain workflows are split into small sealed collaborators.
|
|
- Pure logic helpers move into small focused files.
|
|
- File boundaries reflect change frequency and business ownership.
|
|
|
|
### Frontend
|
|
|
|
- `Workspace` becomes a thin Blazor component coordinator.
|
|
- State, orchestration, admin actions, play/log actions, and live event synchronization live in separate sealed collaborators.
|
|
- Razor bindings reference composed state/actions objects instead of one monolithic code-behind surface.
|
|
- Existing child component boundaries remain intact unless a very small binding adjustment is needed.
|
|
|
|
## Current Problems To Solve
|
|
|
|
### GameService
|
|
|
|
Observed concerns mixed together today:
|
|
|
|
- authentication and session lifecycle
|
|
- admin user management
|
|
- campaign lifecycle
|
|
- character lifecycle and ownership transfer
|
|
- skill and skill-group lifecycle
|
|
- dice validation and roll execution
|
|
- campaign log shaping and roll-detail formatting
|
|
- DTO mapping
|
|
- in-memory state tracking
|
|
- persistence load/save/cloning
|
|
|
|
This creates the wrong coupling pattern:
|
|
|
|
- changing one workflow risks merge conflicts with unrelated work
|
|
- high-level service methods depend on low-level helper implementation details
|
|
- lock/state/persistence concerns are interleaved with domain logic
|
|
- roll engine logic is too close to CRUD flows
|
|
- pure helpers are harder to discover and test in isolation
|
|
|
|
### Workspace
|
|
|
|
Observed concerns mixed together today:
|
|
|
|
- initial bootstrap
|
|
- session reload/logout
|
|
- screen selection and persistence
|
|
- campaign selection and scope refresh
|
|
- management actions
|
|
- play-screen actions
|
|
- campaign log detail caching
|
|
- admin actions
|
|
- live state event synchronization
|
|
- toast/status handling
|
|
- UI state storage and computed properties
|
|
|
|
This creates the wrong coupling pattern:
|
|
|
|
- every change lands in one file
|
|
- state mutation rules are difficult to audit
|
|
- JS interop ownership is spread across unrelated methods
|
|
- event-driven refresh logic is coupled to screen and toast logic
|
|
- the Razor file binds against too many members on one type
|
|
|
|
## Architecture Direction
|
|
|
|
## 1. Backend Composition Strategy
|
|
|
|
### 1.1 Keep a thin GameService façade
|
|
|
|
`GameService` should stay as the application-facing implementation of `IGameService`, but it should stop owning every workflow directly.
|
|
|
|
Target shape:
|
|
|
|
- constructor wires shared collaborators
|
|
- each public `IGameService` method delegates to one domain-oriented sealed service
|
|
- the façade owns no domain-heavy private logic
|
|
- only lightweight delegation and composition remain in `GameService`
|
|
|
|
This preserves integration points while still removing the monolith.
|
|
|
|
### 1.2 Introduce a shared state owner
|
|
|
|
Create a single shared state owner to encapsulate the in-memory model, synchronization gate, and persistence coordination used by the composed backend services.
|
|
|
|
Candidate responsibility set:
|
|
|
|
- lock object
|
|
- dictionaries/lists currently stored in `GameService`
|
|
- lookup helpers for common state access
|
|
- campaign-state tracker storage
|
|
- database load/save orchestration hooks
|
|
|
|
Recommended candidate names:
|
|
|
|
- `GameStateStore`
|
|
- `GameRuntimeState`
|
|
- `GameStateCoordinator`
|
|
|
|
Preferred choice: `GameStateStore`
|
|
|
|
Why:
|
|
|
|
- "Store" matches the fact that it owns mutable in-memory state
|
|
- it avoids implying EF persistence semantics
|
|
- it gives the rest of the services a stable dependency name
|
|
|
|
This shared state owner must be the only place that exposes the mutable collections and the synchronization gate needed by the domain services.
|
|
|
|
### 1.3 Split backend workflows into domain services
|
|
|
|
Create sealed services grouped by real business seams, not arbitrary line-count slices.
|
|
|
|
Recommended services:
|
|
|
|
1. `GameAuthService`
|
|
2. `GameUserAdministrationService`
|
|
3. `GameCampaignService`
|
|
4. `GameCharacterService`
|
|
5. `GameSkillService`
|
|
6. `GameRollService`
|
|
7. `GamePersistenceService`
|
|
|
|
Each service should be small enough to own one family of change, but large enough that one feature does not have to hop across many tiny orchestration layers.
|
|
|
|
### 1.4 Extract pure helper classes into separate files
|
|
|
|
Pure or near-pure helpers should not stay hidden inside the coordinator or domain services.
|
|
|
|
Recommended helper extraction:
|
|
|
|
- `SkillDefinitionValidator`
|
|
- `RollVisibilityParser`
|
|
- `CustomRollOptionsResolver`
|
|
- `RollBreakdownFormatter`
|
|
- `CampaignLogSummaryBuilder`
|
|
- `GameDtoMapper`
|
|
- `GameStateCloneFactory`
|
|
- `RoleSerializer`
|
|
|
|
If a helper remains too broad after extraction, split again. Small helper files are desirable here because they are stable, discoverable, and easy to test.
|
|
|
|
## 2. Backend Responsibility Map
|
|
|
|
### 2.1 GameService
|
|
|
|
`GameService` should delegate the following methods:
|
|
|
|
- auth/session
|
|
- `Register`
|
|
- `Login`
|
|
- `Logout`
|
|
- `GetUserBySession`
|
|
- `GetMe`
|
|
- campaigns
|
|
- `CreateCampaign`
|
|
- `GetCampaigns`
|
|
- `GetCharacterCampaignOptions`
|
|
- `GetCampaign`
|
|
- `DeleteCampaign`
|
|
- users/admin
|
|
- `GetUsernames`
|
|
- `GetUsers`
|
|
- `UpdateUserRoles`
|
|
- `DeleteUser`
|
|
- characters
|
|
- `CreateCharacter`
|
|
- `UpdateCharacter`
|
|
- `DeleteCharacter`
|
|
- `ActivateCharacter`
|
|
- `GetOwnCharacters`
|
|
- skills
|
|
- `CreateSkillGroup`
|
|
- `UpdateSkillGroup`
|
|
- `DeleteSkillGroup`
|
|
- `CreateSkill`
|
|
- `UpdateSkill`
|
|
- `DeleteSkill`
|
|
- `GetCharacterSheet`
|
|
- rolls/log
|
|
- `RollSkill`
|
|
- `RollCustom`
|
|
- `GetCampaignLog`
|
|
- `GetCampaignLogPage`
|
|
- `GetRollDetail`
|
|
- `GetCampaignStateSnapshot`
|
|
- rulesets
|
|
- `GetRulesets`
|
|
|
|
### 2.2 GameAuthService
|
|
|
|
Own:
|
|
|
|
- register/login/logout/session lookup
|
|
- me-response shaping tied to session and active-character resolution
|
|
- username normalization usage in auth flows
|
|
- session creation
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `IPasswordHasher<UserAccount>`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
|
|
### 2.3 GameUserAdministrationService
|
|
|
|
Own:
|
|
|
|
- `GetUsernames`
|
|
- `GetUsers`
|
|
- `UpdateUserRoles`
|
|
- `DeleteUser`
|
|
- role checks used by admin flows
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
- `RoleSerializer`
|
|
- common authorization helpers
|
|
|
|
### 2.4 GameCampaignService
|
|
|
|
Own:
|
|
|
|
- create/list/get/delete campaign flows
|
|
- campaign visibility rules
|
|
- campaign context resolution for viewable campaigns
|
|
- campaign roster shaping
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
- shared authorization/context helpers
|
|
|
|
### 2.5 GameCharacterService
|
|
|
|
Own:
|
|
|
|
- create/update/delete/activate character flows
|
|
- owner transfer rules
|
|
- active-character consistency rules
|
|
- own-character listing
|
|
- character-to-campaign state tracker updates
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
- shared authorization/context helpers
|
|
|
|
### 2.6 GameSkillService
|
|
|
|
Own:
|
|
|
|
- skill-group CRUD
|
|
- skill CRUD
|
|
- character sheet read model
|
|
- skill-group assignment rules
|
|
- skill definition validation orchestration
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
- `SkillDefinitionValidator`
|
|
- shared authorization/context helpers
|
|
|
|
### 2.7 GameRollService
|
|
|
|
Own:
|
|
|
|
- `RollSkill`
|
|
- `RollCustom`
|
|
- campaign log reads
|
|
- log page reads
|
|
- roll detail reads
|
|
- campaign state snapshot reads
|
|
- roll recording
|
|
- log visibility checks
|
|
- compact log summary generation
|
|
|
|
Dependencies:
|
|
|
|
- `GameStateStore`
|
|
- `GamePersistenceService`
|
|
- `GameDtoMapper`
|
|
- `SkillDefinitionValidator` only if needed for custom roll flows
|
|
- `RollVisibilityParser`
|
|
- `RollEngine`
|
|
- `RollBreakdownFormatter`
|
|
- `CampaignLogSummaryBuilder`
|
|
|
|
### 2.8 GamePersistenceService
|
|
|
|
Own:
|
|
|
|
- load state from database at startup
|
|
- persist current runtime state
|
|
- clone/snapshot helpers used for persistence boundaries
|
|
|
|
Dependencies:
|
|
|
|
- `IDbContextFactory<RpgRollerDbContext>`
|
|
- `GameStateStore`
|
|
- `GameStateCloneFactory`
|
|
|
|
Implementation note:
|
|
|
|
- keep EF persistence concerns here
|
|
- do not let other domain services talk directly to EF unless there is a deliberate redesign later
|
|
|
|
## 3. Backend Cross-Cutting Helpers
|
|
|
|
Some logic is shared across multiple domain services but should still remain explicit instead of hidden in the state store.
|
|
|
|
Recommended small helper files:
|
|
|
|
### Authorization and context
|
|
|
|
- `GameAuthorization`
|
|
- user role check helpers
|
|
- can-view campaign
|
|
- can-edit character
|
|
- can-view roll
|
|
|
|
- `GameContextResolver`
|
|
- resolve user from session
|
|
- resolve campaign context
|
|
- resolve character campaign
|
|
- resolve current campaign id
|
|
|
|
These can be static helper classes if they remain pure over store inputs, or sealed services if constructor-injected collaborators make the code cleaner. Prefer the simpler shape once implementation starts.
|
|
|
|
### Mapping
|
|
|
|
- `GameDtoMapper`
|
|
- `ToUserSummary`
|
|
- `ToAdminUserSummary`
|
|
- `ToCampaignOption`
|
|
- `ToCampaignSummary`
|
|
- `ToCampaignRoster`
|
|
- `ToCharacterSheet`
|
|
- `ToCharacterSummary`
|
|
- `ToCampaignStateSnapshot`
|
|
- `ToSkillGroupSummary`
|
|
- `ToSkillSummary`
|
|
- `ToRollResult`
|
|
- `ToLogEntry`
|
|
- `ToLogListEntry`
|
|
|
|
### Persistence and clone helpers
|
|
|
|
- `GameStateCloneFactory`
|
|
- clone user/session/campaign/character/skill/skill-group/roll-log entry
|
|
|
|
- `RoleSerializer`
|
|
- parse/serialize/normalize roles
|
|
|
|
### Roll engine helpers
|
|
|
|
- `SkillDefinitionValidator`
|
|
- expression parse/option validation
|
|
|
|
- `RollEngine`
|
|
- top-level roll dispatch by ruleset
|
|
|
|
- `StandardRollEngine`
|
|
- `D6RollEngine`
|
|
- `RolemasterRollEngine`
|
|
|
|
- `RollBreakdownFormatter`
|
|
- shared textual breakdown formatting
|
|
|
|
- `CampaignLogSummaryBuilder`
|
|
- compact summary + badges + expression extraction helpers
|
|
|
|
The roll-engine area is a strong candidate for several very small files because the behavior is algorithmic and changes independently from CRUD flows.
|
|
|
|
## 4. Recommended Backend File Layout
|
|
|
|
One reasonable target layout:
|
|
|
|
```text
|
|
RpgRoller/Services/
|
|
GameService.cs
|
|
GameStateStore.cs
|
|
GamePersistenceService.cs
|
|
GameAuthService.cs
|
|
GameUserAdministrationService.cs
|
|
GameCampaignService.cs
|
|
GameCharacterService.cs
|
|
GameSkillService.cs
|
|
GameRollService.cs
|
|
GameAuthorization.cs
|
|
GameContextResolver.cs
|
|
GameDtoMapper.cs
|
|
GameStateCloneFactory.cs
|
|
RoleSerializer.cs
|
|
SkillDefinitionValidator.cs
|
|
RollVisibilityParser.cs
|
|
CustomRollOptionsResolver.cs
|
|
RollEngine.cs
|
|
StandardRollEngine.cs
|
|
D6RollEngine.cs
|
|
RolemasterRollEngine.cs
|
|
RollBreakdownFormatter.cs
|
|
CampaignLogSummaryBuilder.cs
|
|
```
|
|
|
|
Exact filenames can be adjusted during implementation if a nearby convention in the repo suggests better naming. The important part is the separation of responsibilities, not the precise suffix.
|
|
|
|
## 5. Frontend Composition Strategy
|
|
|
|
### 5.1 Keep Workspace as the component boundary
|
|
|
|
`Workspace` should remain the Blazor component type used by `Workspace.razor`, but it should stop directly owning all workflow code.
|
|
|
|
Target shape:
|
|
|
|
- `Workspace` keeps injected dependencies and lifecycle entry points
|
|
- composed sealed collaborators handle most behavior
|
|
- `Workspace` exposes only the state/actions needed by the Razor file
|
|
|
|
This avoids breaking the component boundary while still removing the monolith.
|
|
|
|
### 5.2 Introduce a dedicated state holder
|
|
|
|
Create a single state holder for workspace UI state and view-model data.
|
|
|
|
Recommended candidate name:
|
|
|
|
- `WorkspaceState`
|
|
|
|
Own:
|
|
|
|
- authenticated user/session-derived state
|
|
- selected campaign and character state
|
|
- campaign collections
|
|
- rule set collections
|
|
- admin user collection
|
|
- log detail cache
|
|
- toast state
|
|
- screen/mobile-panel state
|
|
- live connection state
|
|
- modal state
|
|
- computed helpers that are pure projections over stored state
|
|
|
|
This state holder should be easy to inspect and should reduce the risk of hidden cross-method mutations.
|
|
|
|
### 5.3 Split workspace orchestration into sealed collaborators
|
|
|
|
Recommended services:
|
|
|
|
1. `WorkspaceSessionCoordinator`
|
|
2. `WorkspaceCampaignCoordinator`
|
|
3. `WorkspacePlayCoordinator`
|
|
4. `WorkspaceAdminCoordinator`
|
|
5. `WorkspaceLiveStateController`
|
|
6. `WorkspaceFeedbackService`
|
|
|
|
These names can be adjusted, but the responsibilities should stay distinct.
|
|
|
|
## 6. Frontend Responsibility Map
|
|
|
|
### 6.1 Workspace component
|
|
|
|
Own:
|
|
|
|
- injected dependencies
|
|
- collaborator construction/wiring
|
|
- lifecycle delegation
|
|
- JS-invokable entry points delegating to collaborators
|
|
- final binding surface consumed by Razor
|
|
|
|
Keep thin. Do not let it become a second coordinator monolith with only renamed methods.
|
|
|
|
### 6.2 WorkspaceState
|
|
|
|
Own:
|
|
|
|
- scalar UI state
|
|
- collections
|
|
- selected entities
|
|
- modal visibility and form bootstrap models
|
|
- log detail cache and loading/error maps
|
|
- toast list
|
|
- computed view properties
|
|
|
|
Recommended computed properties to live here if kept pure:
|
|
|
|
- selected campaign name
|
|
- selected character
|
|
- play-selected campaign
|
|
- play-selected character
|
|
- play-selected character id
|
|
- filtered play skill and skill-group views if still direct projections
|
|
- current-user role flags
|
|
- campaign delete permission
|
|
- screen flags
|
|
- connection-state label/css
|
|
- app CSS class
|
|
|
|
### 6.3 WorkspaceSessionCoordinator
|
|
|
|
Own:
|
|
|
|
- initial bootstrap sequence
|
|
- session reload
|
|
- logout flow
|
|
- health retry flow
|
|
- persisted session-storage reads/writes for screen, panel, campaign, roll visibility
|
|
- clearing authenticated state
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- `WorkspaceQueryService`
|
|
- `RpgRollerApiClient`
|
|
- `IJSRuntime`
|
|
- `WorkspaceLiveStateController`
|
|
- `WorkspaceFeedbackService`
|
|
|
|
### 6.4 WorkspaceCampaignCoordinator
|
|
|
|
Own:
|
|
|
|
- campaign reload and selection
|
|
- campaign scope refresh
|
|
- campaign roster refresh
|
|
- character campaign options reload
|
|
- management-screen campaign and character mutations
|
|
- character modal open/close and bootstrap
|
|
- selected-character synchronization
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- `WorkspaceQueryService`
|
|
- `RpgRollerApiClient`
|
|
- `IJSRuntime`
|
|
- `WorkspaceLiveStateController`
|
|
- `WorkspaceFeedbackService`
|
|
|
|
### 6.5 WorkspacePlayCoordinator
|
|
|
|
Own:
|
|
|
|
- selected character activation
|
|
- selected character sheet refresh
|
|
- skill roll submission
|
|
- custom roll handling
|
|
- campaign log page refresh
|
|
- roll detail expansion/loading/cache trimming
|
|
- roll visibility changes
|
|
- play-panel errors
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- `WorkspaceQueryService`
|
|
- `RpgRollerApiClient`
|
|
- `IJSRuntime`
|
|
- `WorkspaceFeedbackService`
|
|
|
|
### 6.6 WorkspaceAdminCoordinator
|
|
|
|
Own:
|
|
|
|
- admin screen access enforcement
|
|
- admin user load
|
|
- toggle admin role
|
|
- delete user
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- `WorkspaceQueryService`
|
|
- `RpgRollerApiClient`
|
|
- `IJSRuntime`
|
|
- `WorkspaceLiveStateController`
|
|
- `WorkspaceFeedbackService`
|
|
|
|
### 6.7 WorkspaceLiveStateController
|
|
|
|
Own:
|
|
|
|
- start/stop SSE state events
|
|
- JS invokable state snapshot handling
|
|
- JS invokable connection-state updates
|
|
- current campaign-state refresh logic
|
|
- campaign-state tracking reset logic
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- `WorkspaceCampaignCoordinator`
|
|
- `WorkspacePlayCoordinator`
|
|
- `IJSRuntime`
|
|
|
|
Critical rule:
|
|
|
|
- this class should own the live-update reconciliation logic
|
|
- do not let campaign reload logic and live event logic drift into different locations again
|
|
|
|
### 6.8 WorkspaceFeedbackService
|
|
|
|
Own:
|
|
|
|
- status-to-toast behavior
|
|
- live announcement updates
|
|
- delayed toast dismissal
|
|
|
|
Dependencies:
|
|
|
|
- `WorkspaceState`
|
|
- component refresh callback or dispatcher mechanism
|
|
|
|
Implementation note:
|
|
|
|
- if callback wiring becomes awkward, this service can remain a helper owned by `Workspace` rather than a DI service
|
|
- the important part is isolating feedback behavior from campaign/admin/play workflows
|
|
|
|
## 7. Frontend Binding Strategy
|
|
|
|
If composition is used, `Workspace.razor` bindings will need to reference state and actions through the new composed surface.
|
|
|
|
Example direction:
|
|
|
|
- state data from `State`
|
|
- management actions from `Campaigns`
|
|
- play actions from `Play`
|
|
- admin actions from `Admin`
|
|
- session/bootstrap actions from `Session`
|
|
|
|
Illustrative pattern only:
|
|
|
|
```razor
|
|
<AppHeader
|
|
User="State.User"
|
|
CampaignName="@State.SelectedCampaignName"
|
|
LogoutRequested="Session.LogoutAsync" />
|
|
```
|
|
|
|
Another example:
|
|
|
|
```razor
|
|
<CampaignManagementPanel
|
|
Campaigns="State.Campaigns"
|
|
SelectedCampaignId="State.SelectedCampaignId"
|
|
CampaignSelectionChanged="Campaigns.OnCampaignSelectionChangedAsync"
|
|
DeleteCampaignRequested="Campaigns.DeleteSelectedCampaignAsync" />
|
|
```
|
|
|
|
Important guardrail:
|
|
|
|
- avoid replacing one huge component surface with many deeply chained bindings that are hard to read
|
|
- prefer a small number of clearly named composed properties on `Workspace`
|
|
|
|
For example:
|
|
|
|
- `State`
|
|
- `Session`
|
|
- `Campaigns`
|
|
- `Play`
|
|
- `Admin`
|
|
- `Live`
|
|
|
|
## 8. Recommended Frontend File Layout
|
|
|
|
One reasonable target layout:
|
|
|
|
```text
|
|
RpgRoller/Components/Pages/
|
|
Workspace.razor
|
|
Workspace.razor.cs
|
|
WorkspaceState.cs
|
|
WorkspaceSessionCoordinator.cs
|
|
WorkspaceCampaignCoordinator.cs
|
|
WorkspacePlayCoordinator.cs
|
|
WorkspaceAdminCoordinator.cs
|
|
WorkspaceLiveStateController.cs
|
|
WorkspaceFeedbackService.cs
|
|
WorkspaceToast.cs
|
|
```
|
|
|
|
If some files become too small, merge only where the change patterns are clearly the same. Do not merge simply to reduce file count.
|
|
|
|
## 9. Implementation Order
|
|
|
|
The implementation phase should proceed in small safe steps.
|
|
|
|
### Phase 1: Backend enabling extractions
|
|
|
|
1. Extract pure helper classes from `GameService` first.
|
|
2. Keep `GameService` behavior identical while moving algorithmic helpers out.
|
|
3. Add or update tests around the extracted helpers if coverage or confidence drops.
|
|
|
|
Why first:
|
|
|
|
- lowest integration risk
|
|
- creates stable dependencies for later service extraction
|
|
|
|
### Phase 2: Introduce GameStateStore and GamePersistenceService
|
|
|
|
1. Move runtime collections, gate, and persistence primitives behind the shared state owner.
|
|
2. Move database load/save logic into persistence service.
|
|
3. Keep `GameService` still acting as the main orchestrator while the shared dependencies settle.
|
|
|
|
Why second:
|
|
|
|
- makes later service extraction mechanical instead of risky
|
|
|
|
### Phase 3: Extract backend domain services
|
|
|
|
Suggested order:
|
|
|
|
1. auth
|
|
2. campaigns
|
|
3. characters
|
|
4. skills
|
|
5. rolls/log
|
|
6. admin/users
|
|
|
|
This order keeps the highest-risk workflow area, rolls/logs, until the shared helpers and state seams are already stable.
|
|
|
|
### Phase 4: Thin GameService façade
|
|
|
|
1. Replace remaining logic in `GameService` with delegation only.
|
|
2. Confirm constructor wiring stays readable.
|
|
3. Re-run tests and local CI.
|
|
|
|
### Phase 5: Frontend state extraction
|
|
|
|
1. Introduce `WorkspaceState`.
|
|
2. Move raw state and pure computed projections first.
|
|
3. Keep method behavior temporarily in `Workspace` until state references are stabilized.
|
|
|
|
Why first:
|
|
|
|
- it shrinks the cognitive load before moving orchestration
|
|
|
|
### Phase 6: Frontend coordinator extraction
|
|
|
|
Suggested order:
|
|
|
|
1. feedback/toasts
|
|
2. session/bootstrap
|
|
3. campaign management
|
|
4. play/log
|
|
5. admin
|
|
6. live-state controller
|
|
|
|
This order reduces the chance that live event logic is extracted before its dependent refresh flows are already stable.
|
|
|
|
### Phase 7: Razor binding cleanup
|
|
|
|
1. Update bindings to the new composed surface.
|
|
2. Keep markup structure stable.
|
|
3. Avoid stylistic churn unrelated to composition.
|
|
|
|
### Phase 8: Documentation and verification
|
|
|
|
1. Update `README.md` code-organization notes if file layout changed materially.
|
|
2. Run local CI.
|
|
3. Run Playwright smoke flow.
|
|
4. Re-check coverage expectations.
|
|
|
|
## 10. Testing and Validation Expectations
|
|
|
|
During implementation, after each meaningful iteration:
|
|
|
|
- run `pwsh ./scripts/ci-local.ps1`
|
|
- verify test coverage remains acceptable relative to the repo rules
|
|
- add tests where extraction creates untested seams
|
|
- after frontend-affecting iterations, run the Playwright smoke flow
|
|
|
|
Specific test focus areas:
|
|
|
|
### Backend
|
|
|
|
- auth/session regression
|
|
- campaign visibility rules
|
|
- character ownership transfer
|
|
- skill-group assignment and validation
|
|
- D6 roll behavior
|
|
- Rolemaster roll behavior
|
|
- custom roll behavior
|
|
- log paging and detail visibility
|
|
- persistence reload behavior
|
|
- admin role mutation and user deletion side effects
|
|
|
|
### Frontend
|
|
|
|
- workspace bootstrap still restores screen/campaign/panel/visibility preferences
|
|
- admin screen still enforces role access
|
|
- play screen still refreshes log/roster/sheet correctly
|
|
- newly recorded roll still appears and auto-expands as before
|
|
- live connection state still updates announcements and fallback state
|
|
|
|
## 11. Risks and Guardrails
|
|
|
|
### Risk: Hidden behavior drift during backend extraction
|
|
|
|
Guardrail:
|
|
|
|
- keep `IGameService` contract fixed
|
|
- extract pure helpers before orchestration changes
|
|
- preserve method-level tests and add new coverage around moved logic
|
|
|
|
### Risk: Locking and persistence bugs after splitting GameService
|
|
|
|
Guardrail:
|
|
|
|
- centralize mutable runtime state in one store
|
|
- centralize persistence writes in one service
|
|
- do not let each domain service invent its own lock pattern
|
|
|
|
### Risk: Workspace composition creates binding sprawl
|
|
|
|
Guardrail:
|
|
|
|
- expose a small composed surface from `Workspace`
|
|
- keep naming direct and role-based
|
|
- avoid deep object chains in Razor
|
|
|
|
### Risk: Live-state refresh logic breaks after movement
|
|
|
|
Guardrail:
|
|
|
|
- move refresh logic together with live event handling ownership
|
|
- keep campaign roster, selected sheet, and log refresh paths explicit
|
|
- verify with Playwright after frontend extraction
|
|
|
|
### Risk: Over-fragmentation
|
|
|
|
Guardrail:
|
|
|
|
- many small helper files are good for pure logic
|
|
- domain orchestration services should remain coarse enough to own meaningful workflows
|
|
- do not create tiny services that only forward one call with no real ownership
|
|
|
|
## 12. Non-Goals
|
|
|
|
The implementation phase described by this blueprint should not, by default:
|
|
|
|
- redesign API contracts
|
|
- redesign the workspace UX
|
|
- replace Blazor patterns with a different UI architecture
|
|
- change persistence strategy away from the current runtime-state-plus-SQLite model
|
|
- introduce unrelated feature work
|
|
|
|
## 13. Definition of Done For The Refactor
|
|
|
|
The refactor is complete when all of the following are true:
|
|
|
|
- `GameService` is a thin coordinator, not a monolith
|
|
- backend workflows are owned by composed sealed classes with clear responsibility boundaries
|
|
- algorithmic and mapping helpers live in small focused files
|
|
- `Workspace` is a thin component coordinator, not a monolithic code-behind
|
|
- workspace state and workflow orchestration are separated cleanly
|
|
- `Workspace.razor` binds against a readable composed surface
|
|
- tests still pass
|
|
- local CI still passes
|
|
- frontend smoke verification still passes
|
|
- documentation matches the resulting structure
|
|
|
|
## 14. Implementation Reminder
|
|
|
|
During the actual edit phase:
|
|
|
|
- keep changes small
|
|
- validate often
|
|
- do not revert unrelated user changes
|
|
- preserve behavior first
|
|
- optimize for reducing future churn, not merely reducing line count
|