TomGrozev

TomGrozev

I am trying to setup elixir in a microservice architecture and have each of them (i.e. nodes) communicating efficiently. For context, there is a product microservice and a phoenix frontend microservice (there are more but these are relevant). I have got them connected through some custom module that is a dependency on both. This code basically spawns a task locally to spawn a task on another service to do whatever (i.e. create a product), this works great using a call similar to Connector.call(ProductMicroService.Products, :create, [some_product]). The downfall of this is when it comes to using Ecto schemas in Phoenix since it will try to call the schema module functions (e.g. struct) locally and not in the products microservice where the schema actually exists.

Now I have a couple possible solutions but none of them seem to appetizing and all seem very ‘hacky’. Some ideas I had are:

  1. Create copies of the schema modules on the frontend microservice
  2. Create fake schema modules on the frontend microservice that has all the functions but redirects calls the the real schema module
  3. Override the relevant functions in Phoenix (there are a lot)
  4. Some alternative?

For the more synchronous calls I believe the direct node communication is best and for asynchronous things a message queue (RabbitMQ, etc.) but is there a better way that I am missing? Anyway this is done, Phoenix still needs to be able to access these methods on the schema. It doesn’t seem to be an issue with anything other than phoenix.

Showing Posts 1 to 8

olivermt

olivermt

Could you elaborate a bit on why you want it this way? It sounds like you are adding a lot of complexity for gains I don’t see.

To use built in node communication in a somewhat easy manner you need codebases that are either the same or very aware of eachother, which sort of brings you back to a monorepo just split across more servers.

Why not use a http layer for the synchronous parts? Build a authority of sorts on the web node and pass it along with each request to keep the microservice acl etc.

Laetitia

Laetitia

In microservices architecture, the microservices share “raw data”, i.e the interfaces can expose maps, not struct. Each microservice must have its “view” of the model. For instance, a product in microservice A may have some different attributes from a product in microservice B. The output of microservice A will be a map, and microservice B will receive this map and convert it to its struct or schema.
Phoenix offers a backend-template application, so you don’t need microservices and you can use the schema along all your application but if you really need microservices, it’s a good way to use Erlang node communication and to apply decoupling between them.

al2o3cr

al2o3cr

Option 4: follow the first rule of distributed objects and don’t divide the system up this way.

AFAIK there aren’t any common patterns that would recommend turning context or view functions into remote procedure calls. Code in the “frontend” should interact with the “backend” via a clearly-defined API.

One other thing worth thinking about: the remote Task.Supervisor is single-threaded, so unless you add a lot of complexity that can be a bottleneck at high concurrency.

mpope

mpope

I have seen this solved before in a Java microservice cluster and it might work well for your case. Services that communicate had a shared library of DTO class definitions. They weren’t the entire schema, but it was more of a request level object. The same could work for your schema definitions for task spawning. Two services could share a hex package (or even a git submodule) that has the shared definitions. That might not be a very ‘erlangy’ way of solving things, however it seemed clean enough in the Java environment. The downside was that both services needed a redeploy for schema changes, and the receiving serviced needed code around to handle the previous ‘version’ of message format that could be cut away after a full deploy.

TomGrozev

TomGrozev OP

The idea is that each service will likely be split across multiple servers with some extra instances of some whereas others may only have a few instances. Having the services split allows for teams to work on them individually and operate on their own and merely interface with other services. Using a HTTP layer is no different than using node communication, just the node communication is a bit more of a robust thing that will not require as much extra development effort as an entire HTTP API.

I understand what you are saying, so basically each instance has no knowledge of the source or, in a way, meaning of data, hence why structs will not work.

That is quite helpful thank you.

That is a good point, either will need multiple task supervisors or use an alternative for spawning tasks.

Yes, this is a significant downside as it doesn’t isolate the services entirely.

Exadra37

Exadra37

I would recommend you to read the @PragDave Programming 1.6 Elixir book or his video course to see how he suggests to tackle what you are trying to do. Basically you will work with what the Nerves team coined of Poncho projects in terms of folder structure, but with a very nice and clean separation as you want, were teams can work independently and code can live in separated repos, but are deployed in the same server until the day it is necessary to scale it to multiple servers.

al2o3cr

al2o3cr

The phrasing is a little flippant, but the message is serious. Imposing a network boundary where there was only a module boundary before has significant costs:

  • duplication (for things like data definitions)
  • latency (network calls take time)
  • fallibility (network calls can fail)
  • deploy complexity (network calls may interact with servers that are a different version)

The biggest hazard in building a distributed system is that you get all of those headaches automatically, but you only get the good stuff (independent scaling, independent deploy, etc) with the right implementation.

For instance, an Elixir novice who confuses GenServer with “a server” and makes each piece of their application a single GenServer will produce a single-threaded performance tarpit.

The “rule” is a reminder to carefully consider the consequences of your decisions - what are you getting in exchange for all the additional complexity?

TomGrozev

TomGrozev OP

I read this a long time ago, will give it a re-read with this project in mind, thanks.

This is a good point, but the idea of separating services is that this communication between them should be minimal. I see the error I made in having a separate service for the frontend, it does not seperate them correctly as it must know the context behind all the data, which breaks the rule.

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
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
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews