Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage Presentations Engineering Dumb: Modern Mobile Thin Clients

Engineering Dumb: Modern Mobile Thin Clients



Brandon John-Freso talks about building a complex feature at OkCupid and demonstrates a few design patterns to create remotely configurable layouts and behavior on-the-fly.


Brandon John-Freso has recently joined the Intelligent Spaces team at WeWork, where in his role as a Senior Android Engineer he helps integrate mobile technology with the physical world.

About the conference

Software is changing the world. QCon empowers software development by facilitating the spread of knowledge and innovation in the developer community. A practitioner-driven conference, QCon is designed for technical team leads, architects, engineering directors, and project managers who influence innovation in their teams.


John-Freso: I suppose we should just get this out of the way. I'm slightly terrified of public speaking. Just I like to be upfront and honest about that. So if you have boos, just keep them to a minimal. I ask that you do it quietly and probably to a neighbor. The talk is Engineering Smart, Building Dumb. On the outside, it probably said something else. In your schedules, it probably said something even different than that. It's because thinking of titles for things is actually really difficult, especially when talking about more abstract concepts. I stuck with this one because I think it's contentious and people get really worked up about the word dumb and just want to come and see what that's about.

Engineering Smart and Building Dumb. Who am I? I'm Brandon John-Freso. That's my full name. I'm an Android engineer. I work at WeWork right now, but I was previously at OkCupid for four and a half years. A lot of people know OkCupid. It's small in terms of size as far as dating apps go. But it's been around for about 12 years and made its name by doing rapid development and a lot of experimenting, which at times here and there we've gotten in trouble for. But basically, a lot of A, B through Z experiments to figure out the best way of matching people, the whole premise of it being that rather than employ a bunch of psychologists and have them figure out how dating works, you would actually just use analytics and hypothesizing about things then put that out into the field and actually see how people work. So it's mostly an organization full of data scientists and engineers who are working on things.

When I started, previous to OkCupid, I was a full stack developer, mostly front-end. Over my time at OkCupid, I moved the app from being a hybrid application, pretty much every single feature within the app was web view, and this is for Android that we're talking about. So every single web view inside there was running a full feature pretty slow. So we decided to change everything over after a couple of years. That kind of led to where I am now, which is probably actively trying to talk myself out of any sort of job security by telling you that we've gone too far with it.

Your App is Too Smart

What do I mean by we've gone too far? This is sort of my entire thesis. Your app is too damn smart. Am I allowed to say that? I don't even know if I'm allowed to say damn. The fact of the matter is that your app is probably too smart. Mobile technologies have come pretty far away. When I first started working at OkCupid, you go back like five years, mobile apps were pretty crappy, especially Android applications were generally pretty terrible. But we were coming from web views, and JavaScript was just starting to be this big thing.

So everybody got really gung-ho about that, and we jumped on board with Angular, and then after Angular, there was Backbone. Every web developer, at first, went through this massive cyclic change where they were like, "Oh, why should I have to do anything with JavaScript? I just want to do HTML and CSS," and then came to this point where they were like, "I can do absolutely everything with JavaScript." That led into application development. A lot of those same developers eventually moved into native app development once the APIs for native apps became better, once the visual capabilities for native apps became better. Then they carried that same mentality of like, "Forget the server. I can do absolutely everything on the app."

The problem with this - a pretty famous quote right here, "Give a person a fish and you feed them for a day. Teach a person to fish, and they're going to screw the pooch." This is really what I've learned over a few years of native development. When you're working on a large team, you've got iOS developers, you've got your desktop developers, you've got your web developers, you've got your Android developers, and every single one of these people is coming into it with this mentality of like, "I can do everything on my platform." The problem with this is that the more code you write, the more bugs you're going to write. Every single bug that you've ever encountered has gotten there because we were the ones who actually wrote them.

So when you start doing that on every single platform for every new feature that you create, for every new thing that you want to do, you're basically giving everybody license, all of your engineers, a license to create more bugs, which ends up being more problems. It becomes an organizational overhead, and you actually have to coordinate all these people. There's no free lunch and just in allowing people to do these things.

How Did We Get Here?

How did we get here? I spoke a little bit about that. But basically, we got here moving from those web technologies, pushing ourselves to this point where each one of these platforms as the phones have gotten stronger, we've put more and more code on them, and that creates this problem I'm talking about. So we moved away from server-side-anything and moved all of that logic basically onto the clients. But my problem and the issue with this is that we've allowed each one of these devices to get so smart that it's actually sort of become stupid because we can't really make simple things happen, right.

This is an actual example of something that recently happened, and this was at WeWork. But at OkCupid, you experience similar things. At almost every single organization, you experience what I'm about to talk about. So product manager comes along. They want to make a simple change to the application. They just want, okay, for instance here you have a room that can be booked. Now, it's open from 12:30 p.m. to 6 p.m. Two weeks ago, the product manager had a different idea for this. They went to the designers, and they said, "Hey, we just want to display the time. We don't really need to push people into any specific room." Then two weeks later, they're like, "Actually, we want to highlight this color, and we want this text for the time, instead of just being the grey text that everything else was, we actually want this to now be pink to show the person, like, 'This room is active right now. We want you to get in there. This is the one that we want to book.'"

It's a pretty simple change. The product manager goes to the designer, tells them, takes a minute to actually get that concept across. Designer goes to sketch, makes the update, takes them a minute to make this change. The designer then renders that sketch over to the developers, and the developers are like, "Okay. That's pretty cool." That is going to take forever. It's going to take essentially 40,000 thousand minutes, which is just a really big way of saying four weeks.

That's what the cycle for a native app is. If you're doing things in the right way, and you're actually putting yourself through a two-week sprint, you're developing it, putting those tickets in, you're going through Q&A, you're getting those things tested. For Android, you're moving that over into beta testing. You leave your beta in there for one week, then you actually release it to production. A hundred percent? No, you don't start there. You start with a rollout. Fifteen percent, 30%, 50%, we're there by about Wednesday or Thursday, and then Friday, you're out to 100%. We are now literally four weeks away from where we initially were for something that's a simple color change.

Now, the product manager is like, "That makes no sense to me." Everybody in your marketing, everybody on your board is saying, "This makes no sense," and it inherently puts developers and people who are just trying to get projects done as well, make sure those things are going well, and puts them at odds with each other. When you tell people like, "I'm the project manager," nobody likes the project manager, nobody likes the product manager. And it's not because we're not all trying to create the same product, it's just because there's a fundamental misunderstanding in the way that we're actually speaking about features.

Server-Side Rendering Thin Clients

What is the way that we can deal with, actually, solving this problem and making things a little bit faster? So it's server-side rendering and thin clients. The word thin client, also a little bit contentious. When you start talking about thin clients, people automatically jump back to mainframes and little terminals and, you know, essentially the 1970s, and they're like, "No, no. This doesn't make any sense. I have everything rendered on the server. My app has all of these capabilities that I need it to do."

I find that most of this is hyperbole when we actually inspect what we're doing in native applications. Most of the time, almost every one of our apps with the exception of a few- there are things like games and things like that - most of our apps are just glorified thin clients. They're only concerned with two things, displaying data and handling input. If anybody here creates an app that does something that is radically different than this and is production-facing, consumer-facing, this is probably not the talk for you. But I think for about 95% of the people creating products, these are the only two things that you're doing. You are displaying data, you're taking JSON and your de-serializing that, and then you're putting it as a display to the user. Then you're actually handling inputs. The user is doing something to your application, and then you want some other things done.

Now, the problem is that some of these things happen locally on the device and then other things actually need to go back and have a record of them kept with the server. Neither of these is inherently a problem, but the more difficult part of this is the actual handling of the input.

I'm going to talk to you about a real case study. I like telling things through a story. I think it helps people understand things a little bit better. I don't really consider myself just a software engineer. I think my specific focus is product engineering. It's actually getting the product out to users, which is different than other focuses, but this is sort of the bread and butter of what I've been doing for a few years now. So discovery. What's discovery? If you've ever used Tinder, Bumble, Hinge, OkCupid, literally any dating site, Dog Swipe, whatever the case is, you've probably seen the interface where people just come up and are displayed to you in a very linear fashion. This inherently is problematic. If you show people in a very linear way, but you're in New York, and you've got two million possible people that you could be matching, from the jump, you're basically creating an inequity in the system. Because no matter what algorithm you use, it can zip together people who are having less focus or who have more attention on them, different geographic locations, whatever, you're just showing them in one linear way.

Our product managers and designers start thinking up a way of kind of displaying this information in a different way. So if you've used Instagram and you see the Discovery page that shows and surfaces other content to you that you might be interested in, they wanted to do the same thing with dating. Basically, taking a page, creating a page that had a bunch of different modules on it that would be displayed to the user. So if the person likes jazz, here's an entire carousel of all the other users who also like jazz. These are your specific searches, people between 22 and 30 within 25 miles of Brooklyn, different criteria, like the religion, questions that they've answered, all of those things.

The project manager has just got super gung ho, and they were like, "We want to build this. It's going to be amazing." Not only that, but let's throw another wrench in it, is that we want it to all be remotely configurable, and we want it to be dynamic, and we want these layouts and behavior to basically happen on the fly. If you've got all these different modules, all these components, all these sections, and you know that the person actually really likes dogs, and that's the section that they're interacting with the most, we want to display that at the top of the page. But if they like photos of people and snapshots, you want that to be at the top of the page for certain other people.

This means that you cannot do that on the client. Unless you want to build in literal machine learning into your client to have it learn from the user what they like, you're basically going to have to send those things up to the server. For each one of those people, the server is going to have to render a different view for them. So it's a pretty complicated problem. When we started looking at it, it was in the traditional way that we look at things in app development. You have to start talking about a language of essentially porting over what's on the server to the client application. It doesn't really matter if you're doing this in the web world, it's a single-page application, it's iOS, it's Android, it's whatever.

You're dealing with the same sort of concept. You're dealing with entities. Users, match info, questions, photos, interest, messages, that's what all of this displays. We're putting wrappers around it. We're putting visual representations around it. But essentially, these are the entities that we're trying to port over to the client. The fact of the matter is that most of the time, these entities are also directly mirrored in the database level. So your backend is talking about users, and then you pull from that, and your API developer goes, "Well, this comes from the user table, so I'll just make my JSON key also a user." And then the native app developer goes, "The API key is user, so I'll just create an object that's also called user."

Now all of the sudden, for each one of these platforms and each one of these features, you end up having a massive duplication of sort of the same abstract concepts, we’re speaking in terms of entities. So thinking of all of these different ways that you could do it, the amount of new data that we were going to have to add to the native apps and just bloat those binaries, it starts to make my head spin. So went back to the board and started thinking, "How can we actually change the way that we think about this?"

UI is Inherently Entity Agnostic

UI, your user interface, is inherently entity agnostic. There's nothing about any one of those boxes on the screen that the application needs to know that, "This is a user," like zero-zero things. Behavior is a slightly different beast. But in terms of just what you're displaying to the user, your app doesn't care that this is Aaron, she's 26, and that you all are a 96% match. All your app really needs to know is, actually, "Hey, this is a box. I have to put a picture in it. And then there's two other text boxes underneath that, and I've got to put some words in it." That's it. And we allow ourselves to get to this point, and we're like, "No, no, no, it's more complicated than that." It's totally not more complicated than that. It's a picture in two different boxes.

When you look at it from that perspective, and you change the way that you think about the applications, almost every single app looks exactly the same. You have these same elements repeated over and over again. Not only do you have those, it's actually sort of prescriptively told to us that we should be developing things in that way. You're creating a design language. You have your humanistic design, you have your material design. It's ways for designers to get themselves to the point where they're creating the same elements and then putting those around to different pages, different features, but that is how they're thinking of it.

The disconnect and the speed at which we can develop, and the speed at which a designer can create things, is essentially this difference in the way that we're thinking about it. So what I wanted to do is say, "How can we move ourselves from that kind of concept of entity-based to entity-agnostic?" When you're actually doing your development, this is the code that we're seeing. You've got your image view, you got your two text boxes. So we all know that this is the furthest extent of what we actually need to display. The challenge is getting yourselves to the point where from design forward through to your API, forward through to your client, you're actually using this exact same language.

Create Layout-Based Models

This is where we're going to dive slightly more technically. I'll try and keep it agnostic. I realize that this is not an Android developers conference. I know that there's a lot of different people here. The code is written here in Collins, so it's actually pretty simple to understand and reason about. And moreover, nothing here is sort of business-specific. So, in terms of native development, it's strongly typed. You actually have to have those objects on the client device. Now, whereas I was talking before about having user objects or messaging objects on the client device shifting over from entity-based to layout-based meant we actually have things like text boxes, and colors, and titles.

These things came to be layout-based models, and you can see here that we have Avatar three text. It's a pretty descriptive name of exactly what we're showing. That thing is just one box on the page. It's one component on the page. It's a visual component. This is the thing that the server is actually sending over to the client, and then the client is going to deserialize that server response into one of these objects. The actual fields of it, top text, middle text, bottom text, accent color. They're very, very descriptive. You automatically know exactly what I'm talking about. If I showed you one of these models and then I showed you the actual spec, and said, "Hey, find which one of these things is this model." You can look at it and immediately know. There's no problem with it.

When you start thinking of actually breaking down design specs in that way, it becomes really easy. A designer gives you a spec, then all the developers from all the different disciplines get into a room, and they look at it, aven the most complex features, things that seem like, "Oh, what you've got all this stuff going on here," it really just becomes like, "Okay, header component, and then there's a text component, and then that's our custom badge component." And then you start breaking them down. Now, there's different configurations that those go in. But if you start thinking about this way, and then use all these little pieces to build larger components, and then have that be this descriptive, it becomes much simpler to actually reason about.

When we start using all of those visual components to actually say, "What is going on inside of our app?" We're talking about using those visual components to create a state in our app. I think state is probably one of the most overloaded words in all composite in development right now. State can mean absolutely anything. What I'm using state here to speak about is, what is the current state? There we go, used the word and the definition, killing it. So what is the current state of the application? What are we actually looking at? What's on the screen? That's all that's important. So here we have things like, is loading, current page index is logged in, and then the layouts

The important thing from this previous slide is that for each one of these visual components they’re actually extending this class up here, which is called LayoutData. That LayoutData is the superclass where every single visual component under that. It's important for what we're going to get to in a little bit. But I use that LayoutData to actually say, "Hey, this is what's on the screen right now." A series of basically an array from top to bottom of different components, different visual components, different pieces of LayoutData. So that is what state is.

The problem, not the problem - I should stop calling things problems - but the interesting challenge is how you actually go about modifying this state that you've created. If you're creating a thin client and everything is on that phone device right now, but you have actions that need to happen, and you need to modify that state, how would we actually do that? This is where I said that taking that JSON in de-serializing interviews, every single one of us does that. Taking actual behavior and figuring out how you're going to describe that in JSON and then using that to modify the state and modify the views, that is actually the difficult part.

The Action in Interaction

We'll go into a slightly hypothetical land. What if I told you that there was a way to encode actual behavior and expected changes to state in JSON? Crazy, crazy idea. It would take a genius to actually solve that problem. The good news is that it was solved. Better news is, it was not by me. This is actually a concept that's been around for a little while as we developed Redux, React, those things. Over here, on the left-hand side, you'll see just a JSON object, which actually would not compile correctly because it's missing - no, it's not missing anything, that's perfect. So, you'll see a JSON object over here. It's got a type, and it's got a payload. In React, Redux, Flux, this is called a Flux Standard Action. It's got a type on it. It's got a payload that is specific to that type. What this is, is just something that's saying, like, "Here is something that we can do in our application." A lot of times if I ask you, "Okay, in your app right now, do you know every single action that could be taken, everything that's possible?" Most of the time you're not going to be able to say. It's really tied into the view code. It's in your activities. It's in your fragments. It's all over the place. You would have to take a bunch of minutes and actually figure out everything that could happen.

What a Flux Standard Action gives us is a way of saying, "Here are the only things that can happen in the app, and this is what we're actually passing around between our views in the app, and this is actually what we're receiving from the server." So it's really easy when you're in the web world, but you actually, in the native world, it's a little bit more difficult. We don't really have this concept. So not by any stroke of genius, basically, took that same concept and then ported those over into data classes for a column. But these are just objects, they're sealed classes that describe the exact same thing. They're sealed classes that are describing behaviors. Things like navigating to something, liking a user, blocking a user, reporting a user, these are all actions that can be described in JSON and then deserialized in the native world to actually affect change.

In terms of how would we use those things to actually change the state, that state that we are holding previously in a data class, is you have a thing called a reducer. A reducer is just a fancy switch statement. You have the current state of your application and then you have this object that can modify it. So you take that object in, and you check what object it is. And then when you get to the right one, you look at the payload, and you use that information to do things on your local state.

Now, this isn't actually that complex if you don't make your applications hold a ton of business logic in them. When you're doing things, say, in the instance OkCupid, you block a user from a page. You hit a button, you block a user. You want that user to disappear from every single place that's on the page right now. So that block action can actually just be known as a remove action. That's what you want the local client to do. The client doesn't need to know that it's a block. Inherently, what's happening is just you're removing a cell from the page or removing a visual element.

In terms of, "Is that user actually blocked?" Well, that's happening on your server. You don't need to send to your client, "Hey, block," and then the client request that block back at the server, and then there's this whole like, "Was it a success?" All of that stuff. You just make a local change, you find that visual element, you remove that visual element, which could be anything, and then your server knows it and records that down. So this is what the reducer is. It's a pretty simple concept. It doesn't need to be anything more than a switch statement. You can write in any language. It's platform agnostic.

Use Polymorphic Deserialization

The interesting challenge is getting these concepts of the layouts or the visual components, whatever you want to call them, and the actions, and then moving those from JSON into the native world. For Android-specific people, you can do this in a variety of other frameworks. You have things like GSON, you have Jackson. But basically, this is called polymorphic deserialization. JSON will allow you to just define a field. You can call that your type field or you could call it the layout field. Inside of that field, you'll have a string. Now, the value of that string, whatever the value of that string is, you can use that to actually deserialize into an actual native type.

If the layout field says picture, title, subtitle, we can deserialize it into a PictureTitleSubTitle object and basically just wedge all the data into that actual native construct. It's a pretty simple thing to do. You can do it with actions. You can do it with layouts, both of them will pretty much be the same way. Once you've done that, you can then bind your actions to the views. So this is using a view model pattern. So MVVM, but you could do it in MVP, you could do it in MVI. It really doesn't matter. All you're saying is, "When I click on something, this component, which is entirely behavior agnostic, will emit this action," and every single part of your application and your server, everyone is speaking the same language. So you emit that on a click and then that gets handled.

Common Mistakes

Before I get to common mistakes, I gave this talk the first time at the nascence of this feature, which was back in January. Since that time, we've used it to make a ton of different experiments and do many, many different changes. One of the common mistakes or, well, mistake that I made personally is, when you create all of these layouts, and you have your clients talking about the layouts, one of the beautiful things that you'll be able to free yourself from doing is having to actually version the API. Normally, you expect a certain response on your client, and so you send that over and you hit V1 of your API, and it sends you that information, and then you have to version the APIs.

When I created this system, I was like, "This is amazing.” Now all the client has to do is send to the server, "Here are the layouts that I support." And then the server can look at that, and say, "Well, we can actually do a graceful degradation on the server." So if we know that you want to see a particular user, if the client supports a very rich layout for that and sends that up in an enumeration to the server, the server can then actually wedge that information into one of the rich layouts. But if you're actually using a binary version from two versions back, and you only support a picture and the words, then we can send you a sort of less-feature-rich layout. In doing this for every API request, you send an enumeration of the layouts that you support, and then the server sends you back the layouts that it wants you to display the information in.

This was really great until I realized that, one day I opened up the app, and I start clicking things and things are not actually doing anything. And the problem was that the actions, the actual actions, also need to have the support. If your server doesn't know what actions the client can do and it just sends them down any action to happen, then there's actually no way knowing. A common mistake, or a mistake that I made, is not sending up an enumeration of these supported actions. You don't need to do this with every single API request. You can actually bootstrap it, and just do it at the beginning. So when your application starts up, you bootstrap it, you send an enumeration of your supported layouts, an enumeration of your supported actions, and then the server can make decisions based on that.

Another common got-you with all of this, is that when you start creating a design language, and you're sort of speaking about all of these components, it frees you up to develop very, very quickly. With this, we're able to move our development times just amazingly faster than we were doing it before, because you create these components and then you can just drag and drop the components where you want them and build the same things that the designers were making.

The easy part with this is when you start talking about your API, is building into it a lot of platform-specific information. So say, that you have a text box. It's really easy to say, "That text box is 25 pixels. The height of that font is 25 pixels," and then you want to send that with your API response. But on iOS, you're using points. On the desktop, you're using pixels. In Android, you're using DP. Every single one of these has a slightly different idea of what that means. So you have to think back and fall back on abstractions of these concepts. For a text field, start thinking about things in terms of small, medium, large. And then whatever that means to that particular client, they can display it that way. So if I know to get this particular size on Android was 25 pixels here, we actually need it to be 30 pixels within this framework. I'm just reading the small, medium, large values from it and not encoding in that data.

Another really easy place to make a mistake, and this is where it requires organizational changes if you want to start developing in this way, is that you have to have almost perfect communication between your designers, product managers, and your developers, which should be something that we're striving for, anyways. I'll go back a few slides. So for something like this where you have a username and a text box, one actual event that happened was the product manager went to an IRS developer, and said, "Hey, we just want to put an online indicator next to that text, just a tiny little change. Just put like a green dot next to it if the person is online, and then the server will send what color that dot should be." The iOS developer is like, "Yes, great, I can do that in no time. Actually, it'd only took me a couple of seconds." Goes back, changes the pre-existing layout as it was named before. So this just was a text-subtext component, changes their local version of it for iOS.

This feature goes out, Android developers never hear about it, then we're in this issue where we're like, "Well, why is everyone offline for Android?" And it's because you haven't actually communicated over, "Hey, this particular component is changing." What I would say is never change a component once you've created it, just create a new component. If you want a component to have an extra dot on it, an extra badge of it, then make it a text-subtext icon component, and then use that. And then the new clients can send up that enumeration, which allows you to have much better things like analytics. So you know, "Okay, these are the clients supporting these components. Here's how they're interacting with our system."

That's pretty much everything. I'll open up the floor to any questions if anybody has questions about it. There's a whole host of questions I've gone through before. I will address one straight off the bat, is persistence. When talking about thin clients, a lot of times people start mistaking that concept of having a very thin client and a dumb application with the concept of offline capabilities. It's not actually a concern in this case, because your server can send an entire UI to a device, and that thing is only a data response. And the device itself can actually persist that UI, can persist the UI in a database if you wanted it to. The next time you start the application, it renders it from its previous state that it was holding. So it's actually pretty feature-rich in that way.

In terms of things that need to happen on the device without an internet connection, you can still use the actions which are loaded into the components to modify local state optimistically. Then when you get an internet connection again, send those changes up to the server and just basically have a confirmation that they happen. Next time you need a layout or a UI, the server will you the new one. So yes, I guess that's it.

Questions & Answers

Participant 1: When you say there's frontend and there's a backend, did you guys also have a frontend-backend, there's another layer in between that?

John-Freso: Yes. The layer in between is sort of like the middleware or the API team. For OkCupid, the engineers also do the middleware layer as well. But the place where that comes into use is in the concept of graceful degradation of the server. So the place where you're actually deciding, "Here, we're going to get this data from the backend, we're going to get this data from the database for a user," and then figuring out which one of those layouts that you're going to use to actually send back to the client, that's normally what happens in that middleware area.

So you need to have more feature-rich middleware. Like I said, there's no free lunch. You're taking all of that logic that would have normally happened on the client, and then you're moving that back to the server. Your backend developers won't be like super stoked about this, but that allows you to just write it in one place, rather than writing it three times. Hopefully that answers your question.

Participant 2: How do you do to prevent Android-specific stuff from creeping in, for example, fragments losing state or navigation between activities? How do you prevent that? That is only about Android. There's also the difference between navigation logic between iOS and Android. How do you prevent that from creeping in and ruining this obstruction that you’re creating from the server?

John-Freso: I think it's basically about having very tight rules about it. If you know that something is platform-specific, then that never goes into the API response. That's really the only way that you can actually make that happen. Like I said, it becomes really tempting to do that, but you just have to have very strict rules. So what we did with this is actually write up documentation. When it comes to, "What actually constitutes an action, and what action should come from the server?" You write a proper documentation for that. When it comes to, "What would constitute a new layout?" You write a proper documentation for that and make sure that people are actually listening to that.

In terms of the state, the state is also platform-agnostic. Your state is just saying, "Here are the things that are on the page." It's really finding only the visual entities, and you can persist that. The place where you get into platform-specific is normally in your reducer because you're actually changing your state or having side effects from that. You can have side effects from that. That's pretty much the only place that you do it. But that state gets rendered out to your views, and that's where all of your platform-specific logic happens. But you don't need to change anything in your API response for that. Cool?

Participant 3: With these common layouts and components that you were sharing, you mentioned that the platform-specific things - instead of thinking of points and pixels and that kind of thing, how did you document the generic design choices, and then also style guides per platform, because they are slightly different?

John-Freso: Yes. So for that, we just have one source of documentation that pretty much shows you the visual representation of that element and then shows you the expected JSON response for those elements, and then also documents out particular behaviors. One of the pain points of this, that is very different for developers in a lot of ways, is to think on a component-specific level about how that component behaves within a larger system. We normally think about things as a full page, so in matters of responsiveness, if you have a button, well, that button has a badge on it sometimes. Well, what happens if I drag this button into a very small context and doesn't really have space for the words and the badge? Well, which one goes first? The conceptual idea of, “If this is too small, remove the badge from it,” that's something that goes into that sort of platform-agnostic documentation. All of those behaviors, how this thing should animate, what things should go first, how it should respond to different sizing, goes into the documentation. And that's in one repo.

Then, in terms of the actual platforms, those platforms have their own component libraries, their own component repos. So when a designer creates a new component, then you meet with the leads, and you actually speak to the individual platforms, and then you mirror those things into the repos for that particular platform. I like to bring up the example of in a very abstract world. If you were also in coding for a virtual reality headset, and you had this idea of a header. And this was in a virtual space, and the header just showed you some text, but it is floating in space. In your particular repo for that virtual reality application, you have a totally different way of dealing with that, and the way that you would display that would be specific to the virtual reality headset. You would need Z coordinates and X coordinates and Y coordinates, but that's specific to your platform. All you want to account for is the abstract concept of, what is a header? Why is this text field? And then you marry that into your individual platforms in whatever way is specific to you.

Participant 4: I don't know if you've had to deal with this, but what about when apps end up showing different forms of information, so web versus Android, versus a TV or something like that? You don't just show different layouts of the same information, but you're actually showing different information, and you have different features, like maybe some features are not even available on Android. How do you deal with that?

John-Freso: It's sort of two-fold. In terms of different pieces of information, your APIs that you're actually going to and your reducer are the two things that are separate. Your API is one shared API. But normally, within the headers of a particular API request, you're going to be recording what device you're making that request from. So if you know this is a TV device, then the actual data that you're sending is going to be different, which is the same way that APIs is work right now.

All we're basically saying is that you can still choose what kind of different pieces of information in terms of data and business entities that you're wedging into these layouts, and that can be different per platform. It can even be different per Android device. You can have a big tablet, and then you can have a small device. So you can send up that information into the request headers. So if you know that you have a small screen, you can record the actual device-specific properties in the request headers, and then your server can make decisions based on that. But what it's sending back are only layouts.

Say you had a very rich display. This could be something even like a video display. You had a total video component that you're displaying things in, and it had a tracking bar and all of this stuff. So what that device would say is, "Hey, I support the video display component." And then the server would go, "Oh, great. Cool. That means that I can show you this piece of information in this way, and then send you back that information.” So it extends beyond even the concept of just totally different platforms. Even with an Android, you could be in a car, or you could be on your TV, you could be a small device, tablet device. So you send up your device-specific and platform-specific information, the server makes intelligent decisions and then wedges information into visual components that you can support.

Participant 5: What was the resulting turnaround time from, say, changing some text color? You said it was four weeks before or after this implementation. Did it get reduced?

John-Freso: Yes. For something like that specifically, it's an instantaneous change. So if it was four weeks before, you go from four weeks to an hour. Because all text components- which I can't really overstate how important this is, we proxy a lot of meaning and a lot of semantic meaning into pretty basic visual components a lot of the time. We're dealing with pictures, we're dealing with the color of text, those sorts of things. Having every single component on your page, being able to have a bunch of different capabilities in terms of how it's being displayed and having that all already just from the onset. We don't know what product is going to ask next week, but saying, "Here are the changes that you can make to it," puts things in a different mindset.

Then you can go to the product manager, you can go to the designer, and you can say, "Here are all the capabilities that we have for these components that are out there right now." Maybe think in this world as like an MVP, if you want to create a new feature, and tell them, "Here's the best room to choose right now." They start thinking in terms of like, "Actually to do that, we could just change this text color instead of releasing some other crazy feature," and then that's an instantaneous change because all the clients already support that capability, and you'll need to release a new APK. So having those constraints can actually be freeing in a lot of ways for pretty much the entire team.

Participant 6: I'm guessing it runs in production?

John-Freso: Yes, yes. No, thank God. Basically, when I was coming up with this and was doing the research, where work for OkCupid is really chill - my CTO, my engineering manager, really chill - I said, "Hey, this is a really massive feature, and I actually need a month to think about just how we're going to do this." And they let me do that. But after the end of the month, this was right around the Christmas holidays, I was absolutely losing it. I was in a room with a whiteboard, and I had all these little schematics and drawings out there. I got to them, and I was like, "I have no idea if this is actually going to work to achieve what I was trying to do in the first place. I don't even remember what I was trying to do."

But we released it out to the public, and so it's being hit by millions and millions of people, I think like 4.2 million people per month and on multiple platforms. This is used across desktop, iOS, and Android. If you go to the OkCupid app, and you just go to the Discovery page right now, that page is entirely running on this architecture and this principle. Really, it was one of the first releases where we've had pretty much no bugs in it from the outset. Over our experimentation, we haven't really had to release any major APK updates in that time. That kind of concept of designers and product managers start constraining themselves to the already robust capabilities of these components really paid off.

Participant 6: That's really awesome. My question was, can you talk about some tradeoffs you made? If you're sending the whole layout, it increases the date. The client doesn't have anything. So you can cache, but it's not the same - those tradeoffs that you made.

John-Freso: The immediate tradeoffs, I wouldn't say that there were too many things that we actually lost. It was just sort of organizationally. We took a hit in terms of how we can develop these things at the onset, because it was a matter of, "No, actually. Here's how our components work and is this a new component? Is this not a new component?" That kind of documentation I was talking about before, that wasn't really in the culture at all.

And so how to start creating those things was a little bit more difficult, especially given that some changes just to get them done really quickly to create a new feature and not think about it in an abstract way. So if you had this page of data, then you can just say, "Well, I'll just write up the code to animate this button over there," or to, "This component can affect the properties of this other component down there." You could probably write that code really quickly in a few lines, but abstraction of components inherently comes with a lot of - you have to start thinking a little bit more like, "Well, how is this going to affect my teammates? How is this going to affect other pieces of code? How reusable is this?"

So that's the hit that you take. But in terms of actual development speed after that, I don't think that there are too many major changes. If anything, the size of the payload coming back actually was significantly reduced. If you look at one of the first pages, that linear swiping game, the information that comes back from that is massive. I mean, it's sending entire user objects, and these user objects contain all your questions, what you like, where you are, your age, all this information, and then the clients piece that together and literally take maybe 5% to 10% of that to display you a card that you just swipe away.

This is changing from that concept and saying, "The server will literally only send you as much information as you need. Only the URL for this image, and only the text for these two boxes.” So that really helps to actually compress the payload more than anything.

Participant 7: Is this something where a product manager can now do essentially self-service changes to the application and test things out? Did you build almost like a backend for them to try things?

John-Freso: Yes. We already have a system that essentially works in that way. Feature flagging, some people call it gatekeeping, but started to build out more administrative panels that would allow you to turn on features and turn off features. So whereas before, we had rich feature flagging SDKs within the application where you're able to stop leaning on that as hard and just have it happen on the server.

The server understands what experiment this person is, what the feature flag is. A product manager can then go and just use an administrative panel to turn on flags and turn off flags, and then the client doesn't care what you're displaying. The client is just like, "I have a recycler view of different components, and I'll display whatever the server tells me." So, yes, it definitely does free them up to do many more changes on the fly.


See more presentations with transcripts


Recorded at:

Apr 06, 2019