Backwards Compatibility
Compatibility is a sensitive topic for libraries that are widely used. The cost and effort to handle incompatible updates quickly scale with broad adoption, hence compatibility requirements need to be managed well.
Unfortunately, there is no perfect one-fits-all approach, and every solution comes with tradeoffs. Here's the model C#RA has selected, along with the logic chain why this is considered the best option.
Compatibility Philosophy
C#RA aims to minimize breaking changes while maintaining the flexibility to improve the library when necessary. The approach balances stability for existing users with the ability to deliver better solutions for new and evolving requirements.
Compatible Changes
The following types of changes are considered compatible and can be made in minor version updates:
- Bug fixes that correct unintended behavior without affecting legitimate use cases
- Additional functionality through new test blocks, test methods, or services
- Additional overloads for existing methods to support new use cases
- Additional optional parameters on existing methods with sensible defaults
These changes preserve existing code behavior and require no modifications to test programs using previous versions of the library.
Incompatible Changes
While the strong preference is to avoid them, incompatible changes may be necessary when:
- Original design choices prove inadequate for real-world requirements
- New requirements emerge that cannot be satisfied within the existing API structure
- Maintaining compatibility would compromise the quality or usability of the solution
When incompatible changes are required, they result in a new major version. The goal is to minimize such changes and introduce them only when the benefits clearly outweigh the migration effort for users.
Version Numbering
C#RA uses a Major.Minor version numbering scheme:
- Minor version increments (e.g., 1.0 → 1.1) indicate compatible additions: new features, bug fixes, and enhancements that do not break existing code.
- Major version increments (e.g., 1.5 → 2.0) indicate incompatible changes that may require code modifications in test programs.
Every release receives a version number. There are no unofficial versions, patches, or updates to existing releases. Each version is clearly identified through:
- Git tags in the repository
- Assembly attributes in the compiled code
- File names in release packages
- API methods that return the version information
Major version updates have no predefined schedule. They are introduced only when necessary, with the goal of keeping them rare.
Development Model
Development always targets the latest version of C#RA. There are no separate release branches, and features or fixes are not back-ported to previous releases.
This approach keeps the development effort focused and avoids the complexity of maintaining multiple active versions. Because C#RA is distributed as source code, users with specific requirements can apply changes to their local copy if needed.
Impact on Test Programs
The compatibility model is designed around how test programs actually use C#RA.
Version Independence
Test programs include a specific version of C#RA, typically the latest available when the project starts. Each test program operates independently with its own copy of the library.
There is no global installation of C#RA that affects multiple test programs. Different test programs can use different C#RA versions without conflict.
Update Strategy
Released test programs running in production do not require updates to C#RA as long as they function correctly. Updates can be considered when:
- The test program needs modification for other reasons
- New C#RA features would provide significant value
- Bug fixes in C#RA address issues affecting the test program
This means that incompatible C#RA updates have a limited scope. Only test programs that choose to upgrade are affected. Legacy programs continue to work with their embedded C#RA version.
Migration Effort
When a test program does upgrade to a major version with incompatible changes:
- The update is deliberate and controlled by the test program team
- Only the specific test program being updated is affected
- The scope of changes is typically smaller than in IG-XL platform updates
- Test program source code provides full visibility into required modifications
This makes the compatibility challenge more manageable compared to platform-level updates where all programs must adapt simultaneously.
Key Differences from the Previous Model
C#RA previously used a versioned interface approach where multiple API versions (V1, V2, etc.) existed side-by-side within the same release. This model has been replaced with the simpler approach described above.
What Remains the Same
- Version numbers (
Major.Minor) continue to be used - Major version increments still indicate incompatible changes
- Minor version increments still indicate compatible additions
- The commitment to minimize breaking changes continues
What Has Changed
The versioned interface indirection (V1, V2 namespaces) has been removed:
- Previous approach: Multiple API versions shipped together, accessed via
using Library_v1;orusing Library_v2; - Current approach: Single API version per release, with incompatible changes delivered through new major versions
This change simplifies the library structure and reduces maintenance overhead without sacrificing compatibility for users. Test programs still work with their specific C#RA version, but the implementation is more straightforward.
Implications
- Users will encounter incompatible updates when upgrading to new major versions
- There will not be different release branches - development follows a single path forward
- The migration effort is manageable because test programs control when and if they upgrade
- The problem is less complex than IG-XL platform updates where all programs must adapt simultaneously
The new model trades the ability to run multiple API versions in a single program for simplicity in library structure and maintenance. Since test programs embed specific C#RA versions and rarely need to mix API generations, this tradeoff favors practical usability.