Skip to content

API Versioning

Vadym Yaroshchuk edited this page Sep 30, 2023 · 2 revisions

API Versioning Documentation

Introduction

This document provides a comprehensive overview of our API versioning strategy, designed to ensure backward compatibility and simplify the versioning process. Our API, built on an RSocket-based backend with routed, type-safe requests and models, is complemented by an SDK that implements communication between client-server with simplified migration steps if needed by abstracting our. This strategy enables smooth evolution and enhances developer experience.

Goals

  1. Ensure Backward Compatibility: To preserve existing functionality for clients using previous API versions, preventing disruptions during updates.

  2. Implement Simple and Straightforward Versioning: To make versioning intuitive, allowing developers to grasp changes effortlessly.

Versioning Strategy

1. Per-Request/Per-Entity Versioning

Our versioning strategy operates at the granular level of individual requests and entities. This approach allows for precise control over changes and ensures flexibility.

2. Handling Breaking Changes

Breaking Changes Include:

  • Removing or Deprecating Fields in Requests/Entities: Any modification that renders existing fields obsolete or deprecated.

Not Breaking Changes Include:

  • Adding New Optional Fields to Requests: Clients can choose whether to utilize new fields, ensuring backward compatibility.
  • Adding New Fields to Entities (Response Data): New fields in response entities do not break existing contracts as clients can choose to ignore them if not needed.

3. Support for Existing Methods

We commit to supporting existing methods until they are no longer in use by the majority of active clients. This ensures a smooth transition for clients during updates.

Enforcing Client Updates

To maintain a consistent user experience and ensure that clients are using compatible API versions, we employ the following mechanism:

  • Upon App Entry: Clients are required to send their API version (in semantic versioning format) via a designated request. The server responds with one of the following statuses:
    • Client Uses Latest Version: No action needed.
    • Client Has Updates Available: Clients are encouraged to update for the latest features.
    • Client Is Required to Update (Outdated): Essential updates are necessary for continued service.

Example Implementation

Request Versioning Example (Kotlin)

@Serializable
sealed class CreateTimerRequest<R : CreateTimerRequest.Result> : RSocketRequest<R> {
    @SerialName("v1")
    data class V1(
        val name: String,
        val description: String = "",
        val settings: SerializableTimerSettings.V1? = null
    ) : CreateTimerRequest<Result.V1>()

    @Serializable
    sealed class Result {
        @SerialName("v1")
        data class V1(val timerId: Long) : Result()
    }
}

Entity Versioning Example (Kotlin)

@Serializable
sealed class SerializableTimerSettings {
    @SerialName("v1")
    data class V1(
        val workTime: Duration,
        val restTime: Duration,
        val bigRestTime: Duration,
        val bigRestEnabled: Boolean,
        val bigRestPer: Int,
        val isEveryoneCanPause: Boolean,
        val isConfirmationRequired: Boolean,
    ) : SerializableTimerSettings()
}

Note
If entity evolves to new version, new version of affected requests should be created.

Internal Structure

  1. AdapterService: This component acts as an intermediary, adapting incoming requests into a format understandable by the domain layer.

  2. Domain: The domain layer serves as the core of the system, ensuring consistency to support all gateways. Evolution of the domain layer is carefully managed to maintain compatibility.

  3. SDK -> Router -> AdapterService -> Domain: Requests flow through the SDK, router, adapter service, and then to the domain layer. At each step, versioning is ensured, and compatibility is maintained.

Conclusion

By adopting this versioning strategy, we strike a balance between preserving existing functionality and accommodating new features. This approach ensures a seamless experience for both developers and end-users, making the evolution of our API smooth and predictable. The detailed consideration of breaking and non-breaking changes, coupled with the enforcement mechanism for client updates, solidifies our commitment to a robust and developer-friendly API ecosystem.