Phoenix. SessionProcess. ProcessBehaviour behaviour
(phoenix_session_process v1.0.1)
Copy Markdown
View Source
Defines the behaviour for session processes in Phoenix.SessionProcess.
Session processes are GenServer processes that manage isolated state for each user session. They integrate with the Redux Store infrastructure for state management.
Required Callbacks
init_state/1- Initialize the session's application state (required)
Optional Callbacks
combined_reducers/0- Define reducer modules for state slicing (optional)- GenServer callbacks (
handle_call/3,handle_cast/2, etc.) - Handle custom messages (optional)
Usage
To create a session process, use the :process macro:
defmodule MyApp.SessionProcess do
use Phoenix.SessionProcess, :process
@impl true
def init_state(_arg) do
%{user_id: nil, cart: [], preferences: %{}}
end
# Optional: Define reducers for state slicing
@impl true
def combined_reducers do
[
MyApp.CounterReducer,
MyApp.UserReducer
]
end
# Optional: Add custom GenServer handlers
@impl true
def handle_call(:custom_action, _from, state) do
{:reply, :ok, state}
end
@impl true
def handle_cast({:custom_event, data}, state) do
# Custom logic
{:noreply, state}
end
endRedux Integration
Session processes automatically include Redux Store infrastructure:
- State Management: All state updates go through
dispatch/4 - Reducers: Use
combined_reducers/0to define reducer modules - Subscriptions: Automatic subscription management with selectors
- History: Optional action history tracking
- Async Actions: Built-in support for async operations
State Structure
The session process maintains state with two layers:
Application State (
state.app_state):- Your custom state from
init_state/1 - Reducer slices from
combined_reducers/0
- Your custom state from
Redux Infrastructure (internal, prefixed with
_redux_):_redux_reducers- Registered reducer modules_redux_subscriptions- Active subscriptions_redux_history- Action history_redux_throttle_state- Throttle/debounce state- Other internal state
Example with Reducers
defmodule MyApp.SessionProcess do
use Phoenix.SessionProcess, :process
@impl true
def init_state(_arg) do
# Initial state before reducer slices are added
%{session_created_at: DateTime.utc_now()}
end
@impl true
def combined_reducers do
[
MyApp.CounterReducer, # Adds state.counter slice
MyApp.UserReducer # Adds state.user slice
]
end
end
# After initialization, state looks like:
# %{
# session_created_at: ~U[2024-01-01 00:00:00Z],
# counter: %{count: 0}, # From CounterReducer.init_state/0
# user: %{current_user: nil} # From UserReducer.init_state/0
# }Reducer Formats
The combined_reducers/0 callback can return three formats:
Module atom - Uses the reducer's
@nameand@action_prefix:[MyApp.UserReducer]{name, Module} tuple - Custom name, action_prefix defaults to stringified name:
[{:cart, MyApp.CartReducer}]{name, Module, action_prefix} tuple - Explicit name and action_prefix:
[{:shipping, MyApp.ShippingReducer, "ship"}]
GenServer Integration
Since :process uses use GenServer, you can implement any GenServer callback:
handle_call/3- Synchronous requestshandle_cast/2- Asynchronous messageshandle_info/2- Process messagesterminate/2- Cleanup on shutdowncode_change/3- Hot code reloading
Accessing Session ID
Within the session process, use get_session_id/0 to retrieve the current session ID:
def handle_call(:get_id, _from, state) do
session_id = get_session_id()
{:reply, session_id, state}
endCommunication
Use the Phoenix.SessionProcess API to communicate with session processes:
# Start session
{:ok, _pid} = Phoenix.SessionProcess.start_session(session_id)
# Dispatch actions
:ok = Phoenix.SessionProcess.dispatch(session_id, "counter.increment")
:ok = Phoenix.SessionProcess.dispatch(session_id, "user.set", %{id: 123})
# Get state
state = Phoenix.SessionProcess.get_state(session_id)
# Custom calls/casts
{:ok, result} = Phoenix.SessionProcess.call(session_id, :custom_action)
:ok = Phoenix.SessionProcess.cast(session_id, {:custom_event, data})
Summary
Callbacks
Define combined reducers for state slicing (optional).
Initialize the session's application state.
Callbacks
@callback combined_reducers() :: [ module() | {atom(), module()} | {atom(), module(), binary() | nil} ]
Define combined reducers for state slicing (optional).
Return a list of reducer modules or tuples. Each reducer manages its own slice of the session state.
Returns
list()- List of reducer modules or tuples
Formats
Module atom - Uses reducer's
@nameand@action_prefix:[MyApp.UserReducer]{name, Module} tuple - Custom name, prefix defaults to stringified name:
[{:cart, MyApp.CartReducer}]{name, Module, prefix} tuple - Custom name and prefix:
[{:shipping, MyApp.ShippingReducer, "ship"}]
Examples
def combined_reducers do
[
MyApp.CounterReducer,
MyApp.UserReducer,
{:cart, MyApp.CartReducer},
{:shipping, MyApp.ShippingReducer, "ship"}
]
endState Slicing
Each reducer receives its slice based on the name:
# Full state
%{
counter: %{count: 0}, # CounterReducer (if @name is :counter)
user: %{current_user: nil}, # UserReducer (if @name is :user)
cart: %{items: []}, # CartReducer ({:cart, CartReducer})
shipping: %{address: nil} # ShippingReducer ({:shipping, ...})
}
Initialize the session's application state.
Called during GenServer initialization. Should return a map with your initial
application state. Reducer slices will be added automatically based on
combined_reducers/0.
Parameters
arg- Argument passed tostart_session/2(defaults to%{})
Returns
map()- Initial application state
Examples
def init_state(_arg) do
%{
user_id: nil,
cart: [],
preferences: %{theme: "light"}
}
end
def init_state(user_id) when is_integer(user_id) do
%{
user_id: user_id,
loaded_at: DateTime.utc_now()
}
end
def init_state(%{user_id: user_id, locale: locale}) do
%{
user_id: user_id,
locale: locale,
cart: []
}
end