Breaking & Non-breaking changes

The CustomerHub has a lot of clients that use it in production on a daily basis, often with a high load. When we are deploying new changes we can only communicate that on a broadcast basis, i.e. we are not asking anyone if they are OK with the changes or what time would work best for them. However, in order for that process to run smoothly without anyone being surprised by unexpected changes, there is a need to formally express what is and what is not considered a breaking change.

General approach

  • If non-breaking change is planned, there is no communication beforehand to anyone. It is assumed that external clients should always expect that these kinds of changes may happen anytime and they should NOT break their own internal logic.

  • If a breaking change is planned, API Versioning will be applied, meaning the existing URL and current version of the contract will be preserved and a new URL will be specified for the new version. External teams will be notified about potential decommissioning dates of old APIs.

  • CustomerHub is constantly growing and most if not all of the APIs are as generic as possible. This has following implications:

    • Most likely you are not the only client that uses a particular API. You must always assume that some other client or some other APIs may modify a subset of the data (for instance some user profile properties).

    • Additive non-breaking changes to the request or response contract of the API may happen often and without prior notification (see examples below).

Request / Response body contract changes

Non-breaking change

If a new simple or complex property is added to either request or response body, it is considered a non-breaking change. Your code should not have a strict validation of the contract.

Body before change

Body after change

{ 
title: "Some title",
count: 8,
attributes: {
customAttribute1: "ok" }}
{
title: "Some title",
author: "John Doe",
count: 8,
attributes: {
customAttribute1: "ok",
timestamp: 345345345345 }}
Justification

This is not considered a breaking change, because although two new properties were added, all other remained intact.

Breaking change

If a new simple or complex property is renamed, removed or its type has changed, it is considered a breaking change. Be careful with removal though, as it is very rare, and usually it is only a case of an object having only a subset of properties (see comments on non-breaking changes section to the left).

Body before change

Body after change

{ 
title: "Some title",
count: 8,
attributes: {
customAttribute1: "ok" }}
{ 
fullTitle: "Some title",
count: 8,
attributes: {
customAttribute1: 1234 }}

Justification

The logic did not change however the parameter is now named differently which breaks the external client's flow.

Response body varying on underlying object status

CustomerHub's underlying storage is a NoSQL CosmosDB database, schema-less by design. Some objects that APIs are accessing (especially user profiles) are updated very often and are merged from very different data sources. This means a sample user profile that you can check below in User Profile DTO example section is only an example of a profile that contains all available properties and sub-nodes. In a real-world scenario, this is often not the case and user profiles contain only a subset of them, and this state changes over time. Also, very often new users may have different subsets than users who onboarded long time ago. This means you cannot rely your logic on a presence of a particular property but rather on its value.

Response at time A

Response at time B

{
"id":"5274bb79-77ae-4a2e-bc69-191ab50070b5",
"GNObjectId":"007a9431-4b48-4f64-921e-729e2f07da32",
"CrmId":"6e2b50e3-d309-413e-ab94-b491b844373f",
"Email":"john.doe@hotmail.com",
"FirstName":"John",
"LastName":"Doe",
"DisplayName":"John Doe",
"Country":"US",
"State":"US-TX",
"TimeZoneIANA":"America/North_Dakota/Center",
"TimeZoneStandardName":"Eastern Standard Time",
"Status":"Active",
"Roles":[

]
}
{
"id":"5274bb79-77ae-4a2e-bc69-191ab50070b5",
"GNObjectId":"007a9431-4b48-4f64-921e-729e2f07da32",
"CrmId":"6e2b50e3-d309-413e-ab94-b491b844373f",
"Email":"john.doe@hotmail.com",
"FirstName":"John",
"LastName":"Doe",
"DisplayName":"John Doe",
"Country":"US",
"State":"US-TX",
"CompanyId":"68106152-3867-e911-a83c-000d3a2ddc03",
"TimeZoneIANA":"America/North_Dakota/Center",
"TimeZoneStandardName":"Eastern Standard Time",
"Status":"Active",
"Roles":[
{
"Scope":"YellowHub",
"Types":[

],
"BusinessAreas":[
"AUDIO"
],
"Role":"Premium",
"Status":"Pending",
"Attributes":{

}
}
]
}

Justification

This is not considered a breaking change:

  • CompanyId is a new property that didn't change the existing contract. It is filled in only for some users. You are free to read all properties, but you cannot have a logic of "if (CompanyId property exists) then ..." because property presence is undetermined for the reasons mentioned above.

  • Roles node is an array and may contain a variable number of elements added by different APIs. You are free to read and write all necessary properties, but you cannot have a logic of "if (Roles node exists) then ..." or "if (Roles.Count == X) then ..." because property presence is undetermined for the reasons mentioned above. See suggested solutions on how to handle these kind of situations.

  • All array type properties may have undetermined order and number of items.

Suggested solutions
  1. Your API client tools should not strictly validate the response body of the API. If a property or node is missing from the response, it just means that user doesn't have that property now, but may have it in the future.

  2. For [User Role API](/Jabra-Customer-Hub/Engagement-API/APIs-&-clients/User-Role-API) use "Attributes" section to add new custom Attribute that will help you identify if a particular "Roles" node exists or not.

  3. For array-type properties you cannot rely on the order of items in the array. If you need to find a particular one, solution depends on the use case (for example for "Roles" node you should search for a node by "Scope" property value; for "SubscriptionCateogories" node you should search for node by "Id" property value etc.)

HTTP Responses

Non-breaking change

If an API adds a new response that was not supported before and did NOT change the logic of existing responses, it is considered a non-breaking change.

Response before change

Response after change

  • HTTP 503 Service Unavailable

  • HTTP 503 Service Unavailable

  • HTTP 429 Too Many Requests

Justification

The logic of the response did not change, it is now only more precise.

Breaking change

If an API adds a new response that was not supported before and the response changed for the same set of parameters, it is considered a breaking change.

Response before change

Response after change

  • HTTP 404 Not Found - for deleted and unknown objects

  • HTTP 404 Not Found - for unknown objects

  • HTTP 410 Gone - for deleted objects

Justification

The logic of the response has changed, any external client that was expecting a 404 response has now their flow broken.

Query parameters

Non-breaking change

If an API adds a new query parameter without changing the logic of existing parameters, it is considered a non-breaking change.

URL before change

Behavior before change

URL after change

Behavior change

GET /users?orderby=date

Gets list of users, ordered by date, without filtering

GET /users?orderby=date&filter=name eq 'John'

Gets list of users, ordered by date and filtered by name. If filter parameter is not provided, no filtering is applied

Justification

The logic of the response has not changed, additional optional capabilities were added.

Breaking change

If an API renames, removes or changes the logic of an existing parameter, it is considered a breaking change.

URL before change

Behavior before change

URL after change

Behavior change

GET /users?orderby=date

Gets list of users, ordered by date, without filtering

GET /users?order=date

Gets list of users, ordered by date


Justification

The logic did not change however the parameter is now named differently which breaks the external client's flow.

URLs

Any change in URL that is not a parameter is considered a breaking change.

In the URL: https://api-dev.jabra.com/gnh/user/{id} any part cannot be changed. Only parameters can be added due to the rules described in Query parameters section above.

Non-Breaking change

Breaking change

URL before change

Behavior before change

URL after change

Behavior change

GET /users?orderby=date

Gets list of users, ordered by date, without filtering

GET /users?orderby=date&filter=name eq 'John'

Gets list of users, ordered by date and filtered by name. If filter parameter is not provided, no filtering is applied

URL before change

Behavior before change

URL after change

Behavior change

GET /users?orderby=date

Gets list of users, ordered by date, without filtering

GET /get-users?orderby=date

Gets list of users, ordered by date, without filtering (behvior was not chaned)

Justification

The logic of the response has not changed, additional optional capabilities were added.

Justification

The logic did not changed, but the base URL is different now what can cause an exception in external client's flow.

Other

Validation changes

If the request body has not changed, but the parameter's validation has changed (i.e. has become mandatory, expects a different format, etc.), this is considered a breaking change.

User Profile DTO example
{
"id":"5274bb79-77ae-4a2e-bc69-191ab50070b5",
"GNObjectId":"007a9431-4b48-4f64-921e-729e2f07da32",
"CrmId":"6e2b50e3-d309-413e-ab94-b491b844373f",
"Email":"john.doe@hotmail.com",
"FirstName":"John",
"LastName":"Doe",
"DisplayName":"John Doe",
"Country":"US",
"State":"US-TX",
"TimeZoneIANA":"America/North_Dakota/Center",
"TimeZoneStandardName":"Eastern Standard Time",
"Status":"Active",
"ContactType":1,
"CompanyId":"68106152-3867-e911-a83c-000d3a2ddc03",
"MobilePhone":"+1 800 TEST TEST",
"JobTitle":"Woodcarving tester",
"Street1":"Test Street 1",
"Street2":"Test Street 2",
"City":"Test City",
"PostalCode":"Test ZIP",
"Function":4,
"OriginatingLeadId":"80519d43-70c9-e911-a827-000d3a43d098",
"LatestReferral":"https://latest-referral.com.nz",
"OriginatingReferral":"https://test-test-man.com.au",
"Version":5,
"CrmCreatedOn":"2021-11-03T10:02:30Z",
"JchCreatedOn":"2021-11-03T10:02:30Z",
"UserTypes":[
"EPP"
],
"Brands":[
"BLUEPARROTT"
],
"Roles":[
{
"Scope":"Jsn",
"Role":"Partner",
"Types":[
"EPP"
],
"BusinessAreas":[
"AUDIO"
],
"Status":"Active",
"Attributes":{
"RmaType":"Standard",
"ServiceId":"",
"AxCustomerId":"NAC00000109",
"CompanyName":"IBM",
"JsnCountry":"dk"
}
},
{
"Scope":"YellowHub",
"Types":[

],
"BusinessAreas":[
"AUDIO"
],
"Role":"Premium",
"Status":"Pending",
"Attributes":{
}
}
],
"SubscriptionCategories":[
{
"Id":"Permissions",
"Subscriptions":[
{
"Id":"EmailPermission",
"Status":true,
"ModifiedOn":"2022-01-01T09:15:30",
"ModifiedBy":"CRM",
"Attributes":{
"DoubleOptIn":true,
"OriginalEmailPermissionDate":"2012-04-21T18:25:43-05:00",
"EmailPermissionDate":"2012-04-21T18:25:43-05:00"
}
},
{
"Id":"SubscriptionA2",
"Status":true,
"ModifiedOn":"2022-01-01T09:15:30",
"ModifiedBy":"crm-integration-inbound",
"Attributes":{
"DynamicProperty1":"value1",
"DynamicProperty2":"value2"
}
},
{
"Id":"SubscriptionA3",
"Status":true,
"ModifiedOn":"2022-01-01T09:15:30",
"ModifiedBy":"crm-integration-inbound",
"Attributes":{
"DynamicProperty3":"value3",
"DynamicProperty4":"value4"
}
}
]
},
{
"Id":"EnglishCategoryNameB",
"Subscriptions":[
{
"Id":"SubscriptionB1",
"Status":false,
"ModifiedOn":"2022-01-01T09:15:30",
"ModifiedBy":"crm-integration-inbound",
"Attributes":{
"DynamicProperty5":"value5",
"DynamicProperty6":"value6"
}
}
]
}
]
}