Fl4m3Ph03n1x
Background
As the title implies, I am reading Funcional Web Development with Elixr, OTP and Phoenix and I have finished today chapter 4 where they introduce OTP and GenServers.
For those unfamiliar, the book walks you through the process of making a Battleships game (but with a different name).
Up until this chapter, everything was nicely separated - we had our business entities (Ships, Guesses, a Board, etc) and our Business Logic in the form of a state machine (which I think is rather brilliant).
So now we have the entities to play the game and the logic. This is where the Game entity comes along and this is what confused me.
Questions
The Game entity is a GenServer. Up until now, Ships and Guesses were merely modules with functions. But now we introduce an OTP behavior to the business entities. This brings up a few questions:
- Isn’t functional programming supposed to decouple logic and entities from third party concepts like OTP behaviours? I mean, if I decide to port this app tomorrow, I can’t use the functional core because it is coupled to OTP.
- Should OTP behaviours be part of the entity layer?
- Should I even care if an entity is an OTP behaviour or not?
Looking forward to someone with some architectural knowledge for some hints on how to see this.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
LostKobrakai
You could always go as far and do it like Dave Thomas proposes and separate the GenServer code from the pure state manipulation.
But keep in mind: The game state is still a struct and therefore plain data. Also the state transitions are pure (state + input => new_state). There’s just some OTP boilerplate around it, which you’re correct, can’t be simply ported to a different language. But I’m wondering if that’s really a useful metric to judge code by. It seems like wanting to not use e.g. closures, because they might not be available in other languages.
Fl4m3Ph03n1x
I am actually a big fan of Dave Thomas and his talks and approaches actually. His course is on my To Buy list
Wish I could talk to him more though. He looks so provocative in his talks but I don’t see him often here. Maybe I need to be more active? Who knows
Good argument. My counter argument is that closures are a core feature of any functional language and a core feature of most languages these days. OTP is not a core feature of anything besides Erlang (and some of the languages built on top of the BEAM VM depending on the level of inter-operability).
I am willing to push it even further and say that the idea of functional programming is to decouple code from side effects and third party tools and that because OTP is an external element code should be decoupled from it.
As anecdotal as my life may be to be used as evidence I am also going to say that I suffered through some really harsh migrations in the past because logic was coupled with language features. You know the layered architecture schema? (onion, hexagonal, lasagna, you name it) - it is impossible to do when everything is coupled in a petri dish.
I am not saying “don’t use language specific features”. I am saying (and I think this is the purpose of FP in general) “decouple your logic from external systems”.
Going back to the topic, do you agree with the author’s decision?
Would you find this code easy to port to erlang or another language?
jeremyjh
Behaviours are a language feature that are orthogonal to concurrency, and which you may take advantage without using any OTP behaviour. GenServer is one particular behaviour, but anywhere you need to decouple interface from implementation a Behaviour can be useful, even in a single-process. A common use case is for testing, we follow the pattern outlined in Jose’s blog article at some of our application library boundaries.
eta: This is a very common pattern in other languages with contract/implementation systems such as C#, Java, C++, Typescript, and even functional languages like OCaml and Haskell.
A GenServer is appropriate when you need single-threaded access to shared state between multiple processes. This could very well be a good way to model a lot of games, but it wouldn’t be essential and there are other means of sharing mutable data in Elixir.
LostKobrakai
I very much do, because the book is trying to explain to the reader how GenServers work and not how layered architecture works. Also while OTP is indeed a feature of the BEAM in this case it’s just the language specific implementation of “long running process holding state”. You probably could even use an Agent, which will reduce the lines of “stateful boilerplate” even more. If you’d like to port the application you’ll need to find another way to keep the state around, but the fundamental transformations of the actual state will stay the same.
Granted I know Elixir properly, yes. The handle_… callbacks are probably quite easy to translate to e.g. a
function(state, input) :: {new_state, effects}format, like e.g. elm is using it. Then you just need to find a way to persist the state.Fl4m3Ph03n1x
@jeremyjh I am fairly familiar with behvaiours in general. It’s the OTP part hat got me confused (they are special after all, or so I thought). Although you make a good point (if I understand you correctly): “OTP behaviours are no different from any other behaviour.” The logical implication here is that if I port this game core to something else, I just need an implementation that obeys the contract specified by the OTP behaviours and all will be fine.
I got a different idea when I got an exemplar of the book (page xii):
The author then proceeds to describe how each chapter will focus on a specific layer until we reach the outermost layer, which is where we add Phoenix.
Yes I could, but we both know Agents are glorified GenServers
Besides this is not where I want to focus the discussion.
So, you see no downsides whatsoever of coupling logic to specific OTP behaviours?
This brings up another interesting question “What are the disadvantages of coupling logic to OTP behaviours, if any?” (for another post maybe)
So, I take it from both of you that the answer to:
Is a “No, it’s not important because it’s all a matter of implementing the contracts (behaviours) you use”.
Do I get it right?
peerreynders
Other languages don’t have the concurrency primitives (spawn, send, receive), process linking and monitoring that OTP is based on.
At the core a process is an infinitely recursing function where the updated state is supplied on each recursive call - which is equivalent to an infinite iteration.
One uses Erlang/Elixir to take advantage of these features and to be able to structure behaviour in a way that isn’t possible in other environments.
Fundamentally you should chose Elixir/Erlang because it makes it easier to express the solution to your problem - it follows that it would be more difficult to do in another language.
Maybe the problem is that you are trying to classify “Game” as an entity rather than as the “engine” of the application - this reminds me of the thought concerns vs. runtime concerns discussion with regards to To spawn, or not to spawn? which had an influence in the rewrite of the book.
Coupling
Essentially the Game “entity” you are looking for is either the
GenServerstate or some significant part of it.So indeed to allay your concerns you could factor that part out - away from the
GenServerif you wish.I presume we are talking about this:
https://media.pragprog.com/titles/lhelph/code/gen_server/lib/islands_engine/game.ex
lance
Hi @Fl4m3Ph03n1x,
I’m glad you liked the state machine chapter! I know you had some questions before you read it, and it seems like the chapter addressed those concerns.
It seems that the ultimate question you’re asking is, “Is it ok to represent a domain entity as a GenServer?” (Please let me know if I’m mischaracterizing that.)
My answer would be a definite “yes”.
To my eye, bringing OTP and Behaviours into the conversation is having the effect of muddying the waters a bit.
When we build a GenServer, what are we really doing? We’re defining callback functions that will work in a separate process. That’s it. OTP provides for the common wiring and plumbing to make that happen.
We’re still working with modules, functions, and data. The difference is that they are designed to run in a separate process (or processes).
lance
I think your first paragraph here really speaks to the original question. Thank you!
peerreynders
Better?
Now
Statecontains the logic/core (and can be tested separately) -Gamehas been reduced to a process (GenServer) shell.peerreynders
Now while it’s nice to not have the logic conflated with the
GenServerceremony it can get a bit boilerplate-y when you are dealing with numerousGenServers.One compromise:
GenServercallback module so that it is very clear what is whatThat way the “conflation” can be a bit less distracting.