ElixirConf 2019 - Beyond LiveView: Building Real-Time... - Sophie DeBenedetto

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hey everybody welcome happy that you could all join me so today we're going to be talking about live view and Beyond will be building out some real-time features with not just live view but also pub/sub presence and kind of channels so full disclosure I worked you know obviously very hard on this beautiful presentation for many weeks very proud of it and yesterday I was teaching another workshop having to do with real time Phoenix in live view and a very helpful attendee who had gone to the live view workshop the day before clued me into some new features that are now available now that live view is released as of like I don't know 27 hours ago or something so we're gonna look at some cool things you can do with live view we're gonna look at something that you really don't ever have to do and probably will agree that you should never have to do this and then I'll show you some of the cool shiny new okay let's get started before we dive in I would love to introduce myself I'm Sophie I'm an engineer and teacher at the Flatiron School where I build business and education tools in elixir but also Ruby and JavaScript all that good stuff I'm also a contributor to elixir school which is a free open source elixir curriculum and you guys may be landed on some of our lessons or blog posts we're really just sort of like a big group of people around the world that want to build up the resources for this community so certainly we would love to have anybody get involved check out elixir school calm open an issue write an article write a lesson let's spread this knowledge around a little bit one more introduction this is my dog you might see some more pictures of him throughout this presentation he is this cute in real life alright so live view is great I think if you're in this room you probably don't need more convincing you've either played around with it perhaps at a workshop this week or prior to showing up at the conference you're excited enough about it to have decided to sit with me in this windowless room on this beautiful day so live view has given us the ability to implement flexible and responsive you is almost entirely with server-side code and really not that many lines of server-side code on top of that we can build things like complicated games we can handle form validations do autocomplete push out user notifications and even more and we can back it with the concurrency and the fault tolerances fault tolerance of elixir processes but Live View has its limits or at least it seems to so as we use Live View for more and more real-time features I think we're gonna find ourselves naturally reaching for the other real-time functionality that Phoenix has promised us for example user tracking and message broadcasting so with this in mind I asked myself a question I wanted to know if something like a chat application which is like the traditional go-to example for Phoenix channels it's something like a chat application therefore not a good candidate for Live View when building a set of features that require things like user tracking and broadcasted messages do we need to eschew Live View and return to channels do we incorporate Live View onto a page that's like already supported by channels or some of these other technologies so I think the answer to this question is no I think live you can do a lot more thanks to the flexibility of Phoenix's real-time tooling it's really easy for us to incorporate pub/sub presence and even more technologies directly into our live views so we can continue to use live view to build lightweight interactive real-time features and we can continue to enjoy all the rich out-of-the-box capabilities that tools like phoenix channels and presence lend us so in this talk we are going to be building out a chat app that is backed entirely by a supercharged live view that incorporates pubs up presents and a custom live view channel and then we're gonna totally trash the live view Channel and I'm gonna show you the right way to do it as of like yesterday surprise I'm fine with it alright so let's talk a little bit about the features that we're going to build out today we will have of course the most important feature of any chat application is real-time messaging all the users in a chat room should be able to see new messages sent by any other present user will have some user tracking the chat room will display a list of users who are there and we'll also have a really responsive UI so what should happen in our chat window when a new message arrives and gets appended to the bottom of the chat log the window should scroll down that new message should be in focus it's kind of like a given of any really useful chat application so we're gonna dive into the very first set of features for our chat application the most basic functionality that a chat app requires the ability to show all of the users in the chat any new messages so before we talk about broadcasting messages I want to take a look at what Live View provides us out-of-the-box and discuss why we even need to implement message broadcasting from Live View so let's start by running through how this out-of-the-box live view enacts real-time updates for the single user represented by that live view process so live you will receive a message when a user submits a new chat in the new chat form live view will update its own socket assigns and it will rerender the le e ex template accordingly we need to understand that this occurs within the live view process belonging to the user that submitted that new message only let's take a look at what this means from a UX point of view really not good this is why we need message broadcasting so in to side-by-side chat windows open by two different users having a perfectly normal conversation about my dog we can really see the problem right only the person that sent the message sees the new message properly in sees the new message at all at the bottom of the chat window so we need a way to ensure that the other Live View processes representing the other users in the chat room can become aware of these new messages and can update their own templates accordingly so in other words we need pub/sub and this might be the first point in time when reaching for phoenix channels might feel like the move but wouldn't be nice if we could get our existing live view to just broadcast updates to all of the other Live View users and we can use Phoenix pub/sub to do exactly that one thing that I always like to note is that pub/sub takes advantage of distributed elixir so clients across distributed nodes in our app can subscribe to a share topic and they can broadcast to that shared topic because pub/sub can exchange notifications between servers directly when configure to use the PG 2 adapter which it is configured to use out-of-the-box in any new Phoenix app ok so how are we gonna use pub/sub to make sure that all the users in a given chatroom get any new messages I want to break down the procedure that we're going to be taking a look at so when a live view process starts up for a given user that's when we want to subscribe it to that particular chat rooms topic then when a user submits a new chat message that users live view process should broadcast that message to the other chatroom subscribers those subscribers will receive the new message and they'll update their own socket designs and re-render their templates accordingly and in this way we can combine the real-time capability provided by Live View with the ability to pass messages across a distributed set of clients provided by pub sub so the first thing I want to do is just peek at our applications pub sub configuration you get this for free with any new Phoenix app you don't have to do this configuration yourself but I think it's worth taking a look at so what we're doing here is we see that our apps endpoint is configured with the Phoenix pub sub PG - adapter so its distribution friendly that's good and it also means that our pub sub back-end will start up when the app starts and it will expose its function via our apps endpoint module so now that we are assured that our pub sub server will be up and running and that we can interact with it via our endpoint module let's teach our live view processes to subscribe to a shared chatroom topic so when should a live view process start subscribing to a chatroom topic I think it should happen as soon as the process starts up so we're gonna leverage the live views mount function to hook into this moment in time in the mount function we will subscribe to the chatroom topic which we're just specifying as chat : chat ID via the pub sub subscribed 3 function so that's really it now all of our live view processes when they mount are going to subscribe to this chatroom topic and we're ready to start broadcasting messages alright so when should we broadcast message we should do it when a user send it's the form for a new chat message and any such event thanks to a Phoenix submit event them I've added to the chatroom forum it's going to be handled by a handle event three function on the server side and our live view process so we're going to plug into this handle event three function and use it to do the work of updating the chat with the new message record then we're going to use the endpoint broadcast three function to broadcast out this message to all of the other live view processes that are subscribed to this chatroom topic if we're gonna broadcast we need a handle info that listens for and knows how to handle an event of type new message so we'll do that now we implement a handle info two function matches our event type of new message all the other subscribing live view processes are going to receive this event broadcast handle it with the handle info and respond by updating their own socket assigns with the payload describing the updated chat which will cause the page to re-render including the new message at the bottom of the chat log so before we move on I just kind of want to walk through each particular step in step one a user submits that new chat message sending an event to its own live view process that Live View process will receive the message make some change and then broadcast it to all the other live view subscribe to that chatroom topic all those other live view processes are going to receive that message broadcast update their own state and re-render accordingly which is pretty cool alright so what did this approach win us the phoenix pub sub library allowed us to build a real-time feature that broadcast shared updates to a set of users with just a few additional lines of code on top of our live view our Phoenix app was already configured to use pub/sub and already had the pub sub back-end up and running and integrating it with our existing Live View code was pretty trivial and now we have an even more advanced real-time functionality up and running and just about no time so this brings us to our next feature now that our live view is smart enough to broadcast messages to all of the users in the chat room let's build a future that tracks those users so let's say that we want our chatroom template to render a list of users who are present something like this we have a couple of options here we could create our own data structure for tracking user presence maybe reach her like a Gen Server and agent roll all of our own functionality for storing user presence information in there fetching it out rely on pubs up again directly to broadcast out presents related events to all the live views that sounds like a lot of work and I don't want to do it so I think we need Phoenix presents the Phoenix presents behavior abstracts all of this hard work away for us it provides presence tracking for processes it leveraged Phoenix pub/sub behind the scenes to broadcast updates and it also uses something called a CR DT which stands for conflict free replicated data type module which means that it is distribute it is resilient it is fault tolerant okay so first thing we need to do is configure our app to actually use Phoenix presence we need to define a module that uses the presence behavior and specifies that it shares a pub sub server with the rest of our application and this shared pub sub server is really critical this is what allows us to integrate live view and presents really nicely and really easily because they share a pub sub server presents will be able to broadcast events that our live view processes can actually consume so our presence module is going to maintain a list of present users in a given chatroom by storing these users under a specific chatroom topic once again in our case chat : chat ID and now we're ready to actually start tracking these user presences so we need to ask the question at what time do we consider a user to be present in a chatroom and I would say once again it's when the user first mounts their live view so we'll hook again into this mount to function to add the new user to presences list of users in a given chatroom here we're using the presence track for function to track our live view process as a presence so we had the pidove the live view process to presences datastore along with a payload describing the new user under this topic of chat : chat ID and a key of the users ID so the presence processes state for a given chatroom topic is now going to look something like this describes a single user we have that top-level key of user ID and we have the payload stored and metadata describing their email and their first name and we'll be able to use the data in this list to render our present users on the page so let's talk about how we will render those users on the page how can we ensure that the other live view processes representing users and chat rooms become aware of a new user joining when user joins we're storing them in presence that's great but how do we tell the other live views how do we make sure it gets rendered on the page so when user is tracked by the call to presence track for presents we'll update its list of users for the given topic and then for you for free it will broadcast a presence diffident recall that all of our live view processes use pub/sub to subscribe to this same chatroom topic when the live view mounts since presence is tracking users under this topic and presence shares a pub sub server with the rest of our application when presents broadcasts the presents diffident that event is going to be consumed by our live view processes so all we need to do is teach those processes how to handle this presents diff event broadcast with a handle info function will implement a handle info function that listens for the presents diffident this function has a couple of responsibilities the first thing it needs to do is fetch the list of updated users from presents which we'll do with a call to presents list one we're saying hey presents give me all of the tracked presences for this topic then we're simply enumerated over the payload that we get back from presents list plucking out the user names so that we can update socket state giving our socket assigns a key of users pointing to a value of this list of user names which means that our users can now be rendered on the template having added a key of users to our Live View socket assigns we can access our list of users via the at users assignments in our L eex template and actually render them that's it but wait we might have a problem so the code we have so far will broadcast an event when a new user joins the chatroom and this will cause all of the subscribing live view processes in other words all the people that were already there to receive this event grab the list of users and then show that updated list on the page so again we're really talking about people that are already in the chat room having to wait for a new user to join before any users are displayed we need to make sure that we display a list of the people that are already in the chat room for that person that is joining the very first time their template renders and we're going to do this by fetching the presence list when the live view mounts so first we'll grab our list of present users for the chat room topic we're using that same snippet we used earlier to enumerate over them and collect the user names then we will add it to the socket designs so that they show up when a template first renders for our newly joined users pretty cool and once again we're not having to write a lot of code because presence does a lot of this for us and plugs into live view really nicely alright so we've talked about a user joining what about when a user leaves how can we update present state and broadcast changes when user leaves the chatroom so recall that we are tracking presents for a given live view process via the present track for function where the first argument we give to track four is the pidove the live view process so what do we think happens when a user navigates away from a live view page unsurprisingly the live view process terminates and what does presents do when one of its tracked processes dies it will call untracked three function under the hood it will remove that tracked presence from its own state and it will broadcast a presents diff event which our live views already know how to handle by grabbing the most up-to-date list of users from presents which now will not include the person that just left updating socket assigns and causing the page to re-render so we don't have to write a single additional line of code to support users leaving and having that list on the sidebar update in real time all right before we move on I want to just briefly touch on some of the other things that you could do by integrating presents in live view so now that Cousins is working really nicely with our live you you could really imagine using it to track not just whether or not a given user is present in a chatroom but also the state of a particular user so you could leverage presents to back a feature that indicates which user is currently typing how long it is a given user has been active for if somebody sets themselves to away or mutes notifications and the list goes on any such feature would seamlessly integrate into our Live View thanks to Live View and presences shared pub/sub server and topic so live view and pub/sub and presents all play really nicely together and I want to recap what we've built so far before we move on so with our out-of-the-box Live View our chat had the ability to push these real-time updates down to only the user that initiated the change but with the addition of pub/sub we were able to broadcast new chat messages to all of our Live View clients for a chatroom topic and then by pulling in presents we were able to track and display a list of users present in a given chatroom in real-time so the flexibility of pub/sub made it really easy to subscribe all of our running Live View processes to the same topic on the pub sub server and the presents modules ability to share that pub sub server with the rest of our app allowed presents to broadcast events to Live View processes so overall Live View pub sub and presents definitely get along and it enabled us to build a really robust set of features with very little hand-rolled code ok this brings us to the last feature that will build and before I make a case for doing something really weird with live view and then disprove my own case by showing you that you never need to do it I want to talk about a problem that we have in the current state of the world when a new chat message is appended to the bottom of the chat window it appears just out of frame we need the chat window to scroll down to accommodate and focus on and display the new message and this is really easy to do which is one or two lines of JavaScript all we would have to do is grab the new height of the chat window and reset the scroll top attribute if you're familiar with phoenix channels you might want to reach for something like this have your channel client on the front end listening for something like a new message event and respond by grabbing the chat window element and adjusting the scroll top that would be really nice you can't do that sorry not with life you but we do have some options so this gets into what I was touching on earlier before I don't know last week or a day ago reports very responding to a custom live view event on the front end was not quite impossible but it was it was pretty close to impossible the live view javascript library at the time did not expose a way for us to hook into specific events on the front end so the first version of this presentation was created before the recent introduction of live view and JavaScript interoperability with the help of something called live view hooks and we are definitely going to talk about what that means and how to leverage it but I would be remiss if I didn't first show you a weird and complicated thing I did before these existed all right so we're gonna write a lot of cool and ultimately unnecessary code then I'll show you the shiny new easy way to get our custom javascript scroll top firing but first travel back with me in time to a week ago when I lived in blissful ignorance of live view hooks and let's assume that we really want just a little bit of channel functionality we don't want to abandon our live view and all of the awesome real-time features it allowed us to build out with like very little work where developers were lazy we love things that allow us to do very little work so is it possible can we get just a little bit of channel functionality just enough to fire our tiny snippet of j/s and we can because it actually is possible to roll our own custom live view channel and this way we'll get exactly what we want a responsive UI that Scrolls down to accommodate new chat messages without departing from the world of live view so here's an overview of the setup that we're going for each live view process is going to get its very own sidecar channel this channel will be joined over the live view socket when live view 1st renders the template and then later when a user submits a message we will have live view send a message to its sidecar channel which will in turn push it down to the front end client which can fire our scroll top adjustment for the chat window alright so in order to get this up and running we need to extend the live view socket and define a custom channel we'll need to register our channel process so that it's corresponding live you can look up the pin and send it a message later on and then we'll need to teach our live views how to send those messages so that the channel can push them down to the front end client all right first up connecting to the socket and joining the channel so in order to guarantee that a live view process can send a message to the right channel at the right time we need to have our live view share a socket with their sidecar channel so let's take a look at the process what's gonna go down a users gonna visit the chat show page they're gonna point their browser at chat slash ID the controller will mount the live view and render the static template then we'll have our client connect to the live view socket and join a channel on that same socket here's a breakdown of the process we send the initial get request render static HTML send that live view socket connect request and then join a channel over that same socket but how can our Channel and our live view share a socket they can't unless you steal the live socket source code so you could what I did copy and paste the live socket source code so that you're sort of extending your own so I defined a socket module that's a straight-up copy of live views live socket module the only line I added in addition to what was already there is this line with the channel definition where we're mapping the topic of event bus to our soon to be defined custom Channel and then we need to update our apps endpoint module to map the socket mounted at the /live endpoint to this new custom extended live socket that we just defined and now that our socket is up and running we're ready to define our custom channel which right now is pretty simple it knows how to handle the join message for the given topic of event bus : chat ID and with our socket and our channel defined let's take a look at that front-end code for joining the channel yep so right after we call live socket connect we can establish our channel at event bus plus chat ID assuming we've plucked the chat ID let's say off the page somewhere and this means that we're getting this much closer to being able to run this code our scroll top adjustment code all right so our socket is connected to our channel is up and running we're ready to discuss how we get live view to actually communicate or send messages to its sidecar channel alright the process is going to go something like this so each live view process will send a message to its accompanying channel the live view channels will push a message to their own front ends which will allow the front end to update the chat windows scroll top so if we need live view to send a message to the channel its own sidecar Channel it needs to know the pit of that channel we need to give the live view process awareness of that pit or some way to look it up somewhere and we're gonna do this with the help of registry we're gonna register our Channel process under a key of a unique session UUID that is shared with the live view process this way Live View can look up the channel pin and send it a message later on so in step 1 we're going to mount the live view from the controller with a session UUID passed in from the controller live view is going to store this session UUID in its own state and it's going to encode that UUID into a token that it will render let's say in a meta tag somewhere on the actual page then when the live view socket is connected we will include in that connection request a parameter token that will pull off the page this token will then be stored in the channels representation of the socket so that when the channel is joined it can pluck out that token grab the session UUID and use it to register itself as a process in our session registry under a key of that session you UID so in order for this to get up and running we actually need to build a registry we're gonna use a lick sirs native registry module and it's important to note that the registry module is not distribution friendly if you look up a given pit created on one server on a totally different server it's not gonna point to the same process but since our channel shares a socket with LiveView it's guaranteed that the live view process and the channel process are going to be running on the same server so we're good to go in terms of using elixirs registry so we'll tell elixirs registry supervisor to start supervising a registry named session registry when our app starts up and now we're ready to start thinking about how we're going to share this session UUID between a live view and a channel when the live view mounts from the controller we will generate the session UUID we will pass it in to the live view so that we can store it in live view state we're encoding it as a Phoenix token storing it in socket data signs so that we can put it on the page later we'll include this token in our socket connect request which is handled by the connect three function of our custom extended live socket will grab the token out of params we'll verify it will decode it to get the session UUID and we'll store that in the socket so that the channel can register itself when it's joined in the channels join function we will call register what is this era T of three register three we're telling the channel to register itself in the session registry under a key of this unique session you UID that the corresponding live view is storing in its own socket and it's registering it spin in this registry so now that we're registered we can actually teach our live view to send a message to its channel but in order to do that we need to answer a question when should a live view process send a message to its channel and the answer is this needs to happen after the template re-renders and here's why if the channel tells the client to adjust the scroll top before the new message appears there's nothing to adjust we're not scrolling down because there's nothing to scroll down on so how can we ensure this order of operations and I'm gonna go ahead and answer a question with a question and this is how many messages can a process work on at a time this is an easy one I'll give it to the room one just one so we know that a process can only act on one message at a time so when Live View receives that new chat message broadcast we will tell it to send a message to itself that message will hang out in the Inbox and wait to be processed Live View will then finish handling the new chat message broadcast which means update socket assigns and re-render only then will it process that talk to channel message and go ahead and send a message to the channel so it's going to look something like this all the various subscribing live views will receive this broadcast from pub/sub instructing them that a new message has been added to the chat at this point in time each live view will send a message to itself that then hangs out waiting in the Inbox then live view finishes the work it was doing which was to update socket assigns and re-render the template then it can process the message waiting in its inbox which is telling it to tell the channel something then the channel will respond to that message by pushing something down to the client that will fire our custom transcript okay we're gonna take a brief look at the code that underlies it and then I'm gonna finally tell you why you don't need to do this the amount of code that you need to write compared to what we're doing here is really fun okay so let's start by teaching our live view to send itself a message when it receives this broadcast from pub/sub in the handle info function for our new message event we're just going to use a send self and we are sending this send to eventbus message we will also teach our live views how to handle that message with another handle info function and what are we gonna do in this handle info function we need to first look up the channel pit in our registry so assuming that we decode the token from our socket assigns and get our session UUID out of it we can use a registry lookup to function to grab the channel pit that's registered under this same UID and then we can actually send a message to that channel super easily with another send function sending to the channel pit some message we want to teach the channel how to receive that message so in the channel we'll have a handle info function for the new message message all we need to really do here is push it down the socket we can finally respond to the new message on the front end by firing our scroll top adjustment pretty awesome let's see if this plays well so now we should finally see chat messages this is too slow we can finally see chat messages actually appear in view for our users as they add them in real time pretty good stuff we're great at this yes yeah all of us with only a million lines of code so we did it and we feel amazing we feel as amazing as my dog feels when he is on his way to the park especially when it's windy out when it's windy he gets like invigorated so we built it but it was kind of hard it was super hard it was a lot of work so wouldn't it be great if we could hook into live view events on the front end without extending the live view socket creating a custom channel building a registry sharing a session UUID doing all of these things this is a rhetorical question because it would obviously be great so introducing live view hooks surprise so as I said yesterday I found out about these can you guys tell how fine with it I am so I'm very fine with it obviously let's talk a little bit about this really cool new future live view and JavaScript interoperability so we can now leverage jf' slay cycle callbacks to fire custom JavaScript for live view events on the front end we can listen for example for when an element is added removed or updated by the server and this is the full list of things that we can hook into with live view hooks we're gonna pay particular attention to this updated callback when an element has been updated in the Dom by the server this is exactly what we care about when our chat window has been updated by live view on the server with the addition of a new chat message this is when we want our window to scroll down so by hooking into these callbacks such as updated we'll be able to fire some custom J as you can also at this point in time manually push events back up to the Live View process on the server we're not going to do that right now but I thought it was pretty awesome and definitely worth mentioning all right so how does it work how can we take advantage of this amazing new feature so easy so a little code all we have to do is add a Phoenix hook binding to our dom element which in our case is our chat window so we're adding a Phoenix hook with the name of new chat message then we need to define a hook object we've got a hook that she's up for our new chat message it implements an updated callback that will fire when the given Dom element again in this case our chat window is updated by live view and at that time it will finally fire our scroll top adjustment Jas with the help of this dot L which the hook exposes to us and it refers to the bound Dom node once again the chat window the very last thing we need to do is make sure that we pass our hook object to the socket just like this so what the live view Jay s library is doing under the hood and I definitely recommend checking out the source code if you're curious it takes the hooks that you pass in as hooks it attaches them more or less as event listeners to whatever Dom elements you put a corresponding Phoenix hook binding on and then when it is enacting let's say an update from the server side it's just gonna trigger any callbacks that you defined in your hook object pretty pretty cool that's it that's all we have to do so just kind of reviewing how this process works from end to end with hooks user submits a new chat message live view receives it and broadcast that message to all the other subscribers those subscribing live views update their own state via socket assigns and re render their templates this means that our new chat message hook will fire thus invoking our updated callback and finally setting the new scroll top and we're done that was all we had to do aren't you so glad I did that other thing I quit just kidding [Applause] yeah this is very cool it's a very very powerful tool so I do want to recap what we've built and just kind of bask all together and how awesome it is so by integrating pub/sub presents and ultimately hooks directly into Live View we were able to build a full rich chat application that supported message broadcasting user tracking and a highly responsive UI all of these things that you need to have like a really good chat app in a really good real-time application I think I think it's obvious that live you can really do it all the seeming limits of Live View that were presented by the chat application were surpassed by incorporating available Phoenix real time tools so almost all of our chat functionality is handled in like less than 100 lines of live view code and this is as opposed to like all of the channel back in front end code that you might otherwise have had to write furthermore using pub/sub to do what pub/sub is meant to do which is broadcast and subscribe to messages definitely doesn't feel like a misuse of Live View and layering presents on top of that functionality similarly just feels like a really good fit so lastly the recent introduction of Live View hooks makes it really easy to implement any custom JavaScript that we need on the client-side and that's it thanks guys [Applause]
Info
Channel: ElixirConf
Views: 9,224
Rating: 4.9346938 out of 5
Keywords:
Id: AbNAuOQ8wBE
Channel Id: undefined
Length: 37min 38sec (2258 seconds)
Published: Thu Aug 29 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.