Welcome back. In the last lesson, you used protocols to achieve polymorphism based on data types. Today, we shift to behaviours, which define a contract that modules must implement. Behaviours help you design replaceable components (for example, multiple storage backends) and provide compile-time checks for missing functions.
Explanation:
Storagedefines the required functions via@callback. Any module claiming to follow this contract must providesave/2,load/1, anddelete/1.- Type specs describe inputs and outputs. For example,
load/1must return either{:ok, value}or{:error, "reason"}. - The main benefit is safety. If an implementing module misses a function or uses the wrong arity, Elixir warns at compile time.
Note: Behaviours are great boundaries. You can swap implementations (e.g., in-memory vs. database) without changing the calling code.
Explanation:
@behaviour Storageopts into the contract. If you forget any callback, you get a helpful compiler warning.use GenServerbrings in GenServer boilerplate. We keep state in a map stored inside the process.- Public API:
start_link/0starts the server with an empty map and registers it under its module name.save/2,load/1, anddelete/1are synchronous calls that interact with the server.
- Callbacks:
init/1sets the initial state.handle_call/3handles each request and returns{:reply, reply, new_state}. We update the state immutably with or .
Explanation:
- You must start the server before calling it. We register it by name in
start_link/0, so calls don’t need a PID. - The first
load/1returns{:ok, %{name: "Alice"}}. Afterdelete/1, the secondload/1returns{:error, "Key not found"}. - This usage proves the behaviour contract and the GenServer implementation work together.
Note: Because you coded against the Storage contract, you can add another module (e.g., a persistent store) with the same callbacks and swap it without changing call sites.
You defined a behaviour to express a clear contract, implemented it with a GenServer, and exercised the API end to end. This approach gives you compile-time safety, clean boundaries, and easy swapping of implementations.
Ready to practice and make this pattern second nature? Let’s dive in.
