Skip to content

raulpe7eira/stock_stream

Repository files navigation

Stock Stream

A fault-tolerant, real-time stock price streaming system built with Phoenix LiveView and Elixir/OTP.

demo (Preview Knowledge Validation Lab for Arionkoder)

Features

  • Real-time stock price streaming via WebSockets and LiveView
  • Fault-tolerant architecture with supervision trees and circuit breaker pattern
  • Distributed PubSub using Phoenix.PubSub with Erlang distribution
  • Mock API integration with configurable external stock price API
  • Comprehensive testing with Mimic for mocking
  • Responsive UI with Tailwind CSS

Architecture

  • StockPriceStreamer: GenServer that periodically fetches stock prices
  • SubscriptionManager: Manages client subscriptions to stock symbols
  • CircuitBreaker: Protects against external API failures
  • StockSupervisor: Fault-tolerant supervision with restart strategies
  • LiveView Dashboard: Real-time UI for subscribing and viewing stock updates

Setup

You can run this application in two modes:

Feature Mode 1: Containerized Mode 2: Hybrid
Elixir/Erlang Required ❌ No ✅ Yes (1.14+)
Setup Complexity 🟢 Simple 🟡 Moderate
Development Speed 🟡 Rebuild needed 🟢 Fast reload
Production Ready ✅ Yes ❌ Dev only
Resource Usage 🟡 Higher 🟢 Lower
Debugging 🟡 Container logs 🟢 Direct access

Mode 1: Fully Containerized (Recommended) 🐳

No local Elixir/Erlang installation required - everything runs in Docker containers.

Prerequisites:

  • Docker and Docker Compose

Setup:

# Start all services (PostgreSQL, MockServer, and Phoenix app)
docker compose up -d

# View logs (optional)
docker compose logs -f server

Access:

Management:

# Stop all services
docker compose down

# Rebuild after code changes
docker compose build server && docker compose up -d

# View service status
docker compose ps

Mode 2: Hybrid Setup (Development)

Local Phoenix server with containerized database and mock services.

Prerequisites:

  • Elixir 1.14+
  • Docker and Docker Compose

Setup:

  1. Start infrastructure services:
# Start only PostgreSQL and MockServer
docker compose up postgres mockserver -d
  1. Setup application:
# Install dependencies
mix deps.get

# Create and migrate database
mix ecto.create
mix ecto.migrate

# Install frontend dependencies
mix assets.setup
  1. Run application:
# Start Phoenix server locally
mix phx.server

# Or run in IEx for development
iex -S mix phx.server

Access:


Testing and Code Quality

Running Tests

Mode 1 (Containerized):

# Run tests inside container
docker compose exec server mix test

# Run with coverage report
docker compose exec server mix coveralls.html

Mode 2 (Hybrid):

# Ensure test database is ready
docker compose up postgres -d
MIX_ENV=test mix ecto.create
MIX_ENV=test mix ecto.migrate

# Run all tests
mix test

# Run with detailed coverage
mix coveralls.html

# Run with coverage in terminal
mix coveralls

# Run code quality checks
mix credo --strict
mix dialyzer

Available Quality Tools

  • ExCoveralls: Test coverage analysis with HTML reports
  • Credo: Static code analysis for code quality
  • Dialyxir: Static analysis tool for type checking
  • ExUnit: Built-in testing framework with property testing support

Coverage Reports

After running mix coveralls.html, open cover/excoveralls.html to view:

  • Line-by-line coverage highlighting
  • Module coverage percentages
  • Overall project coverage metrics
  • Uncovered code identification

Usage

Web Dashboard

  1. Navigate to http://localhost:4000
  2. Enter a stock symbol (e.g., AAPL, GOOGL, MSFT, TSLA)
  3. Click "Subscribe" to receive real-time updates
  4. View live price updates as they stream in
  5. Click "×" next to subscribed symbols to unsubscribe

Mock API Configuration

The system uses WireMock to simulate realistic stock price APIs with dynamic random values.

Realistic Stock Price Simulation ✨

  • Dynamic Price Fluctuations: Each API call returns different values within realistic ranges
  • Real-time Timestamps: Current timestamp for every request
  • 6 Stock Symbols with Realistic Ranges:
    • AAPL: $145-155
    • GOOGL: $2,700-2,800
    • MSFT: $305-320
    • TSLA: $240-260
    • AMZN: $180-200
    • NFLX: $450-550
  • Zero Configuration: Works immediately with docker-compose up

API Details:

  • Endpoint: http://localhost:1080/api/stock-prices
  • Behavior: Returns different prices every 5 seconds for realistic simulation
  • Custom Ranges: Edit mockserver/expectations/stock-prices.json to adjust price ranges

Distributed Setup

For multi-node setup:

# Node 1
iex --sname node1 --cookie secret -S mix phx.server

# Node 2 (different port)
PORT=4001 iex --sname node2 --cookie secret -S mix phx.server

# Connect nodes (from node2)
Node.connect(:node1@hostname)

Configuration

Environment Variables

Mode 1 (Containerized):

  • Environment variables are configured in docker-compose.yml
  • DATABASE_URL: postgres://postgres:postgres@postgres:5432/stock_stream_dev
  • STOCK_API_URL: http://mockserver:8080 (internal Docker network)
  • PORT: 4000

Mode 2 (Hybrid):

  • DATABASE_URL: postgres://postgres:postgres@localhost:5432/stock_stream_dev (default)
  • STOCK_API_URL: http://localhost:1080 (default)
  • PORT: 4000 (default)

Common variables:

  • PHX_HOST: Phoenix host configuration
  • SECRET_KEY_BASE: Application secret (auto-generated for development)

Stock API Configuration

Edit mockserver/expectations/stock-prices.json to modify mock stock data:

{
  "symbol": "AAPL",
  "price": 150.25,
  "timestamp": "2024-01-01T10:00:00Z"
}

Fault Tolerance

Supervision Strategy

  • one_for_one: Individual process restarts without affecting others
  • Circuit Breaker: Protects against cascading failures from external API
  • Error Handling: Comprehensive error catching and logging

Circuit Breaker States

  • Closed: Normal operation
  • Open: Failing fast after threshold breaches
  • Half-Open: Testing recovery after timeout

Testing

Test Structure

  • Unit Tests: Individual module testing with Mimic mocks
  • Integration Tests: LiveView and PubSub integration testing
  • Fault Tolerance Tests: Supervisor and circuit breaker testing

Mock Setup

Tests use StockStream.MimicSetup.start() to configure mocks for:

  • HTTP requests via Req
  • External API calls
  • GenServer interactions

Docker Services

PostgreSQL

  • Port: 5432
  • Database: stock_stream_dev
  • Credentials: postgres/postgres
  • Available in both modes

MockServer (WireMock)

Phoenix Application (Mode 1 only)

  • Port: 4000
  • Environment: Production mode with runtime configuration
  • Database: Connects to postgres:5432 (internal Docker network)
  • Mock API: Connects to mockserver:8080 (internal Docker network)

Development

Key Modules

  • StockStream.StockPriceStreamer: Core streaming logic
  • StockStream.SubscriptionManager: Subscription management
  • StockStream.CircuitBreaker: Fault protection
  • StockStreamWeb.StockDashboardLive: LiveView interface

Adding New Stock Symbols

  1. Update mock expectations in mockserver/expectations/stock-prices.json
  2. Restart MockServer: docker-compose restart mockserver
  3. New symbols will be available for subscription

Troubleshooting

Mode 1 (Containerized)

Container won't start:

# Check container logs
docker compose logs server

# Rebuild if code changed
docker compose build server

# Restart all services
docker compose down && docker compose up -d

Database connection issues:

# Ensure PostgreSQL is running
docker compose ps postgres

# Check database logs
docker compose logs postgres

Mode 2 (Hybrid)

Database connection failed:

# Ensure PostgreSQL container is running
docker compose up postgres -d

# Verify connection
mix ecto.create

MockServer not responding:

# Check MockServer status
docker compose ps mockserver

# Restart MockServer
docker compose restart mockserver

# Test endpoint
curl http://localhost:1080/api/stock-prices

Mix commands fail:

# Ensure Elixir 1.14+ is installed
elixir --version

# Clean and reinstall dependencies
mix deps.clean --all && mix deps.get

Common Issues

Port already in use:

  • Change port in docker-compose.yml or environment variables
  • Kill processes using the port: lsof -ti:4000 | xargs kill -9

Permission denied (Docker):

  • Ensure Docker daemon is running
  • Check Docker permissions for your user

Erlang Distribution and Clustering

Overview

StockStream uses libcluster for automatic Erlang node discovery and clustering. This enables:

  • Distributed PubSub: Real-time messages broadcast across all connected nodes
  • Fault Tolerance: If one node fails, others continue serving requests
  • Load Distribution: Stock price fetching and processing distributed across nodes
  • Horizontal Scaling: Add more nodes to handle increased load

How It Works

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Node 1    │◄──►│   Node 2    │◄──►│   Node 3    │
│             │    │             │    │             │
│ Phoenix App │    │ Phoenix App │    │ Phoenix App │
│ Stock Data  │    │ Stock Data  │    │ Stock Data  │
└─────────────┘    └─────────────┘    └─────────────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │
                   Shared PubSub Topics
                   (stock:AAPL, stock:all)
  1. Node Discovery: libcluster automatically discovers and connects nodes
  2. PubSub Distribution: Phoenix.PubSub replicates messages across all nodes
  3. Data Consistency: Stock price updates propagate to all connected clients
  4. Fault Recovery: Failed nodes automatically rejoin when healthy

Clustering Strategies

Development (no clustering):

# config/dev.exs
config :libcluster, topologies: []

Production - Kubernetes:

# Environment variables
CLUSTER_STRATEGY=Kubernetes
KUBERNETES_SELECTOR=app=stock_stream  
KUBERNETES_NAMESPACE=default

Production - Static Hosts:

# Environment variables
CLUSTER_STRATEGY=Epmd
CLUSTER_HOSTS=node1@server1,node2@server2,node3@server3

Testing Distribution

# Terminal 1 - Start first node
PORT=4000 iex --sname node1 --cookie secret -S mix phx.server

# Terminal 2 - Start second node  
PORT=4001 iex --sname node2 --cookie secret -S mix phx.server

# Terminal 3 - Connect nodes manually (development)
iex --sname client --cookie secret
> Node.connect(:node1@hostname)
> Node.connect(:node2@hostname)
> Node.list()  # Should show both nodes

When nodes are connected:

  • Stock price updates appear on all nodes simultaneously
  • WebSocket clients on any node receive updates from any other node
  • Circuit breaker state is local per node for isolation

Potential Improvements

Based on comprehensive code analysis, here are categorized improvement opportunities:

🔧 Code Quality & Architecture

  • Add missing @spec type specifications for all public functions
  • Extract stock price parsing logic into dedicated StockStream.Parser module
  • Implement StockStream.Stocks context for business logic abstraction
  • Add proper module documentation (@moduledoc) for all modules
  • Create centralized StockStream.Configuration module
  • Add structured logging with correlation IDs

🛡️ Security Enhancements

  • Remove hardcoded SECRET_KEY_BASE from docker-compose.yml
  • Implement input validation for stock symbols (length, format)
  • Add rate limiting using Hammer or ExRated
  • Implement XSS protection for user inputs
  • Add authentication system for dashboard access
  • Use proper secrets management (HashiCorp Vault, k8s secrets)

Performance & Scalability

  • Add HTTP connection pooling with configurable pool sizes
  • Implement adaptive fetch intervals based on subscription count
  • Add ETS-based caching for hot stock data
  • Implement data retention policies with background cleanup
  • Add composite database indexes for query optimization
  • Configure Phoenix Presence for connection tracking

🔍 Observability & Monitoring

  • Add Prometheus + Grafana integration
  • Implement distributed tracing with OpenTelemetry
  • Add comprehensive health check endpoints (/health/ready, /health/live)
  • Configure structured JSON logging for production
  • Add custom Telemetry metrics for business events
  • Implement alerting for circuit breaker state changes

🧪 Testing Improvements

  • Add comprehensive integration tests for full request/response cycles
  • Implement property-based testing with StreamData
  • Add chaos engineering tests for fault tolerance validation
  • Create performance benchmarks with :benchee
  • Add contract testing for external API interactions
  • Implement load testing scenarios

🚀 Production Readiness

  • Add Kubernetes manifests with Helm charts
  • Implement blue-green deployment strategy
  • Configure automated backup and disaster recovery
  • Add container resource limits and requests
  • Implement graceful shutdown handling
  • Add automated rollback capabilities

📊 Feature Enhancements

  • Implement historical stock price persistence
  • Add price change alerts and notifications
  • Create administrative dashboard for system management
  • Add WebSocket connection limits and backpressure
  • Implement price trend analysis algorithms
  • Add support for multiple stock exchanges

🔄 Development Experience

  • Add pre-commit hooks with code formatting
  • Implement CI/CD pipeline with GitHub Actions
  • Add OpenAPI/Swagger documentation
  • Configure automated security scanning
  • Add development Docker Compose override
  • Create architectural decision records (ADRs)

💾 Data Management

  • Add background jobs with Oban for async processing
  • Implement database read replicas for scaling
  • Add database partitioning for time-series data
  • Configure automated database migrations
  • Add data export/import capabilities
  • Implement soft deletes for audit trails

Production Considerations

  • Configure proper database connection pooling
  • Set up monitoring and observability
  • Use SSL/TLS for external API calls
  • Implement rate limiting for API requests
  • Configure proper logging levels
  • Set up health checks and metrics
  • Use proper secrets management instead of hardcoded values
  • Configure container resource limits and health checks
  • Enable distributed clustering in production environments
  • Configure proper node discovery strategy (Kubernetes DNS or static hosts)
  • Set up load balancers with session affinity for WebSocket connections

License

This project was created for the Arionkoder Technical Test.

Contributors