Up to date code for GenStage?

I’m trying to create a (very) basic GenStage for Elixir 1.7.4 and GenStage 0.14.

I wan to have 3 steps: P -> PC -> C

This is for a very important app (eventually), so I don’t want to use “hello world” type code, but all the examples I’m finding use Supervisor.Spec which is deprecated, and I’m not sure how to do the alternative.

Here is my code so far (lizard_station.exs):

defmodule LizardStation do
  def start(_type, _args) do
    import Supervisor.Spec

    children = [
      worker(FCM.Producer, [0])
      # worker(FCM.ProducerConsumer, []),
      # worker(FCM.Consumer, [])
    ]

    Supervisor.start_link(children,
      strategy: :one_for_one,
      name: FCM.Supervisor
    )
  end
end

and mix.exs

defmodule LizardStation.MixProject do
  use Mix.Project

  def project do
    [
      app: :lizard_station,
      version: "0.1.0",
      elixir: "~> 1.7",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    [
      extra_applications: [:logger]
    ]
  end

  defp deps do
    [
      {:gen_stage, "~> 0.14"}
    ]
  end
end
defmodule LizardStation do
  use Application
  def start(_type, _args) do
    children = [
      {FCM.Producer, [0]},
      FCM.ProducerConsumer,
      FCM.Consumer
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: FCM.Supervisor)
  end
end

might work. That is, if you only needed to get rid of worker/2.

1 Like

Ok, that seems to work… is it possible to explain in a nutshell what the difference was with Supervisor.Spec and whatever this new way is? Everything I read is a bit confusing. Maybe a 2000 foot view would help?

It’s my understanding that when my app boots, its creating a supervisor to monitor my 3 genstage servers, and also passing in their initial states. correct? What is the old way vs new way?

Nothing has changed in the Supervisor itself. What has changed [in Elixir syntax] is how supervisor initializes the children. There is no need to tell the supervisor what kind of child is it—that info might be easily borrowed from the child itself. That said, instead if calling worker—yes, Elixir is open source—we might just provide a child spec which already defines that this chils is a worker (or supervisor.)

2 Likes

Do I have to call start() myself somewhere? I don’t think it’s running because its not showing up in :observer

Btw I’m using iex -S mix

EDIT: I fixed this problem… i needed to just generate a new project iwth --sup option and examine the new application.ex file and folder structure. I get it now

Follow up question, it says the tuple has to have 3 arguments @idi527 … i assume I can just put [] but I want to write idiomatic elixir so im not sure the proper way to handle that.

children = [
  {Consumer, []}, # doesnt need any args, but it forces me to put it
  ...
]

Basically it’s always going to force something in there… so I can see passing it [] but then I have to make ‘fake’ arguments and whatnot in the function itself… something like this I guess? It doesnt seem ‘right’ though

  def start_link(_) do
    GenStage.start_link(FeedAggregator, [], name: FeedAggregator)
  end

I can see this happening, where a callback requires an argument but you dont want to give it one, and you need a placeholder…so i want the ‘best’ way to do it?

As a sidenote, what is the idiomatic elixir way to write a piece of code that doesnt matter when its used in an expression, like here? I tried using _ but it says I cant use that. i think :state_does_not_matter is stupid

GenStage.start_link(FeedAggregator, :state_does_not_matter, name: FeedAggregator)

I tried using _ but it says I cant use that.

What says? I’m not sure I understand …

If the elixir compiler / runtime prints out any exceptions / warnings, could you please past them here?

well nothign is broken, Im just wondering “the elixir way” to deal with a callback that has no argument but you’re required to supply one. In tutorials people write :this_doesnt_matter which is stupid

This is how I currently have it which I dont like: (the _state and the [])

  def start_link(_state) do
    GenStage.start_link(__MODULE__, [], name: __MODULE__)
  end

and:

children = [
  {Producer, 0},
  {ProducerConsumer, []},
  {Consumer, []}
]

I could also imagine doing it like…

  • :ok and :ok
  • _state and []
  • [] and []
    etc etc… I just dont know which convention people choose for clean code

If state does not matter, the most idiomatic way I’m aware of, is to simply use nil or the empty list ([]) or the empty map (%{}).

The important thing is, that you still bind and pass the state around in your handlers, as if it were an actual thing but that you do not touch. That way its much easier to make it a thing later on.

2 Likes

Thanks!

Another question that just came up

I’m reading this article about how stuff is ‘autoloaded’: https://www.amberbit.com/blog/2017/9/22/elixir-applications-vs-extra_applications-guide/

Well… I installed the romeo XMPP library package, and I can see how Elixir autostarted it.

But how is this different from how I started Redis under my own application supervisor by putting it in children = []

Doesn’t this mean romeo is not supervised, and could crash and not restart? How do I know when to put stuff in children vs just letting elixir auto-load it

Each application has its own suppervision tree. So romeo ist supervised as well, at the same level as your application.

Some libraries are created in a way, to get (partially) integrated into your own supervision tree, as it makes configuring them easier, or having multiple instances with a different set of configuration.