Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 844
Levitikon Avatar asked Mar 25 '13 14:03

Levitikon


People also ask

What is the most common method of versioning a REST API?

There are several methods for managing the version of your API. URI path versioning is the most common.

Is REST web service supports versioning?

REST API Versioning. To manage this complexity, version your API. Versioning helps us to iterate faster when the needed changes are identified in the APIs. Change in an API is inevitable as our knowledge and experience of a system improve.

Do you really need API versioning?

Most APIs don't need versioning; they need the ability to support compatible changes over time (not as flashy a term, right?). There are plenty of examples of technology supporting compatible changes like TCP/IP, HTTP, and HTML. Most programming languages do it, too.

Is versioning possible in asp net web API?

Since ASP.NET Core 3.1, you have the ability to version your APIs using a Microsoft NuGet package. Learn how to use this package to version your APIs easily.


1 Answers

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 (i.e. DAL/database) is at V5. The Business Logic (i.e. 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.

like image 85
Karhgath Avatar answered Sep 20 '22 12:09

Karhgath