elixir-otp-patterns

安装量: 54
排名: #13617

安装

npx skills add https://github.com/thebushidocollective/han --skill elixir-otp-patterns

Elixir OTP Patterns

Master OTP (Open Telecom Platform) patterns to build concurrent, fault-tolerant Elixir applications. This skill covers GenServer, Supervisor, Agent, Task, and other OTP behaviors.

GenServer Basics defmodule Counter do use GenServer

# Client API

def start_link(initial_value \ 0) do GenServer.start_link(MODULE, initial_value, name: MODULE) end

def increment do GenServer.cast(MODULE, :increment) end

def get_value do GenServer.call(MODULE, :get_value) end

# Server Callbacks

@impl true def init(initial_value) do {:ok, initial_value} end

@impl true def handle_call(:get_value, _from, state) do {:reply, state, state} end

@impl true def handle_cast(:increment, state) do {:noreply, state + 1} end end

Usage

{:ok, _pid} = Counter.start_link(0) Counter.increment() Counter.get_value() # => 1

GenServer with State Management defmodule UserCache do use GenServer

# Client API

def start_link(_opts) do GenServer.start_link(MODULE, %{}, name: MODULE) end

def put(user_id, user_data) do GenServer.cast(MODULE, {:put, user_id, user_data}) end

def get(user_id) do GenServer.call(MODULE, {:get, user_id}) end

def delete(user_id) do GenServer.cast(MODULE, {:delete, user_id}) end

def all do GenServer.call(MODULE, :all) end

# Server Callbacks

@impl true def init(_opts) do {:ok, %{}} end

@impl true def handle_call({:get, user_id}, _from, state) do {:reply, Map.get(state, user_id), state} end

@impl true def handle_call(:all, _from, state) do {:reply, state, state} end

@impl true def handle_cast({:put, user_id, user_data}, state) do {:noreply, Map.put(state, user_id, user_data)} end

@impl true def handle_cast({:delete, user_id}, state) do {:noreply, Map.delete(state, user_id)} end end

Supervisor Strategies defmodule MyApp.Application do use Application

@impl true def start(_type, _args) do children = [ # One-for-one: restart only failed child {Counter, 0}, {UserCache, []},

  # One-for-all supervisor
  {Supervisor,
   strategy: :one_for_all,
   name: MyApp.CriticalSupervisor,
   children: [
     {Database, []},
     {Cache, []}
   ]},

  # Rest-for-one supervisor
  {Supervisor,
   strategy: :rest_for_one,
   name: MyApp.OrderedSupervisor,
   children: [
     {ConfigLoader, []},
     {DatabasePool, []},
     {WebServer, []}
   ]}
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)

end end

Dynamic Supervisor defmodule TaskRunner do use GenServer

def start_link(task_id) do GenServer.start_link(MODULE, task_id) end

@impl true def init(task_id) do Process.send_after(self(), :run_task, 0) {:ok, task_id} end

@impl true def handle_info(:run_task, task_id) do # Perform task work IO.puts("Running task #{task_id}") {:noreply, task_id} end end

defmodule TaskSupervisor do use DynamicSupervisor

def start_link(_opts) do DynamicSupervisor.start_link(MODULE, :ok, name: MODULE) end

def start_task(task_id) do spec = {TaskRunner, task_id} DynamicSupervisor.start_child(MODULE, spec) end

def stop_task(pid) do DynamicSupervisor.terminate_child(MODULE, pid) end

@impl true def init(:ok) do DynamicSupervisor.init(strategy: :one_for_one) end end

Usage

TaskSupervisor.start_link([]) {:ok, pid} = TaskSupervisor.start_task(1) TaskSupervisor.stop_task(pid)

Agent for Simple State defmodule SimpleCounter do use Agent

def start_link(initial_value) do Agent.start_link(fn -> initial_value end, name: MODULE) end

def increment do Agent.update(MODULE, &(&1 + 1)) end

def decrement do Agent.update(MODULE, &(&1 - 1)) end

def value do Agent.get(MODULE, & &1) end

def reset do Agent.update(MODULE, fn _ -> 0 end) end end

Usage

{:ok, _pid} = SimpleCounter.start_link(0) SimpleCounter.increment() SimpleCounter.value() # => 1

Task for Async Operations defmodule DataFetcher do def fetch_all do tasks = [ Task.async(fn -> fetch_users() end), Task.async(fn -> fetch_posts() end), Task.async(fn -> fetch_comments() end) ]

results = Task.await_many(tasks, 5000)

%{
  users: Enum.at(results, 0),
  posts: Enum.at(results, 1),
  comments: Enum.at(results, 2)
}

end

defp fetch_users do # Simulate API call Process.sleep(100) ["user1", "user2", "user3"] end

defp fetch_posts do Process.sleep(200) ["post1", "post2"] end

defp fetch_comments do Process.sleep(150) ["comment1", "comment2", "comment3"] end end

Task.Supervisor for Managed Tasks defmodule MyApp.TaskSupervisor do use Task.Supervisor

def start_link(_opts) do Task.Supervisor.start_link(name: MODULE) end

def run_task(fun) do Task.Supervisor.async(MODULE, fun) end

def run_task_nolink(fun) do Task.Supervisor.async_nolink(MODULE, fun) end end

In application.ex

children = [ {Task.Supervisor, name: MyApp.TaskSupervisor} ]

Usage

task = Task.Supervisor.async( MyApp.TaskSupervisor, fn -> expensive_operation() end ) result = Task.await(task)

GenServer with Timeouts defmodule SessionManager do use GenServer

@timeout 60_000 # 60 seconds

def start_link(session_id) do GenServer.start_link(MODULE, session_id) end

def refresh(pid) do GenServer.cast(pid, :refresh) end

@impl true def init(session_id) do {:ok, session_id, @timeout} end

@impl true def handle_cast(:refresh, state) do {:noreply, state, @timeout} end

@impl true def handle_info(:timeout, state) do IO.puts("Session #{state} timed out") {:stop, :normal, state} end end

Registry for Process Lookup defmodule UserSession do use GenServer

def start_link(user_id) do GenServer.start_link( MODULE, user_id, name: via_tuple(user_id) ) end

def via_tuple(user_id) do {:via, Registry, {MyApp.Registry, {:user_session, user_id}}} end

def send_message(user_id, message) do case Registry.lookup(MyApp.Registry, {:user_session, user_id}) do [{pid, _}] -> GenServer.cast(pid, {:message, message}) [] -> {:error, :not_found} end end

@impl true def init(user_id) do {:ok, %{user_id: user_id, messages: []}} end

@impl true def handle_cast({:message, message}, state) do {:noreply, %{state | messages: [message | state.messages]}} end end

In application.ex

children = [ {Registry, keys: :unique, name: MyApp.Registry} ]

Implementing GenServer with State Cleanup defmodule FileWatcher do use GenServer

def start_link(file_path) do GenServer.start_link(MODULE, file_path) end

@impl true def init(file_path) do case File.open(file_path, [:read]) do {:ok, file} -> schedule_check() {:ok, %{file: file, path: file_path, position: 0}} {:error, reason} -> {:stop, reason} end end

@impl true def handle_info(:check, state) do # Read new lines from file schedule_check() {:noreply, state} end

@impl true def terminate(_reason, %{file: file}) do File.close(file) :ok end

defp schedule_check do Process.send_after(self(), :check, 1000) end end

Using ETS with GenServer defmodule CacheServer do use GenServer

def start_link(_opts) do GenServer.start_link(MODULE, :ok, name: MODULE) end

def put(key, value) do GenServer.call(MODULE, {:put, key, value}) end

def get(key) do case :ets.lookup(MODULE, key) do [{^key, value}] -> {:ok, value} [] -> :not_found end end

@impl true def init(:ok) do :ets.new(MODULE, [:named_table, :set, :public]) {:ok, %{}} end

@impl true def handle_call({:put, key, value}, _from, state) do :ets.insert(MODULE, {key, value}) {:reply, :ok, state} end end

When to Use This Skill

Use elixir-otp-patterns when you need to:

Build concurrent applications with isolated processes Implement fault-tolerant systems with supervision trees Manage application state across process lifecycles Create worker pools for async task processing Build real-time systems with multiple concurrent users Implement pub/sub or event-driven architectures Create distributed systems with process communication Handle long-running background jobs Build scalable web servers and APIs Best Practices Use GenServer for stateful processes with complex logic Use Agent for simple state that doesn't need custom logic Use Task for one-off async operations Always define proper supervision strategies Use Registry for dynamic process lookup Implement proper timeout handling Clean up resources in terminate/2 callbacks Use via tuples for named process registration Separate client API from server callbacks Keep handle_* functions focused and simple Common Pitfalls Not implementing proper supervision strategies Blocking GenServer calls with long-running operations Forgetting to handle :timeout messages Not cleaning up resources in terminate/2 Using cast when you need synchronous confirmation Creating too many processes unnecessarily Not handling process exits properly Storing large data in process state instead of ETS Not using Registry for dynamic process management Ignoring backpressure in async operations Resources Elixir GenServer Guide Supervisor Documentation OTP Design Principles Elixir in Action Book Agent Guide Task Documentation

返回排行榜