Cadwyn API Versioning for FastAPI

repository·main·Indexed 43 days ago

https://github.com/zmievsa/cadwyn

A production-ready API versioning library for FastAPI that implements a Stripe-like system. It allows developers to maintain a single 'latest' implementation while automatically generating responses for older versions through version-specific migration modules. Key features include VersionedAPIRouter, generate_versioned_models, and a CLI for rendering versioned Pydantic models and enums.

Tokens
27.4K
Snippets
79
Records
119
Agent score
54%

What's inside Cadwyn

  1. Overview of Cadwyn

    main

    Cadwyn is a production-ready, community-driven API versioning library designed specifically for FastAPI. It implements a Stripe-like versioning approach where you maintain implementation logic for the newest API version, and older versions are automatically generated.

    This allows you to:

    • Support multiple API versions over a long period.
    • Automatically backport features and bugfixes to older versions.
    • Keep business logic simple and decoupled from versioning concerns.
    • Encapsulate backward compatibility changes in small, independent 'version change' modules.
  2. The core philosophy of Cadwyn API versioning

    main

    Cadwyn is an API versioning framework designed to eliminate code duplication. Unlike traditional approaches that require duplicating databases, business logic, schemas, or endpoints for every new version, Cadwyn allows you to maintain a single source of truth for your application's latest state.

    Key Mental Model:

    • Single Source of Truth: You define your database, business logic, routes, and schemas exactly once (representing the latest version).
    • Runtime Duplication: Cadwyn handles the duplication of endpoints and schemas at runtime. This means your codebase does not accumulate technical debt as you add versions.
    • Version Changes DSL: Instead of writing new code for old versions, you use a Version Changes DSL to describe how to transform the current application state back to a previous version.
    • Scalability: This approach enables the effortless maintenance of hundreds of API versions because the effort scales with the complexity of the changes between versions, rather than the total number of versions.
  3. Understand API versioning approaches in Cadwyn

    main

    Cadwyn is designed around the Migration-based response building approach to API versioning. To use Cadwyn effectively, it is helpful to understand the three conceptual models of API versioning it compares against:

    1. Versioning a proxy (Application Versioning): Separate apps for each version. High isolation but high duplication and poor user experience for switching versions.
    2. One router, versioned controllers (Logic Versioning): Shared data layer but duplicated business logic and controllers. Easier for clients to migrate but becomes a maintenance nightmare as versions increase.
    3. One router, shared controllers (API Versioning): Single business logic and data layer, with a specialized layer that builds versioned representations (responses). This is the approach Cadwyn automates.

    Cadwyn specifically implements the most scalable subtype of the third approach: Migration-based response building.

  4. How API version context variables work

    main

    Cadwyn uses a Python contextvars object to store the current API version. This allows Cadwyn to perform data conversion and version checks when dealing with side effects.

    By default, Cadwyn uses its own internal context variable, but you can provide a different compatible contextvar when initializing your VersionBundle.

  5. How API versioning works in Cadwyn

    main

    Cadwyn implements an API versioning strategy inspired by Stripe. Instead of duplicating your entire application, endpoints, or schemas for every version, you maintain a single HEAD version (the latest version of your code/logic).

    To support older clients, you define version changes (also known as version gates) that describe how to transform the HEAD version's data back into the format of a previous version. This allows you to:

    • Keep business logic and routes focused on the latest version.
    • Support multiple long-living API versions without code duplication.
    • Automatically generate OpenAPI schemas that include all supported versions.

    Important Constraint: API versioning is for your interface, not your data. Your versions must remain data-compatible. If a change in the HEAD version makes it impossible to represent a user in an older version (e.g., deleting a mandatory field), older versions will encounter ResponseValidationError when trying to serve that data.

  6. Understand the Cadwyn versioning methodology

    main

    Cadwyn uses a methodology centered around 'version changes' or 'compatibility gates' to manage schema and logic evolution. Instead of arbitrary version numbers, Cadwyn focuses on atomic, independent differences between versions.

    Key principles of this methodology include:

    • Atomic Changes: Each version represents a specific, independent difference from the previous one.
    • Breaking Changes Only: A new version is created only when breaking changes occur.
    • Data Compatibility: Versions must always remain compatible in terms of data.
    • Minimal Logic Impact: Versions should have little to no effect on the core business logic.
    • Backporting: Any backward-compatible features must be backported to all compatible versions.
    • Avoidance: Creating new versions is a last resort and should be avoided whenever possible.
  7. How Migration-based response building works

    main

    Migration-based response building is the core philosophy implemented by Cadwyn. It aims to minimize duplication by using a specialized Domain Specific Language (DSL) to handle API evolution. Instead of manually duplicating builders for every version, you define:

    • Schema migrations: For changes in request and response schemas.
    • Compatibility gates: To migrate data in accordance with schema changes.
    • Route migrations: To define how routes are added, changed, or deleted across versions.

    This approach allows you to maintain a single source of truth for business logic while providing automated, backwards-compatible representations for a large number of API clients (similar to the patterns used by Stripe, LinkedIn, and Intercom).

  8. How API version waterfalling works

    main

    When api_version_format is set to date, Cadwyn supports waterfalling. If a requested date does not exactly match any defined version, Cadwyn routes the request to the closest earlier version (the highest version that is still lower than the requested date).

    Rules of waterfalling:

    1. An exact match is always preferred over a partial match.
    2. A request is never matched to a higher-versioned route.
    3. If versions are 2022-01-02 and 2022-01-05, a request for 2022-01-03 will be routed to 2022-01-02.
  9. Evaluating a versioning framework design

    main

    When designing or choosing a versioning framework, evaluate it based on the friction involved in the lifecycle of an API version. A good framework balances the ease of creating new versions against the ease of deleting old ones and the risk of accidental side effects.

    Key evaluation criteria include:

    • Creation vs. Maintenance Complexity: If creating a new version is too simple (e.g., just copying a directory), you may be hiding complexity that makes long-term maintenance or version removal difficult.
    • Deletion Cost: A framework should allow you to clean up old versions cheaply. Avoid designs where new versions inherit from or include old versions (e.g., via routes or class inheritance), as this creates interdependencies that make deleting a single version dangerous and complex.
    • Visibility of Differences: The framework should make it easy to see exactly what changed between API versions.
    • Duplication Trade-offs: Minimize manual duplication to reduce maintenance, but be aware that reducing duplication often increases the risk of breaking older versions when making changes for newer ones.
    • Data Versioning Protection: A robust framework should make it difficult to accidentally version data (or conversely, should provide clear mechanisms to handle it) to prevent data integrity issues.
  10. Define breaking changes with VersionChange

    main

    A VersionChange class describes an atomic group of business capabilities altered in a version. You only need to create a VersionChange for breaking changes. Adding new endpoints or fields to a response schema is not a breaking change and does not require a migration; such changes are automatically added to all versions.

    Naming Conventions

    • VersionChange.__name__: Use a clear, verb-based name that describes the breaking change (e.g., RemoveTaxIdEndpoints or RenameCreationDatetimeAndUpdateDatetimeToCreatedAtAndUpdatedAt). Avoid generic names like RefactorFields.
    • VersionChange.description: Use the class docstring to provide a human-readable description of what happened and why. This docstring is used by Cadwyn as the name and summary for clients.
    versions = VersionBundle(
        Version("2023-05-09", ChangeCreateLogic, AddRequiredFields),
        Version("2023-04-02", DeleteEndpoint, ChangeFields, RenameFields),
        Version("2023-02-10", RenameEndpoints, RefactorFields),
        Version("2022-11-16"),
    )
  11. Why enum expansion can be a breaking change for responses

    main

    Expanding an enum is often a breaking change for responses because of how unmarshalling works. If a client expects a specific set of types (e.g., Array<Euro | Dollar>), adding a new type (e.g., Lari) makes the response Array<Euro | Dollar | Lari>.

    Mathematically, Array<Euro | Dollar | Lari> is not a subtype of Array<Euro | Dollar>. Therefore, an API client attempting to unmarshal a list containing the new enum value into its existing schema will fail. In this sense, extending an enum is equivalent to changing a field from a single type to a union of types (e.g., int to int | str), which is inherently breaking.

  12. Perform schema migrations in Cadwyn

    main

    Schema migrations in Cadwyn are used to manage breaking changes in OpenAPI schemas and their initial validation. These migrations affect how older API versions are handled, ensuring that incoming requests for older versions are correctly transformed before being validated against the latest (HEAD) schemas.

    Important Note: Migrations only affect OpenAPI schemas and initial validation. All incoming requests are eventually converted to your HEAD schemas. You only need a migration if the change is a breaking change for your users.