Manzanit0

Manzanit0

Designing an Agent with side-effects on start_link

Hi all!

Some time ago I started to work on an application and I designed one of it’s workers with a start_link/0 such as this:

  def start_link do
    Logger.info("Starting worker")

    status = @my_service.get_api_response!()
    Agent.start_link(fn -> status end, name: __MODULE__)
  end

I wasn’t too worried because the business logic didn’t have any side-effects apart from that start_link and the workaround I found was to simply run mix test witht the --no-start flag while starting up all the necessary applicaitions in the test_helper.ex.

The issue I’ve encountered is that when trying to adopt Phoenix now, it’s no longer convenient to run mix start --no-start, so reality kinda called back and made me deal with the real issue: How would I design an Agent/Genserver which needs to pull its initial status from an external HTTP API?

Most Liked

peerreynders

peerreynders

How would you test that?

Following dependency rejection you would design the system to obtain the initial state before even attempting to start the GenServer - then there is no dependency that needs to be mocked out because the initial state is handed in as a plain value.

danj

danj

Two things come to mind:
Unless you want the whole application to fail at startup if the initial state request fails, issue an Agent.cast after the start_link to initialize the Agent. Let the cast handler do the init. Since that cast will be first in the message queue, nothing will get to the Agent before it’s initialized. Also, in the init, you can use an Application env to control if it pulls that initial state. While you can’t use Mix.env, you can use a config that’s dependent on the Mix.env that’s set up at compile/release.

LostKobrakai

LostKobrakai

How about just not testing the genserver started by your supervision tree, but starting up additional ones with custom configuration just for your tests:

setup do
  {:ok, pid} = start_supervised({MyApp.TheWorker, mock_config()})
  {:ok, pid: pid}
end

test "whatever", %{pid: pid} do
  assert MyApp.TheWorker.do_stuff(pid)
end
peerreynders

peerreynders

--no-start fixes that

--no-start - does not start applications after compilation

https://hexdocs.pm/mix/Mix.Tasks.Test.html#module-command-line-options

So mix test without --no-start runs integration tests while mix test --no-start only runs the fast isolated tests.

al2o3cr

al2o3cr

The usual idiom would be to have another process, external to the worker, that holds onto that state and then supplies it to the worker when requested (maybe in the worker’s handle_continue?)

One possible sequence of events would look like:

  • supervisor boots the “state fetcher/holder” process

  • fetcher/holder boots up

  • (optional) if in production, the fetcher/holder can make the get_api_response call from handle_continue. Otherwise, that call is deferred until the worker needs the data.

  • supervisor starts the worker process

  • worker process makes a blocking call from its handle_continue to the holder process to get the state

  • in production, that call returns promptly once get_api_response has completed

  • in test, that call would block (maybe with receive?) waiting for the go-ahead from the test setup code

  • if the worker process crashes, the fetcher/holder can supply the already-loaded state to it when it restarts

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
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
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement