Wprowadzenie

Wersjonowanie API jest jedną z niekończących się debat w programowaniu. Niezależnie od tego ile API zbudowałeś, jakie podejście zastosowałeś, zawsze spotkasz się z głosami dobiegającymi z różnych stron. Mogą to być szeroko komentowane tematy na forach programistycznych czy też głos Twoich kolegów z zespołu.

Niektóre osoby są fanami tzw. query strings, inni preferują wersjonowanie nagłówka bądź segmentacje ścieżki URL. Na przeprowadzeniu licznych rozmów decyowaliśmy się na przygotowanie specjalnych atrybutów, obsługę nagłówków lub routing do obsługi różnych wersji naszego API.

Wraz z pojawieniem się .Net Core wersjonowanie stało się znacznie szybsze i czytelniejsze. Teraz wystarczy jedna linia kodu by wprowadzić szereg zmian. W artykule omówimy kilka najskuteczniejszych implementacji, która rozwiązują wiele problemów.

Jeżeli zdecydujesz się na zmianę sposobu wersjonowania, np. z query strings na obsługę nagłówka - możesz zrobić to naprawdę szybko.

Dodawanie wersjonowania do .Net Core Web API

W pierwszym kroku dodajemy do naszego projeku poniższą paczkę: https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Versioning
Po więcej szczegółów zapraszam pod adres: Microsoft.AspNetCore.Mvc.Versioning. Możecie tam znaleźć informacje dotyczące poprawek w kolejnych wersjach, szczegółowy opis czy kod źródłowy.

Następnie otwieramy klasę Startup.cs dodając poniższy kod do metody ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
	// Wymagana paczka: Microsoft.AspNetCore.Mvc.Versioning
	services.AddApiVersioning();
}

I tutaj pojawa się pierwsze pytanie: W jaki sposób mamy wersjonować nasze API skoro tak naprawdę nie zdefiniowaliśmy jeszcze żadnej wersji? Z pomocą przychodzi poniższy kod, który pozwala nam zdefiniować domyślną datę powstania naszego API:

public void ConfigureServices(IServiceCollection services)
{
	// Add framework services.
	services.AddMvc();
	// Wymagana paczka: Microsoft.AspNetCore.Mvc.Versioning
	services.AddApiVersioning(
		v =>
		{
			v.AssumeDefaultVersionWhenUnspecified = true;
			v.DefaultApiVersion = new ApiVersion(new DateTime(2018, 10, 3));
		});
}
Niezwykle ważną rolę odgrywa tutaj flaga ‘AssumeDefaultVersionWhenUnspecified’. Jeżli dokonamy request'a bez dodatkowych parametrów zostanie odpalona domyślna wersja naszego API, która zwróci (w moim przypadku następujący rezultat): Hello word screen

Nie jesteśmy jednak ograniczeni do powyższego przykładu. Wersjonowanie może wyglądać w następujący sposób:

  • /api/example?api-version=1.0
  • /api/example?api-version=2.0-Alpha
  • /api/example?api-version=2018-10-03.3.0
  • /api/v1/example
  • /api/v2.0-Alpha/example
  • /api/v2018-10-03.3.0/example
W następnych sekcjach pokażę różne sposoby wersjonowania.

Query string

Szybkie przypomnienie, Query string jest to informacja dodana na końcu adresu URL. Rozpoczyna się on od znaku zapytania, po którym występuje co najmniej jedna para atrybut/wartość. Jak zatem wywołać nową wersję naszego API? W pierwszej kolejności dodajemy odpowiedni atrybut do naszego kontrolera:
Przykładowy kontroler
A następnie dokonujemy wowołania metody:
Wywołanie metody
Możecie jednak zauważyć, że takie wywołanie skończyło się dodaniem cyferki do naszej klasy. Nie jest to zbyt eleganckie rozwiązanie. Z pomocą przychodzi nam jednak zarządzenie przestrzenami nazw: https://github.com/Microsoft/aspnet-api-versioning/wiki/How-to-Version-Your-Service?WT.mc_id=-blog-scottha
Takie rozwiązanie wraz z jego wywołaniem jest lepsze. W ramach naszego projektu możemy zdefiniować wiele klas ‘ExampleController’, które będą różniły się wersją i implementacją a nie samą nazwą:

namespace WebAPI.Controllers.V1
{
	[Route("api/[controller]")]
	public class ExampleController : Controller
    {
		// GET api/values
		[HttpGet]
		public string Get() => "Hello world!";
	}
}
namespace WebAPI.Controllers.V2
{
	[Route("api/[controller]")]
	[ApiVersion("2.0")]
	public class ExampleController : Controller
	{
		// GET api/values
		[HttpGet]
		public string Get() => "Hello world v2!";
	}
}

Wersjonowanie przez segmentacje ścieżki URL

Przejdźmy od razu do przykładu a potem wszystko wyjaśnimy:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class SegmentedVersioningController : Controller
{
	public string Get() => "Hello world!";
}
[ApiVersion("2.0")]
[ApiVersion("3.0")]
[Route("api/v{version:apiVersion}/helloworld")]
public class SegmentedVersioning2Controller : Controller
{
	[HttpGet]
	public string Get() => "Hello world v2!";
	[HttpGet, MapToApiVersion("3.0")]
	public string GetV3() => "Hello world v3!";
}
Niezwykle istotne jest zwrócenie uwagi na przygotowany routing. Wersja 1.0 naszego API dostęna jest pod poniższym adresem:
Segmentacja ścieżki URL
Request następuje przez wskazanie dokładnej wersji oraz nazwy naszego kontrolera. Wszystko jednak zmienia się w przypadku kolejnego przykładu. Tam mamy (dla dołożenia małej komplikacji) dwie wersje ale w routing'u nie kierujemy się już nazwą naszego kontrolera. Dlatego też wywołanie przyjmuje następującą postać:
Segementacja ścieżki URL

Wersjonowanie nagłówka

Dla wielu osób ten sposób konfiguracji jest najlepszy, jednocześnie wydaje się najtrudniejszy. Dodajemy do ConfigurationServices następujący wpis:

public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Wymagana paczka: Microsoft.AspNetCore.Mvc.Versioning
services.AddApiVersioning(
	v=>
	{
		v.ApiVersionReader = new HeaderApiVersionReader("api-version");
	});
}
Kiedy zdecydujemy się na używanie HeaderApiVersioning musimy również sięgnać po specjalne narzedzia, np. Postman. Z poziomu przeglądarki nie jesteśmy w stanie dokonać testu naszego API. Sposób deklaracji wersji nie ulega zmianie:
[Route("api/[controller]")]
[ApiVersion("2.0")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable Get()
    {
        return new string[] { "value1", "value2" };
    }
}
Z kolei próba skorzystania z Query string zakończy się niepowodzeniem:
Próba z query string
Dopiero skorzystanie z naszego narzędzia zwróci nam rezultat zgodny z implementacją:
postman

Deprecating (wycofanie wsparcia danego API)

Przejdźmy teraz do oznacznia naszego API jako przestarzałe:

namespace WebAPI.Controllers.V2
{
	[Route("api/[controller]")]
	[ApiVersion("2.0")]
	[ApiVersion("1.0", Deprecated = true)]
	public class ExampleController : Controller
	{
		// GET api/values
		[HttpGet]
		public string Get() => "Hello world v2!";
	}
}
Taki zapis pozwala nam zwrócić informację, że wersja 1.0 przestanie niedługo być wspierana. Gdzie jednak możemy zobaczyć taką wiadomość? Warto przeanalizować nagłówek:
Response Headers
Tutaj jedna cenna informacja. Może się okazać, że „zrobiłeś wszystko jak należy”, ale wciąż nie widzisz tej informacji w sekcji Response Header. Poniżej szybka podpowiedź w kodzie:
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
	// Wymagana paczka: Microsoft.AspNetCore.Mvc.Versioning
	services.AddApiVersioning(
		o =>
		{
			o.AssumeDefaultVersionWhenUnspecified = true ;
			o.DefaultApiVersion = new ApiVersion(new DateTime(2016, 7, 1));
			// Flaga wymusza informowanie o wersji API w sekcji Response Headers
			o.ReportApiVersions = true;
		} );
}

Podsumowanie

W powyższym artykule zostały opisane sposoby wersjonowania Web API. Mam nadzieje, że artykuł został napisany w prosty i przejrzysty sposób a wszystkie informacje w nim zawarte są dla Ciebie zrozumiałe i czytelne. Kod w prosty sposób możesz wykorzystać w swoim projekcie i sprawdzić, która metoda wersjonowania API jest dla Ciebie najlepsza.