For me #1 would be to add a version to any data format or communication
protocol. If you want to know how hard not doing so can bite, don't look further than Git and it's tourcherous migration from sha1.
Why? We use protocol buffers for communication between services, evolve them all the time, and don't use version numbers. Any new functionality is enabled by booleans or optional fields in the proto. The approach works fine, I haven't noticed any problems in years.
I've started to lean more and more against versioning as time has gone on. The problem is that it's an invitation for users to stay on a specific version indefinitely. Which makes your life much harder as you have to ensure all of these old versions continue to work while backporting critical fixes. I prefer a continuous deprecation cycle where you support things in parallel for a while, then add deprecation warnings, and finally remove the functionality. How fast this happens depends on how critical the issue is.
Versions make sense in a lot of areas where stability is needed. But they should be seen as issuing binding contracts to your users. You should spend a lot of time thinking about what the terms and conditions are before doing so.
> With each change, we explicitly assign a distinct version to serializers.
> We do this independent of source code or build versioning. We also store the serializer version with the serialized data or in the metadata. Older serializer versions continue to function in the new software. We find it’s usually helpful to emit a metric for the version of data written or read. It provides operators with visibility and troubleshooting information if there are errors. All of this applies to RPC and API versions, too.
Backwards compatibility and the ability to roll out new changes (that can co-exist with current data) are the primary drivers.