Skip to content

Commit f5883b9

Browse files
committed
refactor!: enforce Redux-only state management, remove manual broadcasts
BREAKING CHANGE: Manual broadcast helpers removed, Redux required for LiveView This commit enforces a cleaner architecture where ALL state updates go through Redux dispatch actions, eliminating confusion between manual broadcasts and automatic Redux notifications. Changes: - Remove broadcast_state_change/1-2 from :process macro - Remove session_topic/0 helper from :process macro - Update Phoenix.SessionProcess.LiveView to work exclusively with Redux PubSub - Change default state_key from :get_state to :get_redux_state - Update message format from {:session_state_change, state} to {:redux_state_change, %{state, action, timestamp}} - Update PubSub topics from "session:*:state" to "session:*:redux" Breaking Changes: - Manual broadcast_state_change/1-2 helper removed - Session processes must use Redux for LiveView integration - LiveView message format changed - PubSub topic format changed Migration Required: 1. Convert session processes to use Redux state management 2. Initialize Redux with PubSub configuration in init/1 3. Update all state mutations to use Redux.dispatch 4. Update LiveView handle_info to match {:redux_state_change, %{state: state}} 5. Update PubSub topics from "session:*:state" to "session:*:redux" Example: ```elixir # Session Process def init(_args) do redux = Redux.init_state( %{count: 0}, pubsub: MyApp.PubSub, pubsub_topic: "session:#{get_session_id()}:redux" ) {:ok, %{redux: redux}} end def handle_call(:increment, _from, state) do new_redux = Redux.dispatch(state.redux, :increment, &reducer/2) {:reply, :ok, %{state | redux: new_redux}} end # LiveView def handle_info({:redux_state_change, %{state: state}}, socket) do {:noreply, assign(socket, count: state.count)} end ``` Documentation: - Update CLAUDE.md with Redux-only architecture section - Update README.md with Redux-based LiveView examples - Rewrite examples/liveview_integration_example.ex for Redux - Add TEST_MIGRATION_NOTES.md for test migration guide Tests: - Remove test/phoenix/session_process/pubsub_broadcast_test.exs (12 tests) - Update test/phoenix/session_process/live_view_test.exs (21 tests) - Update test/phoenix/session_process/live_view_integration_test.exs (16 tests) - All 164 tests passing Rationale: This resolves architectural confusion where users had three overlapping notification mechanisms (Redux subscriptions, Redux PubSub, manual broadcasts). Now there is ONE way: Redux.dispatch automatically handles all notifications.
1 parent 8894a1e commit f5883b9

File tree

9 files changed

+598
-638
lines changed

9 files changed

+598
-638
lines changed

CLAUDE.md

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ The library is organized into several logical groups:
9191
1. **Phoenix.SessionProcess** (lib/phoenix/session_process.ex:1)
9292
- Main module providing the public API
9393
- Delegates to ProcessSupervisor for actual process management
94-
- Provides the `:process` macro which includes PubSub broadcast helpers
95-
- The macro injects: `broadcast_state_change/1-2` and `session_topic/0` functions
94+
- Provides the `:process` macro for basic GenServer functionality
95+
- The macro injects: `get_session_id/0` helper function
96+
- **State updates ONLY via Redux.dispatch** - no manual broadcast helpers
9697
- Note: `:process_link` is deprecated - use `:process` instead
9798
- Key functions: `start/1-3`, `call/2-3`, `cast/2`, `terminate/1`, `started?/1`, `list_session/0`
9899

@@ -118,12 +119,12 @@ The library is organized into several logical groups:
118119
- Runs cleanup tasks periodically
119120

120121
6. **Phoenix.SessionProcess.LiveView** (lib/phoenix/session_process/live_view.ex:1)
121-
- PubSub-based LiveView integration for session processes
122-
- **Recommended approach** for connecting LiveViews to session state
123-
- Key functions: `mount_session/3-4`, `unmount_session/1`, `dispatch/2`, `subscribe/2`
124-
- Automatically handles subscription/unsubscription and initial state loading
122+
- Redux-based LiveView integration for session processes
123+
- **Requires Redux** for state management
124+
- Key functions: `mount_session/3-4` (default state_key: :get_redux_state), `unmount_session/1`, `dispatch/2`, `subscribe/2`
125+
- Subscribes to Redux PubSub topic: `"session:#{session_id}:redux"`
126+
- Receives state changes as `{:redux_state_change, %{state: state, action: action, timestamp: timestamp}}`
125127
- Works across distributed nodes via Phoenix.PubSub
126-
- Simpler and more maintainable than manual process monitoring
127128

128129
7. **Phoenix.SessionProcess.Redux** (lib/phoenix/session_process/redux.ex:1)
129130
- Optional Redux-style state management with actions, reducers, subscriptions, and selectors
@@ -152,12 +153,18 @@ The library is organized into several logical groups:
152153

153154
- Uses Registry for bidirectional lookups (session_id ↔ pid, pid ↔ module)
154155
- DynamicSupervisor for on-demand process creation
155-
- `:process` macro injects GenServer boilerplate and PubSub broadcast helpers
156-
- **PubSub-based LiveView integration** (recommended):
157-
- Session processes broadcast state changes via `broadcast_state_change/1-2`
156+
- `:process` macro injects GenServer boilerplate
157+
- **Redux-only state management**:
158+
- All state updates go through `Redux.dispatch(redux, action, reducer)`
159+
- Redux automatically handles subscriptions and PubSub broadcasts
160+
- No manual `broadcast_state_change` or `session_topic` helpers
161+
- Single, predictable way to update state
162+
- **Redux-based LiveView integration** (required for LiveView):
163+
- Session processes use Redux with PubSub configuration
158164
- LiveViews subscribe using `Phoenix.SessionProcess.LiveView.mount_session/3-4`
165+
- Redux PubSub topic: `"session:#{session_id}:redux"`
166+
- Message format: `{:redux_state_change, %{state: state, action: action, timestamp: timestamp}}`
159167
- Works across distributed nodes
160-
- Cleaner separation of concerns vs manual process monitoring
161168
- Telemetry events for all lifecycle operations (start, stop, call, cast, cleanup, errors)
162169
- Comprehensive error handling with Phoenix.SessionProcess.Error module
163170

@@ -189,29 +196,41 @@ Configuration options:
189196
5. Communicate using call/cast operations
190197
6. For LiveView integration, use `Phoenix.SessionProcess.LiveView` helpers
191198

192-
### LiveView Integration Example
199+
### LiveView Integration Example (Redux-Only)
193200

194201
**Session Process:**
195202
```elixir
196203
defmodule MyApp.SessionProcess do
197204
use Phoenix.SessionProcess, :process
205+
alias Phoenix.SessionProcess.Redux
198206

199207
@impl true
200208
def init(_) do
201-
{:ok, %{count: 0, user: nil}}
209+
redux = Redux.init_state(
210+
%{count: 0, user: nil},
211+
pubsub: MyApp.PubSub,
212+
pubsub_topic: "session:#{get_session_id()}:redux"
213+
)
214+
{:ok, %{redux: redux}}
202215
end
203216

204217
@impl true
205218
def handle_cast({:increment}, state) do
206-
new_state = %{state | count: state.count + 1}
207-
# Broadcast to LiveViews
208-
broadcast_state_change(new_state)
209-
{:noreply, new_state}
219+
# Dispatch action - Redux handles all notifications automatically
220+
new_redux = Redux.dispatch(state.redux, {:increment}, &reducer/2)
221+
{:noreply, %{state | redux: new_redux}}
210222
end
211223

212224
@impl true
213-
def handle_call(:get_state, _from, state) do
214-
{:reply, {:ok, state}, state}
225+
def handle_call(:get_redux_state, _from, state) do
226+
{:reply, {:ok, state.redux}, state}
227+
end
228+
229+
defp reducer(state, action) do
230+
case action do
231+
{:increment} -> %{state | count: state.count + 1}
232+
_ -> state
233+
end
215234
end
216235
end
217236
```
@@ -223,7 +242,7 @@ defmodule MyAppWeb.DashboardLive do
223242
alias Phoenix.SessionProcess.LiveView, as: SessionLV
224243

225244
def mount(_params, %{"session_id" => session_id}, socket) do
226-
# Subscribe and get initial state
245+
# Subscribe and get initial Redux state
227246
case SessionLV.mount_session(socket, session_id, MyApp.PubSub) do
228247
{:ok, socket, state} ->
229248
{:ok, assign(socket, state: state)}
@@ -232,8 +251,8 @@ defmodule MyAppWeb.DashboardLive do
232251
end
233252
end
234253

235-
# Receive state updates
236-
def handle_info({:session_state_change, new_state}, socket) do
254+
# Receive Redux state updates
255+
def handle_info({:redux_state_change, %{state: new_state}}, socket) do
237256
{:noreply, assign(socket, state: new_state)}
238257
end
239258

@@ -244,18 +263,40 @@ defmodule MyAppWeb.DashboardLive do
244263
end
245264
```
246265

247-
## State Management Options
266+
## State Management Architecture
248267

249-
The library provides two state management approaches:
268+
Phoenix.SessionProcess enforces Redux-based state management for LiveView integration.
250269

251-
1. **Standard GenServer State** (Recommended) - Full control with standard GenServer callbacks
252-
- Use `handle_call`, `handle_cast`, and `handle_info` to manage state
253-
- Simple, idiomatic Elixir - this is what you should use for 95% of cases
270+
### Core Principle
271+
All state updates go through Redux dispatch actions.
272+
273+
### State Update Flow
274+
1. Action dispatched: `Redux.dispatch(redux, action, reducer)`
275+
2. Reducer updates state
276+
3. Redux notifies subscriptions (automatic)
277+
4. Redux broadcasts via PubSub (automatic, if configured)
278+
5. LiveViews receive state updates
254279

255-
2. **Phoenix.SessionProcess.Redux** (Optional, Advanced) - Redux pattern for complex state machines
256-
- Actions, reducers, middleware, time-travel debugging
257-
- Only use if you need audit trails or complex state machine logic
258-
- Adds complexity - most applications don't need this
280+
### No Manual Broadcasting
281+
The library does NOT provide manual broadcast helpers. All notifications
282+
happen automatically through Redux dispatch.
283+
284+
### When to Use Redux
285+
- **Required** if using LiveView integration
286+
- **Optional** for session-only processes without LiveView
287+
288+
### State Management Options
289+
290+
1. **Redux-based State** (Required for LiveView) - Predictable state updates with actions/reducers
291+
- Use `Redux.dispatch(redux, action, reducer)` to update state
292+
- Automatic PubSub broadcasting to LiveViews
293+
- Time-travel debugging, middleware, action history available
294+
- Use when you need LiveView integration or audit trails
295+
296+
2. **Standard GenServer State** (Session-only) - Full control with standard GenServer callbacks
297+
- Use `handle_call`, `handle_cast`, and `handle_info` to manage state
298+
- Simple, idiomatic Elixir
299+
- Use when no LiveView integration is needed
259300

260301
## Telemetry and Error Handling
261302

README.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,52 +141,64 @@ defmodule MyApp.SessionProcess do
141141
end
142142
```
143143

144-
### With LiveView Integration
144+
### With LiveView Integration (Redux-Based)
145145

146-
Phoenix.SessionProcess provides PubSub-based LiveView integration for real-time state synchronization.
146+
Phoenix.SessionProcess provides Redux-based LiveView integration for real-time state synchronization. All state updates go through Redux dispatch actions, which automatically handle PubSub broadcasting.
147147

148-
#### Session Process with Broadcasting
148+
#### Session Process with Redux
149149

150150
```elixir
151151
defmodule MyApp.SessionProcess do
152152
use Phoenix.SessionProcess, :process
153+
alias Phoenix.SessionProcess.Redux
153154

154155
@impl true
155156
def init(_init_arg) do
156-
{:ok, %{user: nil, count: 0}}
157+
redux = Redux.init_state(
158+
%{user: nil, count: 0},
159+
pubsub: MyApp.PubSub,
160+
pubsub_topic: "session:#{get_session_id()}:redux"
161+
)
162+
{:ok, %{redux: redux}}
157163
end
158164

159165
@impl true
160-
def handle_call(:get_state, _from, state) do
161-
{:reply, {:ok, state}, state}
166+
def handle_call(:get_redux_state, _from, state) do
167+
{:reply, {:ok, state.redux}, state}
162168
end
163169

164170
@impl true
165171
def handle_cast({:set_user, user}, state) do
166-
new_state = %{state | user: user}
167-
# Broadcast state changes to all subscribers
168-
broadcast_state_change(new_state)
169-
{:noreply, new_state}
172+
# Dispatch action - Redux handles all notifications automatically
173+
new_redux = Redux.dispatch(state.redux, {:set_user, user}, &reducer/2)
174+
{:noreply, %{state | redux: new_redux}}
170175
end
171176

172177
@impl true
173178
def handle_cast(:increment, state) do
174-
new_state = %{state | count: state.count + 1}
175-
broadcast_state_change(new_state)
176-
{:noreply, new_state}
179+
new_redux = Redux.dispatch(state.redux, :increment, &reducer/2)
180+
{:noreply, %{state | redux: new_redux}}
181+
end
182+
183+
defp reducer(state, action) do
184+
case action do
185+
{:set_user, user} -> %{state | user: user}
186+
:increment -> %{state | count: state.count + 1}
187+
_ -> state
188+
end
177189
end
178190
end
179191
```
180192

181-
#### LiveView with Session Integration
193+
#### LiveView with Redux Integration
182194

183195
```elixir
184196
defmodule MyAppWeb.DashboardLive do
185197
use Phoenix.LiveView
186198
alias Phoenix.SessionProcess.LiveView, as: SessionLV
187199

188200
def mount(_params, %{"session_id" => session_id}, socket) do
189-
# Subscribe to session state and get initial state
201+
# Subscribe to Redux state and get initial state
190202
case SessionLV.mount_session(socket, session_id, MyApp.PubSub) do
191203
{:ok, socket, state} ->
192204
{:ok, assign(socket, state: state, session_id: session_id)}
@@ -196,8 +208,8 @@ defmodule MyAppWeb.DashboardLive do
196208
end
197209
end
198210

199-
# Automatically receive state updates
200-
def handle_info({:session_state_change, new_state}, socket) do
211+
# Automatically receive Redux state updates
212+
def handle_info({:redux_state_change, %{state: new_state}}, socket) do
201213
{:noreply, assign(socket, state: new_state)}
202214
end
203215

@@ -225,6 +237,8 @@ config :phoenix_session_process,
225237
pubsub: MyApp.PubSub # Required for LiveView integration
226238
```
227239

240+
**Important**: LiveView integration requires Redux for state management. Redux automatically broadcasts state changes via PubSub when actions are dispatched.
241+
228242
## API Reference
229243

230244
### Starting Sessions

0 commit comments

Comments
 (0)