home
post
breaking changes

Breaking Changes

Mar 11, 2024
5 min
1 chart

Breaking changes is a result of a code changes, that how the API behaves. It can be low level code API, REST API, any microservice API or even user interface behavior.

Breaking changes can be of two types: structural and semantic.

Structural Breaking Changes

Structural changes appears when the structure (contract) of an API changed. Changes can be in any code structural element. This is the list of structural breaking changes groups:

  • remove parameter
  • add parameter
  • change parameter type
  • change return type
  • add required constructor
  • make attribute required
  • add type required restriction (also called generic)

Let's explore a lodash popular utils library in JavaScript ecosystem. In a changelog we can find breaking changes in a version 3.0.0. When semver major version changes, it indicates, that API contains breaking changes.

var wrapped = _([1, 2, 3]); // in 2.4.1 wrapped.forEach(function(n) { console.log(n); }); // ➜ logs each value from left to right and returns the lodash wrapper // in 3.0.0 wrapped.forEach(function(n) { console.log(n); }); // ➜ returns the lodash wrapper without logging until `value` is called wrapped.forEach(function(n) { console.log(n); }).value(); // ➜ logs each value from left to right and returns the array

This example depicts the structural breaking changes, where the return type of function changed. In version 2.x this function was calling forEach eagerly. In version 3.x lodash made this function lazy, and returns wrapper instead of void.

Semantic Breaking Changes

Semantic changes appears when output or side effect of API changed/added/removed. This is the list of semantic breaking changes groups:

  • adding/removing/changing sorting return type. Consumer may rely on the way array is sorted or not sorted in the return of API.
  • evaluation side effect. Chat GPT stores and "remembers" history by a chat id. Breaking change may appear if the storing algorithm is changed or how the history is red.
  • additional constraints/validation on input
  • adding/changing/removing implicit modification logic. As an example: submit the form and some default values are applied. Changes in the implicit date formatting, changes in the timezone, setting date to an attribute changes.

Let's explore a lodash again. In a changelog we can find breaking changes in a version 3.0.0. When semver major version changes, it indicates, that API contains semantic breaking changes.

var array = [1], wrapped = _(array); // in 2.4.1 var a = wrapped.push(2), // pushes `2` to `array` b = wrapped.push(3); // pushes `3` to `array` a.value(); // ➜ returns `array`; [1, 2, 3] b.value(); // ➜ returns `array`; [1, 2, 3] // in 3.0.0 var a = wrapped.push(2), // creates a lazy sequence to push `2` to `array` b = wrapped.push(3); // creates a lazy sequence to push `3` to `array` a.value(); // ➜ pushes `2` to `array` and returns `array`; [1, 2] b.value(); // ➜ pushes `3` to `array` and returns `array`; [1, 2, 3] a.value(); // ➜ pushes `2` to `array` and returns `array`; [1, 2, 3, 2] b.value(); // ➜ pushes `3` to `array` and returns `array`; [1, 2, 3, 2, 3] // use `_#commit` to commit a sequence and continue chaining var a = wrapped.push(2).commit(), // pushes `2` to `array` b = wrapped.push(3).commit(); // pushes `3` to `array` a.value(); // ➜ returns `array`; [1, 2, 3] b.value(); // ➜ returns `array`; [1, 2, 3]

This example depicts none of the structural changes. But calling the same function in different major versions (2.x and 3.x) results in different inner state of closure.

How to avoid breaking changes?

Structural breaking changes can be avoided by providing additional method/function/constructor/class. Consider the case with the lodash and forEach. lodash could avoid the breaking changes by adding another method/function instead of forEach, they could add forEachLazy or lazyForEach which will provide the same eager API as in 2.x version. Another option how to avoid breaking changes is create new lazy API, by adding an additional parameter to the default export function as _.({lazy: true}). In this case only lazy wrapper with explicitly called value() method will be created.

Why we are doing breaking changes?

An example with forEach has two options how to avoid breaking changes.

  • add another method/function forEachLazy
  • add function overload for a default lodash function with additional optional parameters.

Both of these cases will complicate lodash API, will make it less consistent with other functions. Also it will increase maintenance cost of two different approaches to be handled for array methods.

These two reasons are not the only reasons to make breaking changes. In my opinion those are the main reasons. To make decision you need to balance between API complexity / maintenance cost and consumer migration cost / benefit.

Related Posts
Defining Microservice Scope - Integrators and Disintegrators
Defining Microservice Scope - Integrators and Disintegrators
Microservice architecture has become a widely adopted approach for building scalable and...
Aug 16, 2024
3 min
1 chart
5 Ways to Fail at Implementing a modular monolith
5 Ways to Fail at Implementing a modular monolith
The [modular monolith](/post/modular-monolith) architecture isn’t new, but it’s become more...
Aug 15, 2024
6 min
3 charts
Modular Monolith
Modular Monolith
🚀 **Understanding the Modular Monolith Architecture: A Balanced Approach** A **Modular...
Aug 11, 2024
1 min
1 chart
Transform View
Transform View
The challenge of rendering HTML markup has been present since the inception of the web. This...
Mar 3, 2024
2 min
1 chart
Model View Controller MVC
Model View Controller MVC
Model View Controller is a well known pattern, which was described in the late 1970s. Since then,...
Feb 25, 2024
5 min
1 chart
ACID in Database Transactions
ACID in Database Transactions
ACID (Atomicity, Consistency, Isolation, Durability) in IT is a properties of a business (or...
Jan 4, 2024
1 min
1 chart
Concurrency Issues and Solutions
Concurrency Issues and Solutions
Concurrency is a complex problem of the IT field. I'll try to explain some issues related to the...
Jan 3, 2024
2 min
1 chart
Domain Model vs Transaction Scripts
Domain Model vs Transaction Scripts
In the book Patterns of *Enterprise Application Architecture by Martin Fowler* two patterns are...
Dec 31, 2023
4 min
4 charts
Microservice Communication Patterns
Microservice Communication Patterns
Microservices can communicate synchronously and asynchronously. These are the main group of...
Dec 24, 2023
4 min
5 charts
CAP theorem
CAP theorem
CAP abbreviation means: Consistency, Availability and Partitioning tolerance. Theorem states,...
Dec 9, 2023
1 min
1 chart
Domain Driven Design
Domain Driven Design
Domain driven design (DDD) is a methodology for designing software with a focus on the domain....
Dec 13, 2023
2 min
5 charts
Distributed "Transactions" in Microservices
Distributed "Transactions" in Microservices
Transactions in microservices are not possible, because of the distributed nature of...
Nov 26, 2023
1 min
4 charts
Coupling & Cohesion
Coupling & Cohesion
Coupling types listed from the weakest: - Domain coupling - different modules shares the same...
Nov 19, 2023
1 min
6 charts
Application decomposition patterns in microservices
Application decomposition patterns in microservices
- Page based decomposition - each microservice has ts own dedicated page. This pattern enables...
Nov 12, 2023
1 min
4 charts
© 2025 buzzchart.info