 You ready to start or? OK, cool. OK, so as you might have gathered from the title of this talk, we're going to be talking today about writing web services with an emphasis on making things not suck for mobile app developers. So a bit about me. I've been developing for Android since 2010. Currently, I work as an Android specialist at Aztec Labs in Hobart in Australia. My name's Christopher Norgivow, by the way. So everything that I've had to do whilst working as a mobile developer, I've generally had to write an app that interfaces with a web service of some description through an API. Now, sometimes the process of writing these apps can be easy because the API has made it easy for me. But sometimes this can be really bad because things have been developed without much thought for mobile. So this talk is basically a series of requests, ideas, and thought experiments for making web service APIs better for mobile apps. So the takeaway idea from this talk is that your infrastructure hates you. Mobile networks can't cope with the level of usage that we put them through now. So the techniques in the talk, I'm going to show you how to work around the ways that mobile networks suck. So primarily, it's a list of things that API developers should include and things that you should demand as a mobile app developer of the person who's designing your back-end API. I don't guarantee that anything here makes sense, but if you see things that you like, feel free to go and use them in the real world. So there's a bit of opinion in this talk. So allow me to set the stage. This talk is about networks. And to be fair, as developers, we've dealt with networks that are pretty good in the past. The quality of the networks that we've dealt with have lulled us into a false sense of security with false expectations. So when we develop code, we're normally working at a high-end workstation, a pretty expensive one. And these workstations are generally connected to a gigabit ethernet network via a very short cable to a highly reliable broadband internet connection. Sometimes we might be connected via wireless instead. But the point is that the connection that your work station is connected to is still highly reliable. And the problem is that we're developing with simulators and emulators for our mobile platforms. We're going to be testing our mobile apps on these same highly reliable networks. Now, our development networks are basically ideal. They rarely disappear. And when they're alive, things like packet loss, connection dropping, these never happen. Web services are built and tested with these sorts of networks in mind. Now, the problem is that actual mobile devices run on actual mobile networks. There's no such thing as an ideal mobile network. So the combination of unreliable networks and erratic users, it basically makes the avenues for failure of network-based mobile apps, the avenues of failure are huge. This is because users can store themselves in Faraday cages. People can use their devices on public transport. They go through train tunnels. They put themselves in black spots like crowded city centers where there's not enough capacity. Or they can just voluntarily lock themselves into a bad mobile network. Basically, mobile networks and mobile apps can't cope with these situations that we put them through. But still, basically every app out there needs to do some sort of communication with a service through the cloud. So for this reason, our apps need to work with the infrastructure that we're provided with. So with that in mind, what I want to do in this talk is explain properly what we as mobile developers need to understand about the networks that we're dealing with. And with that, I'll explain some API design ideas that you can take to your back-end team so that your next network-bound app doesn't have to suck. We'll do this first by explaining a bit about networks and how they work. Then we'll look at ideas for APIs that work in the face of failure. And following that, we'll look at making APIs actually useful for mobile apps. And at the end of the talk, you'll hopefully know how to make an API that doesn't suck. So to design an API for mobile networks, you must first understand how mobile networks work. Mobile networks are inherently different to fixed networks for various reasons. So this is the first mobile phone I owned. It's a Nokia 5110. It was released sometime in the late 1990s, I forget when. And it connected to GSM mobile networks. Now GSM networks are the basis for every mobile network that's out there today. So it had three important features that I can think of. It could be used as a telephone. It could be used to send and receive text messages. And its killer feature was the first real example of an app that I can think of, the game Snake. Now, Snake didn't need to connect to the network, but the other two things did. So to support these features, a network would need to provide a minimum of features itself. So to send voice transmission, you need not much bandwidth at all. You need 12.2 kilobit of voice transmission in either direction. Now, voice transmission protocol is what we call best effort. When you're transmitting voice, you can drop frames and still have your listener understand you. And we're also pretty good at repeating ourselves when a sentence fails to get transmitted. That is, we're really good at repeating ourselves if you don't understand me. Now SMSes, SMS text messages, these also don't require much bandwidth at all. Surprisingly, the protocol for SMS is also best effort. You don't get a guarantee whether your message gets sent. And Twitter has proven that you can't really send anything useful in 160 characters anyway. So at the same time as I had my Nokia 5110, the internet on desktop machines was just starting to take off. In more advanced countries, the first residential cable motors were going in. Millions of 13-year-olds were discovering web design for the first time thanks to things like GeoCities. So the experience of the internet was basically a beige PC of some description. And this experience didn't change very much at all for quite some time. We might have moved to laptops and Wi-Fi, but the way that we consumed the internet didn't change. And then very, very suddenly, mobile phones with internet capability happened. This was really sudden. And now we do a large proportion of our consumption of the internet via mobile devices. But the problem is that when we design internet applications, we assume that we're going to have this antique network. And so when people develop for mobile applications, they still think that they're going to have this antique way of talking to the internet. Also often, they provide exactly the same service, but with just the app as a skin. And this approach really doesn't work. Let's look at a TCP-IP stack, the traditional internet stack. So at the center, we have this thing, transmission control protocol and internet protocol. Now, internet protocol is what we call the best effort protocol. It's like GSM, voice, and SMS. But when you add TCP on the top of it, you get guarantees of reliability. And when you have a reliable broadband or dial up or cable modem type internet connection, TCP and IP is quite reliable. We can expect our network to just work, and this is the usual case. So we can expect our apps to just be able to connect to the network whenever we want them to. So when an error occurs, this is usually due to catastrophic failure. Network devices have broken. A remote server is down. These sorts of failures are very infrequent. So what's different about mobile networks? Well, on a fixed network, we have our reliable data link layer. On a mobile network, we have a best effort, our data link layer. Things don't always work, and they usually don't work. So this creates an entirely different dynamic for network usage. So we can occasionally expect for things to work. And the most frequent error case is not that a server is down, but that our link has failed. And these sorts of things are a part of our day to day usage of mobile networks. So it's clear that we might be doing the same things with these networks, but mobile internet is not the same as internet on our desktop. But users expect them to behave in exactly the same way, because mobile just came along so suddenly. We expect to be able to do the same things with the same reliability. And because our users are using our apps, they think that when it doesn't work, it's our fault, not the network's fault. So that's unfortunate, but that's how users behave. So how is asking for something on the internet work? Well, so in the ideal world, we merely have to negotiate a connection from our network. Then we can request data from our server, and then our server gives a response back. Now in some protocols, you don't need to negotiate a connection for every single thing you want to get off the network. You can reuse the same connection. In HTTP, this is called Keep Alive. You should look into it. Now as it turns out, making a net connection on the internet is surprisingly difficult. Frequently you have to do things like look up a DNS entry for a domain name or something like that, especially if you have multiple servers connecting to the same domain address. So if you make one connection to a server, one HTTP connection from your Android application, you might actually be making a whole heap of extra connections just to resolve a domain name and then just to get the data. That's lots of extra connections that you don't want to make, because making connections on mobile networks is expensive. And it's actually pretty slow as well. So ideally, you want to reduce the number of connections that you create. You can make connections long lived. Long polling is a great thing. Now the problem is, all too frequently, we lose all the signal on our mobile network. We go through a tunnel or something like that. So it's a bad idea to expect any particular connection to hang around for any amount of time at all. So something else we'd like to do is to not rely on connections being alive for a very long time. And as you can see, this is a bit of a conflict of interest. So we know that if we're going to be making a mobile app, we have to make trade-offs with the network that we're dealt with. So designing an API that makes a mobile app happy is a matter of exploiting this particular trade-off. So the primary goal of a mobile API is to be able to get information from some remote service in the cloud down to our mobile device. And the problem is that mobile networks don't guarantee that data will reach you on first request. In fact, they don't guarantee that the data will get to you at all. So planning to make APIs work for mobile is a matter of planning to fail. Now in this section, I'm going to start off with the hard stuff and then go through to the easy stuff. That way you're going to remember all the hard stuff and forget the other stuff when you stop paying attention. So you can go and do the useful stuff first when you go back into the real world and develop things. So the first tip I have is to make everything static. Solving static content is a solved problem because HTTP, which is the protocol that everyone uses for these things, was designed with the intention of serving up linked static documents. Dynamic stuff came as an aft of thought. And HTTP made sure that you could cache things easily in the face of dodgy connections. So the trick here is to make even dynamically served stuff appear like it's static. So you may be thinking, OK, I've got a web service and I'm generating new stuff for every request that gets made. I can't make everything static because then my service wouldn't work. Well, in far too many cases, what you're actually doing is serving up exactly the same content every single time for stuff that is unlikely to change, which is a waste if you're not telling your clients how they can cache stuff you're wasting bandwidth. So as an important rule of thumb, if your app's data doesn't change, then say so. HTTP clients are designed to take advantage of static data. So this here is an HTTP response. It contains a whole bunch of headers that identify various things about this response. Now, most important are these ones here. The last modified and expires header. If a last modified header is present, then a client can tell whether that data has changed since the last time it requested it. And so if it knows that it has that data, it doesn't need to request it again. This ETag thing here is basically a more machine readable version of that. If you provide the same ETag for request, you have exactly the same version of the resource. If an expires header is present, then the client will avoid having to re-download a particular request until expiry. So this means that a particular API call can function even if your device is offline. HTTP caches are pretty good at this. And I believe Android 4.0 has a connection case in HTTP clients. So go look that up if you want to make this stuff work. But you need to make this work from a server point of view. Provide these headers before your Android app can actually exploit that. So the problem is that if you have truly dynamic data, something that changes for every single request that you make, then you can't really cache it. So the trick here is to make sure that once you've made a request for a particular resource, a response can be trivially replayed until you know that you have all of the data. So to do that, we need to talk about REST. Who here has heard of REST? So this is REST here. It's a web page with a pretty URL, a human understandable URL. Well, that's not actually the case. Pretty URLs is sort of a consequence of REST. It's not REST itself. REST is about thinking about resources and how you access and manipulate resources over HTTP. So the idea is that every resource has an identifier, which is your URI, and that resources never change unless you tell them to change. And you make use of HTTP semantics, get, post, put, stuff like that. So if you use REST well, making resources reusable is quite easy. How do you do this? Well, if you let your URLs identify a specific response, so if you ask for a resource to frequently change this, instead give back a URL that identifies a specific response. So if you make a request to something, have your service generate that response and then put it in a memory case of some description, then return a 307 temporary redirect with the case key, which is that bit at the end, as a resource identifier. Now, if you have HTTP keep alive switched on, when you get that 307 header, you can go and make the second request without having to negotiate a new connection. This is basically free. But the thing is that if you have a specific response in memory, this is now a resource. And you can suddenly reuse your dynamic responses. You can use an HTTP 206 partial content, which basically says, I've got this much of the response already. Go and give me the rest of it. So you don't have to download stuff more than once. This is a really cool idea, because clients can resume partially finished responses. So if you have a really long response, you don't have to re-download it if you're going through a bad network. And that's a great thing. Now, if you have resumable requests, if you can do this HTTP 206 thing, then tell your developers about it, because they need to do a bit of work. But it's not much work on the client side. So if you support it, document it. So the next idea is to make everything repeatable. So if you have a really dynamic API, then it's important that requests be repeatable. It's important that a mobile app and the service that it's connecting to maintains its synchronization. It's far too easy to implement a service that gets ahead of itself. So the particular case I'm thinking of is something where you're plotting stuff that changes over time, things that track movement on a map, a particular case where I saw this was in a chat application. Anything where events change over time. So the case I see is this. You have a URL called events. And when you call events, this serves up the latest set of events that you need to process in your app. The problem is, well, you get the next one. So you've got events six through 10. And so basically, what happens if, when you make your call here, the response fails? This is not particularly good. It's all too easy to write a really bad PHP app, which just says, OK, a user has made this particular call. So update the session saying it's now up to event 11, even if it didn't get all the events in the previous call. This is really bad because it means that the mobile app gets out of sync. So a rule of thumb here is that only the app itself knows what state it is in. Oh, oops. I think I've lost my slides. Yeah, so only the app knows what state it is in. So if you don't keep things in sync, you end up with inconsistency. And if your app shows inconsistent data, it's really not worth using in general. So one trick you can do here is to make sure that your app can repeatedly serve up events that it thinks it has already served. You can cache things in memory. So you can make the same request multiple times. And the way that I do this here is by adding a since parameter to my endpoints. So you can ask for old events, even if it wants them. So this means you can ensure consistency. So if things crash, when you've made the second call, well, you can make this call again. And that's ensuring consistency. Now, for bonus points, for saving bandwidth as well, you can use partial data. You can do this by caching your responses, like I showed you before. Or you can present your responses in a way that is amenable to incremental parsing. So to talk about that, let's talk a bit about data encapsulation, how you send your data across the network. So everyone here has probably heard of XML. I don't like XML very much. It's big. It's bloated. It's not particularly good to read. And it got used in far too many places, if you ask me. Now, more recently, JSON came around, and it replaces XML in a large number of cases. Now, there's good reasons for this. JSON is lightweight. It's easy to read. And it maps well to standard data structures that we write up in our apps. So if we like JSON as a format, well, we need to realize that JSON has flaws. And I'm going to go through a couple of these. And these flaws are not particular ones. But if you don't know that they're there, then you won't code around them. So JSON has two ways of expressing compound data. You have lists, which store ordered information. And you can derive useful information from where something appears in the list. So if you have item 1 in a list, you know that it, meaningfully, comes after item 0 in a list. So because something appears after something in a list, it logically appears after something in that data structure. Now, you also have JSON objects. These store unordered clumps of information. So if something appears after something in a JSON object, that does not meaningfully come after something in that object. And I'll give you an example of that. So here's a standard data structure. You've probably seen trees before. So in JSON, you can represent this tree as a set of nested objects. This produces the same representation as that tree. Now, the problem with JSON objects is that objects don't define order. So if we turn this into something a bit more concrete, it's like that. So we have an object here. And just watch those bottom two lines. This JSON object here, with those keys and those values in that order, is exactly the same JSON object as that one, right? So those two lines appear out of order in the two representations, but they are exactly the same object in memory. So with that in mind, we can now look at one useful thing to do with incomplete data. And that's to be able to parse things incrementally. This lets you show incomplete data whilst you wait for the remainder to load. So if you've used a web browser on a slow internet connection, you know that if your connection drops out, your web browser will try to make sense of the HTML it's already received. It will display what it has and try to make sense of it. Now, you can't do this with every data format that's out there. It just doesn't work. So if you have a complete JSON object that looks like this, but only this much is loaded, then you don't have a valid JSON object and you can't make a valid JSON object out of it. You need to have every key present. This is a meaningless object if you lose those last two keys. So something that I see a bit more frequently is to wrap a list of items. And this list of items is the most important part of the response inside a JSON object, which carries a bit of extra information for your display. Well, if you lose your connection halfway through that list, because that list is inside a JSON object, you can't make sense of the rest of the list because you need the outside object to be finished as well. You need all the keys of every object there to parse it. So that's unfortunate. This is also meaningless. You have to start your response again. So we need to think about something called record-oriented data. If you have a list of events or a list of people or something that's in a list, then what you have is a set of records. Now, if you have a set of records, you have record-oriented data, so use a record-oriented format. These are trivial to parse incrementally and you can resume responses of record-oriented data if your connection drops out. And the most common record-oriented format out there is one called CSV. Who's heard of it? Right, everyone knows about CSV. And because you all know about CSV, you're probably thinking why on earth is Chris talking about CSV and to talk about web services? Well, CSV has a few key attributes. Firstly is that every record has the exactly the same number of fields and that every field appears in a predictable place. This means that the format of your records or your objects is rigid. And it also has a defined rule that tells you when you have a complete record. You just look for a new line character. New line character, you have a new record. So in an event-driven situation, using event-record-oriented data means you can do something like this. You make your first request, you have your second request, and that fails after three of the records or something like that. Well, you can parse what you've got so far and then start at everything after event seven instead of going back and asking for everything since five again. So that means you can pull down parts of the response, display those to the user and then go back and get the rest of the stuff that you missed out on. So I don't actually recommend using CSV as a format. CSV is hard to debug, it's not very expressive. And there aren't a lot of languages that have good CSV support out of the box. What you need to think about instead is whether you could represent your data with CSV. So if you've got a record-oriented format that looks in CSV like this, we have a fixed number of fields which means that every record is predictable. And every object is represented with a single CSV record. So this CSV file maps down to a JSON list like that. Right? So every record is a JSON object and every JSON object has keys. But the important thing is that the outside object, the outside, like the wrapper of your response is something that has defined order. And when you have defined order, you have a record-oriented format. So if your connection fails halfway through, I don't know, response number three, then we know everything that we need to know about the first three responses there. We just drop off the bit of the record that we missed the end of, drop the comma at the end and shove a square bracket on the end of it. We can then parse that and then we can go off and make a request for the remainder of the data. And that's what you get for encapsulating in lists instead of objects. So the next thing I want to talk about is keeping your responses small. If you have a small response, you can guarantee that your user is going to get the stuff more often than not. So I'm going to give you a counter-example of this to explain how somebody has done this wrong. And that counter-example is Twitter. Twitter's a pretty common JSON API. I've written a few apps that use it. So I'm going to request a single tweet from Twitter. This is the JSON that gets returned for that. Now, I'm going to look for the actual text. Yeah, here it is just here. Here's our 140 characters' worth of text right there in the middle of 3,396 bytes of JSON. So what have we actually got in there? Well, we've got 24 times the amount of extra information there. It's a bit bloated. Let's see what else we've got. We have here 1,540 bytes' worth of user profile information. Information about the user who has sent this tweet. It's actually there so that you can completely replicate the Twitter website with your background and your colors and everything like that from the single JSON record. And I can't think of a single application other than the Twitter website that needs that. So, yeah, please don't be this bad. Now, the next thing that you want to do is you want to minimize the number of requests that you make. So keep your responses small but minimize the number of requests because the number of requests you make, if that increases, then one of them is going to fail. And you also get lag if you make lots of requests. Initiating a connection is the highest source of latency on 3G. So it's important to keep the number of connections to a minimum. So you need to provide more data than is absolutely necessary. So I'm going to give you an example here and what do you think that example is going to be? No, no, I'm going to show you Twitter again. And I'm going to show you exactly the same piece of JSON. So here is our text here. That's the most important bit of information that we need to display. But there's other things that you want to display. You want to, I don't know, you want to know about the users that are mentioned in it so that you can go off and ask directly for each user that gets mentioned in a tweet. I get their profile information so you can pull that up quickly. It means you don't need to make one request for the user ID for a given user and then another request to actually get that user's profile because you already have their ID. There's a bit of information about which tweet is being replied to so you can go and do your conversation chaining. So you can say this tweet is a reply to that tweet so go and fetch that tweet and then get the tweet before that. The point is that there is useful extra information here that you can provide to your user or to your client app. So yeah, be selective about what information you send down. If your information is highly redundant, then separating things out into a separate request, it might save data, but getting your data to the client in the first place might actually be more important. Now, one major failure that affects every project that I've ever been involved with is a failure to plan for the future. You can add new features to an API, you can delete other endpoints, and this makes your apps a problem because if your app tries to connect to an endpoint that doesn't exist anymore, then that's going to make your app look like a failure. And if you're developing for iOS and you release a buggy version, you've got to wait a week for the new version to go out. You just can't change things whenever you want anymore because there'll just be a broken version of the app out there in the wild for a while. So version your APIs and do this from the very start. Make sure that the first version that you release has version information attached to it. This means that you can break backwards compatibility when you release a new version of the API. You can release a new version of the API, and that means that old, un-updated apps can still work. Users aren't required to update, and, you know, quite often they don't. So here are some ways to do this. The first one, which is pretty common, instead of just having a URL with a resource at the end of it, put a version tag in the middle of the URL. So say, slash v1 slash your endpoint. And that's one way of separating your version out. Another thing you can do is you can identify custom schemers in your MIME type. So instead of saying application slash JSON, you can say application slash JSON plus some custom extension. And that custom extension identifies your JSON schema. Now, the final avenue for failure that you need to plan for is user failure. And as we know, there's no way you can ever avoid that. So the final part of the talk is to design your APIs to be useful. Make sure that consumers of APIs can do what they want and easily. So the topic I'm going to talk about is replication. Your API should allow an app that you're producing to easily replicate every feature on, say, a web application or a desktop application. If you've got some bigger picture product that your mobile app is just a small part of, make sure that you can replicate everything in the bigger version of the app in the mobile app. It should be easy to do this. And the way to do this is to make sure that everything is exposed. Expose as much as possible. If you have functionality, add an API call for it. If you don't want to do that, add the functionality to an existing API call. So design your API in tandem with your web design team or your desktop team. Make sure you are developing for the same way of accessing your data. Because what that means is that if your website is using your API calls, then if they can't get it something, there'll be an API call added for them. Chances are if a website doesn't work with just the API, then a mobile app won't work either. Now you should always consider data encapsulation. JSON and XML are actually pretty similar. Whilst we all prefer using JSON, if you've got a pointy head boss who only knows about XML, then provide XML support because you can encapsulate the same data structures and say, hey, we've got an enterprise XML API and that makes your pointy head boss like you a lot. But on that note, fight against SOAP because SOAP is awful. It's impossible to code SOAP on iOS in particular. There's limited support on Android because they've got all this JavaCraft hanging around forever. But SOAP ties you to XML and being tied to a particular encapsulation format is a bad thing. Finally, don't replicate the structure of your database through your API. And API is meant to be useful for developers. So if your data model is only useful to a database and it's heavily normalized, then you're going to have to make lots of extra requests because you can't do joins over HTTP. You can't take two models and join them together in a single query. You want to make sure that you expose data models that make sense for the consumer, not data models that make sense to the database. This is because joins are impossible to do in anything except SQL. To close off, I'm going to talk about HTTP semantics. HTTP has a whole bunch of useful semantics that you can take advantage of when writing an API and it will make it easy to code when you're writing your client code. Use HTTP properly. So firstly, HTTP has methods. It has get. Get is good for getting things. Posting is for changing things. Putting is for creating new things and stuff like that. These all have defined semantics, so take advantage of them. And that will make it easy because you don't have to document as much because if users know, if your client coders know exactly what methods each endpoint responds to, then you don't need to say, oh, get will get this resource because client codes will already know what get does. Use status codes properly. Status codes like redirects, the 300 series of status codes, actually inform cache behavior. So if you use 307s versus 309s with regularity, then that means you know that you can cache 300 responses. So if you've got a permanent redirect, if your API's changed, then say, so 303, say you got a 303, that means that your cache will know to go and get the correct version or the correct endpoint rather than going to the wrong one. 307s say temporary redirects. Temporary redirects are ones that say, okay, you can go and ask for this endpoint multiple times. There's the same thing for the 400 series. So everyone knows about HTTP 404, right? There's also HTTP 410. If you say HTTP 410, then you're saying that this resource is never going to exist again. So your cache will be able to tell you, oh, that endpoint will never have data at it. So why waste time making a request? 404 instead will say, okay, you might be able to go and ask for this later on. And finally, if your web server is actually a teapot, always respond with HTTP 418. I'm a teapot status code. It's a real thing. So that's the end of the talk. Hopefully you found something useful in it. Now, I have Speaker Connect today at 3 p.m., so it's going to be downstairs next to the registration desk, I think. Are there any questions? Can we get a mic over here? Hi, Chris. Hi. I'm Vidya here. Actually, I would like to know, in one of the slides you've presented that JSON is lightweight compared to XML. In what ways is it lightweight? Could you elaborate on this? Okay, so say you have a JSON object with a whole bunch of keys. So each key will represent a piece of data in a JSON object. Now, if you want to do the same thing in XML, you have to say open tag, key, closed tag, then the data, and then, sorry, open tag, then the data, then the closed tag. And you get all this extra redundancy, which makes it really hard to tell which parts of each object relates to which other part. Whereas with JSON, you just say key, then value, key, then value. It doesn't have all that repetition that XML has. Thank you. Okay. Down the front here? Hi. Hi. So you said that everything should be like repeatable, so that it seems like from 0 to 5, then 6 to 7. So for that, is there any protocol because as a developer, developing the complete API will be difficult for me? So say that again. Is there any protocol there with which I can use? It's more a matter of make, like this is all over HTTP, right? And if you take the approach that I said at the start, which is to cache responses, or in particular to not throw away events until you know that they're at the clients, then just putting this in your service will make sense. And using this since parameter, we'll always make sure that you can go back and get things that you might not have already got. Does that make sense? Okay, great. One over here? So one last question. Great. Yeah. So I kind of build APIs as a day job. So this talk was very informative. So thanks for that. Not a problem. So the question is kind of like what he just asked. So building something like this seems really cool, where you can build for failure. But then the amount of logic that needs to go in on the side of the API and on the side of the client is a little high for something like repeats and stuff. So do you know something that has done this in the right way, probably some open source thing so that we can go look it up and get better? So the particular case that I was thinking of was a project I worked on a few years ago. It was an open source, unfortunately. But I lost my train of thought. Sorry. If you have a... It's not that much of an extra impulse to do the things like if you're serving up lots of events. The logic for caching is not actually that difficult. Once you know that the user has incremented their since counter, then you can throw away everything before that and you can actually implement that logic in the subsequent requests rather than doing it on a scheduled basis. So if you say, I want the events since five and then suddenly it says, I want the events since seven, then you can throw away events five and six, right? It's... You don't have to do it in real time. You just do it whenever... You do it lazily when requests at update the since counter goes in. It's not that much of an issue. Yeah, maybe we'll sync after... Oh, definitely. Yeah, that'd be great. And I have speaker connected at 3 p.m. as well. Yeah, thanks a lot. Great. 2.15 today. So 2.15, apparently the schedule's changed. I think we're out of time for questions now. So we are. Yes. Okay, great. Thank you, everyone, for listening.