 I'm Emily. I'm going to talk about releasing responsibly, and especially about backwards compatibility. So when I was writing this talk, I was a little bit worried because I didn't have any personal experience to share where I totally messed up and had to yank a gem. But luckily, within the last week, I actually released a version of the Ruby driver that needs to be yanked. So I'm giving this talk about releasing responsibly, but I have had my own moments of irresponsibility that I can talk about. So I want to talk about gem files, first of all, and share with you a little animation of the lifetime of our gem file, at least a small portion of it, over the last two and a half years that I've been working at MongoDB. So you might notice that your gem files have undergone the same sort of transformations over the course of time, and just have completely exploded into just lists of gems that are locked at very specific versions, not even using the, I think it's called the Twiddle Locker, not even using anything like that, just locking gems at very specific versions. So that said, over the last two months, I've noticed that I've had to do these emergency fixes to our gem file to lock them at specific versions more often than normal. And I started thinking, like, why is this, and what is it about the Ruby community that makes it that I have to do this quite often? And I realized that I defined in my head that innovation and stability are always at odds in the Ruby community. And it's not necessarily a bad thing. Like, I think we as Rubyists are known to always be on the cutting edge and have a lot of energy and sort of just push forward and release and fix if something's broken. But at the same time, sometimes we're not taken that seriously as a community. I think the innovation is really positive and that kind of energy is really positive, but we also want to be known as people who can be, we want our code to be trusted and we want to make sure that our code is stable for each other as well as for, if like, I don't know if all of us are going to be running Ruby for the rest of our lives, but if you go on to write some other language, I think that these concepts or the importance of stability will be just as important. So, a little bit more detail on what these experiences over the last couple of months having to lock my gems at specific versions. I've noticed also that they're usually like three types of responses or three types of reactions to a gem being released and breaking backwards compatibility. So someone will say, not part of the official API, sorry, you shouldn't really have been using that class, but the reality of Ruby is you can't really have private classes. It's the, you can call private methods if you want. You can work around that. So I don't really think that's a valid excuse. I think it's something that if you are, whether it's an internal code base or a public gem, an open source gem, I think you need to be aware of the fact that every method you expose, whether or not it's supposed to be used internally only by your other classes is going, could potentially be used by users. The second one is my bad, sorry, maybe it'll be fixed, maybe it won't. Another one is yank the gem and release a new one. So I'm sure you've seen a number of these reactions based on a gem version being released and breaking backwards compatibility. So let's talk about releasing responsibly. There are a number of ways that you can be responsible or a number of criteria that make up what it means to be responsible when you're releasing. But we're gonna focus on backwards compatibility because it's something we take extremely seriously at MongoDB. The database, as soon as you deal with databases, I think the stakes become quite high because loss of data or corruption of data can be very, very serious. So we take the backwards compatibility of the server very seriously as well as backwards compatibility of the drivers. Just as a little context, the drivers team were nine official drivers, so we're about 25 people support nine different languages and this is a concept that we have Java developers.net, we have Perl, Python, no matter what language we specialize in, we all speak the same language when we talk about backwards compatibility. I also was thinking about this topic a lot because people would say like, okay, so you support the Ruby drivers, so what does that mean? Does that mean you support all versions of the database? And the answer to that question is not simple. It's not quite, I mean, yes, but not really. So what does that mean? In order to define what backwards compatibility means and what it means for the Ruby driver to be backwards compatible or your own code to be backwards compatible has a number of definitions, a number of things you need to think about. And it's sort of this like, it's sort of a daunting thing and it's pretty serious to say that your code is backwards compatible. But if you stick to three things, I promise your code will be backwards compatible. So let's talk about what those three things are. So first, simple API, clear communication and semantic versioning. So I'm gonna talk about each one of these things and criteria and things that you should consider as someone who's maintaining a backwards compatible API and some things that, some principles that we adhere to as a driver's team that are, I think, applicable to any code base anywhere. So starting with simple API, I give a talk at RubyConf and some other places like Rails Israel last year on writing gem APIs and how important it was for them to be simple and how writing a gem API is like thinking about user interface design. So essentially the API is user interface, designing the API is user interface design. So I'm not going to go into so much detail about that but if we take that concept and we think about it in the context of backwards compatibility, you need to be aware of two things. One, your API needs to expose some amount of configurability of the underlying thing that you're wrapping or your own code and it also needs to maintain stability over time. So what does that mean? I was talking to Eric Michaels-Ober about the Twitter gem. He told me that back in the day when there were multiple Twitter gems that were somewhat comparable, the Grackle gem exposed a lot of the underlying configuration of the Twitter API. And he says that the point at which he saw the greatest increase in adoption of his own gem was when Twitter released a new API and those people using Grackle had to go through every single line where they were making a call to the API and change that line. And whereas his gem abstracted the API enough so that his users didn't really need to know that there was a new API available. So this is where there's stability and there's configurability. And we talk a lot on the driver's team about how much of the server configurability we want to expose. And a lot of times you'll expose maybe to 80% of your users, you'll expose the optimal API or you'll provide the optimal API. But then there's going to be a 20% of people who can't really get to the exact thing that they want to configure. But it's a trade-off. You have to consider these two things and try to code really for that 80%. And it's unfortunate that other 20% is not going to get at the underlying API but your code will be more backwards compatible in the future and will keep users sticking to your API. So this talk is actually a combined effort of all of the speakers at this conference because Tom Stewart mentioned this one to me. I'm just kidding about it being a composite presentation. But so he told me about the UK government has a digital project where they're putting all of their administrative tasks, forms, information, and trying to reduce bureaucracy by having a simple API online that the users of the UK government can make use of. And so they had a blog post about their specific design concepts that they stick to, and one of them is do the hard work to make it simple. And so the point of this is providing a simple API doesn't mean it's going to be simple for you to provide a simple API. It actually usually is a lot more work for you to provide a simple API. But it pays off in the long run. So do the hard work to keep it simple. And so more specifically, more pragmatically and really about the code, some ways to safely make API changes I've thought about these in the last couple of years is they're just things in my head that I do and I don't do. And so one thing is add arguments with default values. So old method calls can still work. So that's sort of like rule of thumb. Add methods, add subclasses, and change behavior in those subclasses, change behavior in those other methods. Don't add arguments without default values. Don't rename methods or classes unless it's a major version and you're planning on doing these breaking changes. And don't remove arguments from method signatures. So these are pretty basic, but just things that as soon as you find yourself doing something this, like this, even if it's, as I said before, a method call that you think is internal that nobody else is using, just keep in mind that there might be people using these methods or method signatures and do rely on them. The other thing is modularize. So I like this idea of hiding all the gory details yourself. And so picking at the Ruby driver, I use this method as an example every time I wanna talk about gory details because it's pretty gory. So this is a method in the database class that lets you add a user to the database. So pretty easy to understand. You just provide a username, password and roles and read only, which is actually a deprecated thing. But the point here is that this method has not changed at all in the Ruby driver, but it works with every single version of the database. So the database first sort of version that we talk about is 1.8. Then there's 2.0, 2.2, 2.4, 2.6, and 2.8 is about to come out. So we're on the development 2.7 release now. And so looking at this code, some things to learn from this code are, I'm doing all the work in this code so that the user doesn't have to worry about what version of the database they're talking to. But what's important here is to catch exceptions, check error codes, and modify your behavior or handle the error according to what the error code is. And so you can see like, so basically the way this works is you try this command. If it says it's not found, we know it's an older version of the server, so then we do this legacy add user, which is like this other method. Otherwise we check to see if the users can exist in that response from the server if it does and we're doing an update, so then we do an update, otherwise we know we're creating a user. And so I have this long paragraph too of like what it's doing and everything. So these are gory details, but do this work, do the hard work to keep the API simple for your users. And another way of handling backwards compatibility is by having a compatibility module in a minor version that you can then delete in a major version. So I think I'm pretty sure Rails did this at some point and that was really useful for maintaining compatibility without really breaking anybody's code and also making it optional for people to use this module. Another thing is I really don't like if else statements, I think it makes your code really messy in this context. So in the gory details slide, there were some if else statements, but I tried to put the code inside each statement inside its own method, so it was much more clear. But where you can use polymorphism instead of conditionals, it'll be a lot easier to deprecate API changes or changes within your own codebase and it'll be a lot easier to understand what's going on. And backport only when necessary. Backporting can be a little bit dangerous, but in a sense that you should be careful not to change existing behavior, but only really to add new behavior where necessary. I personally don't favor backporting, I'd rather create subclasses or separate classes that have this specific behavior that I need. And so one example in the Ruby driver that we do is we need in 187, we support 187, so we need in 187 hashes to have ordered keys because some documents sent to the server to run some commands need to have the keys in a specific order, so instead of backporting ordered keys or whatever to hashes in 187, we have a separate class called ordered hash. And we enforce you to use this class if you are doing some specific tasks in the Ruby driver. So this is just an example of a place where we're using a subclass to get at a specific behavior instead of backporting that behavior. Okay, so that was simple API. What you need to realize is important about having a simple API is that it is a lot of work upfront, but it does help you as time goes on to maintain backwards compatibility and that stability and configurability are at odds. So make sure that you strike the right balance between those two concepts. So clear communication. This is really the meat of the talk. This is really what I think is the most important about maintaining backwards compatibility. So there are a number of channels that you can communicate through, a number of people you have to communicate with and a number of ways that you can communicate. So starting out, when you talk about backwards compatibility, what are you compatible with? Are you compatible with old versions of your API? Are you compatible with the systems you depend on? Are you compatible with different versions of the language that you're writing this code in? So it's really important to define what it actually means to be compatible. And define the scope of what you support. So at MongoDB, MongoDB has its own backwards compatibility or support policy. We have a webpage that describes everything because we provide support contracts. We have to be very clear about what versions of the server will support. And so as the driver person, we have to know what the database supports. And then as the driver, what scope of that do we support? And of course, then I have to support my own API from version to version. So this is not actually available publicly, but it's just something we're conscious of internally and something we stick to when we're doing testing. So to read this chart, the top are the server versions. So as I said, it's 1.8 up until 2.8 are the server versions that have been released in the last three years. And then the driver versions are on the left. And essentially, we make sure that if those are the green boxes are where we absolutely have to support. And then the yellow is where we're aware of diminishing returns. So for example, if you look at version 111, it doesn't really support 1.8. I mean, it could, but we haven't really tested it. But the reason it's yellow is because if a user says that, I mean, MongoDB doesn't officially support 1.8 anymore, but if a client who's paying for support contract is on 1.8 and they say, we really want to use some feature in Ruby driver version 111, can you help us get it to work on 1.8? We'll say we realize that even though it's probably more complex to upgrade a database than to just upgrade a gem, it's probably a better use of our resources to help the user upgrade their database than for us to try to make the new driver version work with an old version of the database. Because to be honest, 1.8 was released. I could be wrong on this. But I think we were like, as a company, 40 people. And now we're 400. So would you be, do you want to use a database written by an engineering team of a company of 40 people or 400? We're like 150 people on the engineering team. So anyway, this is what we're aware of internally and what we stick to when we do our testing. And test everything you say you support. So don't spot check. Don't just check on 2.1. And then maybe 1.8.7. And maybe throw Jay Ruby in there. And then, I don't know, I think it's important to have a matrix. So we use Jenkins. And we have this enormous matrix that takes a bazillion years to run. But it's really important for getting at every single combination of using the server, like replica sets without the C extension, with the C extension, different versions of Ruby. This is really, really important. And the driver wouldn't work without this. This is like, I see green circles in my red and green in my sleep. Super important. Change logs. Change logs are an extremely important way of communicating with your users. It's a really good way of telling them what new features have been added in the release, what bugs have been fixed, what changes have been made, general changes. I even put changes to the readme in there. Because I, like, if someone, I don't know, I think it's important even to tell people that there's some new kind of information available to them. I was talking to Olivier Longcom, or Lecom. And he said that he has a project called keepachangelog.com, where he is, he really feels passionately about getting people to keep detailed change logs and wants to emphasize the importance of that in the community. So he has this website. Check it out. It's really good. I was talking to him yesterday. And he told me that he's going to add a section called unreleased. And so he's going to talk about how to put points into your changelog, warning people of changes that are to come. And that's just as important sometimes as documenting the changes that are already out there. So keep it changelog. And this goes along with user trust. So this is probably the most important slide here. The most important component of communication is so that you can maintain trust with your users. Trust is so important because it takes a long time to build up, but you can lose it in a second. And as soon as you lose it, it's going to take even longer to build up that same level of trust again. So you have to make sure that your communication maintains that trust with your users and sets their expectations for the trust that they can have on your code. And I was talking to Eric also about the Twitter gem. He said that he had a communications problem once where the Twitter API was going to be upgraded to a different version. They were going to completely deprecate the older version. And he had no way of pinging every single one of the Twitter gem users. So the only way he was able to communicate with them and give them a bit of a warning before Twitter deprecated their API, the only ways that he could do that was to yank the gems that supported that older API one week before it actually happened. So that API was still available to them for one week if they needed to make some changes, specific changes in their code base. But he had to resort to yanking gems even to communicate with his users. So sometimes you have to be pretty creative with how you reach out. But again, communication, I think that was a very even though he was yanking gems and quite a lot of them, that was a responsible way to deprecate older versions of his gem. And so now back to our reactions. Not part of the official API. My bad, sorry. Yank the gem, release a new one. So which one of these maintain trust with your users? And which one of these, if you hear that from a gem maintainer, make you feel like you can continue using their code? And so specifically a couple of weeks ago, in one of these times where I had to make an emergency fix to our gem file, I don't want to name the gem specifically because I don't want to put the person on the spot. But we're using this gem. And I noticed that the newest version used the newer Ruby hash syntax. And as I said, the Ruby driver supports 187. So that broke our code when we were testing on 187. So the first thing I did when I saw that it's really obvious immediately what the problem is when you see the build running. So I went to the Ruby gems page and I went to see if they had any dependencies required being above 19. And there was nothing there. There was nothing listed in the dependencies. And I was like, OK, well, that's strange. Why wouldn't they tell everybody that they don't support 187 anymore? So I went to the code and I found the one line where they were using this newer hash syntax. And I was like, oh, that's super easy to change. I'll just submit a pull request, which I did. And so I submit the pull request. And then I asked the maintainer. And I said, so would it be possible? Maybe you can change the gem spec to officially say you don't support 187. I think that would save some people some time. And he was like, yeah, I mean, I can do that. But we don't use the gem spec so much because we only support a live Ruby versions. So old Ruby versions are dead to them. So users of old Ruby versions are dead to them too, apparently. And so I don't know. I don't really think this is really that acceptable. I think it's not that big of a deal to say in your gem spec what you support and what you don't support, because the reality is people are still using 187. And you need to communicate that clearly with the users of your gem. And if that means just changing the gem spec, I don't really think it's a big deal. And so deprecation strategy. How do you deprecate? In the context of communication, you can, again, change your gem spec. A strategy means that you give your users warnings. So one way is to mark methods as deprecated in a minor version, and then say that in the major version, you'll be removing it so that they have enough time. And I think if they have a release schedule of every three months, if you release this and then they have two months to upgrade, I think that's a really good way of giving people a warning. It lets them transition smoothly. And so, for example, MongoDB has a support policy where we deprecate versions of the server. And so we're very clear about this, so that when people are using the Ruby driver, they know exactly when certain versions of the server will be no longer officially supported. Also, features of MongoDB are deprecated as time goes on. And in the driver itself, we'll usually be born saying that support for this will be removed in version blah, blah, blah of the server. And we'll mark it useYardDocs. There's a tag called deprecated, which is great, so that people, when they look at the documentation, will see in a big red box that it will be deprecated. I think that's a really good way of communicating as well. And make sure, as I said, make sure your users have a lot of buffer time to upgrade. And this is one of my favorite things about deprecating is, so, luckily, on the Ruby driver team at MongoDB, we've had the chance to just completely do a read-write of the driver, which is really great, because the driver was written years ago before I started at MongoDB. And before we had things like replicasets and stuff in the server, so the code base grew quite organically. And I feel really lucky as an engineer to have a chance to just completely rewrite the thing that I'm maintaining. I don't think that opportunity arrives very often. So I was thinking about what I've learned about the progress of the server over the last two and a half years. And when I'm designing this new driver, I know the pace of the server. I know things are deprecated. So I've worked that into my design so that some days, when the time comes, that the server is going to deprecate something. I can just delete code and move files and everything will just sort of work out perfectly. So for example, when you talk to the server, there's a wire protocol. So there's a specific protocol of the message type in the format that you send to do certain operations. And so for insert operations, there's a old wire protocol way of doing it, a specific op insert to be more specific. And in newer versions of the server, we have an actual command to do an insert. So the command is the message type, and the command can be update, delete, or insert. So the way that I've designed the new insert operation is to the context tells you information about the server you're talking to. And so I'll check if write commands are enabled. And if so, I'll send that type of message. Otherwise, I'll just send the normal op insert message. And I cannot wait until the day when I can just remove all that code and it becomes this. And so again, I've worked this into my design, and I think this will make my life a lot easier in the future. And I think it'll be really fun to just like delete all these lines of code and have everything continue working. OK, so that's communication. As I said, user trust is extremely important. There are many channels of communication, many ways you can communicate with your users, with your colleagues, with yourself in the future by writing code comments and working deprecation into your design. But really, user trust is so, so important. And the only way you're really going to build that trust and maintain that trust is by making sure that you communicate quite closely with your users. So this talk wouldn't be complete without talking about semantic versioning. As a review, x.y.z, x is major version, backwards breaking changes are allowed, new features, everything. y is minor version, new features. And z is a partial version. So you don't want to break no backwards compatibility, breakage, no new features, just bug fixes really in a patch version. So semantic versioning, we all know it's important, but why don't people follow some there? We all know what it is. We all know we should eat healthy and go to the gym, but why don't we do it? It's a lot of work. It's sort of daunting to say, OK, yes, we're following semantic versioning. It takes a bit of effort. I was talking to Terence Lee of, like, who were some bundler and also were set Heroku. And I was talking to him about some there in the context of bundler. And he said, well, some there is a double-edged sword. And that's why people don't always follow it so closely. Because some there is great for maintaining the contract and trust with everybody who uses your code. But it's also sometimes something that holds you back. And as I said, in the Ruby community, we really favor innovation and pace and energy and always moving forward. And as I said, that's really positive. But sometimes we see Sember as something that's going to prevent us from making changes that would optimize or improve our code. But again, I think in the long run, the user trust and the stability of your code is something that matters even more. If we all follow Sember, our gem files could be cleaners. So going back to this small portion of our gem file, it looks much worse than this. Like, we even check. Ruby versions do different things based on different Ruby versions. But semantic versioning is really important because usually with gems, they have, like, obviously, they have other dependencies. And so the dependency tree becomes quite complex quite quickly. And the only way that's going to work is by having everybody have this contract with each other that we all follow Sember. And it's a cultural and community thing. So I'm certainly not old enough. Or haven't been programming for long enough. But back when Ruby starred in, back when Rails was built and just becoming popular, there really wasn't any Sember. And it was just sort of, like, do whatever you want, like release, like, Fit Breaks. I don't know if you remember the upgrade path between 2.35 and 3.0 of Rails was like a nightmare. And it just, like, it wasn't a thing. But that, and so maybe that set the standard a little bit that Sember wasn't that important. But they're converging towards Sember, both Ruby and Rails. And I think we are also, and I think it's a really good thing for the community and for the integrity and stability of our code. And that's it. Thanks. Thank you.