epitaph

epitaph

Architecting Systems with Elixir

Hey Guys,

I’ve recently been playing around with Elixir (and Phoenix) for a personal project it has been a pleasant experience so far. However, there’s something that’s been nagging at the back of my mind regarding how “enterprise” Elixir systems should be architected.

A bit of background - professionally I work for a small product team building and supporting an internal platform (can’t say much more than that, unfortunately) which is made up of microservices written primarily in Go which communicate using gRPC and are deployed to Kubernetes. Overall, this works exceptionally well. gRPC and Protobufs make it easy to talk to different services in a way that feels “native” with relatively low overhead. Kubernetes and Linkerd make auto-scaling and load balancing work without me having to think about much and I can spin up a new service and have it deployed in a matter of minutes.

From what I have read about Elixir and more so the Erlang VM, software that runs on the BEAM is not too dissimilar to what I just described. You can have many different applications running inside the runtime at once (à la Microservices) and the BEAM handles orchestrating these applications (à la Kubernetes). But where my confusion comes in is when you progress from something like a CRUD application to something more complex.

As an example (hypothetical) if I wanted to build some sort of real-estate analysis application at a high level it might involve the following “services”:

  • Broadway to ingest data from various real-estate data providers.

  • NX/Torchx for doing some sort of analysis on the ingested data.

  • Oban for background tasks, like downloading images of listing and persisting them to S3 or sending scheduled property update emails to users.

  • Phoenix for the web frontend to view the analysed data.

What is considered the best practice for architecting something like this? From what I have managed to gather these are the two ways that seem most intuitive to me:

  • Creating an umbrella project to keep all the services together in a mono-repo but with some level of separation (however, I have seen that some people seem hesitant to recommend using umbrella projects). Coupling this with something like libcluster and/or Horde means it could easily be deployed to something like fly.io and would have good utilization of the underlying hardware.
  • Create a project per “service” and have them communicate via some sort of API (I see there is a gRPC package for Elixir, tho it doesn’t seem production ready so I guess this is not a common way of doing things). The downside here is we now have network latency between service calls and would have worse utilization of the underlying hardware if using something like fly.io

Sorry if this was a bit of an essay, but would appreciate any advice and if there are any good resources for me to read on the topic that would be great too!

First Post!

tj0

tj0

There’s no need to do either actually since each genserver pool is supervised and could be considered a service. BEAM has its own communication fabric, so gRPC is also unnecessary unless you are planning to do polyglot services.

I’d just start with a regular phoenix project and add modules / genservers as required.

Most Liked

josevalim

josevalim

Creator of Elixir

My suggestion would be to roll with a single repository, no umbrella, specially if you are a small team.

For example, if your Broadway pipeline may need to use some database resources, and if that’s on a separate application, now you need either a separate RPC service or an additional application to encapsulate shared logic.

The benefit of the Erlang VM is exactly to juggle many things at once (Go as well?), umbrella or not. :slight_smile:

I would go with umbrella and/or separate services if you have a larger team and you would naturally organize around some segments or if certain parts of the application have distinct scalability needs.

PS: using gRPC with Elixir is also completely fine. :slight_smile:

cjbottaro

cjbottaro

Ohh buddy, you’re gunna stir the hornet’s nest with this one! :wink:

My day job has a similar setup as you. We started with an umbrella app, but then just merged everything into a monolithic Elixir app with multiple top level namespaces… and I think it’s MUCH more simple.

We also use Kubernetes… but it’s more about individually scalable deployments, not code separation. We have dozens of Kubernetes deployments, most of them hooked up to HPA, but they all use a shared “Elixir app” build image.

Also, the concept of “RPC” in a dynamically typed language is interesting to me… :slight_smile: We have “RPC” which is really just API calls over BSON+NATS (as opposed to JSON+HTTP). But is that really RPC? Isn’t RPC all about static type analysis, i.e. making remote calls look like local calls, including static type checking?

LostKobrakai

LostKobrakai

I’d add to the already given suggestions, that elixir is usually rather nice to refactor. So this is really less of an “either-or” question as one might expect. In other languages I’ve seen people opt to start out with the most complexity from day 1, because the expectation is that things become a big ball of mud and you cannot change things later. To a degree this might happen with elixir as well, but the functional nature of elixir will allow you to change things quite a bit easier, so you really can start with the MVP (even in architecture) and change things only when there’s demand for it.

Last Post!

cjbottaro

cjbottaro

No, our dir structure is vanilla Phoenix app, but with top level namespaces in lib…

/our_app/lib/our_app/application.ex
/our_app/lib/our_app/*
/our_app/lib/our_app_web/*

/our_app/lib/foo/*
/our_app/lib/bar/*

I.e. Foo and Bar are top level namespaces, and we have only one BEAM application which is :our_app.

If it wasn’t clear, I meant we reverted from umbrella apps to just normal Elixir/Phoenix apps, i.e. whatever is spit out by mix new or mix phx.new. And we don’t mind having top level namespaces. So instead of OurApp.Datastores, we simply have Datastores.

We conditionally start processes/supervisors based on environment variables, so something like this in application.ex…

children = if System.get_env("START_WORKER") do
  [OurApp.Worker | children]
else
  children
end

I don’t see much benefit to umbrella apps.

Where Next?

Popular in Questions Top

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New

Other popular topics Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement