Defining Behaviours: What You’ll Learn

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.

Declare a Behaviour (the Contract)

Explanation:

  • Storage defines the required functions via @callback. Any module claiming to follow this contract must provide save/2, load/1, and delete/1.
  • Type specs describe inputs and outputs. For example, load/1 must 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.

Implement the Behaviour with a GenServer

Explanation:

  • @behaviour Storage opts into the contract. If you forget any callback, you get a helpful compiler warning.
  • use GenServer brings in GenServer boilerplate. We keep state in a map stored inside the process.
  • Public API:
    • start_link/0 starts the server with an empty map and registers it under its module name.
    • save/2, load/1, and delete/1 are synchronous calls that interact with the server.
  • Callbacks:
    • init/1 sets the initial state.
    • handle_call/3 handles each request and returns {:reply, reply, new_state}. We update the state immutably with or .
Start and Use the Storage

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/1 returns {:ok, %{name: "Alice"}}. After delete/1, the second load/1 returns {: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.

Summary and Next Steps

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.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal