Files
cbsnexora-backend/fineract-doc/src/docs/en/chapters/architecture/api-backward-compatibility.adoc
T

191 lines
7.5 KiB
Plaintext

= API Backward Compatibility
== Overview
Apache Fineract enforces API backward compatibility using https://github.com/docktape/swagger-brake[swagger-brake], an automated tool that compares OpenAPI specifications between the base branch and a pull request to detect breaking changes. This ensures that existing API consumers are not broken when new changes are deployed.
The check runs automatically on every pull request via the `verify-api-backward-compatibility.yml` GitHub Actions workflow.
== How It Works
The workflow follows these steps:
. **Generate baseline spec** — Checks out the base branch (e.g. `develop`) and runs `./gradlew :fineract-provider:resolve` to generate the current OpenAPI specification.
. **Generate PR spec** — Checks out the PR branch and generates its OpenAPI specification.
. **Sanitize specs** — Patches known issues in the generated specs (e.g. missing `schema` entries in `requestBody` content) to prevent false positives.
. **Compare** — Runs `checkBreakingChanges` via the swagger-brake Gradle plugin to compare old vs new specs.
. **Report** — If breaking changes are found:
** A deduplicated summary table is written to the GitHub Actions Step Summary (visible on the workflow run page).
** A comment is posted on the PR (when token permissions allow).
** The full JSON report is archived as a build artifact.
** The workflow **fails**, blocking the PR.
== Breaking Change Rules
swagger-brake detects the following categories of breaking changes:
=== Endpoint Rules
[cols="1,3", options="header"]
|===
| Rule | Description
| R001 | A stable (non-beta) API was changed to beta
| R002 | An API path was deleted
|===
=== Request Rules
[cols="1,3", options="header"]
|===
| Rule | Description
| R003 | A request media type (content type) was removed
| R004 | A request parameter was deleted
| R005 | An enum value was removed from a request parameter
| R006 | A parameter location changed (e.g. `query` to `header`)
| R007 | A parameter was made required
| R008 | A parameter type was changed
| R009 | An attribute was removed from a request body schema
| R010 | A property type was changed in a request schema
| R011 | An enum value was removed from a request body schema
|===
=== Response Rules
[cols="1,3", options="header"]
|===
| Rule | Description
| R012 | A response code was deleted
| R013 | A response media type was removed
| R014 | An attribute was removed from a response schema
| R015 | A property type was changed in a response schema
| R016 | An enum value was removed from a response schema
|===
=== Constraint Rules
[cols="1,3", options="header"]
|===
| Rule | Description
| R017 | A request parameter constraint was tightened (covers `maxLength`, `minLength`, `maximum`, `minimum`, `maxItems`, `minItems`, `uniqueItems`)
|===
== Gradle Configuration
The swagger-brake plugin is configured in `fineract-provider/build.gradle`:
[source,groovy]
----
apply plugin: 'com.docktape.swagger-brake'
swaggerBrake {
newApi = "${project.buildDir}/resources/main/static/fineract.json"
oldApi = findProperty('apiBaseline') ?: "${projectDir}/config/swagger/fineract-baseline.json"
outputFormats = ['JSON']
outputFilePath = "${project.buildDir}/swagger-brake"
deprecatedApiDeletionAllowed = true
strictValidation = false
}
----
=== Configuration Options
[cols="2,1,4", options="header"]
|===
| Option | Default | Description
| `newApi` | — | Path to the new (PR branch) OpenAPI spec. Generated by the `resolve` task.
| `oldApi` | — | Path to the baseline OpenAPI spec. Provided via `-PapiBaseline` in CI, or falls back to a local file.
| `outputFormats` | `['STDOUT', 'HTML']` | Report formats. We use `['JSON']` to avoid STDOUT spam and parse the report programmatically.
| `outputFilePath` | `build/swagger-brake` | Directory for generated reports.
| `deprecatedApiDeletionAllowed` | `true` | When `true`, removing a deprecated endpoint is NOT a breaking change.
| `strictValidation` | `true` | When `false`, schemas without an explicit `type` field log a warning instead of failing. Set to `false` for Fineract because the generated spec has many type-less schemas.
| `excludedPaths` | `[]` | List of path prefixes to skip (e.g. `['/v1/smscampaigns', '/v1/internal']`). Useful for excluding endpoints undergoing cleanup.
| `ignoredBreakingChangeRules` | `[]` | List of rule codes to suppress entirely (e.g. `['R001']`).
| `betaApiExtensionName` | `x-beta-api` | Vendor extension name for marking beta APIs. Beta endpoints can be freely modified without triggering violations.
| `maxLogSerializationDepth` | `3` | Controls nested object serialization depth in logs (range 1-20). Increase if you see `StackOverflowError` from circular schema references.
|===
== Running Locally
To run the check locally, you need a baseline spec to compare against:
[source,bash]
----
# 1. Generate the baseline from develop
git stash
git checkout develop
./gradlew :fineract-provider:resolve --no-daemon
cp fineract-provider/build/resources/main/static/fineract.json /tmp/baseline.json
git checkout -
git stash pop
# 2. Generate your current spec and compare
./gradlew :fineract-provider:checkBreakingChanges \
-PapiBaseline="/tmp/baseline.json" \
--no-daemon
----
The JSON report is written to `fineract-provider/build/swagger-brake/`.
== Handling Breaking Changes
=== Intentional Breaking Changes
If your PR intentionally introduces a breaking API change (e.g. removing a deprecated field):
. The workflow will fail and report the violations.
. Document the breaking change in the PR description with justification.
. A committer will review and approve the PR with the understanding that the API contract is changing.
=== Excluding Paths Under Cleanup
If you need to fix incorrect API annotations on endpoints that are not yet stable, use `excludedPaths` to temporarily exclude them from checking:
[source,groovy]
----
swaggerBrake {
excludedPaths = [
'/v1/smscampaigns',
'/v1/email',
]
}
----
Path exclusion is **prefix-based** — excluding `/v1/smscampaigns` will skip all paths starting with that prefix.
Remove the exclusion once the cleanup is complete.
=== Marking Endpoints as Beta
For endpoints that are experimental or under active development, mark them as beta in the Java code:
[source,java]
----
@Operation(
summary = "...",
extensions = @Extension(
properties = @ExtensionProperty(name = "x-beta-api", value = "true")
)
)
----
Beta endpoints can be freely modified, created, or removed without triggering violations. Promoting a beta endpoint to stable (removing the extension) is also non-breaking. However, demoting a stable endpoint to beta **is** a breaking change (R001).
== Report Format
When breaking changes are detected, the workflow produces a deduplicated summary table:
[cols="1,2,1,3,1", options="header"]
|===
| Rule | Description | Detail | Affected endpoints | Count
| R014 | Response attribute removed | `totalOverpaid` | `GET /v1/loans`, `GET /v1/loans/{loanId}`, `GET /v1/loans/external-id/{loanExternalId}` | 3
|===
The deduplication groups violations by rule code and affected attribute, collapsing multiple endpoint occurrences into a single row. This is important because a single schema change (e.g. removing a field from a shared response type) can generate dozens of raw violations — one per endpoint that uses that schema.
== Tool Reference
* **Tool**: https://github.com/docktape/swagger-brake[swagger-brake] v2.7.0
* **Gradle plugin**: `com.docktape.swagger-brake`
* **Documentation**: https://docktape.github.io/swagger-brake/
* **License**: Apache 2.0