Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
88 changes: 88 additions & 0 deletions API-breaking-change/answers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Handling API Breaking Changes – Answers

## 1. What Is a Breaking Change?

Any update made to an API that has the potential of breaking the current client applications, cause them to act improperly, or necessitate code changes to shift compatibility is considered a breaking change. Because breaking changes can affect user experience, interrupt live services, and necessitate immediate fixes for numerous clients, they are particularly important in real-world production systems. Breaking updates usually result in a non-backward compatible change to the sync between the API and its users.

**Examples of breaking changes for the Weather API:**

1. **Renaming or Removing Fields:**
- Changing the field name from `temperature` to `temp`, or removing the `condition` field entirely. Clients expecting the original field names will fail to parse the response or display incomplete data.
- _Example:_
```json
// Old approach:
{ "hour": 0, "temperature": "18", "condition": "Clear" }
// New (breaking):
{ "hour": 0, "temp": "18" }
```

2. **Changing Data Types:**
- Modifying the type of a field, such as changing `temperature` from a string to a number, or from Celsius to Fahrenheit without clear indication. This can break parsing logic or cause incorrect calculations in the frontend.
- _Example:_
```json
// Old approach:
{ "temperature": "18" }
// New (breaking):
{ "temperature": 18 }
```

3. **Altering the Structure of the Response:**
- Wrapping the `Weather` array in an additional object, or changing the array to an object keyed by hour. This breaks clients that expect a flat array.
- _Example:_
```json
// Old approach:
{ "Weather": [ ... ] }
// New (breaking):
{ "data": { "Weather": [ ... ] } }
```

4. **Removing or Reordering Array Elements:**
- If clients rely on the array having 24 elements (one per hour), returning fewer elements or changing the order can break UI logic or analytics.

5. **Changing Field Semantics Without Notice:**
- For instance, changing the meaning of `condition` from a string description ("Clear") to a numeric code (1 = Clear, 2 = Cloudy, etc.) without documentation or versioning.

I have been there, in my experience even though it seems like a minor change, like renaming a table field, it can cause widespread issues if not communicated and coordinated properly. I once saw a production MVP dashboard go blank for hundreds of users because one of our backend engineering team member renamed a table field without notifying the two frontend guys in the team. This actually led everyone of us to implement git-slack notification with proper commit message for PR or any change whatsoever. It is a lesson learnt that there should be no gap in communication channels between teams especially when the team is working async. In real-time systems, it’s essential to treat every change with caution and also involve all stakeholders in the process so that everyone can be in the loop.


## 2. Coordinating Across Multiple Frontends

Managing API schema changes when multiple frontend clients update at different rates is a common challenge in real-world production environments. In my experience, the safest approach is to use versioning and backward compatibility to ensure that no client is forced to update immediately. For instance, when we needed to introduce a new required field to our API, we released a new version (such as tag release, /v2/) while keeping the old version (/v1/) available for clients that hadn’t updated yet. This allowed frontend teams with different release cycles to migrate at their own pace, reducing the risk of outages. Beyond API management I have adopted this extensively and in fact there is a framework I built in GO which I often use tag release to push up.

Some practical strategies I’ve used include:
- **API Versioning:** Maintain multiple API versions (like /v1/, /v2/) so older clients continue to function while newer clients adopt the changes. Clearly communicate deprecation timelines well in advance.
- **Deprecation Notices:** Add warnings in API responses or documentation to alert clients about upcoming changes and encourage migration.
- **Feature Flags and Conditional Logic:** Sometimes, we introduce new fields or behaviors behind feature flags, allowing specific clients to opt-in before making the change default.
- **Comprehensive Documentation:** Keep changelogs and migration guides up to date, so frontend teams know exactly what’s changing and how to adapt.
- **Regular Syncs and Communication:** Schedule regular meetings or async updates ( either via Slack or email) with all client teams to discuss upcoming changes and gather feedback. Just like the way we usually jump on call during sprint planning.

A lesson I learned is that even with good documentation, some clients may lag behind. In one project, we set a hard cutoff date for an old API version, but a key client missed the communication and their app broke in production. Since then, I always make sure to over-communicate, provide clear migration paths, and monitor usage of deprecated endpoints before final removal. This approach minimizes disruption and keeps all teams aligned.


## 3. How to Catch Breaking Changes During Development

Catching breaking changes early is very very important for maintaining stability in production systems. In my experience, the most effective way to detect breaking changes is to integrate automated API layer testing and schema validation into the development workflow. For instance, in one of my previous teams, we used tools like Swagger/OpenAPI to define our API layers, and set up CI pipelines that would automatically run layer tests whenever a pull request was opened. If a proposed change broke the API layer, the build would fail and the team would be notified immediately.

Some practical steps I’ve found valuable:
- **Automated Contract Testing:** Use tools like Swagger/OpenAPI, Postman, or Pact to define and validate API schemas. Integrate these tests into CI/CD pipelines so that any breaking change is caught before merging.
- **Consumer-Driven Contract Tests:** Encourage frontend teams to contribute tests that reflect their expectations of the API. This way, backend changes are validated against real client usage.
- **Schema Validation and Linting:** Use schema validation libraries to enforce response structure and data types during development and testing.
- **Code Reviews and Pair Programming:** It makes every sense to make it a habit to review API changes with both backend and frontend engineers present, so potential breaking changes are discussed earlier within the engineering team.
- **Staging Environments:** Deploy changes to a staging environment where all clients can run integration tests before production rollout.

My experience within 7years: We once missed a breaking change because a developer updated the API response but forgot to update the OpenAPI spec. The frontend team only discovered the issue after deployment, causing a scramble to hotfix. Since then, we made it mandatory to update and validate the API spec as part of every pull request, and breaking changes have been caught much earlier. This process has saved us from several potential incidents. We often call it all hands on desk with asynchronous promotion.


## 4. Policy for Releasing Changes

In my previous teams, we established a clear and disciplined policy for managing API schema changes to ensure safety and minimize disruption. The core principle was that no breaking change should ever reach production without thorough review in fact for you to merge a PR three Engineers must review the PR, communication, and a migration plan. We formalized this with a documented process that every engineer was expected to follow.

Key elements of our policy included:
- **Change Proposals and Design Reviews:** Any schema change, especially breaking ones required a written proposal and a design review meeting with both backend and frontend leads. This ensured all stakeholders understood the impact and could raise concerns early.
- **Deprecation and Sunset Policy:** We always introduced breaking changes in stages: first, mark fields or endpoints as deprecated, then communicate a sunset date (often 3–6 months out), and only remove them after all clients had migrated.
- **Versioning:** All breaking changes were released under a new API version. We never made breaking changes to existing versions in production.
- **Automated Testing and Validation:** CI/CD pipelines enforced contract tests and schema validation for every pull request. No change could be merged without passing these checks.
- **Comprehensive Documentation:** Every change was documented in a changelog and communicated via Slack, email, and internal wikis. We also provided migration guides for client teams.
- **Release Coordination:** Major changes were coordinated with release managers and product owners to ensure proper rollout and monitoring.

Early in my career while working for Lidya Finance a fintech company, we once pushed a breaking change without a formal deprecation process, assuming all clients would update quickly. One client didn’t, and their service broke, leading to a loss of trust I attributed this to being novice at that time. Since then, I’ve always insisted on a formal deprecation and communication process, and it’s paid off—no more surprises for clients, and much smoother releases overall.
247 changes: 247 additions & 0 deletions venv/bin/Activate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)

<# Function declarations --------------------------------------------------- #>

<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values

# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}

# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}

# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}

# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}

# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}

# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}

# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}

<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"

# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue

# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }

if ($pyvenvConfigPath) {

Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath

$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]

# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}

$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}


<# Begin Activate script --------------------------------------------------- #>

# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath

Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"

# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}

# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir

# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}

Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"

# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive

# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir

if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {

Write-Verbose "Setting prompt to '$Prompt'"

# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt

function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}

# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}

# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
Loading