habutre
Application communication best practices
Hi all!
This is my very first question here!
I was playing a bit with Phoenix and Elixir in a solution where I would like to use Phoenix as a client application which will provide solely a web interface for backend services allowing in future using other interfaces such as CLI or integration with other technologies through messaging or http.
Between Phoenix and Backend Elixir communication I would like ideally to take advantage of Erlang/Elixir ecosystem and make that communication through processes and I decide to use Genserver for that.
What I did was define the callback handle_call in the backend service and on client I wrap the Genserver calls in a MyApp.BackedEndApp.Client module which uses purely Genserver.call to invoke the backend functions.
I had received some critics from my team members about that approach. What could be a best practice in that case when I want to keep client and server decoupled in the same time I want to take the ecosystem advantages? I mean without HTTP/Rest or Messaging communication
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
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 14 Posts
kokolegorille
Welcome to the forum,
What about defining the GenServer call inside public api of the GenServer? (Remember, there are two part in the GenServer)
This way, Client will call client api of the GenServer instead of calling itself GenServer.call. It would be easy to decouple and use CLI. See pragdave’s approach if You can…
In practice, it would be like moving your code from Client to GenServer api, and replace them with api call instead.
habutre
Hey @kokolegorille thx a lot for your point, I saw the youtube video from PragDave in the past and I really like the aproach. Now I realize my first mistake on relying in the backend service implementation directly, but I still keep with some noisy in my head.
What make me think using
GenServer.callwould decouple web and service applications is because the GenServer module is available all around and having a backend moduleMyBackendApp.funcpresent on my client wrapper sounds weird and looks like my web client is now heavily dependent on my backend service. Is that the way to go? I mean using a HTTP client, Message queue or GenServer.call seems to be a standard way to communicate between applications/components/services rather than use a explicit API which the client should not know the details or even be aware of.kokolegorille
Without code sample it’s difficult to tell.
I am sorry, I have the same critical point of view than your team members, I really don’t like your approach.
Mainly because
And I am not sure I follow You on this idea of client/server.
Decoupling should allow You to switch between CLI, or Web Interface. There is exactly this in pragdave’s course.
I don’t want to be discouraging, but I invite You to read your post later and see if You still find it’s a good idea.
tty
If we split the server side to public API and
handle_callcallbacks this allows us to change both in tandem easily. In addition clients do not need to know the internal structure of the server.e.g.
At this point the client doesn’t need to know anything about
context, it doesn’t need to know howsend_updateworks.send_updatemight perform some pre-checks before calling the Server, it might chain several otherGenServer.cast/callbefore calling the service. It might change the call toGenServer.call(pid, {:update, %{context | callee_ts: DateTime.utc_now}})This style of programming is very old and widely used by C/Ada programs to hide implementation details.
jola
Based on your description I would recommend against GenServer. The thing is that the way Phoenix works is that it spawns a process for each request that comes in. This allows it to scale nicely over multiple cores. Assuming you start a single process to handle all the work you are effectively turning your app single-threaded. You’d get around that if you create a pool of processes etc, but that’s a lot of effort for what gain?
I would not use GenServer for this. Just create a client module and call those functions. This means your business code runs in every process spawned for requests, the way it is intended, and you still get the separation of concern and decoupling that you’re interested in. I don’t understand why you’d have two apps speaking over “HTTP/Rest” if all you’re concerned with is decoupling.
habutre
I will try to be clearer about my doubts regarding communication best practices, all of you give me good points but I still have an unanswered point specially because I believe I brought two question in one unintended.
The first thing @kokolegorille has clarified already and I agreed since the critics from my co-worker are correct and I was wrong. So I will keep the Genserver client/server together or at least in the same application.
Below I draw the applications that I have and a snippet on how I did, but it seems still wrong to me and I really appreciate any help.
Backend service relevant code
Web client relevant code
When I mentioned coupled code I mean this line
MyBackendApp.Stock.stock_prices(server, {period: dayBefore})where the client App is heavly dependent on my backend service.Initially I have written this line like
Genserver.call(:my_bakend_server_as_atom, {period: dayBefore})because I thought that way I will rely in a standard GenServer module instead on my backend implementation. Now I had regreted thatGenServer.callcalling the backend module direct but it seems still coupling my phoenix with my pure elixir backend application.How shall I proceed in that case?
In this post Elixir in times of microservices « Plataformatec Blog josevalim had mentioned about communication and as far as I could understand he was using agents for that.
peerreynders
I recommend you have a good look at this topic, the associated video and in particular this post to get a better sense of the relevant use cases for distributed BEAM.
In short the general recommendation would be to not use distributed BEAM to communicate between
MyPhoenixAppandMyBackendApp(given that they are running on separate nodes and consequently physically separate machines connected via an unspecified network).Now that doesn’t mean that it can’t be used but one needs to be aware of a number of issues that this approach has which imposes some constraints on the technical infrastructure (e.g. the nodes are running in a secured environment on a common backplane or similar localized arrangement) that may or may not be a showstopper.
al2o3cr
“Coupling” takes a lot of forms - calling a function in one module from another is one kind, but using a generic function like
GenServer.callrequires the caller and recipient agree on the shape of the tuple ({period: dayBefore}here) so it’s also coupling.MyBackendApp.Stock.stock_pricesabove reminds me of the example given in “When (not) to use a GenServer” in the documentation. Using GenServers for code organization can cause undesirable side-effects: for instance, using code likeGenServer.call(:my_bakend_server_as_atom, {period: dayBefore})with a fixed name means there’s a single process handling messages one at a time.axelson
Thanks for sharing more about your architecture! To better understand your use case I was wondering if you could share your need for two separate nodes. Is there a specific reason that you want two separate nodes instead of running all of the code on one node? (There are definitely good reasons for both architectures, it’s just when you add multiple nodes you add complexity so I’m trying to understand if it seems worth it in this situation)
habutre
@al2o3cr This is exact what I wanna learn with that question, how can I make an efficient inter-components communication follow the practices used on Elixir world. My experience with other technologies in general follow two common ways
Rest APIorMessaging brokers, as Erlang/Elixir provides the benefits of using distributed programming in its soul I wanna use in the right way. So ifGenServeris not the way to go could beTaskor simpleProcess?@axelson I just used
node0andnode1for didactic reasons to explicitly demonstrate I want to ensure both components are running independently. I can run multiple nodes inside the same server without so many networking issues. But is just to create isolation betweenMyBackendAppandMyPhoenixApp@peerreynders brought a lot of resources that I take as homework in order to better understand how to proceed.