Skip to content

ivan-podgurskiy/elixir-design-patterns

Repository files navigation

Elixir Design Patterns

Practical, runnable examples of OTP and functional design patterns in Elixir. Each pattern is a self-contained module with tests, docs, and a real-world use case.

CI Elixir Version OTP Version

Overview

This repository demonstrates deep Elixir/OTP expertise through comprehensive, runnable examples of common patterns. Each pattern includes:

  • Clean, documented code with full typespecs
  • Comprehensive tests covering happy path, edge cases, and error scenarios
  • Detailed guides explaining when and how to use each pattern
  • Real-world examples from production experience
  • IEx demonstrations you can try immediately

Quick Start

git clone https://github.com/ivan-podgurskiy/elixir-design-patterns.git
cd elixir-design-patterns
mix deps.get
mix test

Try a pattern in IEx:

iex -S mix
# Start a cache with TTL
{:ok, cache} = Patterns.GenServerCache.start_link()
:ok = Patterns.GenServerCache.put(cache, "user:123", %{name: "John"}, 5000)
{:ok, user} = Patterns.GenServerCache.get(cache, "user:123")

Pattern Index

Phase 1 — Core OTP Patterns

Pattern Module Description Guide
GenServer Cache Patterns.GenServerCache In-memory key-value cache with TTL expiration and statistics 📖 Guide
Supervisor Tree Patterns.SupervisorTree Fault-tolerant supervision with different restart strategies 📖 Guide
Agent State Patterns.AgentState Simple shared state for counters, config, and statistics 📖 Guide
Task.async Patterns.TaskAsync Parallel execution, timeout handling, and result aggregation 📖 Guide

Phase 2 — Process Patterns

Pattern Module Description Guide
Registry & Dynamic Supervisors Patterns.RegistryDynamicSupervisor Runtime keyed process lookup, on-demand startup, and crash recovery 📖 Guide
Pub/Sub with Registry Patterns.RegistryPubSub Topic-based event fan-out with duplicate-key Registry dispatch 📖 Guide
Process Pooling Patterns.ProcessPool Fixed-size interchangeable worker pools with checkout/checkin 📖 Guide
Circuit Breaker Patterns.CircuitBreaker Fail-fast protection for unreliable services with automatic recovery 📖 Guide

Phase 3 — Functional Patterns

Pattern Module Description Guide
Pipeline with with Patterns.Pipeline Step pipelines using with chains for linear happy paths and first-error short-circuiting 📖 Guide
Railway-Oriented Programming Patterns.Railway Pipe-friendly result combinators (bind, map, tee, recover) for two-track success/failure pipelines 📖 Guide
Behaviour & Protocol Patterns.BehaviourProtocol Module-dispatched behaviours vs. value-dispatched protocols, wired into one notification system 📖 Guide
ETS-Backed Store Patterns.EtsStore GenServer-owned :public ETS tables with direct reads and read-path benchmarks 📖 Guide

Phase 4 — Real-World Patterns

Pattern Module Description Guide
Rate Limiter Patterns.RateLimiter Token bucket rate limiting with global and per-key buckets, lazy refill, and retry hints 📖 Guide

Coming Soon

  • Retry with exponential backoff (standalone module)
  • Graceful shutdown handling
  • Event Sourcing fundamentals

Pattern Categories

🔧 State Management

  • GenServer Cache: Production-ready caching with expiration
  • Agent State: Lightweight shared state for simple scenarios
  • ETS-Backed Store: Shared in-memory tables with optional read bypass

🚦 Process Supervision

  • Supervisor Tree: Fault tolerance with configurable restart strategies
  • Registry & Dynamic Supervisors: On-demand keyed process management
  • Pub/Sub with Registry: Topic-based in-process event distribution
  • Process Pooling: Bounded concurrency with worker checkout/checkin
  • Task.async: Concurrent execution with proper error handling

Concurrency & Performance

  • Task.async: Parallel I/O and CPU-bound operations
  • Agent State: Thread-safe atomic operations

🛡️ Fault Tolerance

  • Supervisor Tree: "Let it crash" philosophy in practice
  • GenServer Cache: Graceful degradation and recovery
  • Circuit Breaker: Fail-fast protection against failing dependencies

🧩 Functional Composition

  • Pipeline with with: Linear happy paths with first-error short-circuiting
  • Railway-Oriented Programming: Pipe-friendly combinators for two-track result flows

🔌 Polymorphism & Extensibility

  • Behaviour & Protocol: Compile-time module contracts and runtime type-based dispatch

Code Quality

This project maintains high code quality standards:

# Run all quality checks
mix test                    # Full test suite
mix credo --strict         # Code analysis
mix dialyzer              # Static type checking
mix format --check-formatted  # Code formatting
mix docs                   # Generate documentation

Quality Metrics

  • Comprehensive Tests: 80+ tests covering happy paths, edge cases, and error scenarios
  • High Type Coverage: Public APIs include @spec annotations
  • Zero Credo Issues: Strict code quality enforcement
  • Dialyzer Clean: Static analysis passing
  • Detailed Docs: Every pattern module has documentation and a companion guide

Usage Examples

GenServer Cache

{:ok, cache} = Patterns.GenServerCache.start_link()

# Store with TTL
:ok = Patterns.GenServerCache.put(cache, "session:abc", "active", 300_000)

# Retrieve
{:ok, "active"} = Patterns.GenServerCache.get(cache, "session:abc")

# Statistics
%{hits: 1, misses: 0, total_keys: 1} = Patterns.GenServerCache.stats(cache)

Supervision Strategies

# Test different supervision strategies
{:ok, sup} = Patterns.SupervisorTree.start_link(:one_for_one)

# Inspect running processes
info = Patterns.SupervisorTree.info(sup)
# %{strategy: :one_for_one, running_count: 3, children: [...]}

# Test fault tolerance
:ok = Patterns.SupervisorTree.crash_child(sup, :worker_1)
# Worker automatically restarts

Parallel Processing

# Fetch multiple URLs concurrently (simulated I/O for demo/testing)
urls = ["http://httpbin.org/delay/1", "http://httpbin.org/ip"]
{:ok, responses} = Patterns.TaskAsync.parallel_fetch(urls, timeout: 5000)

# Process data in parallel
results = Patterns.TaskAsync.parallel_map(1..1000, fn x ->
  expensive_computation(x)
end, max_concurrency: 8)

Statistics Collection

{:ok, stats} = Patterns.AgentState.Statistics.start_link()

# Time operations
result = Patterns.AgentState.Statistics.time(stats, :api_call, fn ->
  Process.sleep(10)
  :ok
end)

# Get metrics
avg_time = Patterns.AgentState.Statistics.average_time(stats, :api_call)

Real-World Applications

These patterns are used in production for:

  • API Gateways: Parallel service aggregation with fault tolerance
  • Caching Layers: High-performance in-memory caches with TTL
  • Background Processing: Job queues with supervised workers
  • Microservices: Service coordination and health monitoring
  • Data Pipelines: ETL processing with error recovery

Learning Path

  1. Start with Agent State — Simplest pattern for shared state
  2. Move to GenServer Cache — More complex state with business logic
  3. Explore Supervision — Understand fault tolerance fundamentals
  4. Master Task.async — Concurrent programming patterns

Each pattern builds on concepts from the previous ones.

Architecture Principles

The Elixir Way

  • Let It Crash: Use supervision for fault tolerance
  • Immutable Data: Functional programming principles
  • Actor Model: Isolated processes communicating via messages
  • OTP Design: Battle-tested concurrency patterns

Code Organization

  • Single Responsibility: Each pattern solves one problem well
  • Composability: Patterns can be combined for complex systems
  • Testability: Every pattern is thoroughly tested
  • Documentation: Self-documenting code with comprehensive guides

Development

Prerequisites

  • Elixir 1.19+ with OTP 26+
  • asdf (recommended)

Local setup with asdf

Only the Elixir plugin is required — the OTP version is encoded in the release tag:

asdf plugin add elixir
asdf install   # reads .tool-versions → 1.19.5-otp-28
mix deps.get
mix test

Pinned version in .tool-versions:

elixir 1.19.5-otp-28

The -otp-28 suffix is the OTP version that asdf uses for that Elixir build. You do not need a separate erlang plugin.

CI installs the same stack explicitly (OTP 28.1 + Elixir 1.19.5) and additionally tests OTP 26.2.5 as the minimum supported version.

Running Tests

# All tests
mix test

# Specific pattern
mix test test/patterns/genserver_cache_test.exs

# With coverage
mix test --cover

Code Quality

# Format code
mix format

# Static analysis
mix credo --strict

# Type checking
mix dialyzer

# Generate docs
mix docs && open doc/index.html

Contributing

This is primarily a demonstration repository, but improvements are welcome:

  1. Bug Reports: Open an issue with reproduction steps
  2. Documentation: Clarifications and corrections
  3. Performance: Benchmarks and optimizations
  4. Examples: Additional real-world usage scenarios

See CONTRIBUTING.md for commit message conventions (Conventional Commits) and PR workflow.

Security & Dependencies

Dependabot alerts (security only)

This repository uses Renovate for dependency version updates. Dependabot is used only for vulnerability detection — not for opening version-update PRs.

Enable Dependabot alerts manually in GitHub:

  1. Open Settings → Code security and analysis
  2. Enable Dependabot alerts
  3. If Dependabot security updates or Dependabot version updates are enabled, disable version updates (Renovate handles those)

Renovate opens weekly update PRs (security updates are prioritized immediately). All dependency PRs require manual review and approval before merge.

Project Status

  • Phase 1 Complete: Core OTP patterns with full documentation
  • Phase 2 Complete: Registry & Dynamic Supervisors, Pub/Sub, Process Pool, and Circuit Breaker
  • Phase 3 Complete: Functional programming patterns (Pipeline, Railway, Behaviour & Protocol, ETS-backed stores)
  • 🚧 Phase 4 In Progress: Rate Limiter complete; retries, graceful shutdown, and event sourcing planned

Resources

Learning More

Pattern Guides


Built with ❤️ for the Elixir community

Demonstrating production-ready patterns that power reliable, concurrent systems.

About

Practical, runnable examples of OTP and functional design patterns in Elixir. Each pattern is a self-contained module with tests, docs, and a real-world use case.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages