From iOS to Distributed Systems
Michele Titolo at Swift Summit 2017
Good morning everyone, welcome to Swift Summit day 2. As Andyy just said, my name is Michele Titolo and I'm here to talk about “From iOS to Distributed Systems”. As Andyy also said I'm a lead software engineer at Capital One and eight months ago I changed teams. I moved from working on the flagship Capital One app to working on backend services that support that app as well as a bunch of other things that Capital One releases. And the biggest reaction when I was telling people that, "Oh, I'm moving to backend. I'm moving to backend. I'm so excited." Everyone was like, "But you're a mobile developer." As if the skills that I had and the software that I built was somehow significantly different than any other software that I'd build in the world. So, I started thinking about okay, if people are going to be reacting like this when I say I moved from iOS to backend, I need to figure out what I can talk about.
The result is actually this talk. So when I was thinking about, okay, mobile - backend, how do my skills translate? I reframed the question; what are the kinds of problems that I solve when I'm writing mobile apps? What are the things that I do every single day? And mobile apps actually do a lot. They're really complicated. Gone are the days when you can make an app that has like four screens to release it to the store and make thousands of dollars. That was like 2008. We're in 2017, the world is different.
So when I think about mobile apps, I think about state. These are really large state full complex applications. I think about concurrency because mobile apps have a lot going on, whether it's network requests, user input, animations, background processes. There's a lot going on in mobile applications these days. I think about instrumentation and metrics and keeping track of how my app is performing, how users are using it. I think about testing because obviously we all write test for everything, right? Yeah. And lastly I think of developer efficiency of actually being able to ship my app to the rest of the world for other people to enjoy.
So that was iOS, now let's talk about distributed systems. It's a great buzzword. I love the buzzword except it can be kind of confusing. People tend to use this term to mean a couple of different things. If you hear the term distributed computing, that's actually something entirely different from what I'm talking about. When I say distributed systems, I mostly mean microservices. So you're building an ecosystem of different pieces of services that work together, communicate, and so you have a big whole made up of a lot of different pieces. And these are really focused around single responsibility. 'Micro' is obviously the word for 'small' in Greek.
So these are really, really tiny, really, really focused services. And if you are interested in learning more about microservices, there's this wonderful book by Susan J. Fowler. It helped me a lot in my transition, kind of getting to know the basics of microservices and how you want to release them into production. And she has seven kind of core tenants of micro services, stability, reliability, scalability, fault tolerance, performance, monitoring and documentation. And so I was thinking about that for this talk and being like, "Okay, how can I tie these two together?" And it turns out these seven things relate back to the things that I just said, because if you're dealing with reliability and scalability, you need to manage your state really well. If you're dealing with concurrency, you need to deal with fault tolerance, you need to have really good documentation. If you're dealing with instrumentation, that's your monitoring, that's also your fault tolerance because you need to know if something's going down and your resiliency because you need to know if you need to stand up more servers.
Testing is just universal, I hope everyone agrees with that. And developer efficiency for shipping fast and being confident that your services will work in production. So these are the five aspects of software that I'm going to talk about today in detail and then we'll wrap it up at the end. So first is everyone's favorite topic. Who loves talking about state, right? Yeah. Cool. As I said, iOS apps are large and incredibly stateful. We have all of these things going on in the apps. There's a lot of different use cases and apps grow really, really fast, and then you get massive view controllers. Just again, another favorite topic of conference talks specifically. I'm not gonna go into super detail, but you can definitely find other talks online if you're dealing with one of these. But if you're trying to fix one of these because massive view controllers are not a great thing to have in your application. They're long, they're big, they do too much. They're really complex to change. People don't like touching them. So you generally try and remove these from your codebase.
So how do you do that? You break it down into your functional parts. You figure out what are the different things that this view controller is doing and separate those out. That should sound kind of familiar, right? You break a big system out into lots of little parts that then work together. It's a lot of how microservices work, except microservices have way more inflexible boundaries, so where in iOS, you can just start using another class or import a framework. In microservices really you actually have to set up lines of communication between two different services. So it's even more of separation than you would get on a view controller, but at the end of the day, it's still kind of the same idea.
But it's still really popular to use dynamic languages in microservices. And you're probably wondering, well why? Like statically typed languages are really important. We love them. We're all here at Swift Summit, like statically typed languages are awesome. Well, what I found is that the smaller your codebase, the fewer language features that you use. If you're building a really, really small service, that really only does one thing well, you don't need a huge object graph. You're not going to be writing thousands and thousands of lines of code. Most of the services I've written in the past few months have been like less than a thousand lines of code. It's a lot less state for you to encapsulate, which means you don't necessarily need as many language features. They're great when you have them, don't get me wrong, but you're not necessarily gonna be using them all the time.
Shared state. You can never get away from shared state. It is there, all around us, even though anything that people say are stateless services, they have state. That's just a buzzword. So when you think about shared state, most of you probably were like, "Well, I know exactly what she's gonna talk about. Singletons.” Everybody loves to hate singletons, and why? They're giant bags of mutable state. So when you're dealing in your iOS apps with a singleton and you're trying to refactor those away, make it easier to deal with, what do you do? You use dependency injection. You modify your code so that when it's time to use one of these things, it's passed in instead of you just calling it and trying to get at the shared thing somewhere else in the world. That may change, it may go away and it just gets really complicated. So dependency injection is a design pattern that we use on iOS to work around singletons.
But hey, there's this thing in the microservices world called Service Discovery and it's a lot like dependency injection. So you have this discovery service and every micro service that pops up tells the discovery service, "Hey, I do this. Hey, I do that." So if you need to have one service that talks to another, you go through this middleman because if something goes down, you have a way of knowing that ahead of time. You're not just blatantly like over and over trying to call a single service and you're just like getting five hundreds over and over again. There are ways to help an ecosystem be healthy. So they register and route to your dependent services because even in microservices ecosystems, you do have things that are dependent on one another. That's always going to be the case. Software is big and complicated. It's just the different ways in which software is big and complicated.
Okay, you're also probably thinking, "Okay, singletons or one kind of shared state. What about databases?” We work with a lot of databases on the backend and well you treat databases a lot like you treat singletons when you can't get rid of them, that is. And that is you make a reading from them really, really easy. You wanna be able to get data out of your databases whenever you want and as quickly as possible, but you wanna make modifying any data a little bit harder so that it doesn't happen quite as often and then that you know that things are happening in a particular order. And it turns out this is a universal concept and all of the major cloud providers and all of the major databases that you would use in backend have this kind of functionality built in and it's called a Read Replica.
You can stand one up with Postgres, MySQL, any of those things. You can just create a read replica that duplicates a database for you. It will manage the changes as things get changed and that's it. So kinda the same thing. Next up is concurrency. iOS applications are incredibly concurrent. We're always having to do multiple things at once. Gone are the days when users would let a spinner to sit on the screen for 10 seconds while some big process happens. That's not acceptable anymore. The thing on iOS is you have potentially 1,000,000 users each using one instance of your application, on their own device, on their own cellular network, on their own iCloud storage plan, all of that stuff. One user, one instance. On distributed systems microservices, you'll have 1,000,000 users using some number of instances that is significantly less than 1,000,000. So you have as was mentioned yesterday, you're working in a multi user situation, multiple different people using the same instance of your application.
And for me this was kind of shocking. I'm used to like; “But there's only ever going to be one user. Why would there ever be another user?” Nope, you can't do that. You have to be able to handle multiple users. It's a little crazy, but you deal and what this really means is that concurrency on your microservices matter, as soon as your service starts not being able to handle incoming requests, it's gonna affect everybody else using your service. Whereas on iOS it might just get a little bit laggy. Users might see a spinner for a couple extra seconds. It's only gonna affect that one user probably, but in microservices you could be affecting thousands of your users if you just add like a two second delay.
And we're all familiar with Don't Block The Main Thread. This is like a thing, on iOS, the main thread is the golden important thread that you shall never block, but this is also a thing in microservices because all of the different systems that handle multiple concurrent pieces of work have some sort of main thread like concept. Whether it's a runtime, whether there's actually a main thread. So this concept is extremely applicable when you're doing backend. So cues, threads, callbacks, all of the things that we do on iOS to make our applications concurrent all matter. And GCD is amazing, which is dispatched in Swift 3. Oh my gosh, it's amazing. I miss it so much. Because there are a lot of languages that you'll use on the backend that just don't have anything quite as sophisticated. There are ways to do it, but it's not as nice. I've got to say GCD works great.
Instrumentation. So now we're halfway through. So what do I mean when I say instrumentation? I'm talking about two things, both performance and usage. Because iOS apps are black boxes. You put your app into the store, you might get downloads counted automatically, but you have no idea when users are using your app, you have no idea what they're doing in the app. You have no idea how long network calls are taking to complete in the app. You just don't know. So you have to track something. If you don't track something in your iOS app, you have no way of knowing how it's behaving in the wild. And so as iOS developers, we start getting these instincts on knowing what to track. Do we track when a user opens a screen? Do we track when an API has an error? The answer is yes. You track literally everything because it's the only way you're ever going to know.
Because apps behave differently in the wild as we've all seen. So on iOS, when you're talking about instrumentation, you might use Instruments.App locally, check your performance, your core data, you're drawing speed. There's also the Xcode 9 memory debugger for Swift, which is really awesome.
You also have app store analytics built in, so you can check downloads, page views, all that sort of stuff, but chances are if you wanna get that really fine grained information about how your app's performing, you're gonna need to use some third party analytics. You're gonna need to set up points in your app where you're saying, "Check this thing.” Whatever this thing is. And it turns out microservices, they're also black boxes. This, I guess shouldn't have been a surprise to me when I moved to backend, but it was and that developers shouldn't have Prod SSH Access. So I can't just like log in to a production server whenever I want, which is a good thing, but it also means that you still need to be able to track everything that's going on in your servers and surface it to different people who have a stake in keeping your service alive.
Also backend has alerting. So if your service goes down, your service starts having problems, you need to tell people like, "Oh hey, we can't process orders. Like no one's buying anything from our site." So you need an alert for that. And for that you need to be tracking something. Again, if you don't track it, it doesn't get surfaced. So again, local development is different. It's even more different than on iOS because you're running on different hardware. Most of us use Mac’s to do development. Your hardware is on some cloud providers somewhere in the world and it's running Linux. It has a completely different CPU and GPU and Ram configuration than your local laptops. Your app is going to behave extremely differently. You can simulate your load, so if you expect kind of spiky stuff, Black Friday's coming up, you can simulate that load.
But it's imperfect, it's imprecise. You never know exactly when the extra load's gonna hit. You never know that you might be in the middle of deploying something and a marketing email goes out and suddenly you have twice the traffic, but half your servers are down. There are all of these things that you need to track so that you can react. So instrumentation helps you surface and diagnose your issues regardless of where you're building your software. It's just kind of one of those core tenants that will pay off in the long run.
Testing, who loves testing. Yeah, wake you up a little bit. So this is the testing pyramid. I forget where I first saw this. I tried to find the credit. This is my own version of it. But when you talk about testing, there's generally three levels. You have your unit tests, you write a lot of unit tests, cover all your use cases, all of your things and those are in really isolated pieces.
You have your integration tests where you're testing how different pieces work together, and then you have your end to end test, which is your full tests on iOS. On iOS your end to end tests are usually your UI tests. Say that 10 times fast. So as I've already said, testing is universal. Software needs to have tests, right? I could just stop there, but I'm going to talk a little bit more about testing. So testing on iOS. You create your isolated state. If you're running your tests in the simulator, you try and clear the simulator or you try and clear the caches so that your tests run in the exact same configuration every time. You use dependency injection to deal with things like singletons and databases so that you don't necessarily have to have real data. You don't necessarily have to make real network requests, all that sort of stuff.
And of course your tests run frequently. If your tests aren't running frequently, they're not actually being used, in which case, why are you writing tests in the first place? And of course test for everything. It doesn't matter if you have UI code, backend, like service or network code, data processing code, animation code, everything needs to be tested because that's the only way you can really move forward with confidence.
Now let's talk about microservices. But first there's one little thing I kind of glossed over. Microservices or what we call infrastructure heavy, so when you're building microservices, you're not just going to like put up a microservice and that's it, and call it a day. I already mentioned Service Discovery and that's just one of the many pieces in a microservices ecosystem. Microservices also are usually released with a continuous integration and continuous deployment pipeline, aka CICD. And what this means is from end to end, you make a pull request, pull request gets merged, and then a whole bunch of stuff happens automatically, which results in your code going to production usually within a few hours, sometimes minutes, depending on your pipeline. How that’s set up. So if your code is going into production basically as soon as it's merged, how important do you think tests are?
Yeah, very important. Because if your stuff's going into production, you don't want production to break, right? Um, so when you automate everything, when every single step that you're doing is automated, you really, really need lots of tests. And I can't stress this enough. Testing is incredibly important. So when you're doing tests for microservices, you need isolated state. You need to be able to dependency inject just like you would before, mark everything and you need to test all the different layers. You might not have UI but you still have some sort of integration and end to end tests that your application can perform once it's up to validate that everything's working. And so this is just like on iOS, tools will be a little bit different, ecosystem will be a little bit different, but all of the work that you've done to learn about testing, like you can go write tests in another language really easily.
And last but not least, developer efficiency. It took me a long time to actually figure out what to call this section because I originally wanted to call it, 'Move Fast and Break Things’, which is the famous Facebook slogan, where they used to be like, "Our engineering culture moves fast and break things", but they realized that was a really bad idea. It turns out that people don't like it when you break things. And so Facebook's new motto is 'Move Fast with Stable Infrastructure.' And the thing is you can only move fast if you do have stable infrastructure. This makes a lot more sense to me now as a back end developer than it did on iOS because developers really like shipping. Even now, like if an app releases once a month, that's still a pretty slow cadence in the modern world, and with your microservices and your CHT pipelines, things are happening all the time.
So if you can think back to the good old days of, "It works on my machine", you can't do this anymore. It can't just work on one person's machine, that person can quit, that person can go on vacation, that person can have their hard drive fail and then you lose everything. So this isn't something that is really scalable anymore and codesigning is like one of the worst places for this to happen. How many people have tried to release an app only to find that the person with the certificates wasn't around? Like because only one person has release certificates. Yeah, it's a big problem. So unless you're able to efficiently release your software, you're not really going to be able to do much. Thankfully on iOS we have the wonderful fastlane tools that have been making this a lot nicer to deal with and that you can run things on your CICD servers, because easy releases means you can release frequently, and it's exactly the same for microservices. When you're building really, really, really small pieces of software, there's no reason why you should wait a month to deploy it. It's like a couple hundred lines of code, just put it up there.
And again, because of microservices speed this is actually more important, because you don't want to be waiting a month to release your 500 line service. That just sounds ridiculous. And just one last thing, when it comes to backend services, this is something we don't deal with on iOS because you only have one instance per user. But on the backend, if you ever want to have your services automatically create more services, when you get more load, all of that needs to be automated. You need to be able to stand up new servers, test the new servers so that if you do have twice the number of load, you can compensate for that. So automation is really important if you wanna scale on the backend. Because software moves fast. iOS is getting to the point where you can do really quick release cycles. And microservices are essentially built for speed.
So to summarize everything that I've talked about today, making iOS applications does absolutely require specialized knowledge. I hope none of you thought that I was saying anything different. That's another reaction that I got when I was like, "Oh, but they're kind of the same." People are like, "What? They're so different." Absolutely. There are assistant frameworks and patterns and things you have to take into account on iOS that you wouldn't necessarily need to on backend, but a lot of those things are rooted in more fundamental concepts that are just generally applicable to different kinds of software. The tools will definitely be different, so if you are making the switch, even if you're just looking at doing Swift on the server, you're going to be dealing with different tools. But at the end of the day you do know more than you think as iOS developers and mobile developers and Mac OS developers, and that's all I have. Thank you.