One of the most challenging parts of being a software developer is handling change.
Whether you're working at a software agency developing a project with specific client requirements, or you're at a startup constantly testing what works and what doesn't, change is a constant.
On one hand, you're always adding new features, making improvements, fixing bugs, and sometimes implementing major architectural changes. On the other hand, your software is constantly becoming outdated as dependencies, hardware, browsers, users, requirements, managers, devs, and the entire software supply chain evolve.
In both scenarios, it's crucial to closely monitor the concept of the breaking change which can cause users' applications to fail. In a world where API connections exist everywhere, this is a major concern.
A breaking change is aptly named—it's a change that can break a client's application. It's a situation that may require you to update your application to prevent service disruptions.
Broadly speaking a breaking change is one that changes a function’s signature, changes the behavior of a function, or that changes dependencies. Specific examples include:
- Deleting a function
- Deleting or modifying a response field
- Deleting of modifying a field name
- Modifying the list of required function parameters
- Modifying rate limiting
- Changing the order of the parameters in a function
- Changing the data types of parameters or return values
- Renaming a function
- Changing error handling
- Modifying DB schema
- Changing file format or data serialization
- Removing software dependencies, and modifying them in some cases
- Changing the scope of function variables
- Changing network protocols
- Allowing a parameter to accept a shorter range of values
In the snippet below, we modify the list of required parameters by adding an address field. This change will cause a breaking change wherever we call the register function in our app.
Examples of what a breaking change isn’t include:
- Adding a function
- Adding a response field
- Adding an optional function parameter
- Changing the order of the properties in a response
- Allowing a parameter to accept a broader range of values
- Providing default values for function parameters
- Changing a font
- Refactoring code that doesn’t change function behavior
Breaking changes are sometimes necessary. First, let's discuss strategies to prevent a breaking change. Then, we'll focus on the main idea of this article: how to handle a breaking change if it's truly unavoidable.
Try the following strategies to prevent a breaking change occurring:
- If you’re going to add a mandatory query parameter to your API, think if you can make it optional and instead make it mandatory on the client-side.
- If you really have to change a field name, see if you can somehow map it to the old field name in order to prevent the breaking change.
- Use openapi-diff command line utility for comparing two different OpenAPI specifications.
- If it applies to the kind of business that you’re part of, you cold also offer Long Term Support (LTS) versions and not kill APIs.
Bonus: We also suspect that code diff tools in our version control systems are going to substantially improve thanks to recent advances in LLMs, which will help us understand changes at a much more granular level during the code review process.
If you really need to introduce that breaking change after think about everything listed above:
- Update developers who use your API. It could be a changelog, it could be a Slack channel or a Discord server, hopefully everywhere!
- Use a deprecation header field to tell consumers of your API that such resource will or has been deprecated.
- Thoroughly review the breaking change and don’t just be the developer who LGTMs every pull request. This will sound cliché but it’s true: A breaking change needs to be debated, communicated and documented.
This is particularly relevant for product companies where business requirements constantly evolve and the software is never fully complete.
Above all, companies should prioritize innovation and meeting customer needs. While preventing breaking changes is beneficial, it can sometimes hinder the evolution of a product's value proposition.
Consider, for example, the experience of using products from major API providers. Stripe’s changelog is a case in point. They periodically release new API versions that include breaking changes, typically once or twice a year in recent times.
Such changes enable Stripe users to set up payment methods with no-code, enhance debugging with more descriptive error codes (Stripe excels in clarifying the developer's intended actions), and simplify the creation of discount coupons, among other innovations.
Other major API companies like Twilio and OpenAI also introduce breaking changes as part of their product development. This is true for internal API developments as well. Breaking changes are sometimes essential for product evolution. They should be avoided as much as possible, but occasionally they act as a catalyst for innovation.
The more gradual these necessary breaking changes occur, the better. We’ve built a GitHub app that helps teams understand breaking changes by building a tribal memory.