I'm looking for patterns or best practices for maintaining different versions of an API contract. There's lots of information on the internet about how to version API's (URL's vs. headers etc), but this question is related to the mapping from one object model to different versions of the contract types (DTO's), and for that I haven't been able to find any information really.
Imagine you have a web project with an endpoint, and a client app (Could be desktop or mobile) that communicates with the web project using that endpoint. The endpoint is only used by your client app, i.e. it's an internal endpoint, not public. The communication between the web project and the client app works by the client app sending some data to the endpoint, and the web project receives it, processes it and sends back a response object. There's a contract for how the structure of the data sent to the endpoint and sent back from the endpoint should look like. It's fairly complex in terms types of the number of object types and their nesting. You can't control when your users upgrade their client app, and it's a business requirement to be backwards compatible.
You have an object model that evolves and that's no problem since you've separated the API contract (The DTO's) and the object model. You have mapping code that maps from the object model to the contract and vice versa.
You can make non-breaking changes to the contract, but if you need to make breaking changes, you'll need a new version of the contract, and you'll need to make sure you can map from your object model to the new contract. But as you need to be backwards compatible, you also need to make sure you can somehow map from the object model to the previous version of the contract.
My question is then: How would you architecturally handle this? Do you know of any patterns or best practices that can be used for this?
The challenge is when you change the object model and create a new contract, you also need to make sure that there is some way that the old contract keeps working. One way I see this could be done, is by maintaining a mapping from the object model to each version of the contract.
Another way could be to only maintain a mapping from the object model to the latest contract, but then maintain a migration path from the different versions of the contract (So when needing to map from v3 to v1, you would map from object model to latest contract (v3), and then map to v2, and then map to v1).
Another issue to consider is the amount of duplication. When the contract has a lot of types, and you need to just delete a single field from one of the types, this is a breaking change and requires a new version. Would you copy the whole contract (All types) to a new folder in your project, and then delete the one field? This would mean you have an almost identical contract, and a lot of types would be duplicated. Duplication in this instance can be justified, but is there a better way?
Any pointers, insights and suggestions are appreciated.