What's the idiomatic way to broadcast messages related to context updates

Hello, I have a code organization question.

Let’s say I have a Blog app with posts.ex context which is responsible for managing posts. When a new post is added - I’d like to send a pubsub message about it using BlogWeb.Endpoint.broadcast.

What would be the right way to do it? The simplest one to me is to broadcast inside Blog.create_post, but it feels that Blog shouldn’t depend on BlogWeb. Other options I see are:

  1. Add another posts.ex context inside BlogWeb, that will either wrap methods like create_post (or for clarity create_post_and_broadcast and add broadcast there. This feels not to so bad, but caller has to know that for some operations he has to go to BlogWeb.Posts instead of Blog.Posts.
  2. Or again create this new posts.ex and just provide broadcast methods there, then caller must call broadcast after create_post, which sounds error prone.

Could you please tell if there’s a clear idiomatic way of doing it or share a good example? Thanks!

The simplest one to me is to broadcast inside Blog.create_post , but it feels that Blog shouldn’t depend on BlogWeb

I agree.

I would think: If there is only 1 place where I want to broadcast after create then, just broadcast it from there after create =)
If I find that there are multiple different places where I want to do that, then it feels like there is a need for such create_post_and_broadcast :man_shrugging:t2:

1 Like

What version of Phoenix are you running? Phoenix 1.6 starts the pubsub server under the app (Blog, not BlogWeb). Previous versions (don’t remember how far back) started it under the Endpoint.

So if you are using Phoenix Channels, when your channel connects, it should subscribe to changes on the models/contexts it is concerned about. Your context then broadcasts on the app pubsub (Blog.Pubsub) and the channel then maps that application pubsub to it’s channel client(s). It’s basically the same if you are using LiveView, just the LV does the subscription instead of the channel.

Chris McCord’s video intro to LiveView on the Phoenix home page explains the basic concept (it’s about mid-way through). Build a real-time Twitter clone in 15 minutes with LiveView and Phoenix 1.5 - YouTube

So it’s kind of flipping what you said on its head. The client (web-side) should subscribe to the app’s pubsub topic it cares about (i.e. posts). The client manages what to do with the info it receives from the app. The app-side is only concerned about publishing on the app’s pubsub. That subscriber could be a web client, or it could be another piece of the app, or even another application that shares the cluster. So the publisher is blissfully unaware of which clients are subscribing to it and receiving the broadcasts, and even what they are going to do with that information.

The app never reaches OUT from its domain. It only broadcasts on its internal pubsub. The client-side reaches INTO the domain from the outside, just as it does when interacting with context actions, and decides what it wants notified of.

Hope that rather long-winded explanation was helpful :wink:

3 Likes

@RudManusachi thanks for feedback! Yeap, there are many places where post can be updated from, so there should be a shared method. Would you put create_post_and_broadcast into Blog.Posts or BlogWeb.Posts or somewhere else?

Actually, I like what @drl123 is saying!

The app never reaches OUT from its domain. It only broadcasts on its internal pubsub. The client-side reaches INTO the domain from the outside, just as it does when interacting with context actions, and decides what it wants notified of.


However, even if we “push” events via BlogWeb.Endpoint.broadcast. I think it’s is still not the part of the Web layer (even though, it’s defined under BlogWeb), I would put it somewhere along Blog inside core…

And if our inner perfectionist doesn’t want the core domain to be coupled with Phoenix we could abstract “broadcaster” into a separate “behaviour” with an implementation based on Endpoint.broadcast injected into app.

As a result, the core domain doesn’t depend on Phoenix, but on “any” module, that can broadcast… it just happen that for this particular case it’s configured with the module provided by Phoenix =)

a basic example with compile time global configuration could be:

defmodule MyApp.Blog do
  @broadcaster Application.compile_env(:my_app, :notifier)

  def create_post_and_broadcast(params) do
    post = create_post(params)
    @notifier.broadcast("post", "created", post)
  end
end
defmodule MyApp.Notifier do
  @callback broadcast(String.(), String.t(), map()) :: :ok
end

defmodule MyApp.Notifier.Phx do
  @behaviour MyApp.Notifier

  @impl true
  defdelegate broadcast(topic, event, msg), to: MyAppWeb.Endpoint
end
# config.exs
...
confg :my_app, :notifier, MyApp.Notifier.Phx

Guys, take a step back here and look at how Phoenix.PubSub is used by Phoenix. It basically is configured to be the ‘Notifier’ for the Phoenix application. When you configure the Phoenix Endpoint, you specify the pubsub (aka Notifier) to be used as MyApp.Pubsub…just as you were proposing to do with MyApp.Notifier.Phx, BUT from the other side. Now the Web side depends upon the Core and the Core is not dependent upon and unaware of the Web.

IMHO that you might be better off to just have your core application explicitly list a dependency on the phoenix_pubsub library in mix.exs, rather than have it injected by the Phoenix (Web Framework). You don’t need all of Phoenix here if you aren’t going to use it…just the pubsub library which is a standalone Hex package. Then that phoenix_pubsub library is akin to your Notifier module, but you have a one-way dependency (Web depends upon Core, Core does NOT depend upon Web).

Think of it this way: What if you wanted to use your core app with a ‘client’ other than Phoenix? Your Core would still have references to MyAppWeb.Endpoint in your proposal. Following Chris McCord’s, it would not.

The original post asked what the most idiomatic way to broadcast messages was and I would tend to trust the framework author’s methodology here! :wink:

PS…bonus points: it’s a whole lot easier to grok for someone new to your code when there isn’t that extra layer (that still has you coupled in the wrong direction)!

2 Likes

I think it’s is still not the part of the Web layer (even though, it’s defined under BlogWeb)

Yeah, that’s how I think of it as well. I can use Endpoint for broadcasting kinda unrelated to the web layer. I agree that even though it’s in BlogWeb - it feels like it just happened to be there and it’s ok to reach it out.

And if our inner perfectionist doesn’t want the core domain to be coupled with Phoenix we could abstract “broadcaster” into a separate “behaviour” with an implementation based on Endpoint.broadcast injected into app.

Looks like a clean approach but I’m trying to fight perfectionism :slight_smile:

Thanks! I’m going to follow the way it’s done in the video (that’s a very good source) - use Phoenix.PubSub instead of BlogWeb.Endpoint. I guess I’ve been using Endpoint here not for it’s intended purpose.

1 Like