ZenWebsocket Usage Rules
View SourceCore Principles
- Start Simple: Use direct connection for development, add supervision for production
- Only 5 Functions: The entire public API is just 5 functions
- Real API Testing: Always test against real endpoints, never mock WebSocket behavior
Quick Start Pattern
# Simplest possible usage - connect and send
{:ok, client} = ZenWebsocket.Client.connect("wss://test.deribit.com/ws/api/v2")
ZenWebsocket.Client.send_message(client, Jason.encode!(%{method: "public/test"}))The 5 Essential Functions
# 1. Connect to WebSocket
{:ok, client} = ZenWebsocket.Client.connect(url, opts)
# 2. Send messages
:ok = ZenWebsocket.Client.send_message(client, message)
# 3. Subscribe to channels
{:ok, subscription_id} = ZenWebsocket.Client.subscribe(client, channels)
# 4. Check connection state
state = ZenWebsocket.Client.get_state(client) # :connected, :connecting, :disconnected
# 5. Close connection
:ok = ZenWebsocket.Client.close(client)Common Patterns
Pattern 1: Development/Testing (No Supervision)
# Direct connection - crashes won't restart
{:ok, client} = ZenWebsocket.Client.connect(url)
# Use the client...
ZenWebsocket.Client.close(client)Pattern 2: Production with Dynamic Connections
# Add to your supervision tree
children = [
ZenWebsocket.ClientSupervisor,
# ... other children
]
# Start connections dynamically
{:ok, client} = ZenWebsocket.ClientSupervisor.start_client(url, opts)Pattern 3: Production with Fixed Connections
# Add specific clients to supervision tree
children = [
{ZenWebsocket.Client, [
url: "wss://api.example.com/ws",
id: :main_websocket,
heartbeat_config: %{type: :ping, interval: 30_000}
]}
]Configuration Options
opts = [
# Connection
timeout: 5000, # Connection timeout in ms
headers: [], # Additional headers
# Reconnection
retry_count: 3, # Max reconnection attempts
retry_delay: 1000, # Initial retry delay (exponential backoff)
reconnect_on_error: true, # Auto-reconnect on errors
# Heartbeat
heartbeat_config: %{
type: :ping, # :ping, :pong, :deribit, :custom
interval: 30_000, # Heartbeat interval in ms
message: nil # Custom heartbeat message (for :custom type)
}
]Platform-Specific Rules
Deribit Integration
# Use the Deribit adapter for complete integration
{:ok, adapter} = ZenWebsocket.Examples.DeribitAdapter.start_link([
url: "wss://test.deribit.com/ws/api/v2",
client_id: System.get_env("DERIBIT_CLIENT_ID"),
client_secret: System.get_env("DERIBIT_CLIENT_SECRET")
])
# The adapter handles:
# - Authentication flow
# - Heartbeat/test_request
# - Subscription management
# - Cancel-on-disconnectError Handling
# All functions return tagged tuples
case ZenWebsocket.Client.connect(url) do
{:ok, client} ->
# Success path
client
{:error, reason} ->
# Errors are passed raw from Gun/WebSocket
# Common errors: :timeout, :connection_refused, :protocol_error
Logger.error("Connection failed: #{inspect(reason)}")
endTesting Rules
# NEVER mock WebSocket connections
# Use real test endpoints
@tag :integration
test "real WebSocket behavior" do
{:ok, client} = ZenWebsocket.Client.connect("wss://test.deribit.com/ws/api/v2")
# Test against real API...
end
# For controlled testing, use MockWebSockServer
{:ok, server} = ZenWebsocket.MockWebSockServer.start(port: 8080)
{:ok, client} = ZenWebsocket.Client.connect("ws://localhost:8080")DO NOT
- Don't create wrapper modules - Use the 5 functions directly
- Don't mock WebSocket behavior - Test against real endpoints
- Don't add custom reconnection - Use built-in retry options
- Don't transform errors - Handle raw Gun/WebSocket errors
- Don't avoid GenServers - Client uses GenServer appropriately for state
Architecture Notes
- Gun Transport: Built on Gun for HTTP/2 and WebSocket
- GenServer State: Client maintains connection state in GenServer
- ETS Registry: Fast connection lookups via ETS
- Exponential Backoff: Smart reconnection with backoff
- Real API Testing: 93 tests, all using real APIs
Monitoring
# Telemetry events are emitted for monitoring
:telemetry.attach(
"websocket-logger",
[:zen_websocket, :client, :message_received],
fn _event, measurements, metadata, _config ->
Logger.info("Message received: #{measurements.size} bytes")
end,
nil
)Module Limits
Each module follows strict simplicity rules:
- Maximum 5 public functions per module
- Maximum 15 lines per function
- Maximum 2 levels of function calls
- Real API testing only (no mocks)
Getting Help
- Examples: See
lib/zen_websocket/examples/directory - Tests: Review
test/for usage patterns - Deribit: See
DeribitAdapterfor complete platform integration
Common Mistakes to Avoid
- Creating abstractions too early - Start with direct usage
- Mocking in tests - Always use real WebSocket endpoints
- Custom error types - Handle raw Gun/WebSocket errors
- Complex supervision - Use provided patterns (1, 2, or 3)
- Ignoring heartbeats - Configure heartbeat for production
Migration from Other Libraries
From Websockex
# Old (Websockex with callbacks)
defmodule MyClient do
use WebSockex
def handle_frame({:text, msg}, state), do: {:ok, state}
end
# New (ZenWebsocket - simpler)
{:ok, client} = ZenWebsocket.Client.connect(url)
# Messages handled via message_handler configurationFrom Gun directly
# You're already using the right approach!
# ZenWebsocket is a thin, focused layer over GunPerformance Characteristics
- Connection Time: < 100ms typical
- Message Latency: < 1ms processing
- Memory: ~50KB per connection
- Reconnection: Exponential backoff (1s, 2s, 4s...)
- Concurrency: Thousands of simultaneous connections
Required Environment Variables
For platform integrations:
# Deribit
export DERIBIT_CLIENT_ID="your_client_id"
export DERIBIT_CLIENT_SECRET="your_client_secret"
Best Practices Summary
- Start with Pattern 1 (direct) for development
- Move to Pattern 2 or 3 for production
- Configure heartbeats for long-lived connections
- Test against real endpoints
- Handle raw errors with pattern matching
- Use telemetry for monitoring
- Keep it simple - just 5 functions!