Is an Elixir GenServer so different from an Object of a Class?

That’s right you don’t “object”! :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley: :smiley:

…I’m sorry. And I’m not even a dad!

@derek-zhou has the most succinct advice for newbies who would otherwise just go ahead and repurpose module+process as an object anyway. Of course, there is always this package.

2 Likes

I was a Java web programmer. IMHO, GenServer processes are somewhat like “service objects” or “controller objects” or simply Servlet objects (I don’t know much about the C# ecosystem, sorry). These objects share the same properties:

  • Effectively singleton
  • Has an infinite lifespan
  • Stateless

Well, GenServers don’t have to be stateless, but if it can be stateless, then you might be better just use a simple module with a bunch of pure functions in it (maybe not that pure if you need to access a database).

I personally believe that those Java objects mentioned above do not even have to be objects. Classes with only a bunch of static methods should work fine, too. The only reason that those objects are objects is for the purpose of mocking in testing.

2 Likes

While you’re certainly free to use them only for what you described, a GenServer can model a specific instance among countless others, hold state, and be short lived all at once. I think they’re a bit too generic (by design) to nail down for a specific purpose. :slight_smile:

6 Likes

I think that this is something that must definitely be stressed. There is no way that processes and modules are related or have some inherent connection. Any process can use any module and any module can be used by any process. So as someone later pointed out I can have functions in mu GenServer module which can be called by other modules or used by other GenServers or processes. This is not something you should be very careful if you do it but there is nothing stopping from doing it.

And remember GenServers are just normal processes, there is nothing special about them. It is the same with modules.

Actually Erlang/Elixir is a very egalitarian system, all processes are created equal and all modules are equal. :smile:

EDIT: I can for example kill any process in the system I want. The fail safe and unstoppable way of always crashing the system is:

iex(1)> init = Process.whereis(:init)
#PID<0.0.0>
iex(2)> Process.exit(init, :kill)
System process <0.0.0> terminated: killed

Crash dump is being written to: erl_crash.dump…done

And no, there is no way of prohibiting this!

12 Likes

I think the comparison to classes is very apt, but there is quite a difference in application.

While in “OOP” languages almost everything is a class, I find that in Elixir I don’t write GenServers that often.

You said you are going to be working on an API. While there will be a lot of processes (one for each request, etc.), in your code most of the time when you want state, you’ll also want persistence, so will probably be using a database. Or you want concurrent lookups and to avoid a process bottleneck, so will use ETS.

In Java, you are immediately writing classes, at least one per source file. In Elixir I don’t think I’ve written 10 GenServers.

3 Likes

One thing I do in this regard is use a GenServer process to own the ETS table and to process writes which I actually want serialized and strictly managed (such writes are rare). Reads of ETS, however, are not processed by the owning GenServer and are made via direct query to the ETS table. Naturally, the GenServer and ETS reads are handled via a module defining API functions so users of such services are blissfully unaware that they’re ultimately talking to a GenServer, an ETS table, etc. but this shows a certain use of GenServers in the ETS using context.

A broader comment is that I’m always thinking of GenServers as really a runtime “service” mechanism more than a way to implement logic. The GenServers I use (whether they’re defined by a library or I’ve written them) are not really carriers of much logic beyond mediating the processing of requests and dispatching those requests to logic defined elsewhere. I’m also thinking about what failure modes might exist and how things restart (supervision trees, etc.)… again those are primarily worries of runtime mechanics and not business logic around processing data.

My perspective anyway.

3 Likes

Two main reasons for accessing ETS tables through processes, GenServers or others, are transactions and restricting access. A reason is that ETS has very llimited built-in transactions, basically all you have is updating counters. If you need to do a sequence of operations on an ETS table you need to do through a single process, and limit access to that table for other processes. Maybe allow many processes to read the table but only one process to write it.

Another reason is if you need for the data in a table to be kept secret. In that case you only allow to owning process to access it and if that process crashes then the table is automatically deleted. This is one way of keeping secret data of keeping data secret inside a process.

7 Likes