7N7D Realtime Architecture Audit
Last Updated: December 2024
Status: Migrating from Socket.IO to GraphQL Subscriptions
Overview
This document audits the realtime communication architecture in the 7N7D trading platform and tracks the migration from Socket.IO to GraphQL subscriptions via graphql-ws.
Current Socket.IO Implementation
Server-Side Entrypoint
File: apps/api/src/websocket/websocket.gateway.ts
The WebSocket gateway uses NestJS @nestjs/platform-socket.io and broadcasts the following event types:
| Event Name | Description | Payload Type |
|---|---|---|
trade_executed | New trade executed by the agent | Trade data with timestamp |
decision_made | Agent made a trading decision | Decision data with reasoning |
performance_update | Performance metrics updated | Metrics object |
position_update | Position changed | Position data |
balance_update | Account balance changed | Balance snapshot |
agent_status | Agent status changed (running/paused/stopped) | Status object |
agent_message | Agent chat message (Q&A, activities) | Message object |
long_term_position_opened | Long-term position opened | Position data |
long_term_position_updated | Long-term position updated | Position data |
long_term_position_closed | Long-term position closed | Position data |
profit_transfer_executed | Profit transferred between accounts | Transfer data |
strategy_allocation_updated | Strategy allocation changed | Allocation snapshot |
error | Error occurred | Error message and context |
heartbeat | Connection keep-alive (every 30s) | Timestamp |
connection_established | Initial connection confirmation | Welcome message |
Client-Side Implementation
Files:
apps/app/contexts/WebSocketContext.tsx- Socket.IO client context providerapps/app/hooks/useWebSocketQuerySync.ts- React Query cache invalidation on eventsapps/app/hooks/useAgentChat.ts- Real-time agent message handling
Features:
- Connection status tracking (
isConnected) - Event subscription system via
subscribe()function - Automatic reconnection with configurable delays
- React Query cache invalidation on relevant events
Disabled GraphQL Subscription Infrastructure
The codebase has GraphQL subscription infrastructure that was disabled pending graphql-ws integration.
Disabled Code Locations
| File | What's Disabled | Reason |
|---|---|---|
apps/api/src/app.module.ts | PubSubModule import | "PubSubModule disabled until GraphQL subscriptions are implemented" |
apps/api/src/trading/trading.module.ts | TradingSubscriptionResolver | "Disabled - requires graphql-ws package" |
apps/api/src/websocket/websocket.gateway.ts | PubSubService import | "re-enable when GraphQL subscriptions are implemented" |
Existing Subscription Resolvers
File: apps/api/src/trading/trading.subscriptions.ts
Already defined (but disabled):
tradeExecuted- Trade execution notificationsbalanceUpdated- Balance change notificationsdecisionMade- Decision notificationsagentStatusChanged- Agent status notifications
PubSub Service
File: apps/api/src/common/pubsub.service.ts
Uses graphql-subscriptions package with in-memory PubSub. Defines event constants:
TRADE_EXECUTEDBALANCE_UPDATEDPOSITION_UPDATEDDECISION_MADEAGENT_STATUS_CHANGEDAGENT_MESSAGE
Migration Decision: GraphQL Subscriptions as Default
Why GraphQL Subscriptions?
- Single schema, single mental model - Queries, mutations, and subscriptions share types
- Type safety - Codegen generates typed subscription hooks
- Consistent auth - Reuses existing GraphQL auth/context logic
- Infra simplicity - One gateway (
/graphql) over HTTP + WS - Better React Query integration - Direct cache updates from subscriptions
Socket.IO Exception Criteria
Keep Socket.IO only if:
- Complex room/broadcast patterns not cleanly mapped to GraphQL
- Binary streaming requirements
- Very chatty low-level events outside GraphQL schema
Current Exceptions: None identified. All 12+ events map cleanly to GraphQL subscriptions.
Scalability Considerations
Current Implementation (In-Memory PubSub)
The graphql-subscriptions package uses an in-memory PubSub which:
- ✅ Works perfectly for single API instance
- ❌ Does NOT work across multiple API instances (horizontal scaling)
Future Scaling (If Needed)
For horizontal scaling, migrate to graphql-redis-subscriptions:
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';
const options = {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
};
export const pubSub = new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
});
This is not required for current single-instance deployment.
Migration Progress
Phase 1: Audit Document ✅
- Document current Socket.IO events
- Document disabled GraphQL subscription code
- Document migration decisions
Phase 2: Enable GraphQL Subscriptions in API ✅
- Install
graphql-wspackage - Configure Apollo Server subscriptions with auth
- Re-enable PubSubModule and TradingSubscriptionResolver
- Add missing subscription resolvers (14 subscriptions total)
- Wire PubSubService alongside WebSocketGateway
Phase 3: Setup GraphQL Subscriptions Client ✅
- Install
graphql-wsin frontend - Create subscription client with reconnection (
graphql-subscription-client.ts) - Create GraphQL Subscription Context (
GraphQLSubscriptionContext.tsx) - Create React Query integration hooks (
useGraphQLSubscriptions.ts)
Phase 4: Migrate Frontend Components ✅
- Migrate useWebSocketQuerySync → useGraphQLQuerySync
- Migrate useAgentChat to use GraphQL subscriptions
- Add GraphQLSubscriptionProvider to app layout
Phase 5: Testing and Validation ✅
- Add subscription integration tests (25 tests passing)
- Manual verification of all features (pending)
Phase 6: Cleanup (In Progress)
- Remove Socket.IO client code (keeping during parallel running period)
- Update documentation
Event Mapping: Socket.IO → GraphQL Subscriptions
| Socket.IO Event | GraphQL Subscription | Status |
|---|---|---|
trade_executed | tradeExecuted | Exists (disabled) |
balance_update | balanceUpdated | Exists (disabled) |
decision_made | decisionMade | Exists (disabled) |
agent_status | agentStatusChanged | Exists (disabled) |
position_update | positionUpdated | To be added |
performance_update | performanceUpdated | To be added |
agent_message | agentMessage | To be added |
profit_transfer_executed | profitTransferExecuted | To be added |
strategy_allocation_updated | strategyAllocationUpdated | To be added |
long_term_position_opened | longTermPositionOpened | To be added |
long_term_position_updated | longTermPositionUpdated | To be added |
long_term_position_closed | longTermPositionClosed | To be added |
error | errorOccurred | To be added |
heartbeat | N/A (graphql-ws has built-in ping/pong) | Not needed |
connection_established | N/A (connection event) | Not needed |