quesurifn

quesurifn

How to store state in module?

So, I’m building an SMTP client that I’d like to be able to be called from different processes and have different state for each process. The SMTP client needs to track for example if the HELO command has been used or not. How do I handle state?

My current code is this:

defmodule Client do
  @moduledoc """
  Documentation for `Smtpclient`.
  """

  use Agent

  def start_link(_opts) do
    Agent.start_link(fn -> %{} end, name: __MODULE__)
  end


  def connect(pid, tld) do
      set_value(pid, "tld", tld)
      {:ok, records} = DNS.resolve(tld, :mx)
      Enum.sort_by(records, fn(r) -> elem(r, 0) end)
      sock = do_connect(records, 0)
      set_socket(pid, sock)
      set_value(pid, "hostname", tld)
      {:ok, sock}
  end

  @spec helo(any) :: any
  def helo(pid) do
      already_helloed = get_value(pid, "already_helloed")
      if already_helloed do # if already helloed return early
        {:error, "already helloed"}
      end
      sock = get_socket(pid)
      tld = get_value(pid, "tld")
      sock |> Socket.Stream.send!("HELO #{tld}")
      set_value(pid, "already_helloed", true)
      sock |> Socket.Stream.recv!
  end

  def noop(pid) do
    try do
      sock = get_socket(pid)
      sock |> Socket.Stream.send!("NOOP")
      sock |> Socket.Stream.recv!
    rescue
      Socker.Error -> {:error, :no_connection}
    end
  end

  def quit(sock) do
      try do
        sock |> Socket.Stream.send!("QUIT")
        sock |> Socket.Stream.recv!
        sock |> Socket.close!()
      rescue
        Socket.Error -> {:error, "something is wrong"}
      end
  end

  def rctp_to(pid, email) do
    try do
      sock = get_socket(pid)
      sock |> Socket.Stream.send!("RCPT_TO #{email}")
      sock |> Socket.Stream.recv!
    rescue
      Socker.Error -> {:error, "something is wrong"}
    end
  end

  defp set_socket(pid, sock) do
    Agent.update(pid, &Map.put(&1, "socket", sock))
  end

  defp get_socket(pid) do
    Agent.get(pid, &Map.get(&1, "socket"))
  end

  defp set_value(pid, key, value) do
    Agent.update(pid, &Map.put(&1, key, value))
  end

  defp get_value(pid, key) do
    Agent.get(pid, &Map.get(&1, key))
  end

  defp do_connect(hostnames, i) do
    try do
      host = elem(Enum.at(hostnames, i), 1)
      Socket.TCP.connect!(to_string(host), 25, packet: :line)
    rescue
        Socket.Error ->
          if Enum.at(hostnames, i) == nil do
            {:error, "all mx servers down"}
          end
          do_connect(hostnames, i + 1)
    end
  end
end

Most Liked

IloSophiep

IloSophiep

I think there are two “default” ways to handle state (and a lot more specific ones):

  • Store them in the client process
  • Store them in a dedicated process

Because Elixir is really good with processes people sometimes expect everything they code to do “process-y” stuff. But if you can, i think, it is recommended to start simple and do not introduce processes if you don’t need to. Your first sentence makes me think, that might work for you.

As an example: You have a module TodoList with a function add/1 that adds one item to the list. You might think “How / Where do i keep that list, so calling add/1 three times keeps all the old entries still there?” The easiest answer is "Just return the data to the caller and let them keep the state around! So instead of

defmodule TodoList do
  def add(item) do
    # Where does before come from?!
    [ item | before ]
    :ok
  end
end

you use

defmodule TodoList do
  def add(state, item) do
    # renamed "before" to "state", so the
    # concept hopefully becomes clearer
    [ item | state ]
  end
end

so the caller of your client keeps the state around and hands it to you for you to work on it.

Sebb

Sebb

I find :gen_statem very hard to understand. I prefer functional fsm with the state in map/struct and pattern matching in multiclauses as handlers. Also because fsm can become hard to test if they have side effects (like timeouts) I think its worth to think about how to make them as pure as possible.

Where Next?

Popular in Chat/Questions Top

woohaaha
I’m coming from Ruby and Rails. I have read some Elixir and Phoenix books. They shed a lot of light about building applications in Elixir...
New
dopomecana
The title of this topic may sound silly at the first glance. But why I trying to ask you guys is how to go to the next level? I am curre...
New
loganhelms
A while back, I read a great book by Luis Atencio titled, Functional Programming in JavaScript. In section 7.3, he discusses memoization ...
New
prxssh
I’ve been writing Elixir and Phoenix professionally for the past year and have grown quite fond of them. I’ve read books like Elixir in A...
New
asfand
Hi Everyone, I am a student and know basics of web development, used some php and ruby, but I am not an expert in any. I want to learn E...
New
wallyfoo
Long story short, I have a real world need to create a view for hooking up a shipping terminal to live data, but because of some early ar...
New
younes-alouani
I’m studying Phoenix Framework but I want to understand basics first. Which book/mooc explains better the Design of Network-based Softwar...
New
InkFlo
Hi everyone, This year I’m graduated from Bachelor Degree (in computer science) from France (not really a bachelor, the exact term is “L...
New
imanuelgittens
Hey all! Just joined the forum and I’m very excited to learn the language. I just finished reading the documentation and I was wondering ...
New
Fl4m3Ph03n1x
Background I am trying to do the typical Ceaser cypher exercise in Elixir. The description of the exercise is as follows: Create an impl...
New

Other popular topics Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement