Laden...

Versionierung von Rest und Datenmodelle

Erstellt von Unfug vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.637 Views
U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren
Versionierung von Rest und Datenmodelle

Hallo mycsharp,

Ich stoße derzeit auf eine neue Herausforderung. Versionierung.
In erster Linie geht es um die Datenmodelle selbst.

Beispiel: ich speichere simple Datenmodelle 1:1 in einer noSql Datenbank.


User {
String Name
}

Im Laufe der Zeit kommen aber neue Properties hinzu oder werden sogar entfernt, weil sie komplett überflüssig werden.

Von Coffebeans habe ich folgenden Link bekommen

https://blog.qmo.io/ultimate-guide-to-api-design/

Der Autor beschreibt dort, dass man die neue Version parallel betreiben soll. Ich finde das macht auch Sinn, jedoch frage ich mich: wie überführe ich die existierenden Daten in das Datenformat v2?
Wenn ich mir vorstellte ich habe 25 Datenformate und alle wurden angepasst, dann graust es mir dafür eine Upgraderoutine zu schreiben.
Mit den Problem bin ich sicherlich nicht der Erste. Vielleicht habt ihr ja Erfahrungen wie man es gerade in c# machen sollte. Gerade mit Blick auf Software as a Service, wo Software sich ja täglich ändern kann, frage ich mich wie die Firmen das so sauber umgesetzt bekommen.

Danke

Nachtrag: ich würde ungern alte Zöpfe mitnehmen. Wenn zb Name bei User rausfliegt, dann würde ich ungern das Property behalten und ein nullable oder Defaultvalue machen. Wenn das geht

16.835 Beiträge seit 2008
vor 7 Jahren

API Versionierung kann man nur falsch machen.

Troy hats in Your API versioning is wrong, which is why I decided to do it 3 different wrong ways gut beschrieben.

Such Dir also aus, welchen falschen Weg Du gehen willst.
Wird auch in jeder Lektüre zum API Design genannt.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo Unfug,

Scott Hanselman hat das auch mal gut beschrieben

ASP.NET Core RESTful Web API versioning made easy

Vielleicht kannst du da auch was rausziehen.

Gruss

Coffeebean

D
985 Beiträge seit 2014
vor 7 Jahren

Von den 3 falschen Wegen klingt für mich der 1. Weg (Version in der URL) als der Richtigere von den Falschen.

Allerdings sollte man dann auch gleich richtig starten und egal ob heute eine neuere Version überhaupt angedacht ist (weil es morgen bekanntlich eh wieder anders wird) gleich mit v1 in der URL starten.

Das Hauptproblem was Troy beschreibt wäre damit vom Tisch gewesen:

But what if you don’t specify a version?

You know the bit where I said you can’t break what’s already out there? Yeah, so that means that if you do what you do now – not specify a version – then you get what you get now. In other words, no request for a specific version means you get version 1.

Ich würde auch Zugriffe ohne Versionsangabe einfach ins Leere laufen lassen "Das ist nicht die API nach der du suchst."

Das mag dann zwar die eine oder andere Regel verletzen und damit auch falsch sein, bringt aber im Vergleich zu den Alternativen (die auch falsch sind) wesentlich weniger Kopfschmerzen.

U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren

Danke.
Ich lese mir die gerade alle durch.

Was sind denn eure Best-Practice Erfahrungen?

Das eine ist die Versionierung der API (url/api/v1). Das andere die Datenmodelle, welche schon gespeichert wurden.
Wenn ich ein produktives System gemäß Saas und Continues Integration/Delivery immer aktualisiere, dann muss ich doch auch die "Alt" Daten in das neue Datenmodell migrieren.

Wenn ich z.B. den User oben erweitere, lese ich oft so eine Variante


User
{
String Name;
[Default(0)]
int Age;
}

Doch das widerspricht irgendwie meinem Bauchgefühl. Die Modelle in V1 hatten kein Age, also warum sollen sie jetzt 0 sein, wenn ich sie in V2 überführe.
Und was ist wenn Age entfernt wird? Für jede Datenbank gibt es "Workarounds". Aber ich glaube, dass vieles weit entfernt ist von der Realität. Die Datenmigration scheint mir das größte Probleme zu sein gegenüber Versionierung der API. Große Unternehmen ala Salesforce , Microsoft bekommen es aber ja auch mit ihren Online Anwendungen hin. Nur wie?

Danke

709 Beiträge seit 2008
vor 7 Jahren

Hallo,
du könntest in dem Fall den Typ von Age mit int? bzw. Nullable<int> festlegen.
Dadurch ist es möglich, dass ein Nutzer aus dem alten Bestand kein Alter hat.

D
985 Beiträge seit 2014
vor 7 Jahren

Es gibt Situationen, wo man eine alte API nur noch lesend oder auch gar nicht mehr unterstützen kann. Dann muss man sich eben davon trennen, denn für Zaubern gibt es noch keine API. Auch das ist Realität.

Ob und wie man eine Eigenschaft simulieren/ersetzen/umwandeln kann hängt davon ab, was man mit dem Wert der Eigenschaft zusichert. Pauschale Aussagen kann man hier nicht treffen.

Ein optionales Kommentarfeld, was in der neuen Version (warum auch immer) gestrichen wurde, kann man bedenkenlos für die alte Version mit null zurückliefern.

Die Eigenschaft Name, die einen unique Namen für die Entität zusichert muss da anders behandelt werden als das Kommentarfeld.

Wird in der neuen Version das Alter einfach nur als nette Information abgefragt, dann ist die Umsetzung mit null oder 0 ausreichend. Werden mit dem Alter aber z.B. bestimmte Funktionen freigeschaltet, dann kann man über die alte API nur noch die Baby-Funktionen ausführen.

Ist ein bestimmtes Mindest-Alter gefordert (muss älter als 18 sein), dann ist die alte API im Prinzip tot (s.o.)

16.835 Beiträge seit 2008
vor 7 Jahren

Best Practise is die Version in der URL; das aber halt wieder andere Nachteile hat.

Und ja, auch Nullable/Default-Werte hat seine Berechtigung.
U.a. deswegen sind schemalose Datenbanken auch so beliebt in Webumgebungen.

Nur wie?

Teilweise mit eigenen Datenbanksystemen, die wir gar nicht kennen.

VSTS basiert zum Beispiel auf einer völlig eigenen Engine.
Facebook, Amazon und Co nutzen primär NoSQL, für gewisse Dinge auch relationale DBs.

U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren

Habe die Beiträge durchgelesen. Finde die API Variante url/api/v1 und url/api/v2 auch am einfachsten. Ich kenne es so auch von anderen Projekten.

Was ist aber wenn das Datenmodell komplexer wird?

Ich habe ein sehr einfaches Datenmodell in V1.
In der neuen Version dagegen ist es so komplex, dass man es nicht mehr vergleichen kann mit V1.
Im Prinzip sind es wirklich unterschiedliche Datenmodelle, die aber den gleichen Zweck eigentlich in der Anwendung erfüllen. Nehmen wir den User.
Während in V1 ein Name ausgereicht hat, ist der User in V2 so ersetzt worden, dass nichtmal mehr Name drin vorkommt, dafür aber viele andere Propertys.


User
{
 string Hash;
 string ComplexProperty;
}

  1. Würdet ihr dann dem DatenmodellV2 eine neue Rest-Ressource zuordnen? (Geht in Richtung von Sir Rufos Antwort)
  2. Per Skript die V1 Modelle in V2 überführen?
  3. Neue Propertys Nullable machen(siehe Pinkis Antwort)?
  4. Eure Vorgehensweise?

Bei 1. sehe ich das Problem, dass die Daten V1 und V2 getrennt sind und parallel betrieben wird. Ich muss also auch unterschiedliche Businesslogiken haben. Was ist, wenn alle User auf Version 2 umgestiegen sind? Oder ich Sie dazu zwinge ? Was passiert mit den Daten aus Version 1, die noch gespeichert sind? Die Daten könnten ja noch interessant sein für eine Auswertung.

Bei 2.: Jede Aktualisierung würde ein Skript benötigen. Viel Arbeitsaufwand, erneute Fehlerquelle

Bei 3.: Möglich sehe ich aber als unschön an. Ich hätte nach einem Jahr ein riesen Datenmodell, in dem viele Objekte ggfs Nullable sind. Im Code müsste ich auf


if (User.Property ==null)

testen. Ein Graus.

Liege ich da richtig? Oder bin ich auf dem Holzweg?

Danke

Nachtrag:
Gerade den Beitrag von Dir Abt gelesen. Wie machen das denn dann kleinere Unternehmen? Millionen von Apps und Dienstanbieter, die ihre Software upgraden. Da muss es doch auch für die Datenbankebene ein Best-Practice geben oder?

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo Unfug,

du hast doch bei der Schreibweise

[ApiVersion( "1.0" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
    public string Get() => "Hello world!";
}
 
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
    [HttpGet]
    public string Get() => "Hello world v2!";
 
    [HttpGet, MapToApiVersion( "3.0" )]
    public string GetV3() => "Hello world v3!";
}

die Trennung schon gemacht und kannst in den Methoden mit deinen gewollten Klassen arbeiten. Oder schnalle ich etwas nicht?

Gruss

Coffeebean

U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren

Hallo Coffeebean,

doch das finde ich auch gut so. Die Variante würde ich auch für die API bevorzugen.

Frage wäre nur noch. Wie unterscheide ich die Klassen.
Nutze ich ein Datenmodell mit Nullable Propertys oder baue ich eine UserV2.cs und UserV3.cs.
Ich möchte die alten Daten aus UserV1.cs nicht verlieren.


[ApiVersion( "1.0" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class UserController : Controller {
    public string Get() => UserList<UserV1>();
}

[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class User2Controller : Controller {
    [HttpGet]
    public string Get() => UserListV2<UserV2>();

    [HttpGet, MapToApiVersion( "3.0" )]
    public string GetV3() => UserListV3<UserV3>()
}

Also Best-Practice auf Datenmodell/Datenbankschema Ebene.
Es geht auch nicht um Richtig oder Falsch (so wie ich das ja aus den Links gelernt habe), sondern um eure Erfahrung 😁

D
985 Beiträge seit 2014
vor 7 Jahren

Wenn es absolut keine Chance gibt die vAlt nach vNeu zu mappen, dann ist vAlt in dem Moment gerade gestorben.

Für jede API-Version erstelle ich in einem eigenen Namespace (com.example.foo.api.v2) immer komplett alles neu (was kopiert werden kann, wird natürlich kopiert, kommt aber in den neuen Versions-Namespace).

Spricht man also mit der API v2 dann läuft auch nur Code der ausschließlich zu dieser API gehört.

Am einfachsten von der Strukturierung geht das natürlich mit einer eigenen Klassenbibliothek pro API

U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren

Genau solche Antworten hatte ich noch gesucht. Deine Variante hatte ich z.B. noch gar nicht im Blick. Alles zu kopieren und Namespace erweitern auf V2.

Gibts weitere Umsetzungsideen?

16.835 Beiträge seit 2008
vor 7 Jahren

Sowas steht aber in Guidelines....

Normalerweise gibt es immer eine Versionierung der url /v1, /v2, /v3... sowie ein alias /latest das jeweils auf die neueste zeigt.
Jede Version ist dabei eine Instanz! Nie mehrere Versionen aus einer Instanz liefern!

Aus Sicht des Application Lifecycles ist es falsch, dass es mehrere Namespaces gibt.
Die Versionierung der API ist eine fixe Version im ALM-Cycle.

Änderungen an der API gibt es dann nür über entsprechende Hotfixes, die dann auch zurück in den Dev Branch comitted werden, sodass diese dann in den nächsten Versionen auch enthalten sind.
Software Entwicklung beginnt schon bei der Quellcode-Verwaltung.

Schau Dir dazu den Git Flow an.

A successful Git branching model direkt das erste Bild.

P
1.090 Beiträge seit 2011
vor 7 Jahren

Hallo Unfug,

zu der Versionierung der Web Apis wurde ja schon einiges gesagt.
Und ich würde mich da Abts letzter aussage anschließen, schau da du es in dem ALM-Cycle unterbringst.

Was die Persistenz (Datenbank) angeht, ist noch nicht viel gesagt worden.
Mit NoSQL Datenbanken kenne ich mich da nicht so wirklich aus. Es kommt aber ja auch bei SQL Datenbanken vor.

Hier ist dann immer die Frage was du änderst.
Spalten hinzuzufügen macht meistens in der Abwerte Kompatibilität keine Probleme. Dafür können dann Defoult Values gesetzt werden.

Spalten wegnehmen schon.
Um das möglichst zu vermeiden, solltest du YAGNI befolgen.
Wenn du nur die Sachen/Funktionen zu Anbietest, die wirklich gebraucht werden. Ist die Wahrscheinlichkeit geringer, das sie in Zukunft wegfallen.

Wenn es doch dazu kommt, musst du eigentlich im Einzelfall entscheiden wie du damit umgehst.
(Im DAL kann man da schon ein bisschen was machen oder man kann für die alte Version eine Datenbank haben und sie mit der neuen Synchronisieren u.s.w.)

Im Zweifel wird dir da aber nichts anders Überzubleiben, als die alte Version sterben zu lassen.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

U
Unfug Themenstarter:in
133 Beiträge seit 2006
vor 7 Jahren

Habt alle vielen Dank.
Das bringt mich aufjedenfall weiter. Ich werde versuchen mich an den Tipps zu orientieren.
Berichte dann wie erfolgreich ich damit war.