habutre

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

First 10 of 14 Posts Switch mode

kokolegorille

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

habutre OP

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.call would decouple web and service applications is because the GenServer module is available all around and having a backend module MyBackendApp.func present 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

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

  • I would not use GenServer.call outside the client part of a GenServer, but send
  • GenServer already is a client-server, and I would put the API here.

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

tty

If we split the server side to public API and handle_call callbacks 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.

defmodule Server do
use GenServer

def init_context(), do %Server{}
def send_update(pid, context), do GenServer.call(pid, {:send_update, context})

At this point the client doesn’t need to know anything about context, it doesn’t need to know how send_update works.

send_update might perform some pre-checks before calling the Server, it might chain several other GenServer.cast/call before calling the service. It might change the call to GenServer.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

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

habutre OP

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.

    MyPhoenixApp                              MyBackendApp               
  +------------------------+              +-------------------------+    
  |                        |              |                         |    
  |                        |              |                         |    
  |                        |              |                         |    
  |                        |------------- |                         |    
  |                        |              |                         |    
  |                        |              |                         |   -
  |                        |              |                         |    
  +------------------------+              +-------------------------+    
     node0 phoenix app                      node1 backend elixir app 

Backend service relevant code

defmodule MyBackendApp.Stock do
  use Genserver

  def stock_prices(server, {period: dayBefore}) do
    # ... supressed code
    GenServer.call(server, {period: dayBefore})
  end

  # ... supressed code

  def handle_call({period: dayBefore}, from, state) do
    Stock.retrieve_all_stocks(dayBefore)
  end
  # ... supressed code
end

Web client relevant code

defmodule MyPhoenixApp.MyBackendApp.Client do
@moduledoc """
  This client will be used on Controllers or other modules interested on stock's prices
"""

  def stock_prices(server, {period: dayBefore}) do
    # ... supressed code
    MyBackendApp.Stock.stock_prices(server, {period: dayBefore})
  end

  # ... supressed code 
end

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 that GenServer.call calling 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

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 MyPhoenixApp and MyBackendApp (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

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.call requires the caller and recipient agree on the shape of the tuple ({period: dayBefore} here) so it’s also coupling.

MyBackendApp.Stock.stock_prices above 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 like GenServer.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

axelson

Scenic Core Team

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

habutre OP

@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 API or Messaging brokers, as Erlang/Elixir provides the benefits of using distributed programming in its soul I wanna use in the right way. So if GenServer is not the way to go could be Task or simple Process?

@axelson I just used node0 and node1 for 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 between MyBackendApp and MyPhoenixApp

@peerreynders brought a lot of resources that I take as homework in order to better understand how to proceed.

Where Next?

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
silverdr
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated! To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead. Sta...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
michallepicki
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement