How We Do API Design at Cord
This is a transcript of our podcast about API design, which you can listen to on Spotify
Adam: An important part is really putting yourself in the caller’s shoes. And making sure that you understand what their mindset is. Because they’re not trying to call your API. One of the key things about APIs. There’s no one calls APIs for APIs’ sake. No one loves your API so much that they’re just gonna, they’re just gonna send you operations to do for fun.
Like they’re trying to get something done and you are standing in their way.
Jackson: Hey everyone today, we’re going to talk about API design. Things that make good APIs good and bad APIs bad. Specifically, we’re going to talk about the story of how we built one of the APIs of Cord, the Presence API. And then towards the end, we’re going to talk mad trash about some popular APIs. So stick around until the end if you want the controversy. Before we dive into that, maybe we should establish why do we think that we’re worth listening to?
Adam, maybe you can take it away. Would you like to introduce yourself?
Adam: Sure thing. So I’m a senior engineer here at Cord, and I’m one of the people who is working fundamentally on our APIs and guiding them and building them out and reviewing them and making sure that they’re really nice to use really convenient that they have all of the features that people want.
They’re flexible. All of those facets that are really important to us. Before this, I was a principal engineer at a company called Synthace, that’s doing lab automation work, where I led the data team. Before that, I was at Google for 12 years. I was one of the founding team members for a production monitoring system called Monarch.
There was a paper that came out a couple of years ago on that, that you can go look at. And I also maintained the crypto and network security APIs…
Jackson: Oh! Crypto! Crypto! Wait, sorry. Were you the first non fungible engineer at Google?
Adam: No, no, no – real, real cryptography. Though there was actual crypto experts that did the low-level implementations, but I was in charge of maintaining the libraries that if you use Android and you need to make TLS connections, or you need to encrypt something or hash something, all of those, all those functions go through a network security library called Conscript that I was the maintainer of for a while.
Jackson: So, across this experience, what I’m hearing is you build API for developers, like constantly. Whether it’s a monitoring input API or consumption API, or how you make your low-level Android encryption calls, it sounds like you were there in that space.
Adam: Yeah. A lot of the work that I’ve done over the years… I’ve done a lot of different things, but a lot of it has ended up being surfaced as APIs to other developers that then they would use in order to build the products that use.
Jackson: I would imagine with crypto APIs, sorry with cryptography APIs. You don’t want to get that wrong. You don’t want to make it easy to get wrong. You want to make it easy to get, right? That
Adam: That’s really one of the big challenges in cryptography and network security APIs is that you want to make the easy way, the secure way, because most people are not going to dig deep into cryptography and how it all works and what they need to do. They’re going to take the easiest path because what they want to do is they want to check a box and move on to the next thing. Right? Like getting the network security right in their application is not the bit that moves units and like moves the needle at all for their business.
It’s something that they know that they need to do, but they’re not going to become a cryptography expert before they can then finally ship their app. They’re going to just try and make sure that it works and they’re going to do their best guess. And you want to make sure that their best guess is the secure way to do it as often as possible.
Jackson: Y’know this is actually a perfect tie-in for what we do at Cord, because we build collaborative features that you can bolt onto, extend your existing product with. And while it’s nowhere near the level of danger as low-level cryptographic APIs, it has exactly the same character that most companies want these features and do not have the time or energy to try to build them themselves.
They’re not going to take the time to figure out how to make a typing indicator, look and feel exactly like it should. They just want to bolt the typing indicator on there and move on to the next thing. So there’s a… there’s a funny connection here. You would never think cryptographic APIs. And Cord’s and stuff might have a connection, but the connection is we’re trying to help developers get this stuff done as quickly as possible and move on with creating the value they’re trying to bring to the world.
So we’re going to dig into this even deeper before we move on. I guess I can introduce myself. So I’m Jack, I’m the CTO here in previous iterations of my life. I was a senior engineer and a user interface engineer. Funny enough, Adam and I worked together at Synthace and there I was the lead product engineer, I believe was my job title.
Uh, which basically meant I worked a ton on the developer facing… I called the developers, sorry, scientist facing, I worked on these scientists facing product surface, the tools we offered to scientists to let them model experiments. And before that I spent six and a half years at Facebook where I was a security engineer for part of that time. I was an internal tools engineer for a lot of that time, building the tools that Facebook engineers used to make. Facebook. And then I spent several years, probably the majority of my experience, there was actually as a user interface engineer. So I was working on the buttons that, you know, the UI library, the affordances that actually makes Facebook what it is.
And from that, I worked on stuff that people who are listening to this podcast will have touched. For instance, I was the front end tech lead for the mobile web implementation of Timeline when we built that in the first place. And I worked on Facebook Groups some. I worked some on Facebook Messenger way, way back in the day.
So my background is really all about giving tools to developers or building the tools that I, as a developer, needed to use to build something for end-users. That’s sort of what brings me to APIs.
So now that we’ve introduced ourselves, I’d like to turn this into a conversation about real APIs and about real API design. Starting with the API is for a thing called Presence, which is a feature that we offer at Cord that has a bunch of different levels of API. Adam, I don’t know, would you like to introduce Presence to the audience?
Adam: Sure. So Presence is basically being able to see who is currently on the same view, the same page as you are and who has been there historically. So you see this in tools like Google Docs or Notion where you have a little set of faces and profile pictures up in the corner, and you can see, “Ah yes, like these are the people who are currently editing this document at the same time I am.”
And it it’s really the key indicator that you have a product that is real-time collaborative. As opposed to a Microsoft Word or something like that, where you can collaborate on it, but it’s much more asynchronous.
Jackson: Fun fact about those faces that you talked about in that sort of the top corner of the page that showed you who’s on the page now, or who has been here when we were building these at Facebook back in the day, we called that a Face Pile.
And then if you get sort of super pedantic about it, you’d be like, well, well, it’s not a pile, it’s actually like a, just a queue or a list. But anyway, we called it a face pile. And if you add that to a page, an existing tool, it’s an order of magnitude, more human, more accessible to people. You’d be like, oh, my team mate was here yesterday. That’s a piece of really important information for me to process what’s in front of me.
Adam: And it really gives you the opportunity to understand, is this a view that people visit frequently? Am I the only person here? Do I need to bring people in or are they already here and know what’s going on? Like it really just gives you a good connection with your team.
To understand where you all are on the work and what you’re all doing in there.
Jackson: So we talked a little bit abstractly about what kind of stuff you get out of Presence when you have it in the page. Like we talked about sort of making it more human about making it feel more real time. Those are all great reasons to have Presence in your app.
We haven’t really addressed the what? Like, why did Cord build this of all the things that we could build to help teams be collaborative? Why would we choose to create collaboration APIs focusing on Presence?
Adam: Presence is one of those features that is a key marker that this is a real time collaborative product.
If you want to add collaboration to your product, you basically start with either messaging and comments or you start with Presence. Because those are just like the most fundamental things where if they’re missing then anything else that you have it doesn’t feel right. And on the surface Presence feels like one of those things that people build themselves really easily, but when we’ve looked at it –and we’ve spent a lot of time working on our Presence implementation– it’s actually a lot harder than you think. There’s a lot of little details around what does it mean to be in a location in a tool. Notion for instance has Presence indicators on each block in the tool. Google Docs, you can see where people’s cursors are in the text. And you can see when they select text or where they’re editing. And that gives you a much better idea of where they’re at, but adding that extra richness and enabling that increases the complexity much more than it looks on the surface. And so having those APIs available makes it so that our customers can implement those features much more easily. And they can worry about, oh, where should a person be able to be in my tool? Like, what are the locations, which is really fundamental to their tool.
They don’t have to worry about, oh, okay, like how do I propagate this to everybody? And I need to like set up subscriptions of some sort and be able to like push things from the server to the client. They don’t have to deal with all those details, which are sort of a distraction from the product of they’re trying to build.
Jackson: I’m just thinking back to when we were prototyping these features in the first place. You know, we built Cord first as like a proof of concept everything-all-at-once sidebar. And we had Presence in that original version of it that we built. And we failed at building Presence ourselves multiple times over for reasons that were completely unexpectable to us.
For instance, for most of the SaaS tools that we’ve spoken with, people often have five or 10 or 15 tabs open of the same tool for different views. Often it’s the same tab, open multiple times to the same page itself. And they’re all fighting to tell us where the user actually is. And sorting out, which one of those tabs is actually the active tab is a huge pain in the [censored].
It’s really hard. It’s so much harder than you would expect it to be on the face of it.
Adam: To dig into the technical details there a little bit, just to give you an example of what we’re talking about. So if you imagine. Right. You have two browser tabs open and they’re both in the SaaS tool. So the naive situation is just like, each of them says, “oh, I’m present on this page” when you load the page.
And then, you know, when you close the page or something like that, they say that you leave. And so the last one opened wins fine. Then you have the problem of, well, what if the user just closes their laptop and wanders off? Do they just appear present on the page forever? That doesn’t make any sense. Like you come back a month later and they’re still present on the page?
Okay. Well then you have to time these things out so that they will disappear. Great. Well, okay. But then that means that while you have the page open, you need to constantly reassert, “Hey, by the way, I’m still here. Like you haven’t lost me.”
Jackson: [singing] Are you still there?
Adam: Exactly. And so then you have this problem with ping-ponging Presence, where if both of these tabs are saying, “Hey, I’m still here.”
Then you go, “Oh, they’re over on Page A… oh, now they’re on Page B… Oh, they’re on Page A again. Oh, they’re on Page B.” And so in our implementation, we actually just support you being in multiple locations at once. So it says, “All right, you’re on Page A and you’re on Page B. No problem.” But then you have the Google Doc situation of, “we want to track where their cursor is.”
Their cursor is in one place where their input is going to go. And so you actually want to have those be mutually exclusive. You want to have the cursor location… when you send a new cursor location, you want it to invalidate any previous cursory location. So, now you have this problem of, “Oh, well, sometimes I want them to be in multiple places at once. And in other cases, I want them to be mutually exclusive and they can only be in one location at once.”
And so our Presence API handles this. If you’re building this on your own, you’re going to hit every one of those roadblocks. And you’re going to go through exactly the same cycle that we went through, building the API.
At some point, you’re just going to run out of time on your sprint or whatever it is that you’re doing and you’re going to like, “Welp, it’s good enough.”
Jackson: The funny thing about this is how our human brain is so good at understanding what the right thing ought to be. If you show someone a page where the user isn’t actually present or isn’t actually present the way you representing it, people instantly feel it. They’re instantly like, “No, that’s not right.” They just know. Like, our human brains are so wired for understanding the social context of situations, such that if your technical implementation doesn’t match this multi-million year evolutionary model that our brains have, but they don’t match up, people are like, “This doesn’t feel good. This doesn’t feel like the real thing.” So you actually have to get it really darn close to right. And that means a really, really high bar. It means that all of these little things you think, “Oh, well that, you know, like not that big of a deal.” Actually it is a deal breaker.
If you’re creating software for humans, it’s gotta be as good as the human brain is discerning.
So I think that’s an interesting discussion of some of the hard problems that you will come across trying to create Presence. Now let’s bring it back to API design. Let’s talk about how we have modeled Presence as a set of APIs for. And it bring us to glory on this. What has Cord done? What API have we offered for Presence?
It’s just, how do you represent the concepts that the API is operating on?
Jackson: So from a human standpoint, if we’re talking about like the human notion of Presence. If you’re at work, one piece of Presence information you have –physical human Presence– is you can see if the person is there right now, but then there’s also the question of, Has the person been in this week?” “Has the person worked on this thing that I need to know if they’ve worked on?” And then an even more immediate idea of Presence for work is, “Are we working on the same thing together right now?”
Adam: In our data model, we have two concepts of Presence, actually. There’s a thing that we call Ephemeral Presence, which is basically Presence of who is here right now. Like, “who could I actually collaborate with, right now?”
And then there’s Durable Presence, which is Presence of someone was here in the past. And potentially you can have records for the same user in both of those categories. A user could have been here in the past and is here right now. And in fact, any time that a user is there right now, you often have a Durable Presence record for them because well, you marked down that they were here right now and so two seconds ago, they were also here.
Jackson: Could you maybe like dumb it down for the… for the muggles, like me out there. What would I use Ephemeral Presence for? And what would I use Durable Presence for?
Adam: One of the interesting things is that sometimes you actually want to divorce these two concepts from each other.
So if you’re looking at something like Google Docs, where someone has their cursor in the document somewhere, you don’t actually want to record any historical information about that. It doesn’t matter where their cursor was 10 seconds ago. No one cares, right? That that level of information is too detailed to be useful.
And so there you only want Ephemeral Presence. You only want to know, “where is their cursor right now?” Whereas for Durable Presence, that’s like… who has opened this document and you do want to know who is using this document right now, but you also want to know in the past, “when was the last time that my coworker opened this?” Like, “did they edit it yesterday so that I can go in and check their edits?”
You know, I’m looking for feedback and I can see that they haven’t opened it in a week…
Jackson: Those jerks!
Adam: …I need to go, you know, I need to add a comment that mentions them to be like, “Hey buddy, come, uh, edit my doc.” So these two concepts are actually really useful in isolation, as well as in tandem.
Jackson: That makes a ton of sense. Let’s imagine that GitHub had amazing Presence. It doesn’t at the moment. No, no slight on GitHub. They’re making progress. They’re doing big things, but their Presence game is maybe a little bit weak. If they were using Cord’s Ephemeral Presence API, it sounds like you’re saying they could do something like show where someone is, “Hey, they’re on this line of your code and they are in the middle of typing” and they could just represent that as Ephemeral Presence.
And if they stop typing and go away, you’ll know, “oh, they started typing a line there and then they stopped and they went away.” Or if they hit enter and sent it or whatever, then you could switch from Ephemeral Presence to just representing it in the data model of your app as the comment itself. But it sounds like Presence could close that gap.
Adam: Exactly. You know, you could imagine in GitHub, you could see what tab they’re in, because they have a tabbed interface. One of the interesting things is that sometimes you want to display Presence for locations that you’re actually not at. Each of those tabs in the GitHub user interface has its own URL.
And when you click on a tab, your URL changes. And so it’s not just, “Oh, I want to show everybody who’s on this URL, but nowhere else.” It’s also, “Oh, potentially I’m looking at the overview of a pull request in GitHub and I want to see that my coworker is currently looking at the actual file changes and is leaving comments,” which is a different URL entirely.
It’s a different view, but I want to surface that, “Oh they’re over there working on that piece.” You can also imagine one of our model use cases is kind of a bug tracking system where maybe you have a bunch of different views of individual bugs, right? Like maybe you have a Kanban board or then you have a prioritization queue.
You know, you have some kind of triage list. There’s a bunch of different views where a bug can show up. But if a user is editing that bug, you want to be able to show that they are on that bug in all of those different views where that bug might appear. And so you need to be able to query, “oh, who is in places that I care about that aren’t even necessarily the place that I am.” Like, those two things don’t have to be the same.
Jackson: I want to jump in here. Cause you, you kind of just blew my mind. Like we were talking about something really, really straightforward, at least intuitively straightforward: “Hey, we’re in Google Docs together, and I can see where your mouse cursor is.”
And we’re not going to record that historically, because it doesn’t matter where you’ve mouse cursor, but now you’ve introduced something much richer. The idea of like ambient awareness of where people are across multiple services within the same tool. That is mind blowing. Eh, fill me in, like, how can you make that work?
Adam: This actually comes down to the way that we built our APIs. Um, because you want to make sure that you are able to express the things that matter to you, but simultaneously that you’re not inundated with information, right. A simple way to implement this would be to say, “Okay, I want to just get every Presence update and then send it all to the client and then I’ll filter it all out to like, oh, I need to show the things that I care about.”
Right? Like you could build it that way, but if you have thousands of users that you potentially can see the location of, you know, you work in a large company and you have a set of Google Docs and anybody could open that doc. Right? Like it’s available with the whole company. You don’t want to be sending thousands of users’ Presence updates to your browser tab, just so you can throw them all away.
And so we need to be able to express, so that we can filter them on the server, this is the set of locations that I care about. This is what our user is currently able to see the Presence of. On the server side, we can efficiently filter down to only the pieces that you care about.
And we do that with the concept of Matchers, which is basically you can classify the locations in your application in a certain way, and then you can set up a subscription to say, these are the locations that I care about. Only send me updates that match that set of locations so that you can efficiently handle just the things that actually will impact your user interface.
Jackson: Okay. Okay. I think I’m starting to understand this. Like, it sounded like you described some sort of like complex many-to-many pub-sub thing on the server, which I find personally quite scary, uh, as a product engineer. I don’t want to have to do that stuff. But you’re saying that’s all handled on the server side by Cord’s stuff.
And that as a product engineer, all I have to do is say, “Hey, Cord, these are the particular bits of Presence information I want to know about.” So like I subscribe to them and then Cord’s server side implementation streams updates about those particular pieces of Presence directly to me. Is, is that how it works?
So the first one is an update function that says “I’m at this location,” which obviously you need to have.
The second one is to fetch the users that are at a location, or a collection of locations that are expressed by a Matcher, so that you can figure out at page startup “okay, who’s here now? Who has been here in the past?”
And then the third one is a subscription where you provide us with a Matcher and a callback function, and we will tell you every time that Matcher matches a new piece of data. Whether that’s someone arrived, someone left, someone has a new Durable Presence record so that you know that, “oh, they’ve come here more recently than the last time we told you.”
And between those three, you can actually build anything really that you want. And you can build really complicated stuff on top of that. But the API is really simple and understandable and we didn’t want to have any more than we needed. There’s a bunch of specialty functions that you could potentially add to that API, but we actually really valued keeping it simple so that a developer can read it and go, “Great! I understand exactly how every one of these functions should be used.” There’s no complexity. There’s no question of like, “Oh, okay. I have this use case, which of these do I need to do?” It’s all very simple.
Jackson: You use the word. When I think of “Matcher,” I think of regular expressions. And then I think you don’t have one problem. You have two problems. You have your first problem, which is Presence. And your second problem, which is regular expressions. Is a Matcher, a regular expression?
Adam: It’s not, it’s much simpler than that. So we should talk about how we represent locations in an application. Each application has its own way that the different views or pages or whatever they want to call them are laid out and are related to each other.
So we couldn’t have a really strict way of saying you have to represent your locations like this. Sometimes they’re hierarchical. Sometimes they’re not. Like in the bug tracker example, like bugs don’t necessarily have a hierarchical relationship with each other. In some cases they can belong to multiple tags or multiple projects or things like that.
So we didn’t want to be too prescriptive and say, “oh, you have to set up your locations this way, or else our system won’t work” because that is just not flexible enough for all of the different applications that might want to use Cord. We actually have a very simple model for representing locations that can handle just about anything, which is just a flat, basically JSON object, like just a flat sort of struct, which is you have keys and you have values and those can be strings or booleans or numbers.
And a Matcher basically says, “okay, I want to match the locations that have these values in some places and any other value anywhere else.” And we’ve actually looked at a bunch of different use cases and it turns out this is really general, really simple. Everybody can understand, for instance, what is going to be matched, unlike regular expressions where sometimes it over matches or under matches or you go like…
Jackson: “My conditional look behind didn’t work the way I thought it was going to do!”
Jackson: So like, if we go back to the GitHub example for the folks who are listening to this, I think that’ll be an easy thing to reason about. If you imagine that you’re inside of a pull request in the middle of a code review, looking at a specific comment, if you wanted to add Presence, information to that, like show me who else is looking at exactly this comment right now.
It sounds like the location dictionary, the location JSON representation, would be something like this: this comment and its comment ID as a piece of that. And then this tab in that tabbed interface saying the contents of a file, and that value for whatever that is. And then I guess like the idea of the pull request and the, the fact that it is the pull request thing.
And then I guess there’d be something at the top level about like what repository you’re in. And so like, that is technically hierarchical because of the way GitHub works. But in many tools, that’s not at all, like, in some cases, like you said to the bug tracker, maybe those same bugs shows up in five or six different places.
In which case you might care about that bug ID, but you don’t care about each of the pages that it’s on.
Adam: Exactly. So there’s no requirement that you set up the hierarchy ahead of time. If it’s hierarchical, then that’s no problem. Then you just supply the higher levels of the hierarchy and you can say, “okay, for this repository, this pull request number, subscribe me to update so that I can see anybody who’s on any comment on any tab.”
And you can render those in the UI however, you need to do it. If you have something that isn’t the way that you lay out your views isn’t hierarchical, then you can set it up however you want. Where, “okay. I care about this bug ID, but I don’t care about anything else. Just subscribe me to updates of people who are on that bug ID, regardless of where else they might be. What other parts of that location might be set.”
Jackson: Okay. I think I’m really starting to get my head around this. Cord’s API has modeled location within an application in this really, really straightforward way. It’s a JSON dictionary and you put whatever keys and values in it you need to know where the user is within your application.
And then there’s the ability to set that for a given user on the server. You just make a call to tell the server, “Hey, this is where the user is now.” And then you’ve got a couple of other means for understanding where the user is. You can “get” – you can say, tell me specifically, there’s sort of a “get” function there. And then there’s the sort of real time subscription, like update me anytime it changes.
Is that the whole API?
Adam: Yeah, so we wanted to make it simple. And this sort of gets into more general API design questions. API design is actually sort of a sibling discipline to UX design in a lot of ways. Like what you really care about is understanding the caller of these APIs and like, as a human, how do they conceive of the operations they want to do?
Because there’s a lot of different ways that you can structure an API. So for instance, the set function, we could have had two separate set functions. One of them sets Ephemeral Presence, and one of them sets Durable Presence. But I think to the developer, those are basically the same operation. Like they understand that as two variations on one operation that they can do, which is set where this user is now.
And so instead it’s an option. The set call to say, “oh, by the way, I want to make a Durable Presence record versus I want to make an Ephemeral Presence record.” And there’s interesting questions about, for instance, defaults. Okay. If you don’t tell us which one you want, what do you get? And in our case, you get Ephemeral Presence for a couple of reasons.
One is it’s substantially cheaper to do Ephemeral Presence than Durable Presence…
Jackson: You mean, users don’t have to pay as much?
Adam: No, no, no, we don’t charge per function call, but just in terms of latency and in terms of the server load, it’s a cheaper operation. So we don’t want people to accidentally do something that turns out to be expensive that they don’t actually need.
And also it’s sort of the more compelling, more important use case. Usually people want to have the information about where a user is now. And it’s more important to them and makes their product feel more responsive than the historical information. You don’t usually want just historical information because that sort of feels a little bit Web 1.0, of like, “oh, I can see who was here, but I can’t see who’s here now.”
It makes more sense to make the default the more common use case. And if you don’t even think about Durable Presence, Then actually the whole system works sort of the way that you expect it to. And you just don’t end up with any of these historical records, but that’s fine if you don’t care about that. As opposed to having what we think of as a, a “sharp edge” out on an API where the thing that is sort of natural to do ends up not really doing the thing that you want.
That’s a classic thing that a lot of APIs fall down on is if the thing that is natural to do is the wrong thing to do then basically, you just force every developer to stumble over that problem and then fix it. And that’s just a bad experience, right? Like every time that you call an API and you go, “why did it do that?!”
And then maybe you go look at the docs and it’s very well spelled out that this was the thing that your API call would do. I mean, that’s still a poor API design. If just your first instinct is not the right one, that’s a bug in the API design.
Jackson: It reminds me of when you go to a shop or I’m thinking of a particular coffee shop nearby here, where you walk up to the door and you grab the handle. And you pull it. And you, and it doesn’t go. And you’re like, well, but, and you’re pulling the handle and then you look and it says push underneath. And you’re like, “oh right… It’s a push handle.” Even though it’s clearly labeled. And it’s clearly my fault for user error, the door, it looks like you’re supposed to pull it.
It is a handle that is projecting out from the door. It looks like it should pull in. I, I guess API is suffer from exactly the same liability.
Adam: Yeah. And you really have instincts about you look at the names of the functions and the arguments that they take and things like that. And you immediately jumped to conclusions about how they work, and we really want to make sure that those conclusions are the correct conclusions that you jump to without having to read paragraphs of documentation that explained exactly what all of these mean. And if there’s very complicated operations, in some cases, that can be okay if you’re doing something that is a really involved operation and we expect everyone will just not know what it does when they read it.
But if it’s something as simple as “set the user’s location,” then it really needs to do the thing that they probably want to do if they call it in the naive way without having read all of the details of the API reference, because nobody is going to do that before they start trying it. The API reference is really the last ditch thing that developers will try before they give up.
Jackson: This makes me think about Git rebase and how almost nobody actually understands what Git rebase is really doing. You’ve got to really want it. So what you do instead is you kind of copy and paste the lines of Git codes that you’ve seen before. And you hope that it does the thing that you want, then when it doesn’t you delete the repo and reclone it.
Adam: Yeah. I feel like Git is the classic example of a incredibly unergonomic API. I do not understand where there’s all of these. Operations like checkout where it’s like, oh, checkout… sort of in a like weird theoretical world, all of the things that checkout does are the same thing where it basically, like, overwrites the index. And sometimes it changes what head is and sometimes it doesn’t. And there’s all of these things where if you understand the unusual mental model behind how the command was written and how it sort of built up. Then you can kind of back form, “oh, I see why it works this way.” But basically everybody, when they first encounter checkout, they go, I have no idea why all of these operations are all called checkout.
This makes no sense at all. And to Git’s credit, they’re starting to fix some of that, right? Like they’ve added Git Restore to be like, okay, this is not checkout to anybody, but the person who wrote checkout years ago. So we’re going to add a new operation that people can actually remember what it does instead of having to go look at Stack Overflow every time.
Jackson: You’re making me think about the first time I understood the joke of a “detached head.” When you finally understand the API well enough to get what a detached head state is, then you’re like, “oh, this is really… this is a clever nerd joke.” But also, I didn’t know that for the first eight years that I was a professional developer, because the API was too difficult to overcome.
Adam: Yeah, these are exactly the kinds of things that you want to avoid when you’re designing APIs.
A huge amount of this actually gets to one of the key things that we do here at Cord. When we’re designing APIs, one of the first things that we do is we build client code for that API to understand what the experience of someone calling it will be. Because sometimes, you know, you can think about an API for a long time and kind of write out multiple things and send around a document for comments and, you know, have workshops and things like that. But until you actually go to call that API in a real use case that is not sort of test code or whatever, but someone actually building some user application that does something.
That’s the moment where you go like, “oh no, I just immediately tried to do this operation and it did not do the thing that I wanted to.” And you just trip over all of these roadblocks that you left in the way that you didn’t realize were there because you just never tried to call it. So, an important part is really putting yourself in the caller’s shoes and making sure that you understand what their mindset is.
Because they’re not trying to call your API. One of the key things about APIs. There’s no one calls APIs for API sake. No one loves your API so much that they’re just gonna, they’re just gonna send you operations to do for fun. Like they’re trying to get something done. And you are standing in their way, is really what it is. And so what you want to do is you want to get out of their way as much as possible so that they don’t have to think about your API.
They think about the thing they’re trying to achieve, and then they look at your API and go, “oh, great. I understand how to get the thing I want, which is some functionality for my user out of your API, without having to care about your API.”
Because the API means nothing to them. It is unimportant to their life. They have no emotional attachment to the API. They want their product to do something. And your API hopefully is a more efficient way of doing it than just giving up and writing it themselves.
Jackson: If memory serves, when you and Andrei built our Presence API, didn’t you try building with it? So you build the API and then tried building something with the API. Didn’t you decide on the spot that it was actually incomplete and you knew it needed?
If we had to resort to calling API methods that external developers couldn’t call, then that was really an indication that our API was not rich enough. So when we were setting out to build these and we were building the Web Components, the initial test bed that we used for this was sort of a Notion clone with all of the complexity stripped out and just the collaborative features left.
The ability to have Presence per block and things like that. And when we were implementing that, we realized that in order to have multiple places on the page that you could be present at, so you can be present in the whole page. Plus you can be present at the individual blocks. Maybe some of those blocks are sort of nested and things like that.
We had to write some really gnarly, edge-case-riddled coordination code in order to call the APIs properly, to make sure that if you’re in a block and also on the page at the same time, that those two bits of code coordinated with each other in order to send the right updates to the server.
When we looked at the code that we had to write, we went, our API is wrong because we cannot expect every developer who wants to do this to write this code themselves. That should be complexity that we are handling so that our developers lives are easier.
We went back to the drawing board a bit and tried to figure out, okay, how can we change the data model behind our API and the way that we express all of these concepts so that now you can do it just sort of the straightforward way, the way that you would expect.
So the way that you implement Notion-style block Presence is actually very simple. You listen for events on the block and then when a user puts their mouse over it, or whatever you consider them being present in the block is, you say, “the person is here.” And then when they leave, “you say the person is gone.” And those are the only API calls that you have to make. And that is just the way that you would expect to have to do it.
Jackson: I love that we paused. I love that you guys sort of stepped back from that and thought, “no, this is not what we want. We want to give developers better tools than this.” That’s that’s a really exciting thing. It makes me very happy to be part of this team.
Adam: And it’s great that we have the opportunity to do that. One of the things that I’m really invested in is . Making sure that for things like this, that we really get it right. That the developers don’t come and they go like, “it’s…. fine. Like… it does the thing that we need it to do.” Right? That they don’t have specific technical complaints, but they just don’t like it.
Everybody has used APIs that are like that, where you go. Like, “I mean, it does the thing that it says on the tin. But every time I use it, I kind of hate it.” And that’s not where we want to be. Right. We want people to honestly forget about our APIs, right?
Like we want them to be so straightforward to use that when you go, “oh, how was using the Cord API?” They go like, “I… don’t know… I wrote the code and it did the thing. And then I forgot about it and moved on to the next part of this feature that I’m trying to build. And so the feature went really smoothly!”
Jackson: Okay. So I think we were coming to a good place to, uh, to, to go for a lightning round. For those of you who have made it this far in the podcast, we promised a lightning round of API design hottakes. I think it’s time. I think it’s time to go for the hot takes. You ready? You ready for the hot takes Adam?
Jackson: All right. I have compiled a list of controversial API related things. And we’re just going to go at this. Just free for all. Just anything. Whatever comes up, comes out.
So the first question is about GraphQL. GraphQL is an open specification now, that was born at Facebook. It serves the Facebook complex data model, multi data source problem extremely well.
And it’s also been adopted widely across the industry. And you find people who both love GraphQL and swear by it and other people who really [censored] hate it. So, Adam, the hot take question is, is GraphQL a bad choice for public APIs today?
Adam: I think the answer is yes. And that isn’t something that’s fundamental necessarily to GraphQL.
Like one of the things that I like about GraphQL is that at the specification level, like at the wire level, it’s actually very straightforward. You send JSON and you get JSON back. I think that you could actually serialize in some other format as well. Like the specification is open about it, but literally everybody uses JSON.
So, and that’s something that you can do from a bunch a bunch of different languages and places. And, and so it doesn’t tie you down in that way, but one of the things that I think today makes it a problematic choice for a public API is that you do want to have some framework around it. Often, one of the things that you want to do is have a client side cache that’s lets you combine the results of different queries and avoid querying the server if you’ve done it before. Or a way to integrate GraphQL subscriptions, if you’re doing real time changes and understanding updates in real time with your base queries that you issued, initially. All of that is made a lot easier by client side handling. And in a lot of places, those support libraries just aren’t in place. This is something that if you’re doing sort of a REST style API…
Jackson: I’m glad you bring up REST because I’m going to follow up with a hot take question about RESTful APIs, but, but carry on may make you make your REST point here.
Adam: If you’re doing a more normal API where you’re just making a request to a particular endpoint, and it’s sending you back to the data often, that’s something where there’s very good support in a wide variety of languages for doing that. And a bunch of helper libraries and things like that, which makes your developer’s life easier and GraphQL is spotty and its support nowadays in terms of, “oh, I want to call this from Rust. I want to call it from Go. I want to call it from Perl 6 or whatever.” Right? Like, your callers potentially might be all over the map in terms of. Their infrastructure is built on and their technologies they’re using. And you want to meet your developers, where they. Are a big part of it, right, is making sure that you don’t make the developer’s life harder in the process of making their life easier. Your job is to take stuff off their plate and if you go, “oh, okay, but before that I have some requirements…” it really is compromising a bit on the deal that you’re making with the API user of “I’m going to get out of your way and do the thing that you asked me to do without a bunch of trouble on your end.”
The other thing about GraphQL that I think people maybe discount when they adopt it is just how complex it makes your query side layer of this. You know, when you have a zillion different data stores and a zillion different cache layers, the way Facebook does, you need something exactly like this, like something has to do this.
But when you have one database and then you have lots and lots of different entities that have fields that have subqueries, you can end up hammering your database with thousands of queries for straightforward things that you could get over a simple REST API in a much more straightforward way. I feel like the, the hidden cost there becomes more apparent to you as your product matures.
As you add more complexity to your data model. You know, I I’ve seen at two companies now how the cost of the, of that complexity rises sharply sort of inexplicably. Like you didn’t expect it to be there. It’s the sudden like, “oh right now we have to think about those 10,000 queries we’re hammering our database with, I guess we’ll do some sort of query aggregation…” which is new work you have to do because of GraphQL.
Adam: Yeah. I mean, this is a classic case of, “oh, this worked for a really big company and they’re very successful, so we should use it because that will make us successful.” Right. This is a common pattern that you see and often the reason that the big company did it is because they’re very big. They have concerns that normal size companies just don’t have, right?
Like Facebook has to look at a graph of user relationships of a billion plus users and figure out how all of them connect and which fields to pull and all of this kind of stuff. All in, you know, milliseconds so that it can serve you the set of POSTs that are most relevant for you to read or ensure that you can see the comments that you’re trying to load and all of these pieces that go into it.
Whereas if you have a normal size product, you know, you could have tens of thousands, hundreds of thousands, millions of users may be still, but if it doesn’t have the incredible complexity inherent to the way that it operates, you don’t necessarily need those complicated tools. If you are querying the same set of fields, every time you load a page, you don’t actually need GraphQL. Like you can just make a request that always returns those fields. And that’s fine.
Jackson: So we talked a little bit about choosing REST. At the lowest level of our server side API, it is a vanilla bog standard HTTP REST API, and we have not followed someone’s definition of strict RESTfulness. Ask 10 engineers. No, I’m sorry. Ask a hundred engineers. What good API is. Look like, you will get 97 different answers.
Sound effect: Well, actually…
So, the question I have for you here, Adam. Is a RESTfulness something that is even worth worrying about as developers? Like, should we care about this?
Adam: So my opinion is that it doesn’t matter…
Sound effect: Oh no.
Adam: …as long as you follow good API principles. I think that a lot of people stress about things like, oh, what HTTP verb should be used for this particular operation. And I think that honestly, like your callers don’t care. If you just make everything POST, it’s fine.
It will all work. One of the things that is interesting here is that the origin of the REST concept was about editing HTML documents. And so the URL part of it was a parameter, right? Like that’s an argument to the function that you’re doing. And the verb you’re using is the action that you’re taking. And nowadays, usually, actually the URL says what action to take.
And so also having the verb there that you have to combine properly in order to define what action to take, I think is a little bit pointless. You can encode it in the URL if you want, and you can just use POST or whatever. And everybody sort of understands how to make a POST request. And it does all the things that you need to do.
And the handling of, for instance, how the browser is going to do it if you refresh the page after a POST, everybody understands how that works, whereas it might work slightly differently if it was a PATCH request or whatever. There’s browser support for like submitting forms via POST and all of this kind of stuff that works the way that you expect it to.
I think that a lot of the discussion around, “oh yes, like the PUT verb means exactly this whereas the PATCH verb it looks like this and the UPDATE verb looks like that. I think all of that is mostly… wasted effort.
Jackson: This this connects to a, I don’t know, a bunch of experiences I’ve had over the years, working with REST API is, and especially working with people who are ardent – ARDENT – RESTful advocates of different sorts and every super hardcore RESTful minded person that I’ve ever talked to has a different hardcore opinion about exactly what good RESTfulness is to all of the rest of the hardcore RESTful ardent investigative journalists. Um, that’s a Zoolander reference for folks who were playing the home version of this.
Anyway, the other thing about RESTful APIs that I think is a hidden cost. The fact that HTTP requests have cache implications. The fact that idempotency is a thing and that GET requests work the way they work. I think a very large number of engineers and developers in the world have no idea how this works.
They don’t get it. It’s, it’s black magic to them. The fact that so many people trip over it, the fact that even after years and years of using it… Maybe that’s a smell. Maybe that’s an indication that this was a bad API design choice in the first. Even if it’s technically correct.
Adam: Yeah. I 100 percent agree.
Jackson: Okay. Let’s let’s do some more hot takes. Quick hot takes. Talk me through an API pet peeve or anti-pattern that just is just right underneath your skin. Something that just really makes you just want to flip the table.
Adam: Oh, I have a great one for this. This is API calls, that return results that have different shapes, depending on what arguments you’re providing to them.
This is a classic case of, oh, if you query for something and I there’s one item, I give you the item, but if there’s two items, I return an array of items and these kinds of APIs are just waiting for someone to call them wrong. Especially in these cases, there’s often a specific argument that you can send that changes the shape of the result in a way that is a very unusual argument to send.
And so you can have code that uses is API that works totally fine for a really long time. And then something changes and you end up passing this argument and then all of your code explodes. Because your result looks completely different than what you expected it to. And so like making it so that those kinds of landmines aren’t sitting there, which is like, oh, you passed the magic number 4 here, now you get it object here when, before you just got a string! Hope you’re ready for that! That kind of thing really gets under my skin.
Jackson: It’s funny. The thing that was in my mind for this is not too far off from what you’ve described. The thing I was thinking about is something that I’ve seen in jQuery. It’s something that I’ve seen in a bunch of different server side libraries, which is where the same end point is both a read action and to write action, depending on what things you give to it.
Sound effect: It is bad and you should feel bad.
Jackson: The attr() “atter” function and jQuery, you can call it to get the value of an attribute. And if you give it an extra argument, you set the value of that. Which is just a nightmare. That’s just like a don’t you just don’t do this. You know, that’s the thing where someone accidentally edits the line and now suddenly your app is like writing data into it from somewhere. You, potentially, you don’t even know where it’s coming from. It actually connects with our discussion earlier about HTTP calls becausea write-on GET is a super, super hardcore anti-pattern. You should never be doing writes on GET requests. And yet many of the popular APIs out there work exactly this way. Some of the key things you need to do with the APIs are HTTP GETs. Drives me nuts.
Adam: And it runs into your issue with caching, where part of the reason that you don’t do that is, well, maybe that GET request GETs served out of the cache. It maybe never even arrives at the server. And especially because like the cache control header has, you know, values like nocache, which allows you to cache it, which makes the matter so much worse because nobody really understands all of the details of caching and like making sure that all of their cache control headers are done in the right way and max age and everything like that.
Like, there’s so many pieces to making sure that GET requests actually get served the way that you want them to. That if you also expect them to have side effects, that really is just setting yourself up for trouble.
Jackson: Okay. I think with that, we’re going to wrap this up for the folks who are listening, I hope you enjoyed this. I know there’s some hot takes here. Our inboxes are ready for the hate. Bring it. Tell us why we’re wrong about HTTP. Tell us why we’re silly to think that Git is an unintuitive API. But most importantly, come and check out what we’re up to at Cord. That’s Cord.com.
We’re building developer centric APIs to make collaboration happen for your product, for any product. For the whole internet. Collaborative products are eating the world and we want to help make your product one of them.
And I want to give a special thanks to Adam for joining us for the first time.
Adam: Yeah, absolutely. Thanks for having me on.
Jackson: And look for more content like this from us. We’re going to do a thing that I haven’t seen much where we bring the developers who made the thing on and talk about the thing that we made. Thank you all for listening. I hope you enjoyed it.
Adam: The API means nothing to them. It is unimportant to their life. They have no emotional attachment to the API. They want their products to do something.