Architecting Web Apps - Lights, Camera, Action! (Chrome Dev Summit 2018)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] PAUL LEWIS: So I was on the way to the office this morning, and I realized it is very much like web dev, rush hour. SURMA: OK. PAUL LEWIS: Rush hour is like all the traffic. Everyone is trying to get to the office at 9:00 AM, right? So all these people are in their cars, and on trains, and everything. Everyone's just rushing, and nobody can move for anybody else. And I think that's like web because-- SURMA: Yes, please explain this a bit more? PAUL LEWIS: Yes. Because you've got the main thread. You've got this one thread. And on that main thread, you've got all these work styles, JavaScript, layout, paint, composite, your framework. Everything is running, and everybody's competing for this one resource, the road or, in this case, the main thread. SURMA: Oh, so Mr. Framework has a car, Mr. Paint has a car, Mr. Business Logic has a car. And they all just want to go on the road, but it's already full. PAUL LEWIS: Exactly. And everybody's in the same boat where everybody gets the angry tweets and sees all this performance advice. And nobody knows what to do because all this stuff is just constrained into this one place-- rush hour. As we described in that video, that's kind of how we feel when we look at the web at large. We look at it, and we go, all this code should be here. But it just feels like the traffic is the problem. There's just too much going through the main thread. SURMA: And traditionally, the main thread is full. It's overworked and underpaid. You would say, cool, I'd use threads. On any other platform, you could do that. Just spin up a thread, put some code there, run it there, call a function. Hooray! Everyone's happy. But it turns out JavaScript and the web is special. And it is inherently single threaded, so you can't do that. PAUL LEWIS: Right, exactly. Every thread is kind of its own little universe, isn't it? Like we heard, it's a [INAUDIBLE],, right? And so you can't just go, just call this on another thread, but you've got some shared stuff that you can work on. So that's a challenge. And then it gets more interesting because say, for example, you're trying to build-- I don't know-- a chess game just for argument's sake. And you've got a chess engine. And the chess engine-- it takes a few hundred milliseconds to calculate a move. SURMA: It gets exponentially difficult. PAUL LEWIS: And you build it with DOM. And then some bright spark goes, do you know what we should do? 3D. And you're, like, [LAUGHS] I was already behind on my [INAUDIBLE] budget. If you could just not do the 60-frames-a-second thing, that'll be great. And some even brighter spark says, how about VR? SURMA: Yeah. I want to stand on the chess board. PAUL LEWIS: Yeah, I want to be, like, right in the game. And you're thinking, um, there was already rush hour. SURMA: Turns out frame rate-- quite important when it comes to VR. PAUL LEWIS: Yeah, and it could be voice. There are so many things that it could be. SURMA: So this becomes increasingly unlikely for you to be able to do this successfully on the web currently. PAUL LEWIS: Right. And so this is the question that we have been thinking through for the last little while. Is there anything we can do, anything we can suggest, think of to help? SURMA: We have two birds. We're looking for a stone. PAUL LEWIS: Exactly. And-- [LAUGHTER] SURMA: Ah, that went well. PAUL LEWIS: OK. Wow, I really need to think of what I was going to say next. OK, this. SURMA: Actor model. So we kind of stumbled over this. The actor model is, as it says right here, about 45 years old. And it's been made popular by Erlang and then continued with Elixir and Pony. It's languages that use the actor model to this day and successfully so. And we realized that it's actually a really good fit for the web. PAUL LEWIS: Yeah. Because what it does is it makes a feature of that single-threadedness of JavaScript. But we like to explain-- if you've come across it before, great. If you've not come across the actor model, we like to explain it in a very specific way. So check this one out. SURMA: So when we did Supercharge, you saw us on screen. But behind the cameras, we had an entire crew. And that means we had one person working the camera. We had one person worrying about if our audio was good. We had a director. And each of these people were solely responsible for that specific device. PAUL LEWIS: And instead of going over and pressing one person's buttons or just messing with settings, those people actually have to communicate with one another to get the job done. It you like, there are actors in the system, but they've got to send messages to one another and communicate and collaborate in order to get the final thing working. SURMA: So that's kind of where-- more video, more production value. It's good, right? So that's where we see a mentality that fits the web really well. And you start thinking about, where can you draw a line for individual pieces of responsibility in your app. And instead of thinking about classes and how you call the method on the other class, you can now think about these actors and how you can send the right message to request something to happen. PAUL LEWIS: Right. There are these areas of ownership. So let's think about, how, at a conceptual level, how would you think this through? What would it look like a little bit? OK. So imagine you have an actor, and it's job is to run your user interface. That's its area of ownership. That's what it does. That's its job and only that. You might also have another actor whose job is to handle state for your application and yet another one who handles the storage. Now, imagine in your app a typical interaction would be something like favoriting an item. The user interface, when the user taps on it, it will send a message over to the state that says this was favorited. In turn, the actor handling the state will send a message to the storage to say we need to remember that they favorited this item. Now we could also, at this point, introduce a new actor into this story, something that could broadcast. Because when the state changes, typically, what we'd want to do is, we'd want to send that both to the user interface and the storage to be reflected in both, I suppose, really. SURMA: Yes. And you can kind of see here, that it really is a separation of concerns. Ah, the click, there you go. [INAUDIBLE] transition-- do recommend. It really helps you to think about your app in a different way, helping you to figure out where does new code go, which module can you switch out to fix a problem that you're having? It's a really good way to structure the app in this way. PAUL LEWIS: Yeah, absolutely. And we use to separation of concerns when we talk about HTML, JavaScript, and CSS, or when we talk about components in a modern framework. So it's another version of that same story I suppose. SURMA: Another benefit that you get-- and we have heard about this problem a couple of times, I think yesterday and today-- is that we often see big chunks of monolithic JavaScript just run-- frameworks updating the virtual DOM, and then the DOM, or something like that. And with this pattern, you introduce a natural breaking point where you give the browser a chance to ship a frame. Because every time you send a message, there is a point where you say, OK, browser can intervene and ship a frame if we are out of frame budget. PAUL LEWIS: Exactly. Now, a little side effect a positive one of this is location independence. And we'll come back to this. This is a sort of repeating refrain that we're going to get into a little bit more. But think about actors as, they're not all the same. They have different requirements. Some actors will not need to have access to the main thread, for example, because of the kind of work they do. And as such, we might be able to run them in different locations, i.e., not on the main thread. As I say, we'll come back to that. But the idea here is maybe we just bought ourselves a little capacity for rush hour. SURMA: Because as long as the messages get delivered to the actor, the actor will then do the same work as it did before and will respond with the same message. So the entire app keeps behaving the same way, no matter where it runs. And because of that location independence, we can lower the likelihood of long work impacting the main thread and making your app janky. PAUL LEWIS: Exactly. So conceptually, that is what it is. But I like seeing code. I think code helps. And so we're not launching a product. We're not launching a framework, or even a library. We just wanted to have a chat with you about architectures. And we've been using some actor-based stuff for the last little while with our colleague Tim. And the three of us have just been putting some code together. So what we're going to do is, we're just going to show you a little bit of the code that we've been using. We've been using it to build some of our apps. SURMA: And we'll share that at the end. You're very welcome to try it out. And you're also very welcome to write your own. We don't really care if we use our version or someone else's version. It's more about the concept, about the architecture. PAUL LEWIS: Absolutely. But with this in mind, let's talk about a particular app, something like a stopwatch app, which you would start, it would count up in seconds-- pause, play, that kind of thing. And then you might reset the time if you're done. So in our code, we have this Actor base class. And that top function up there, hookup, is the first thing that you'd need to know. And the job of hookup is to kind of register an actor in the system so that we can talk to it later, so we can send it messages later on. Because ultimately, we won't know where this actor is in the system. And so we just need almost like a registry, I suppose, where we can say, I'm going to tell you there's an actor, and it's found under this name. SURMA: It's basically the equivalent of what you might know from custom elements. There's custom elements that. define. And you say, this custom element is now known under this name. And this does the exact same thing but for actors. PAUL LEWIS: So then we have our two actors, a Clock one and a UI one. And then in the bootstrap, we instantiate both our UI and hook it up, so it's available under UI as a string name. And then we do the same with the clock like so. So now, we can talk about how you might implement something like the clock itself. And in our case, when you've got something like this, it's almost like a pure data actor. It doesn't have any need to go near the DOM. It just wants to tick, and pause, and all those kinds of things. SURMA: I mean, what do you need, really. You need a set interval, and that's pretty much it. PAUL LEWIS: So time out? SURMA: Sure. PAUL LEWIS: I have a thing against set interval. It's a long story. Come find me after, and I'll explain why. Anyway, imagine this then. We're going to model this as a state machine. We start with a paused state. Our clock is paused. We can transition to a running state. Every second, we'll tick, and we'll go to the tick state. And that will take us back to the running state. And you can imagine being in this tick, running, tick, running state like in a clock. SURMA: Like ping-pong. PAUL LEWIS: Like a clock, Indeed. We could pause, and we could reset. And when we reset, we go back to that. SURMA: And that's a really nice pattern here that plays along well with the message passing of the actor model. Because all of these triggers, as they are called, in the state machine world could just be a message. You send a message to this state machine, it's being ingested, and then a transition happens. PAUL LEWIS: Absolutely. SURMA: So we found, actually, that there are a lot of implementations for state machines out there in the wild, and that's not very unexpected. And we have kind of been using xstate, which is written by David from Microsoft, and it's working really well. It allows you to declare your state machine as a JSON object. So you just declare your states and then what the transitions are. And then you just pass this to this machine constructor, and you get a state machine. PAUL LEWIS: So our clock extends the Actor base class, and what we do is we instantiate our state Machine. We say, go to the initial state, which happens to that paused state. And then later on, imagine that we receive a message. And our Actor base class has this onMessage callback, which is, I got a message. What do I do? In this case, we would assume that the state would be changing inside of our state machine. So we use the state machine transition to get from wherever it was to wherever it needs to be. So the message is basically driving the clock. And we'll inspect what the new state value is. So if we find that our clock is running, we'll set a tick time out for one second. If we tick, we increment our tick count. And then the clock will send itself a message. Now, it could call its own functions. But we tend to be a little bit-- we like it fair. And so what we do is we make sure the clock sends itself a message like every other actor would have to send it a message. SURMA: And you can add it like a message queue that buffers all the messages. And the actor goes through one message, processes, then it goes to the next message. And so if you just call your own function, it would kind of be cutting that line. So if you want to keep it fair, you would just queue your app message at the end and wait like a good human. PAUL LEWIS: Exactly, or a computer. SURMA: Sure. PAUL LEWIS: Cancel the tick if you're paused if there's one pending. When we reset, we'll reset the tick count. And again, the clock will send itself a message to pause. And now, let's talk about sending messages. That's next, yes. So the clock, is going to have to send a state update to the user interface so that it can reflect the time going up and so on, and so forth. And this is the opposite to the hookup. This is lookup, which is also in-- SURMA: Hookup, lookup, hookup, lookup, hookup, lookup. PAUL LEWIS: I lose him for hours at a time when he does this. OK, hookup and lookup. So we look up the UI, and then we can send it a message. SURMA: And that's very important to note here. The handle, this UI variable that we have there, it is not the actor instance. You can't go in and change a member variable of that class. It is just an object with a send method and only that method. Because that's the only way you're allowed to interact with any of the other actors. PAUL LEWIS: Exactly. And so in this case, we're going to send the UI a message. And the message is going to say what the time is and whether or not the clock is running. We found that TypeScript is really helpful at this point, because those messages need to be well-formed, and well-understood. And there needs to be a data contract. And we've just found, from practical experience, that TypeScript is a really good way of saying, this object looks like this. This is a number. This is a string. This is another object, and so on and so forth. So just take that as what it is, really. SURMA: A recommendation. PAUL LEWIS: Yeah, a recommendation. We found that useful. Let's talk about the UI a little bit. Interestingly, you can bring your own framework. SURMA: Yeah. In this model, we don't really care what kind of framework you use. You can use React, you can use Vue, you can use Lit, you can use Svelte-- whatever you feel comfortable with or whatever makes sense in your scenario. The interesting shift here is that the UI framework is not your base platform, not your entry point any more. The center of the universe has kind of moved. PAUL LEWIS: It's that bootstrap that we showed [? at the top. ?] SURMA: UI is just one participant of many in the system of actors. PAUL LEWIS: And if you find that it's not behaving well, for whatever reason, you can swap it out. The only thing it has to do is listen to messages of a particular type, which is kind of cool. In our case, then we're going to use Preact. It works just great. So we're going to import preact. Our UI extends the Actor class. And when it receives a message, we're going to have it render using Preact to, in this case, the document body. SURMA: Same again here. We need to send messages back. PAUL LEWIS: Absolutely. And to do that, it is a case of, on the UI side, we find our clock actor, we do a lookup on it, and we will send it a message, in this case, for this particular example, sending it a message to start. There'll be one for stop, or reset, and so on. Now, we get to talk about that location independence a teeny bit more. Because one of the questions that Surma, Tim, and I ask ourselves when we're making our actors is, does this actor need access to the main thread? And this is kind of to do with the rush hour thing. Our general rule of thumb-- and there are some caveats we'll mention in a moment. But out general rule of thumb is that a UI actor is the one that really needs the DOM. And therefore, it's the one that ought to be on the main thread wherever possible. There is an exception. The exception here is that certain APIs-- those for media security, device capabilities, and identity-- are only available on the main thread today. SURMA: We think it's a bug. And we've been talking to some Chrome engineers about exposing these kind of APIs in a worker and somewhere else. But that's just not the world we live in today. So for now, that's a restriction. PAUL LEWIS: Tools not rules-- so you might be thinking, ah, I should move all my actors away from the main thread. We'll get to that. But the thing is, if you've got a really chatty actor that needs to talk to the UI actor, you might want to leave it alongside the UI actor on the method. Because as Jason and Shubhie were talking about earlier, that there's a cost to the thread hop, and that might be more expensive than just sending a message and just keeping the actor alongside the UI actor, OK? SURMA: So basically, if you want to do that, just measure and see what the impact is. PAUL LEWIS: Exactly. So the location independence-- for all that notwithstanding, imagine we were back here where we started with our four actors. And they're all on the main thread, which is probably where we put them by default. We're sort of saying, you might want to look at it more like this. And you might be thinking, why do they say, not main thread? Surely, they just meant Web Workers. And we kind of did because, in most cases, when we build these apps, web workers do feature heavily. We do move quite a lot of our actors to web workers, especially if they're non-chatty. SURMA: But not quite. So we actually think, or have tried, that it's sometimes very, very useful to run an actor, for example, on the server side. And this is kind of an interesting jump to make because it allows you to incorporate your back end into the architecture of your entire app. It is just another actor in the system. And as a matter of fact, the game that you've been playing all day-- and it totally had no bugs at all whatsoever-- is actually written in this model. So every player that is playing is an actor. The admin panel that the MCs use to control the app is an actor. The presentation view-- just another actor. And then the Firebase Storage-- a shared actor that's running on the server side. PAUL LEWIS: Now interestingly, the mechanism by which they chat using the hookup and lookup can be anything. It could be Fetch. It could be a WebSocket. And it really doesn't matter. SURMA: Your Firebase database. PAUL LEWIS: Yeah. So long as talk, these actors can talk, and they've got a way of sending messages to one another, you're all set. So back to our original question. Did we actually help with rush hour? Would this actually help? Well, let's review. SURMA: So one thing that we definitely did achieve is that we are making it less likely to have big chunks of uninterruptable JavaScript and more little chunks where the browser can stop it in between and ship a frame. So that's definitely one advantage that we have. PAUL LEWIS: The location independence, hopefully, some of our actors can be run successfully away from the main thread. Hopefully, it's like fewer cars on the road at rush hour. That's good for everybody typically. SURMA: And as a result, a lot of work that can happen in an unexpected way, if you're processing a big API response to something, that can happen in the worker and not affect your main thread from going into jank mode. PAUL LEWIS: There are some other benefits that Surma, Tim, and I have noticed as well working in this particular pattern. One is better testing. With that kind of area of ownership, it's easier to look at an actor and go, oh, well I know what you should do. And you've got an onMessage that I can call, and I can make sure that you do the right things. So the testing seems to become a little bit easier. SURMA: Yeah. Off on the other side, you can mock another actor by just implementing the messages that that actor needs to receive and not do the actual work, but just send prerecorded messages back. PAUL LEWIS: You have a clear separation of concerns, which again, it helps you in terms of maybe dividing the work with your teammates or even just deciding for yourself which actor needs to be responsible for this part of the system? SURMA: And you get the code splitting. Because you have actors that can be hooked up to the system at any point in time, really, it allows you to split them up and load them lazily. You can just import them when you need them. PAUL LEWIS: Yeah. That's good. And bring your own framework. If you want to use a particular library or framework, you can. There's not a prescriptive way. If you want to use the [? one ?] thing, use it. Great. Now, there are some considerations in this world, in this setup that we described. One is actor performance challenges. If you imagine your UI actor, imagine it decides to run along and just be not very yieldy. You still have that problem. It's no different to a process or an application in an operating system deciding it's going to hog the CPU. This is not going to go away. But we do think that the Scheduler API that Jason and Shubhie mentioned in the previous talk is a huge part of this story. Because it's a great way for individual actors to start breaking their work up into smaller chunks. You may also be sitting there going, I'm not sure I could actorize my blog. And we would agree. It's not necessarily for all use cases. This works really well when you've got apps and in particular apps where you think, actually, I can ring fence. I can mark off particular parts of this application. And I can have some kind of owner for it. That's where it works really well. SURMA: And there's definitely a different mental model to this. As I said, it kind of shifts the center of the universe away from the UI framework into many center pieces-- all the actors are just communicating. So it definitely took us a time to build an intuition for where do we draw the line? What becomes an actor? What is just part of an already existing actor? What kind of messages should we send? How granular should this entire set up be? So if it seems weird at first, if you're playing around with this, that is kind of to be expected. It's a very different way of architecting a web app. PAUL LEWIS: So that was the rush hour bit. That future-facing stuff like the VR, AR, and so on-- well, we have some thoughts there too. Watch this. So you were talking about actors before. And I want to talk about cameras. And the reason I want to talk about cameras is because it all plays into that story, right? So if can, have one of these lenses before I drop it, because I don't want to. Your modern camera has two bits-- the camera body, which is the thing that holds the state when you're shooting in JPEG or RAW-- SURMA: Yeah. It's like the business logic. It knows how to take the picture and how to store it and it does all that. PAUL LEWIS: Yeah. Whether you're shooting video, or taking photos with it, or whether you are autofocus or manual focus-- you get the idea. Similar to like a web app, that's the state of what's going on. But you've got different lenses for different tasks. So that one would be something like a portrait lens. This one might be a wide-angle-- take something of a landscape. SURMA: As long as we make sure that the mounts are compatible, which I guess, in actor world, means that they speak with the same messages to each other. PAUL LEWIS: Yes. So the messages that they send are really important. They're standardized, right? And everybody kind of plays to that same data contract. Other than that, you can do what you like. SURMA: Plug it in. PAUL LEWIS: Off you go. SURMA: Last video, I promise. PAUL LEWIS: Yeah. So camera lenses-- why camera lenses? How does that apply to this story? When we talked about this earlier, I think very naturally, we would have all thought of the DOM. We would have thought of, in the chess game, we would have thought of this version. But there's the freedom that you get from a UI actor that, as long as it can speak the right messages, it can be implemented in different technologies. So you could have a different actor that does 3D. And now you get that. It just has to send the same messages as the standard DOM version. Or maybe one full XR, similarly, it needs to be able to do that-- send the right messages. And perhaps something like Voice as well-- similar kind of story. So as a byproduct of the actor model, we've all of a sudden got these different swappable UI actors that we can take with us wherever we need to go. And you might say, well, actually, all I need in this particular app is the XR and Voice. Or maybe I want DOM and Voice. SURMA: And you could even imagine a setup where you have a DOM actor with lots of effects and visuals and one DOM actor implements the same app, but with a much less intense memory consumption version, like a low-end version of your website. And once you detect that the device we're running on is actually kind of struggling to keep up. You can actually switch it out in the middle of the app and downgrade to the lower visual version. PAUL LEWIS: Or one would say reduced motion or something like that. And technically, this will be called multi-view or multi-modal, which is a very fancy way of saying there are lots of ways to interact with your web app. And as a byproduct, within the actor model, it enables you to do that pretty well. So if you're interested in looking at the hookup and lookup of the Actor base class that we mentioned earlier, this would be the place to go if you want to take a snap at that. SURMA: This is actually, not only the Actor base class, but it's actually a boiler plate. So it gets you started. There's rollup configured where you can just build it, does the code splitting, the lazy loading. It should get you started really quickly, so you can start writing actors and get a feel for how it feels. PAUL LEWIS: Let's be clear. It's experimental. It's just the stomping ground that we've been using for the last little while. And we'd love you to take a look and have a chat with us and just tell us what you think. So in summary, we're actually quite excited that something from 45 years ago has kind of come full circle, and it seems to be the thing-- SURMA: It's been hiding in plain sight. PAUL LEWIS: Yeah. And from some of the places in computing, we've kind of brought it over, not in a purist way. We have our own take on these things. But it respects that single-threadedness of JavaScript. It seems to help with rush hour. And it also seems to enable us to go multi-modal, which is really kind of exciting. And on that note, thanks. SURMA: Thank you very much. [MUSIC PLAYING]
Info
Channel: Google Chrome Developers
Views: 51,073
Rating: 4.9417005 out of 5
Keywords: type: Conference Talk (Full production);, pr_pr: Chrome, purpose: Educate
Id: Vg60lf92EkM
Channel Id: undefined
Length: 25min 11sec (1511 seconds)
Published: Tue Nov 13 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.