Simple GenServer inheritance

I know we aren’t supposed to use inheritance, and I’m all for not doing so, but in the simple case of a bunch of GenServer’s that all share exactly the same common handle_cast/3 of a particular message, I would like to put that handle_cast definition in a shared module where all the functions (API and callbacks) are as to avoid duplicating code in each individual GenServer module. Putting shared API fns works in this way because they are called client side, but how or can I put shared handle_xxx callbacks in a shared module this way as well?

I looked at behaviours, and that seems to be how general shared functions can be implemented, but GenServer callbacks seem special because they are ‘used’. Do I implement a new ‘dummy’ basemodule GenServer, and then have the other GenServers ‘use’ that basemodule Genserver?

Thanks.

You could make a “using” macro module and then “use” that in each module that needs the logic.

I did that recently to attempt to DRY up several dynamic GenServer modules, initially just using it in the newest module. I wound up ditching that and un-DRYing it because the new module was having some weird behavior with our APM tracing and I didn’t have the time to chase that down. I want to take another stab at it though at some point.

You could probably also just make a module with all your common handle_ functions and import it, though I haven’t tried that with a GenServer.

It’s funny though, since leaving behind over 12 years of Ruby and switching to Elixir a couple years ago, how little I miss/need inheritance or encapsulation. I really did explicit, magic-free pipelines of data processing.

1 Like

If you use ‘using’ macro to fill in boilerplate/repeated code, the next four people who have to maintain your code will curse you under their breath when they do code chases through that module… (Yes I’m aware that GenServer does this)

1 Like

Fair enough. I was adding another API worker GenServer to an app that already had 5 modules with 95% the same code. I’m basically fine with the code duplication (it’s really not that much code) but in an attempt to answer the OP’s question, what approach would you take to eliminate the duplicate code?

Agreed. Try not to use using unless you really think it through and the saving is large. The OP’s problem can be solved by taking the meat out of the handle_cast and put in a shared function. One line handle_cast shall be good enough.

4 Likes

That‘s the way to go. Callbacks are about implementing an interface one should be able to see in that one module implementing it. It‘s fine to share implementation details, but trying to share the whole callback will just make things worse. It‘ll severly hinder composition (like 2 custom functions heads + 1 shared one for the same function/arity) as well as the already mentioned downsides for the next person working on the module.

2 Likes

+1 to this.

A great example is in :gen_server / :gen_statem / :gen_event - they all use functions from :gen extensively, for instance to define their own variants of start:

That is a viable option. Indeed, given the responses here that indicate that I’m just not missing some syntactic sugar or something obvious, I will try to factor out all similar code and put them into their own plain module, and just pass the state into and out of the funs.

1 Like

Ssh! The first rule of :gen club is that we don’t talk about :gen club

1 Like