[erlang-questions] Beginner trying to figure out idiomatic OTP architecture
Matthew Shapiro
me@REDACTED
Tue Mar 22 04:06:16 CET 2016
I am trying to learn Erlang in order to create an application that seems to
check off all the boxes of the Beam VM (low latency requirements, extremely
concurrent, long lasting connections making hot code swapping desirable for
upgrades, etc...). In order to learn I've gone through most of Learn You
Some Erlang For Great Good, and so far have really been enjoying the
process.
I am now at the point where I want to test some of this knowledge out, and
I thought a good idea was to create a (basic) IRC server (as I've written
them in the past in other languages, and it seemed like a good use case for
Erlang). Basic idea is to support allowing users to connect, set a
nickname, connect to channels, send messages to channels, and send messages
to users.
However, I am a bit confused on the proper way to architect an OTP
application and hoping that my current idea of how to architect this is
correct or not. Also, I find rubber ducking helps :)
The high level supervision tree I have floating in my head is to have:
* application
** app_supervisor
*** tcp_listener
*** handshake_supervisor
**** handshake servers (dynamic)
*** user_supervisor
**** user servers (dynamic)
*** channel_supervisor
**** channel servers (dynamic)
My thinking is the tcp_listener, handshake, user, and channel modules would
each be gen_servers, and the 4 supervisors would be standard otp
supervisors. The app_supervisor would have a one_for_one spec while
user_supervisor, chandshake_supervisor and channel supervisor would have
simple_one_for_one spec since they can spin children up and down
dynamically based on users connected and channels with at least one user in
it.
The flow I have in my head is:
1) tcp_listener is a server that waits for connections, accepts them and
tells the handshake_supervisor to spawn a new handshake server for the
accepted socket (and changes ownership of the socket to the handshake
server.
2) The handshake server handles the IRC handshake, and when the user
requests a nickname it asks the user_supervisor if any children exist with
the id matching the requested nickname.
3) If the nickname is available, the handshake server tells the
user_supervisor to start a new user server with the accepted socket and
nickname, sets the user server as the socket owner, and then the handshake
server terminates
4) When the user server is started, the user_supervisor would set its id of
the child spec to be the user's nickname.
5) When the user joins a channel, the user module sends the accepted socket
and the users nickname to the channel_supervisor and asks it to send it to
the correct channel. The channel_supervisor will look for a child process
with the id matching the name of the channel, and if none exists it will
create a new child with the channel name as the id in the child spec. It
will then send the child process with the matching id/name a message with
the users nickname.
6) When the user leaves a channel it will call on the channel_supervisor to
send a message to the child process with a matching id/name with the
nickname of the user leaving the channel (or disconnecting).
7) The channel server will keep a state with a list of connected users, and
when a join, leave, or channel message is received it will modify the
client list as needed.
8) When required (user joins, user leaves, someone says something, etc....)
The channel server will send a message to a specific user by calling the
user_supervisor to send a string to a child process with an id matching the
nickname of the user, and send that child process an erlang message with
the irc message to send to the user. The user server will then take that,
translate it into the IRC protocol and send it to the socket.
9) On client disconnection, the user server will announce to the channel
supervisor that it has disconnected, and the channel supervisor will tell
all children it no longer is connected to the channel.
So the problem I have on this architecture is it requires a lot of logic in
the supervisors (which I am not sure is a good or bad thing). It also
seems to invite myself to race conditions due to all the
supervisor:which_children() calls that would be required to identify the
correct child process to send a message to, since theoretically a child
process could die between a which_children() and actually sending the
message. It also means going through several layers of supervisors in
order to find the process another process needs to communicate with (for
example, user server would have to ask the user_supervisor to ask the
app_supervisor what process the channel_supervisor is, which would then
have to ask the channel_supervisor what process the correct channel server
is). This seems hacky but I am unsure of how to work around that.
Am I on the correct path with this? Is there an easier way to facilitate
servers talking to other servers managed by other supervisors?
Thanks.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20160321/5154dba2/attachment.htm>
More information about the erlang-questions
mailing list