title: "Thinking in Processes" layout: course.njk course: "Deep Dive: The BEAM for Developers"
Module 1: Thinking in Processes
Suggested Reading:
- The BEAM Book, ch. 1–2 (Introduction to the BEAM and process model)
- Erlang/OTP Documentation: "Processes and Message Passing"
- “Concurrency Oriented Programming in Erlang” (Joe Armstrong)
Goal
- Develop a process-oriented mindset for Erlang/Elixir/BEAM.
- Understand how isolation, lightweight concurrency, and message passing create fault-tolerant, scalable systems.
- Apply these concepts to real-world problems: system decomposition, flows, and robust design.
Outcome
- Recognize process patterns and archetypes in any system.
- Design systems that use BEAM’s strengths for error containment, scalability, and maintainability.
- Confidently choose between modules, processes, and data flows for effective architectures.
Content
Overview
- What does “Thinking in Processes” mean for BEAM developers?
- Why process isolation and message passing—not objects, modules, or shared state—are at the heart of robust Erlang systems.
- The difference between BEAM processes, OS threads, and objects in other languages.
Session 1: BEAM Processes – The Mental Model
1.1 What is a BEAM Process?
- True lightweight green threads: Each process has its own heap, stack, mailbox, and control block.
- Designed for millions of concurrent processes (not “just a thread”).
- Error isolation—when a process dies, its memory is reclaimed, and faults don’t cascade.
1.2 Process Life Cycle and States
- Lifecycle: spawn → run → block (waiting/receive) → terminate.
- States: waiting, runnable, running, garbage collecting, etc.
- Message flow and mailbox growth: what happens when you don’t keep up.
1.3 Why Not Objects, Modules, or Threads?
- Modules are code containers—processes are state and behavior containers.
- No shared mutable state.
- Message passing vs. method calls vs. shared memory.
1.4 “Gnomes” Metaphor
- (Based on BEAM Book/slide deck): Each process is a “gnome” doing one job, independently.
- Visualization: see memory diagrams and gnome illustration from the slides.
Activity:
- Use
:erlang.processes/0and:erlang.process_info/1to inspect real processes. - Diagram: Draw the life cycle of a process in a simple chat or queue system.
Session 2: Designing with Processes
2.1 Domains and Responsibilities
- System decomposition: Domains (Users, Orders, Accounts, Sessions).
- Tasks and flows, not just “entities” or “objects.”
- The “let it crash” philosophy and why supervisors matter.
2.2 Process Archetypes
- Workers (short-lived, pooled, one task at a time).
- Supervisors (monitor and restart others).
- Resource owners (keep state—e.g., a user’s shopping cart).
- Routers/dispatchers (manage flows and routing).
- Gatekeepers (rate limit, circuit breaker, lock, serial).
2.3 GenServer vs. Simple Process
- When to use GenServer vs. raw processes vs. Task/Agent.
- “GenServer Trap”: not everything needs to be a GenServer!
Activity:
- Identify archetypes in your system. Sketch a process diagram.
- Use
spawn/1,receive, and message passing to build a minimal process “relay.”
Session 3: Message Passing & Selective Receive
3.1 Message Passing Semantics
- Send (
!) is async, unidirectional (“send and pray”). - Mailboxes: unbounded, per process; can become a bottleneck.
- No delivery guarantees—always handle potential message loss.
3.2 Selective Receive
- Processes can match specific messages, discard or delay others.
- Common pitfalls: mailbox bloat, messages “stuck behind” others.
3.3 Patterns for Safe Message Handling
- Pattern matching for robust receive blocks.
- Use timeouts and pushback to prevent overload.
- Implementing acknowledgment patterns for “guaranteed” delivery.
Activity:
- Write code to demonstrate selective receive; simulate mailbox bloat and fix it.
- Analyze a failed message delivery—where did the message get stuck?
Session 4: System Decomposition – Flows & Boundaries
4.1 Breaking Down a Real System
- Example: chat server, payment router, or job queue.
- How to split a system into independent, cooperating processes.
- How to avoid too much serialization (“global locks” or “single GenServer” traps).
4.2 System Boundaries & Process Ownership
- When does a domain need its own process? (E.g., per-user session, per-order worker)
- Process registry, lookup, and naming patterns (
:global,Registry, etc.)
4.3 Pushback and Backpressure
- Preventing overload by pushing back to producers.
- Strategies: drop, queue, reject, or slow down.
Activity:
- Decompose a real application (web chat, payment pipeline) into BEAM processes.
- Identify single points of failure or bottlenecks.
Session 5: Real-World Patterns and Anti-Patterns
5.1 Good Patterns
- Supervisors and restart strategies (one_for_one, rest_for_one, etc.)
- Decoupling processes—avoid the “big GenServer” anti-pattern.
- Using “let it crash” responsibly.
5.2 Common Anti-Patterns
- Overusing process dictionary.
- Single-process bottlenecks.
- GenServer handling too many responsibilities (“God process”).
Activity:
- Review and refactor code samples to eliminate anti-patterns.
- Build a supervision tree for your sample app.
Worksheets
Worksheet 1: Process Decomposition
- List the main domains in your system.
- For each, sketch how processes would be structured.
Worksheet 2: Process Life Cycle
- Draw the process life cycle and state transitions for a worker in your system.
Worksheet 3: Message Flow Analysis
- Map message flows for a chosen feature (e.g., login or payment).
- Identify where selective receive might be useful or problematic.
Worksheet 4: Bottleneck and Anti-Pattern Review
- List all GenServers in a given app. For each, note if they might be “doing too much.”
- Propose a supervision tree and error-handling strategy for your core service.
Checklist
- [ ] Can you explain the difference between a BEAM process and an OS thread?
- [ ] Can you identify when a system should add a new process?
- [ ] Have you diagrammed flows and tasks as processes, not just “classes” or “modules”?
- [ ] Can you implement safe message passing with selective receive?
- [ ] Do you know common process archetypes and their roles?
- [ ] Can you spot a GenServer trap or anti-pattern in a codebase?
Conclusion
Thinking in processes is the foundation of scalable, resilient BEAM systems. Practice decomposing your next feature into flows, tasks, and processes. Don’t try to shoehorn OO or class-based patterns into Erlang/Elixir—embrace process orientation for clarity, reliability, and fun.
What’s Next?
Next module: Memory, Data Types, and Garbage Collection—how BEAM handles data and memory at process level, and what you need to know to avoid common pitfalls.
Printable Worksheets (cut and paste for handouts or docs)
Process Decomposition Worksheet
Domain: ___________________ Process Type(s): ___________________ Responsibilities: ___________________ Dependencies: ___________________
Process Life Cycle Worksheet
Process Name: ___________________ [ ] Spawned [ ] Running [ ] Waiting (blocking) [ ] Handling message [ ] Terminated
Diagram or notes: ___________________
Message Flow Analysis Worksheet
Feature/Flow: ___________________ Sender: ___________________ Receiver: ___________________ Potential bottlenecks: ___________________ Where selective receive is used: ___________________
Anti-Pattern Review Worksheet
GenServer Name: ___________________ Responsibilities: ___________________ Signs of “God process”: ___________________ Refactor plan: ___________________