Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing a Slackbot in Elixir

Writing a Slackbot in Elixir

Animations are a little garbled in the PDF; see https://docs.google.com/presentation/d/1EwYcrZ7avlwdU9C6yntaoqN5ANCbapg_Gp7QJqb5ACo/edit?usp=sharing for proper animations

Jan Fornoff

July 05, 2017
Tweet

More Decks by Jan Fornoff

Other Decks in Programming

Transcript

  1. What we’ll be talking about • Slackbot specification • OTP

    introduction & building blocks • How they fit together • Learnings along the way • Open Q&A - Anything you like :-]
  2. What’s our Bot supposed to do? • Maintain a Slack

    connection • Allow scheduling standup times • Standup session via DM at scheduled time • When done, send a fancy report to a Channel
  3. What is OTP? • Framework for building fault-tolerant, scalable, distributed

    applications • Prebaked generic “behaviors” for most common use cases • Battle-tested for decades
  4. The Actor model - What is an actor? • =

    computational entity • Completely isolated from other actors ◦ No shared memory or state • Draws messages from a Mailbox • Possible reactions to a message ◦ Sends messages to other actors (asynchronously!) ◦ Creates new actors ◦ Change its behavior for future messages
  5. OTP Intro - Processes = Actors • Unit of concurrency

    • Follows the “Actor model” ◦ Communicates via messages • Can hold state (and share nothing) • Can fail in isolation Process 1 Process 2 send pid_p2, “Hello” Mailbox ... receive do msg -> IO.puts msg end
  6. OTP Intro - Processes = Actors • spawn != spawn_link

    • Links are bidirectional • Monitors are unidirectional Process 1 Process 2 spawn_link(Module, :fun, []) ... Crash exit signal • Processes may be linked
  7. Imagine trying to handle every error that could possibly happen

    What if we’d just acknowledge that we can’t handle them all? Fault tolerance - the Erlang way
  8. Supervisors • Responsible for looking after processes and taking actions

    if they fail • Allows for “let it crash”-philosophy ◦ Explicit definition of what happens in case of failure ◦ Mix applications can have a central application supervisor mix new --sup <name>
  9. Talking to Slack - SlackInterface.Connection • Using open-source library that

    provides a GenServer-ish interface • Provides: ◦ Sending messages to users / channels ◦ Invoking functions on events that we care about • Let’s check out some code!
  10. Hold up, wtf is a GenServer? • GenServer is an

    OTP behavior that makes it easy to define servers ◦ Not as in web server, but in “long running, serving requests” • Abstracts away all the hard stuff about servers ◦ State ◦ Messaging (synchronous = “call” / asynchronous = “cast”) ◦ Tracing / error handling / supervision
  11. StandupSession Supervisor Standup Session for Bob Standup Session for Jane

    Standup Session for Anna Standup Session for Will Cron library Hey, 9 o’clock! Time to start a Standup for Will! Starting standup sessions - StandupSession.Supervisor
  12. Where the magic happens - StandupSession • GenServer-based worker process

    that gets started by Supervisor ◦ triggered by Cron library (or manually) • Keeps track of already given answers • Reacts to new messages / message changes • Triggers status report as soon as it runs out of questions to ask • CODE?
  13. Keeping track of standup times - ScheduleManager • Keeps track

    of scheduled standups • Writes a binary file to disk / reads it on startup ◦ for persistence across application restarts! • Registers jobs in Cron library (called Quantum)
  14. The Slack connection process is supervised and gets instantly restarted

    Application Supervisor Slack connection Schedule manager StandupSession Supervisor
  15. Testing interaction with the outside world • How do we

    test that our bot sends DMs correctly? • Neither do we want to connect to real Slack on test runs • We swap out “adapters” through configuration ◦ Behaviours can ensure a common interface! • And write a fake adapter that we can assert on
  16. Your interface is not your application • Common problem with

    web applications • Ideally, your interface (Slack, HTTP API, Command line?) is a thin layer • Gives you clearer testing boundaries • Maybe even portability to another platform?
  17. How to avoid flaky tests on asynchronous messages • GenServer.cast

    is a pain to test • Especially on multi-step test cases • Separating application logic from the state-bearing GenServer ◦ Allows you to have more reliable tests! • If your GenServer is only a thin layer of storage, no significance lost!