简体   繁体   中英

Web Api response versioning solution?

Before you consider this a duplicate, please take a second. When I research Web Api on the matter of versioning, everything is concerned about versioned controllers and best practices around specifying version in url vs. headers.

What I'm trying to figure out is what is the best way to version the output response so I don't break the version 1 clients when I come out with version 2.

Lets say I have a continuously changing DAL for a website suite that feeds a website and other services. I'm working on a new Web Api project that should have responses that adhere to versioned schemas.

My question is, what are proven solutions/best practices for implementing versioning in Web Api projects past versioned controllers and before un-versioned DALs?

I came up with a solution that involves an extra layer of versioned repositories and an extra layer of versioned models, so the versioned controllers use versioned repositories that use versioned models. And I've setup Automapper to map between the un-versioned domain models (from the DAL) to the versioned models. But the inherit flaw of this setup is, I have to update all the maps for each new version; an exponentially growing problem.

There has to be a better way. Thanks!

In my experience, the cleanest (but not easiest) solution we found comes in 5 parts:

  1. Having an authoritative data model and a back end that is always up to date: DAL/Database/Services.
  2. Having version-specific data all understandable to the system: Multiple Data Model.
  3. Ensuring that changes between version are identified and tracked properly and that changes are reversible. Rules must be explicitly defined to handle that (that might be harder): Converters.
  4. Making the client explicitly tell you which version they use : Query Strings/Headers.
  5. Having an authoritative business logic that is always up to date but knows how to move between different data version - backward compatible : Controllers.

The Data Model we have is Supplier for example.

(1) The Back End (ie DAL/database) is at V5. The Business Logic (ie services) is also at V5.

(2) However, we need to serve client Supplier on V1, V2, V3, V4 and up to date client on V5 all at the same time. Let's make V1 to V5 of our data model: SupplierV1, SupplierV2... (you could use namespace, or other ways to differentiate them)

(3) You need to define converters and manage them. They must handle both Forward and Backward compatibility between data model versions. This could be done with a strategy pattern, lambdas, a manager with DI converters, etc. You'd need to handle V1->V2->V3->V4->V5 but also V5->V4->V3->V2->V1 .

Rules in the converters are the hardest part. Sometimes it's easy - Default Values for new fields. At other times you need to run some business logic. Sometime you need to convert/merge existing data; you have to make sure it is reversible! For example, if the values are in mixed case in V1 and you convert it to upper case in V2... you won't be able to get it back to V1, since you have no idea which characters were upper and lower case. You could handle that two ways:

  • Keep it in upper case for V1. Afterall, it's only V2 that requires it, V1 can probably work with all upper cases (obviously wouldn't work on keys).
  • Keep the old value in a field in V2. For example, if you decided to upper case the field State, you could keep an OriginalState that is mixed case and copy it to State when going back to V1.

As you can see, you need to think about those hard and it's often non-trivial.

(4) Then, in controllers, you need to know which version the client works with to do the conversion in and out of the controller when needed. For this you could use query strings, headers (X-API-Version or Accept for example, but beware some host/proxy strips some of them), post parameter, etc.

(5) Finally, when the controller receives a data model, you need to check which version the client sent (let's say V2) and upgrade it to the latest version for the back end (V5). Then use it properly, and afterward, if you need to send data back, you need to downgrade it to the client version (V2). To do this, you could do custom binding, custom request actions, custom action results, etc. For example (please do automate that):

public IHttpActionResult DoSomethingWithSupplier(JToken supplier) // Receiving Json Supplier
{
   // TODO: Get the client version type, for example in the X-API-Version header/query strings
   // (beware, some proxy/hosts strips some headers)
   // Type clientType = ...

   var clientSupplier = JsonConvert.DeserializeObject(supplier.ToString(), clientType);

   // You should probably detect latest version automatically (instead of typeof)
   var latestSupplier = VersionManager.Upgrade(clientSupplier, clientType, typeof(SupplierV5));

   outputSupplier = DoSomething(latestSupplier);

   // You should probably detect latest version automatically (instead of typeof)
   var clientOutputSupplier = VersionManager.Downgrade(outputSupplier, typeof(SupplierV5), clientType);

   return Ok(clientOutputSupplier);
}

This is a very crude way to show you the idea. This is something we did in one of our system. You could have Managers that detect types and versions and handle the conversion themselves, you could dynamically load assembly/converters with dependency injection, you could automate most of the conversion in custom bindings/request actions, and so on.

Note: There is also a part (6) you might need to consider. To actually update the client data in your database to V5, you might do it when migrating to V5 (batch migration of data), OR, do it at runtime. When you receive SupplierV1, you load that from your database (still V1 data), do the upgrade to V5, and save back the updated data, all in the Converter. This means you now have on-the-fly migration of your backend. Obviously, it's not as easy as it sounds, as you need to support both version in the same database, but might work well for you depending on the kind of changes or data you have.

I would suggest versioning your models. You can do this by namespace to keep things simple. In fact, NServiceBus does something similar for it's messaging. Here is their example: http://support.nservicebus.com/customer/portal/articles/894151-versioning-sample

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM