Skip to content

Instantly share code, notes, and snippets.

@kf0jvt
Last active April 15, 2016 21:33
Show Gist options
  • Save kf0jvt/626e4f680b8001774e7583100a6553fe to your computer and use it in GitHub Desktop.
Save kf0jvt/626e4f680b8001774e7583100a6553fe to your computer and use it in GitHub Desktop.
Elixir event handling -- am I failing at "let it crash?"

I have a genserver that is taking messages from a websocket and passing them to an event handler. The event handler receives a map from the genserver and takes action where appropriate. So for example, in response to a new user create event, the event handler would check if the user's email address comes from a suspicious domain, and if so, take some actions to reduce abuse.

So in event_handler.ex you can see that when a new message comes in for a user create event, I pass the message to UserHandler.handle_suspicious_domain. That function returns the same map which I pass to UserHandler.handle_suspicious_ip. But I'm not sure if that's idomatic since I'm not transforming the map, I'm just passing it from function to function.

Then you can see in user_handler.ex that I take the domain name through a set of transformations and then make a decision. Looking at line 25 you can see that I have a function that does nothing and only exists to prevent a crash. That seems wasteful and also runs contrary to let it crash.

Any ideas on how to make this more idiomatic?

Hypothesis

It might be better in EventHandler to spawn a task for each of those steps steps(handle_suspicious_domain and handle_suspicious_ip) and then the task can crash if any part of the function doesn’t go exactly right. Does that seem like a better approach?

defmodule EventHandler do
# behaviors and other message handling happens above
def handle_cast({:message, msg}, state) do
case is_a(msg) do
{"user", "create"} ->
Logger.debug "User create event detected."
msg
|> UserHandler.handle_suspicious_domain
|> UserHandler.handle_suspicious_ip
{"thing1", "create"} ->
Logger.debug "Found a thing1 create event"
{"thing2", "create"} ->
Logger.debug "Found a thing2 create event"
_ -> {:skip, "not implemented"}
end
{:noreply, state}
end
end
defmodule UserHandler do
@doc """
Receives a map corresponding to an api message. Extracts the username
and domain and checks if that domain is in the database table of known-
bad domains and takes action if necessary.
"""
@spec handle_suspicious_domain(map()) :: map()
def handle_suspicious_domain(msg) do
msg # %{"_internal" => ...}
|> get_username # "[email protected]"
|> get_domain # "example.com"
|> lookup_domain # true
|> act_on_suspicious_domain(get_username(msg)) # :acknowledged
# Return the msg so it can move onto the next step in the chain.
msg
end
def act_on_suspicious_domain(true, username) do
Logger.warn "#{username} came from a suspicious domain"
# TODO -- add some actions here that I don't want to make public.
:acknowledged
end
# The sole purpose of this function is to prevent a crash in the event that
# the domain is NOT suspicious.
def act_on_suspicious_domain(false, _username) do
:acknowledged
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment