Ben Smith - An event-driven approach to building Elixir applications | Code Elixir LDN 19

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
afternoon everyone so for the next 20 minutes I'm going to be talking about how to build elixir applications using a event-driven approach so there's quite a lot of content to fit in the next few minutes so it will be at a brisk pace but the main takeaway I'd like you to leave today with is that there is this alternative approach to using events domain events to store applications state and building event driven applications so let me start by introducing this application an application that I'm going to use to demonstrate how you would migrate from a typical crud based approach using phoenix web framework and ecto into a more event-driven application architecture and if anyone here is an athlete who likes to go out running or cycling you've probably come across a website called Strava which is one of the main social networks for athletes to go out and record their runs and their rides using a GPS device and then when they get home they can upload it to Strava and they'll see their activity as a map of where they went plus some analysis so how fast they ran and the main the main reason people use driver is because you can have friends on Strava and when they see your activity they can give you a virtual kudos so the reason why I decided to use Strava is because they have an API where anyone can build an application that accesses data from this driver platform and you can create you can use that activity data to build third-party applications so I decided to use a website to build a website called segment challenge which allows anyone who has a striver account to create a challenge that serve could be a virtual race virtual 10k virtual marathon it could be a distance based challenge so who can write the the furthest in a particular year so this challenge was Ride 2019 miles this year and you can see I'm down in sixth place here when this screenshot was taken and there's no no chance that I'm going to make the 2,000 mile mark with halfway half the year gone and over anyone 694 miles so anyone can create a challenge and similarly if you are a Strava user you can click the button to join a challenge you'll be redirected to Strava and you will have to authenticate and allow segment challenge access to your data and then once that's happened every time you upload an activity from a ride or run Stryver will send a webhook request to my website segment challenge and it will say an activity has been created or an activity has been deleted so I've built a phoenix web web application which has a an API controller that listens out for these web book events and it does this import activity processing and eventually the data gets stored into a Postgres database and it's used to render the leaderboard so that's the high level what the application does and what it's used for how its implemented so let's drill down into the activity or import process so you can imagine this is a typical Phoenix application we initially we need to get the Stryver activity from the API because the webhook data only includes information about the type created or deleted and an identifier for that activity so first we make an external request to go and get the rest of the information so the location the start and end time distance etc for the most important bit of information that we need at this point is which athlete went out and did this activity so then once we have the the athlete we can go into the database and we say get all of the active challenges that this person has joined so it might be none it could be one it could be many challenges and for each of those challenges we're going to import the activity so we have we import the activity and then we do we rank the leaderboard to change where that person is and the leaderboard if applicable and we store that information into the Postgres database so this can be used to generate the leaderboard on screen so this is a typical way of building elixir applications using Phoenix and it's got some good points it's the code is relatively easy to understand it's quite it's somewhat extensible and it's it's quite straightforward the downsides are that we we've embedded our domain logic within data access so to make this test of all we need to do some seeding of the database and we need to do I owe in amongst our tests which make it slightly harder to build the test form we need to use something like echo sandbox and additionally we could have multiple concurrent webhook requests coming in from Strava and we might be trying to update the same challenge within two different price the dashed line here represents what happens within a single elixir process so this could be happening twice for two different webhook requests for the same challenge and we might have the fact that we read the current leaderboard in both processes modify it and then try to save it and we've got corrupt state now because the second change wasn't aware of the first so to counteract that we could use something like echoes optimistic locking where every time you changed the leaderboard you increment a version number and the second right would fail because the versions an out-of-date so we can we can fix some of the deficiencies with the existing design so the product manager of this application which has made decided that it would be really good if we added two new features one is a loss placed notification email every time you lose a position in the leaderboard we're going to send out an email to say sorry you've just bet you just lost your place you should how about you go out and record another activity and secondly we want to have this Activity Feed so we can see for a particular challenge what's happening who's going out and recording activities so a naive way of implementing this would be we just take our existing code this activity important we add on the two new behaviors we're recording the activity into the Activity Feed and that goes into the database and then we're sending this lost place notification now you might think this this is relatively this is okay we've extended the code but one of the problems we have here is that the email provider can't enlist within the database transaction we used to do the rest of the updates so you can imagine that importing the activity ranking the leader boredom doing Activity Feed is all within a single database transaction and we get the benefit of all succeed or nothing which is good but we can't enlist a third party email provider within that database transaction so to fix that we need to use something like the outbox pattern where we persist the email into a database table and have a second process watching that table and actually doing the email sending so we get the benefit of that but there's one additional requirement I didn't mention earlier on every one of these webhook requests that comes from Strava has a two-second timeout period so we're doing a lot more work now within this single request and we might we're potentially going to breach that two-second timeout especially if we have concurrent updates to the same challenge having to be we tried so we want to be able to do all of this and we'll return back within a finite period of time so one way we can potentially solve this is by using the publish/subscribe pattern where we separate recording the activity from the things that are going to react to that happening and do the updates so initially within the web put request we're going to store the activity into the database and then we're going to use elixirs built-in registry module to publish that out to any interesting to interested subscribers and once we've done that we're done and that request is finished so that process is done and we can do that with quite low latency and then we have three other processes which are listening for that message and they can update the database and they can do the email sending so now we have loose coupling between these three they're running concurrently they're isolated their fault tolerance so if there's a failure to send the email we haven't affected the activity feed and similarly with the leader board we don't impact each other's changes but then we kind of lose the transactional integrity for that so there's a separate problem there and also the elixir registry is only limited to running on a single node and it runs in memory so if for whatever reason one of these subscribers is processes isn't currently running a message will get lost which is a bad thing so there's no transit there's no message delivery guarantees which if we wanted to implement we'd have to implement some kind of third-party message queue or message bus which guarantees delivery of messages and one other more important problem with this approach is that we've separated to saving activity with publishing it so if we persist the activity into a database and then that process crashes and we don't publish it we've then lost all of the things that happen on the right hand side of here won't happen or they potentially lose those so those are - that's a significant problem with this with this approach so what if we use this one small trick where instead of recording the activity and then publishing it what if we use domain events as the source of truth of our application where we store the fact that activity has happened as a fact and then we publish that out so a quick delve into what a domain event is so it's just a record of something that's happened in the past and it's an immutable historical fact so these are were recording facts they're in a pertinent or application and the name of the event should be using the business language and Tattaglia mentioned earlier that this is a bit Curtis language is what we want to be used using for our domain events so things from segment challenge our challenge started competitor joined activity recorded leaderboard rank those would all be well named domain events because they've happened in the past and they're in the language that anyone who knows the domain could understand them so now we we look at how we record events domain events so now the activity import process is we store this activity recorded domain event into an event store an event store is just a data storage mechanism which is designed for storing events within streams and that's its main purpose to store an event in a stream and to read back a stream to get all of the events so it's purpose-built for this particular data access pattern and then once we've restored when to restore the event into the event store it will provide a subscription mechanism which allows our independent loosely coupled components to receive the event and process it as before one of the guarantees that the event store provides is that each of these subscriptions will will receive the event at least once and the way it does that is by using the acknowledgment not acknowledgment pattern so the event gets passed to say the leaderboard processing component if it crashes whilst it's doing the leaderboard update when it resumes its subscription to the event store you'll be passed the same event and that will keep happening until either acknowledges it or it not acknowledges it and enough acknowledgement would be will stall there into some kind of dead letter q retry q which we can manually go in and try to re fix the code and replay or manually go and fix it well if we allow the application to carry on running so one other change that you may notice here is on the right hand side have now got two separate databases called the read store so the event store deals with right so we're recording domain events and then we have a separate at least one separate read store database in this case we've got two for answering queries so we separated writing from reading and these databases are logically separate don't have to be physically separate you separate you could have one database with your events in one table and your leaderboard and activity feed in separate tables in the same database or you could have two separate databases and the benefit of this model one of the benefits is that you can use two different read models for different purposes so you might have a sequel database and no sequel database you might have a document database you might have a full-text search index so each read store is purpose-built to the type of querying and data access patterns you want so you can tailor the read model to the queries and your write model is very fast because you were just storing events there's one of the benefits of using this approach and also with the publish subscribe pattern which we've now made fault-tolerant we also get better testability because the inputs to each of these components is one or more domain events so we're passing in domain events and they were testing that some side effect has happened and we can also test our challenge can be tested using pure functions because the import with having a command come in import activity and the output is a domain event the i/o is is centered around the event store and the read store we can test the components are our encode with just using pure function with pure function so we can test them using pure functions and they're very fast to test so we've seen how we could go from basic crud approach to using domain events to store current states so how do we design using events there's a tool called event storming which is a modeling approach where we use different types of sticky notes to model different parts of the system so the key is the center of the screen the orange domain event and that's the main thing we want to model and then each domain event comes into existence from a command which is the pale blue sticky in front of it and one one command will create at least one of it it may create multiple events and to tie those together we use an aggregate which is just a consistency boundary to simplify things and we can also denote who the actor of the command will so who's the user initiating this command so is it the challenge host is it an athlete is it some something else some third parties and we can also denote external systems such as Strava or an email provider in pink and workflows are defined as policies in purple and then remodels our views of our application that we want to be at a query and display on screen so if I show you an example of using this event modeling approach with the challenge so the first thing a challenge comes into existence by someone creating a challenge that's the challenge host and we have this challenge created event and right at the end we have this challenge ended so we have start in an end event and we have other commands and corresponding events so we host a challenge and then you'll notice that this start challenge is kicked off by some scheduler component the reason that the scheduler starts that command is because when you create a challenge you specify the start and end date so we know that when the change is due to start when it's due to end so we can shed with those commands to be executed at that point in time so they're done automatically so time-based triggers that are handled very nicely with this event driven approach because we're scheduling commands and then we're actually denoting when when these when these things have happened and recording that as facts within within our store so if we drill down into this importing the activity using the event modeling approach you can see that Strava is an external system is kicking off this import activity command and we're recording the activity and then we're ranking the leaderboard and from those events we're then updating our our read models our view models so we're updating the activity feed from the activity being recorded and the leaderboard being ranked and we're updating the leaderboard view from that event and we're also integrating with a third party system we're sending out this lost place notification via the leaderboard ranked event and the way we can do that is because the event will contain information about who's lost a place within the leaderboard so we can use that event to then send out the email and so often people ask when you're using event sourcing how do you deal with changing requirements because an event for a stream of events is an immutable log so how do we deal with come with additional requirements or changing your clients well if we go back to hosting a challenge so initially you can create a challenge and you can host it and it becomes immediately hosted what if we said we want to be able to charge people for hosting challenges on the on the application so now we can say when you host a change instead of immediately being hosted we're going to require some payment and that the fact that that event is recorded can kick off some third party payment processing and we either we can confirm the payment or reject the payment depending on whether whether we were able to debit the person's account and we record the fact that payment was confirmed or failed as an event and then if payment was confirmed we have this challenge hosted event which was if you remember what we had originally so now that we've created a challenge and we've hosted the challenge we've added this whole new payment processing flow we've bolted that in between without affecting any of the starting and the ending events so things that are listening out for challenge hosted will work if there's no payment flow or with with our new payment flow because we we've haven't changed the events that the key life cycle events we've just added some additional event and processing in between so that means that it's really easy for other other things that are listening for that particular end event to continue working even though we've changed change the logic within within the challenge so now how would we deal with external events well one of the things we can do is every time Strava sends us this web hook ping we can record that as an external event within our system so we there was a the talk earlier on about not trying to break we're not trying to go against what's happening in the real world this is a good example of implementing that whenever Strava tells us an activity was created or deleted will record that as a fact within our system so we're not trying to interpret it we're just recording it as something that happened and we could have a separate stream for Stryver events and then we have this thing this process in the middle this challenge competitive process and the reason we need that is because if you remember right back at the beginning when we import an activity we need to know for this particular activity and Strava which athlete it's associated with and which challenge is there a member of so we need to do some processing in in the middle to know that we want to import an activity or remove an activity corresponding to which which which challenges they are they've joined in our currently at so how do we work out who how do we do that mapping how do we know who to who to assign this activity with well we can do what we do in the rest of the system we can just listen out for different types of events and we can use those events to record some internal states so we can listen for challenge started to say this challenge isn't active we listen for competitor joined and left challenge so we know for this challenge this person is joined now they've left so we can have a list of challenges and all of their active competitors and then when a challenge is ended we can say that that challenge is no longer applicable once we have that state built up as a map if you like we can then say for every time an activity gets created by Strava we can look into our local state and we say right that means that this athlete has joined these two challenges I need to then import activity into both of those challenges so now we have this reactive driven we have this reactive process where everything is everything is reacting to events and we don't have the kind of declarative imperative code that we originally had where it was kind of do this do this do this we just have a process in the middle which is reacting to different events being created and then what happens if Strava decides to cut ax a cut off access to your your application and you need to extend with third party providers well you can use the similar approach so you want to integrate with Garmin or Fitbit will build our integration code will record those as new event so every time someone uploads an activity to Garmin we will record that as a garmin activity creative with whatever data we need and then our process manager just needs to listen for those new events and everything else is unchanged so we can integrate with many different third party consumers and as long as we have a contract for our activity data to say we need some kind of superset of all the information we need everything else is unchanged so we make our our own application reactive by adding new events which are specific to third party providers and we can just keep adding on new integrations as we need to so the last bit of my slide is what elixir tooling is available so there's this list that I've created called awesome elixir and CQRS which has got all my all of the open-source libraries and tools and books and articles that you might come across in the elixir world and there's a open source project called commanded that I've written which allows you to do CQRS and event sourcing in elixir and there's a few add-on so you can do read model projections using echo and you can distribute your processes amongst all of the clusters in a node using a couple of libraries and there's the scheduling and there's an event store which uses Postgres for persistence and in elixir and it can run on multiple multiple nodes and if you want to learn more there's a book that I've written about 3/4 of which takes you step by step building an event sourced application in elixir using commanded and that link allows you to get a copy for free it's free to read online and there's a charge for a PDF or epub version but if you that link gives you a free copy and if you want to read more there's my website at the end that is everything I have today thank you you
Info
Channel: Code Sync
Views: 3,880
Rating: 5 out of 5
Keywords:
Id: TdGxvekg6xM
Channel Id: undefined
Length: 21min 19sec (1279 seconds)
Published: Tue Aug 06 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.