vlad.grb

vlad.grb

Feeling lost with umbrellas

Just straight to the point.
Probably I do some overthinking but just give me a try.

The best way to understand something is to take live example. When user makes a post all the subscribers should receive 1 notification which is just regular record in the feed. So once you have a huge amount of subscriptions the posting process becomes slow.

How can I leverage the power of actor model and delegate to another process to select all my subscribers and insert back notifications for everyone?

So in my mind it must be solved within umbrella app which has an app for API as before and other one aka notifications system. But what is the best approach suits here? The questions in my head rapidly grow if I go deeper. Do I need to worry about downtime of notifications system? What are the costs to transfer ~1k user ids to another app? What if want to move notifications system to another physical machine which increases downtime of a node in case? Shall I store locally last successful notifications fanout?

Most Liked

cmkarlsson

cmkarlsson

Firstly. Your questions has more to do with how to design applications using processes than it has with umbrellas. For me Umbrella application is more a way to organize your code rather than how to design the actual software.

The building blocks for OTP are: Releases → OTP applications → Supervision trees → Processes.
The code organization is: Modules → Functions

So in your case you clearly want a separate process for doing notification. This can be done in the same module and even function as your initial code. You are still using the “actor” model.

Once you start formalizing and handling corner-cases with normal processes you end up re-inventing OTP so that is why this is the recommended approach to start with.

To work a little bit with your “live example”

In this case I would have started by hiding the “notification” call behind a module.

Lets say your notifications are email notifications to each subscriber. Your original code looks like this:

def new_post(author, params) do
    {:ok, post} = Blog.new_post(author, params)
    subscribers = Blog.get_subscribers(author)
    Enum.map subscribers (fn s -> send_email(s, post) end)
end

Then I want to hide the implementation detail of the notifications into a module.

def new_post(author, params) do
    {:ok, post} = Blog.new_post(author, params)
    subscribers = Blog.get_subscribers(author)
    Notification.notify(subscribers, post)
end

defmodule Notification do
  def notify(subscribers, post)
      Enum.map subscribers (fn s -> send_email(s, post) end)
  end
end

Once this is done you can re-factor Notification whichever way you like. In the first take you just spawn a process to make the notify asynchronous.

def notify(subs, post) do
    spawn(fn -> Enum.map subs (fn s -> send_email(s, post) end) end)
end

Then you want to add some safety to it so you implement, spawn_link and/or use monitors.

Then you move into having the Notification system as a GenServer so that you can have it supervised.

Once this is done it can be plugged into any supervision tree. It may be running locally in the same OTP application as your original app. Perhaps you want it to be a separate OTP application. Then you have the choice of using umbrella apps, which I tend to do if the OTP application is not really stand-alone and depend on the other apps in the umbrella, or a stand-alone OTP application you bring in through mix dependencies.

If the notification system is truly important so that you should not be able to save a new post without it you reflect this either through the supervision tree. Or through having your original OTP application depend on the notification system.

In either case if the notification system dies, your system will die (and hopefully get restarted by the BEAM VM).

The cost of transfer is really between processes and not between apps. And sending stuff between processes is basically how you do anything in elixir so even sending quite large data structures is OK. Something like 1000 user id’s is no problem at all.

The great thing about erlang processes are location transparency. You interface with the process the same way if it runs on a separate node or not. In terms of failure handling it makes it a little bit harder though as you can’t (or should not) use supervision trees between physical nodes and relying on “erlang distributed applications” is not really recommended as they are prone to split-brain scenarios.

If you worry about the downtime here you would have to have some sort of synchronous reply from the notification system, at least so that you know it has received your message and perhaps persisted it. What if the reply is missing? Well, if you go down this route you are in for a treat (Two Generals' Problem - Wikipedia) and you will either become a professor of distributed systems or (as the rest of us) just completely insane.

At the end of the day you have to decide how important notifications are for you. There are very few notifications systems that can guarantee delivery.

So when you save your post you have to decide how long to wait for. Your notification system could persist the notification task before replying to the caller and then clear it off once you have sent all notifications.
In any case you always run the risk of not sending a notification or sending too many.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
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
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

We're in Beta

About us Mission Statement