Fat channels. Is it ok?

I really love using Phoenix Channels, but I find my channels files growing too fast. Is there any conventions how to organize your code? (for instance in rails you should keep fat models)
Also I use a lot of defps in my channels. Is it a good practice?
Thanks in advance!

2 Likes

If you’re asking then you probably start to feel yourself that something is wrong :slight_smile: I guess no single module should be too fat, whether that’s a controller, model or anything else. It’s the same in Rails where models were only the first layer that tended to get fat but with time, patterns emerged to make every part of MVC slimmer.

Is it easy enough to understand what the channel module does and how the function calls flow within it? You could move some parts to separate modules with good descriptive names. If you need help, post the code or at least function signatures and then we could propose some candidates for extracting.

2 Likes

I tend to break things up into lots of channels. On my single websocket I have about 7 channels just for the messaging part.

3 Likes

Out of curiosity, can you break down what the different channels do?

1 Like

One handles the messaging interface and what else to join. One for public channels (broadcasting to sync). One for each private channel set (1 for normal users, more for admins depending on who they are interacting with). One for the currently active room. One for notifications. Etc
 Mostly separated so make Endpoint.broadcast trivial to use for each case.

1 Like

Lots of channels is a good idea :grinning:. I have only 2 and it becomes a lil bit tedious

1 Like

Something that I wish were added to channels were a way to alias a single ‘channel’ in elixir with multiple topic names. I want to be able to broadcast, say “room:38174” with a “user:joined” or a “roomlist:271” with a “room:joined” message or so without needing to make a dozen processes on the server.

hinthintphoenixdevshint ^.^

1 Like

Why is the process count problematic?

1 Like

Ease of holding the data internally primarily. Currently I have to duplicate data between them, which makes me twitch


1 Like

Waiting eagerly to see what architecture sins will people start committing when designing their channels. And what patterns and conventions will emerge to amend that. RESTful channels? Channel broadcaster modules? Channel joiner modules? :slight_smile:

1 Like

Heh, I think the main thing that causes multiple channels right now for me is Phoenix Presence sends the same “presence_diff” regardless of topic or anything, so no way to differentiate which names the presence is trying to sync.

1 Like

I’ve found that simply allowing the channels to handle traffic and delegating that traffic off to something else in the lib or better yet, another OTP app enables a good separation and also makes testing easier. This way, much like a controller in Rails is just dealing with HTTP, your channels just deal with Websockets data, and your actual application logic is somewhere else. Just my 2 cents though, I’m sure everyone has different ideas on this.

4 Likes

That is what I do. Like for my example above my Messenger Channel set (topic “Messenger:*”) for "Messenger:Lobby sends basic connection info and what else should be connected to. The client then connects to (for example) “Messenger:RoomList:Public” and “Messenger:RoomList:437891231123236178” (where 437891231123236178 is the user ID, it denies if they do not have access to view that users joined channels) and “Messenger:Notifications:437891231123236178”. Then when they click a room to view it then they also join “Messenger:Room:76” (where 76 is the room id, it denies if they are not allowed to join the room).

The point of the separation is that Phoenix.Presence always sends it presence information via “presence_diff”, and as I got tired of hooking that to translate to other channels for each tracked topic I just make each topic its own full channel instead. Now I can do Endpoint.broadcast("Messenger:RoomList:76", "users:joined", %{users: userlist}) and so forth, in addition to being able to sync room information via presence as well as internal room information like joined users simultaneously.

1 Like

I’ve also thought about so-called “Channel Dispatcher”, which could catch messages flow from channel to channel all in one place and let them share their payload.

I do not know if I understand exactly your situation. This is how I separate my channels logic:

AQ: How to split channel source code in multiple ex files
AQ: How to delegate message handling handle_in to another file

###Context

So we had all our logic in room_channel.ex like this

defmodule MyAppWeb.RoomChannel do
  use MyAppWeb, :channel

  def handle_in("show_user", params, socket) do
    IO.inspect params

    {:noreply, socket}
  end
end
```

..and we want to move the user logic.

###Solution

We can create another module/ex file like user_module.ex, move source code that you like and call it simply by full name.

in room_channel.ex

```
defmodule MyAppWeb.RoomChannel do
  use MyAppWeb, :channel

  def handle_in("show_user", params, socket) do
    UserModule.handle_in("show_user", params, socket)
  end
end
```

in user_module.ex

```
defmodule MyAppWeb.UserModule do
  use MyAppWeb, :channel

  def handle_in("show_user", params, socket) do
    IO.inspect params

    {:noreply, socket}
  end
end
```

Hi, please note that Phoenix’s way of organizing code has changed significantly since this thread was created and it may only be useful for those running older versions. For this reason I’m closing the thread but if you have any further questions about the issue please feel free to start a new thread.

1 Like