Making Our Server into a Component

Understand how to split the server code into major components.

Earlier, we said that what Elixir calls an application, most people would call a component or a service. That’s certainly what our sequence server is: a freestanding chunk of code that generates successive numbers.

Implementation

Our implementation puts three things into a single source file:

  • The API.
  • The logic of our service (adding one).
  • The implementation of that logic in a server.

Have another look at the code in the previous lesson. If we didn’t know what it did, how would we find out? Where’s the code that does the component’s logic? Imagine working with a really complex one with lots of logic. That’s why we’re experimenting with splitting the API, implementation, and server into three separate files.

We’ll put the API in the top-level lib/sequence.ex module, and we’ll put the implementation and server in the two lower-level modules. The API is the public face of our component. It’s simply the top half of the previous server module:

defmodule Sequence do

  @server Sequence.Server
  
  def start_link(current_number) do
    GenServer.start_link(@server, current_number, name: @server)
  end

  def next_number do
    GenServer.call(@server, :next_number)
  end

  def increment_number(delta) do
    GenServer.cast(@server, {:increment_number, delta})
  end


end

This forwards calls on to the server implementation:

defmodule Sequence.Server do
  use GenServer
  alias Sequence.Impl
  
  def init(initial_number) do
    { :ok, initial_number }
  end
  
  def handle_call(:next_number, _from, current_number) do
    { :reply, current_number, Impl.next(current_number) }
  end

  def handle_cast({:increment_number, delta}, current_number) do
    { :noreply, Impl.increment(current_number, delta) }
  end

  def format_status(_reason, [ _pdict, state ]) do
    [data: [{'State', "My current state is '#{inspect state}', and I'm happy"}]] 
  end
end

Unlike the previous server, this code contains no business logic (which in our case is adding either 1 or some delta to our state). Instead, it uses the implementation module to do this:

defmodule Sequence.Impl do

  def next(number),             do: number + 1
  def increment(number, delta), do: number + delta

end

Run the code using the following:

iex> Sequence.Impl.start_link 123  
{:ok, #PID<0.179.0>}
iex> Sequence.Impl.next(123)
124
iex> Sequence.Impl.increment(123,100)
223

Use commands in the above snippet to run the code below:

Get hands-on with 1400+ tech skills courses.