Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modellevolution und API-Änderungen ohne Maintenance-Alptraum

Modellevolution und API-Änderungen ohne Maintenance-Alptraum

Jörg Pfründer, Peter Sauer

Eine neue Anforderung erfordert eine Änderung meines Modells. Sowohl öffentliche APIs als auch die persistenten Strukturen in der Datenbank müssen angepasst werden. Wie geht das im Rahmen von Continuous Deployment? Wie kann ich die Datenstrukturen anpassen, ohne Offline-Zeit für die Migration? Wie kann ich meine öffentliche API anpassen ohne die Implementierung der Kunden zu brechen? Wie kann ich viele dieser Schemaänderungen machen, ohne dass mir viele API-Versionen zur Wartungshölle werden?

Wir stellen verschiedene Lösungen für diese Probleme vor, die wir ausprobiert haben. Manche haben uns geholfen, andere haben uns in die Irre geführt.

http://www.bed-con.de/2017/talks/Modellevolution-und-API-Aenderungen-ohne-Maintenance-Albtraum

Jörg Pfründer

September 21, 2017
Tweet

More Decks by Jörg Pfründer

Other Decks in Programming

Transcript

  1. Agenda • Modell und Modellevolution • Persistenz (Jörg) • API

    erster Versuch (Jörg) • API aktuelle Lösung (Peter) • Zusammenfassung
  2. { "antragsteller": { "beschaeftigung": { ... }, "nachname": "Mustermann", "vorname":

    "Max", "zusatzAngaben": { "arbeitgeber": "EUROPACE AG", ... } } "immobilie": { ... }, "vorgangId": "543067" } { "antragsteller": { "beschaeftigung": { "arbeitgeber": "EUROPACE AG", ... }, "nachname": "Mustermann", "vorname": "Max", "zusatzAngaben": { ... } }, "immobilie": { ... }, "vorgangId": "543067" }
  3. @Override public void convert(DBObject dbObject) { if (readValueForKey(dbObject, "antragsteller.zusatzAngaben") !=

    null && readValueForKey(dbObject, "antragsteller.beschaeftigung") != null) { copyPropertyValue(dbObject, "antragsteller.zusatzAngaben.arbeitgeber", "antragsteller.beschaeftigung.arbeitgeber"); } }
  4. @Test public void "Arbeitgeber Zusatzangaben -> allg Antragsteller Daten"() {

    //given def source = dbObject.antragsteller.zusatzAngaben.arbeitgeber //when converter.convert(dbObject) //then assert source == dbObject.antragsteller.beschaeftigung.arbeitgeber }
  5. { "antragsteller": { "beschaeftigung": { ... }, "nachname": "Mustermann", "vorname":

    "Max", "zusatzAngaben": { "arbeitgeber": "EUROPACE AG", ... } } "immobilie": { ... }, "modelVersion": 163, "vorgangId": "543067" } { "antragsteller": { "beschaeftigung": { "arbeitgeber": "EUROPACE AG", ... }, "nachname": "Mustermann", "vorname": "Max", "zusatzAngaben": { ... } }, "immobilie": { ... }, "modelVersion": 164, "vorgangId": "543067" }
  6. DB v128 -> v129 v129 -> v130 v130 -> v131

    ... v234 -> v235 deserialize aktuelles Java-Modell internes Modell Modell wird im Backend immer in der aktuellsten Version instanziiert
  7. @Override public void convert(DBObject dbObject) { if (readValueForKey(dbObject, "antragsteller.zusatzAngaben") !=

    null && readValueForKey(dbObject, "antragsteller.beschaeftigung") != null) { copyPropertyValue(dbObject, "antragsteller.zusatzAngaben.arbeitgeber", "antragsteller.beschaeftigung.arbeitgeber"); } }
  8. Rolling Update Software
 alte Version Software
 neue Version Modellversion 163

    Modellversion 164 Node 1 Node 2 Modellversion
 164
  9. { "antragsteller": { "beschaeftigung": { ... }, "nachname": "Mustermann", "vorname":

    "Max", "zusatzAngaben": { "arbeitgeber": "EUROPACE AG", ... } } "immobilie": { ... }, "modelVersion": 163, "vorgangId": "543067" } { "antragsteller": { "beschaeftigung": { "arbeitgeber": "EUROPACE AG", ... }, "nachname": "Mustermann", "vorname": "Max", "zusatzAngaben": { ... } }, "immobilie": { ... }, "modelVersion": 164, "vorgangId": "543067" }
  10. Lazy vs Eager Version Update • alte Daten meist nicht

    mehr gebraucht, darum: • Migration auf aktuelle Version nur bei Bedarf • auch komplette Migration via Job möglich
  11. DB v128 -> v129 v129 -> v130 v130 -> v131

    ... v234 -> v235 deserialize aktuelles Java-Modell • Kein SQL-DDL • Modell-Aktualisierung ohne Downtime • Datenmigration nur bei Bedarf Immutable Code
  12. Vorgang Anlegen-API 2013 API -> v128 v128 -> v129 v129

    -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API
  13. Vorgang Anlegen-API 2013 API -> v128 v128 -> v129 v129

    -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API v9 API v10 API v12 API v13
  14. { "antragsteller": { "beschaeftigung": { ... }, "nachname": "Mustermann", "vorname":

    "Max", "zusatzAngaben": { "arbeitgeber": "EUROPACE AG", ... } } "immobilie": { ... }, "vorgangId": "543067" } { "antragsteller": { "beschaeftigung": { "arbeitgeber": "EUROPACE AG", ... }, "nachname": "Mustermann", "vorname": "Max", "zusatzAngaben": { ... } }, "immobilie": { ... }, "vorgangId": "543067" }
  15. Vorgang Anlegen-API 2013 • kleinste Änderung ->
 neue API-Version •

    Mapping Code
 schwierig zu schreiben • gleicher Mapping Code für alle API-Versionen • wenige Änderungen des internen Modells in der API API -> v128 v128 -> v129 v129 -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API v9 API v10 API v12 API v13
  16. Vorgang Anlegen-API 2013 software- implementierte Aufschieberitis API -> v128 v128

    -> v129 v129 -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API v9 API v10 API v12 API v13
  17. DB v128 -> v129 v129 -> v130 v130 -> v131

    ... v234 -> v235 deserialize aktuelles Java-Modell API -> v128 v128 -> v129 v129 -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API v9 API v10 API v12 API v13 internes Modell APIs: erster Versuch 2011 2013
  18. Neue APIs • alle Funktionen der Plattform auch via APIs

    verfügbar • API first • RESTful JSON-APIs
  19. Semantic Versioning • Non-breaking Changes • neue Attribute • gelöschte

    Attribute • Breaking Changes • Änderungen von Typen • Verschieben von Attributen • Erweiterungen von Enums neue Minor-Version neue Major-Version
  20. {
 "produktAnbieter": "ABGELEHNT",
 "kommentar": "optionaler Kommentar“,
 "ablehnungsgrund": "KEINE_ANGABE"
 } Backend

    Edge-Service Backend Request Response Ablehnungsgrund fehlt ! nächste Minor-Version: Attribut ablehungsgrund optional
  21. Backend Edge-Service Backend Request V2 Response V2 Request V1 Response

    V1 Convert Request V2 Response V2 Edge-Service
  22. Converter v1->v2 && v2->v1 V2RestController delegate; V2RestController V1RestController v2Antrag =

    delegate.getAntrag(…) v1Antrag = transform(v2Antrag) private String transform(String v2Antrag) {…}
  23. Als separater µService Als Function as a Service Version Converter

    Als Controller im Edge-Service Sehr schnell, da kein extra HTTP-Call Keine extra Contract-Tests Converter-Code ändert sich nie Höhere Latenz durch extra Call
  24. Regressionstests Swagger Spec v1.0 Swagger Spec v2.0 Swagger Spec v2.1

    Client v1.0 Client v2.0 Client v2.1 CodeGen CodeGen CodeGen Test Stage Requests Requests Requests
  25. deserialize aktuelles Java-Modell API v1.x API v2.x v1.x -> v2.x

    API-Modell -> internes Modell DB v128 -> v129 v129 -> v130 v130 -> v131 ... v234 -> v235 deserialize aktuelles Java-Modell API -> v128 v128 -> v129 v129 -> v130 ... v234 -> v235 deserialize aktuelles Java-Modell API v9 API v10 API v12 API v13 internes Modell APIs: erster Versuch neue APIs 2011 2013 2017
  26. deserialize aktuelles Java-Modell API v1.x API v2.x v1.x -> v2.x

    API-Modell -> internes Modell DB v128 -> v129 v129 -> v130 v130 -> v131 ... v234 -> v235 deserialize aktuelles Java-Modell dokumentenorientierte Datenbank immutable Code JSON-Format Semantic Versioning immutable Code