Add payload refactor plan
This commit is contained in:
285
TASKS.md
Normal file
285
TASKS.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Payload And Serialization Refactor Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Reduce the risk of future Blazor Server circuit disconnects by shrinking payloads, removing unnecessary serialization hops, and making live refreshes more granular.
|
||||
|
||||
The current payload split fixed the immediate failure, but it did not remove the most expensive transport pattern:
|
||||
|
||||
- browser `fetch`
|
||||
- JSON parse in JavaScript
|
||||
- JS interop result marshalled over the Blazor circuit
|
||||
- JSON deserialization in .NET
|
||||
|
||||
That path means every read model still competes with the SignalR hub message ceiling and pays serialization cost twice.
|
||||
|
||||
## Current Baseline
|
||||
|
||||
- `GET /api/campaigns`: about `222 B`
|
||||
- `GET /api/campaigns/{id}`: about `1.0 KB`
|
||||
- `GET /api/characters/{id}/sheet`: about `11.3 KB`
|
||||
- `GET /api/campaigns/{id}/log`: about `13.8 KB`
|
||||
- Workspace refresh currently reloads roster, selected character sheet, and log together when the state SSE reports a version change.
|
||||
- The API client still uses JS interop for all reads and writes through `rpgRollerApi.request`.
|
||||
|
||||
## Target Outcomes
|
||||
|
||||
- Keep normal interactive responses well below the default Blazor circuit limit without depending on hub-size increases.
|
||||
- Eliminate double JSON handling for the workspace read path.
|
||||
- Avoid retransmitting unchanged roster, sheet, and log data after every state change.
|
||||
- Establish payload and allocation guardrails so regressions are detected in tests.
|
||||
|
||||
## Recommended Delivery Order
|
||||
|
||||
1. Remove JS interop from workspace API reads.
|
||||
2. Split live refresh into change-specific reloads.
|
||||
3. Make campaign log loading incremental instead of retransmitting the latest 100 entries.
|
||||
4. Trim DTO shape and serialization overhead.
|
||||
5. Add measurement, tests, and payload budgets.
|
||||
|
||||
## Phase 1: Remove The JS Interop API Bottleneck
|
||||
|
||||
### Goal
|
||||
|
||||
Move workspace data reads off the `fetch -> JS -> SignalR -> .NET` path.
|
||||
|
||||
### Recommendation
|
||||
|
||||
Introduce a server-side workspace query facade and call it directly from Blazor components instead of routing workspace reads through `RpgRollerApiClient`.
|
||||
|
||||
### Why This First
|
||||
|
||||
- Highest impact on serialization overhead.
|
||||
- Removes the hub-size ceiling from normal workspace query results.
|
||||
- Simplifies error handling and reduces duplicate parsing logic.
|
||||
|
||||
### Implementation Tasks
|
||||
|
||||
- Add a scoped server-side query service for the authenticated workspace.
|
||||
- Resolve the session token from the current `HttpContext` or a dedicated session abstraction.
|
||||
- Move these read flows from `RpgRollerApiClient` to the query service:
|
||||
- `GetMe`
|
||||
- `GetCampaigns`
|
||||
- `GetCharacterCampaignOptions`
|
||||
- `GetCampaign`
|
||||
- `GetCharacterSheet`
|
||||
- `GetCampaignLog`
|
||||
- Keep browser JS interop only for browser-only concerns:
|
||||
- session storage
|
||||
- SSE wiring
|
||||
- DOM scrolling helpers
|
||||
- Keep HTTP API endpoints for external callers and integration tests.
|
||||
- Leave mutation endpoints in place initially, then decide whether mutations should also move server-side or stay as HTTP calls.
|
||||
|
||||
### File Areas
|
||||
|
||||
- `RpgRoller/Components/RpgRollerApiClient.cs`
|
||||
- `RpgRoller/Components/Pages/Workspace.razor.cs`
|
||||
- `RpgRoller/Api/SessionTokenHttpContextExtensions.cs`
|
||||
- new workspace query service under `RpgRoller/Components` or `RpgRoller/Services`
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Workspace reads no longer call `rpgRollerApi.request`.
|
||||
- Opening play and management screens does not depend on JS interop payload size.
|
||||
- Existing API integration tests still pass.
|
||||
|
||||
## Phase 2: Replace Full Scope Refreshes With Targeted Refreshes
|
||||
|
||||
### Goal
|
||||
|
||||
Stop reloading roster, selected sheet, and log together for every state change.
|
||||
|
||||
### Recommendation
|
||||
|
||||
Replace the single campaign version event with typed change notifications or multiple independent versions.
|
||||
|
||||
### Options
|
||||
|
||||
- Preferred: emit typed SSE events such as `roster-changed`, `character-sheet-changed`, and `log-appended`.
|
||||
- Acceptable: keep one event stream but include separate version counters for roster, character state, and log state.
|
||||
|
||||
### Implementation Tasks
|
||||
|
||||
- Extend the server-side state event model to expose change categories.
|
||||
- Update mutation paths in `GameService` to mark the relevant change areas.
|
||||
- Update `Workspace.razor.cs` so the handler refreshes only the affected slice:
|
||||
- roster changes reload `CampaignRoster`
|
||||
- skill and group changes reload `CharacterSheet`
|
||||
- roll events append or refresh only the log
|
||||
- avoid reloading the selected character sheet when another character changes
|
||||
- avoid reloading the log when only roster metadata changes
|
||||
|
||||
### File Areas
|
||||
|
||||
- `RpgRoller/Api/StateEventEndpoints.cs`
|
||||
- `RpgRoller/Services/GameService.cs`
|
||||
- `RpgRoller/Components/Pages/Workspace.razor.cs`
|
||||
- `RpgRoller/wwwroot/js/rpgroller-api.js`
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- A new roll does not trigger a roster reload.
|
||||
- Renaming a character does not trigger a log reload unless log labels depend on that mutation.
|
||||
- State refresh traffic is materially lower in browser and server traces.
|
||||
|
||||
## Phase 3: Make Campaign Log Loading Incremental
|
||||
|
||||
### Goal
|
||||
|
||||
Stop retransmitting the same log entries after every roll.
|
||||
|
||||
### Recommendation
|
||||
|
||||
Add incremental log APIs and append on the client.
|
||||
|
||||
### Implementation Tasks
|
||||
|
||||
- Add query parameters such as:
|
||||
- `afterRollId`
|
||||
- `sinceTimestamp`
|
||||
- `limit`
|
||||
- retain an initial bounded load for first render
|
||||
- add an incremental mode for live updates
|
||||
- keep server ordering stable and deterministic
|
||||
- update the workspace to append new entries instead of replacing the whole log
|
||||
- trim old entries client-side to a fixed window
|
||||
- preserve the visibility rules for GM, owner, and observers
|
||||
|
||||
### Contract Changes
|
||||
|
||||
- Introduce a dedicated log page result:
|
||||
- entries
|
||||
- cursor or last seen roll id
|
||||
- optional `hasMore`
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- A new roll causes only the new log entry or entries to cross the wire.
|
||||
- Reconnect can rebuild log state without downloading unnecessary history.
|
||||
- Existing visibility behavior remains unchanged.
|
||||
|
||||
## Phase 4: Split Log Summary From Log Detail
|
||||
|
||||
### Goal
|
||||
|
||||
Reduce the size of the hottest payload even further.
|
||||
|
||||
### Recommendation
|
||||
|
||||
Do not send full dice arrays and long breakdown strings for every log row by default.
|
||||
|
||||
### Implementation Tasks
|
||||
|
||||
- Introduce `CampaignLogListEntry` for the list view.
|
||||
- Keep only fields needed to render the collapsed row:
|
||||
- roll id
|
||||
- roller label
|
||||
- skill label
|
||||
- character label
|
||||
- result
|
||||
- visibility
|
||||
- timestamp
|
||||
- compact summary text
|
||||
- add `GET /api/rolls/{rollId}` or equivalent detail lookup for expanded inspection
|
||||
- update the log UI to lazy-load detail when a row is expanded
|
||||
|
||||
### Expected Benefit
|
||||
|
||||
This should cut the log list payload materially because `Dice` and `Breakdown` are currently repeated for every row and are the least compressible fields in the list.
|
||||
|
||||
## Phase 5: Trim DTO Shape To Match The View
|
||||
|
||||
### Goal
|
||||
|
||||
Remove repeated fields that are not needed by the consuming UI.
|
||||
|
||||
### Recommendations
|
||||
|
||||
- Replace `CampaignSummary.Gm` and `CampaignRoster.Gm` full `UserSummary` usage with a slimmer campaign GM DTO if the UI only needs `Id` and `DisplayName`.
|
||||
- Remove parent-scope identifiers from child records where the endpoint already provides that scope.
|
||||
- candidate examples:
|
||||
- `CampaignLogEntry.CampaignId`
|
||||
- `SkillSummary.CharacterId` inside `CharacterSheet`
|
||||
- `SkillGroupSummary.CharacterId` inside `CharacterSheet`
|
||||
- review whether owner ids are needed in all list views or whether some can be replaced with display labels and booleans
|
||||
|
||||
### Guardrail
|
||||
|
||||
Do not over-optimize DTOs until the consuming components have been made explicit. Only remove a field after all consumers are verified.
|
||||
|
||||
## Phase 6: Reduce Serializer CPU And Allocation Overhead
|
||||
|
||||
### Goal
|
||||
|
||||
Lower per-request CPU and allocation cost after the major transport fixes are in place.
|
||||
|
||||
### Recommendations
|
||||
|
||||
- Introduce source-generated `System.Text.Json` contexts for the hot contracts.
|
||||
- Reuse serializer options consistently rather than relying on repeated default metadata discovery.
|
||||
- Review whether any list contracts can be exposed as arrays end-to-end to reduce intermediate allocations.
|
||||
- If HTTP remains in the path for some calls, ensure response compression is enabled for normal API responses to reduce browser transfer cost.
|
||||
|
||||
### Note
|
||||
|
||||
This phase is worthwhile, but it should follow the transport refactor. Serializer tuning alone will not solve circuit-size problems.
|
||||
|
||||
## Phase 7: Add Payload Budgets And Regression Tests
|
||||
|
||||
### Goal
|
||||
|
||||
Prevent a future regression from silently reintroducing oversized read models.
|
||||
|
||||
### Implementation Tasks
|
||||
|
||||
- Add integration tests that serialize representative contracts and assert upper bounds.
|
||||
- Add service or API tests for log pagination and incremental fetch semantics.
|
||||
- Add workspace tests for targeted refresh behavior.
|
||||
- Add a small benchmark or diagnostic test for hot payload serialization if practical.
|
||||
- Document soft payload budgets for any remaining JS interop responses.
|
||||
|
||||
### Suggested Budgets
|
||||
|
||||
- Any remaining JS interop response: prefer under `16 KB`
|
||||
- initial character sheet response: target under `12 KB`
|
||||
- initial log list response: target under `8 KB` after summary/detail split
|
||||
- incremental live update response: target under `2 KB`
|
||||
|
||||
## Delivery Notes
|
||||
|
||||
- Do not raise the Blazor hub message limit again as the primary fix.
|
||||
- Keep the existing HTTP API stable where possible so tests and external tooling do not break.
|
||||
- Prefer introducing new, view-specific contracts instead of reusing broad aggregate models.
|
||||
- Measure payload size with representative admin and non-admin datasets after each phase.
|
||||
|
||||
## Proposed Milestones
|
||||
|
||||
### Milestone A
|
||||
|
||||
Move workspace reads off JS interop and keep behavior unchanged.
|
||||
|
||||
### Milestone B
|
||||
|
||||
Introduce targeted SSE-driven refreshes without yet changing log contract shape.
|
||||
|
||||
### Milestone C
|
||||
|
||||
Add incremental log loading and client append behavior.
|
||||
|
||||
### Milestone D
|
||||
|
||||
Split log summary from log detail and trim DTOs.
|
||||
|
||||
### Milestone E
|
||||
|
||||
Add serializer optimizations, payload budget tests, and final documentation updates.
|
||||
|
||||
## Definition Of Done
|
||||
|
||||
- Workspace read models are no longer limited by Blazor JS interop payload size.
|
||||
- Live updates no longer reload unrelated slices.
|
||||
- The campaign log is loaded incrementally.
|
||||
- The hottest contracts are explicitly sized for their views.
|
||||
- Payload budgets are enforced by tests.
|
||||
- The default Blazor hub receive limit remains unchanged.
|
||||
Reference in New Issue
Block a user