Repository navigation
Vortrag I Mitschrift
Oliver Drotbohm
DDD + REST
Ohne gutes Domain Model nur schwer gute REST-API
speakerdeck.com/olivergierke
- REST BEYOND THE OBVIOUS
- DDD & REST
Roy Thomas Fielding -> Was für fundamentale Prinzipen in HTTP -> Architekturstil -> System aufgebaut aus vielzahl von Einzelsystemen eröffnet viele Gestaltungsmöglichkeiten, Regeln werden aufgestellt (Constraints), schränken Gestaltungsmöglichkeiten ein -> Welche Effekte auf Gesamtarchitektur?
Microservices System von System Mehrere Systeme interagieren um Mehrwert zu schaffen -> sollen lose gekoppelt, evolvabel sein parallele zum Internet: Internet fundamental Vorgesehen das Systeme auftauchen, ausfallen usw. -> Skalierbar
GITHUB: odrotbohm spring-hateoas-sample - Beispielcode
Beziehung zw. zwei Aggregaten: Suboptimal: Beziehung zu anderem Aggregat Strenge Konsistenz nur innerhalb eines Aggragats -> Beziehung zw. zwei Aggreagaten immer nur ID-Beziehung (üblicherweise)
Repositorys sind sicht auf Collection von Aggregaten, Aggregate können gefunden werden (alle, alleMitWert, o. ä.)
ANMERKUNG: Codebeispiele überlicherweise immer zu sehr speziellem Thema, alle anderen Sachen so modelliert das sie möglichste wenig Reibung erzeugen!
API sollte qualifizert werden, meint man WEB-API o. Java-API o. ä.
Rest-HTTP eine möglichkeit, andere bspw. Messaging (Event), HTML Seite (Servergeneriert)
REST -> HTTP Requests (Get, usw.) an Server, Spring Boot bringt Server mit
HTTP (Client) -> Server (wie kann Code geschrieben werden der auf diese Anfrage reagieren kann) localhost:8080/customers = URI (Unique Resoure Identifier) -> in HTTP alles Resourcen (identifizierbar über URI) z. B. Liste von Kunden (/customers) URL (unique Resource locater -> referenzierbar, beispiel:isbn nicht direkt referenzierbar (urn))
Fehleindruck: Möglichst viel Zeit um zu überlegen wie die URIs aussehen -> Es geht darum verständlich zu sein
SPRING: über Stereotypannotation Klasse dem System bekanntmachen Mappingannotationen: Binden von Klasse an bestimmte URI Anfrage -> SpringBoot Server (Controllerklasse sucht nach passender Klasse und Methode) Controller kann Java-API verwenden, Rückgabe wieder übersetzt in Repräsentation -> Repräsentation heißt nicht das Objekt selbst, sondern eine Beschreibung -> Repräsentation (JSON, Bild, o. ä.) Content Negotiation evtl. möglich (Teil von HTTP)
Wichtiger Teil von HTTP-API: Was steckt in Repräsentation, Welche Metadaten die der Client nutzen kann um sinnvolle Dinge zu tun
SpringMVC: Framework serialisiert Objekte automatisch in HTTP Repräsentation API bildet Sprache der Domäne ab: HTTP-Status Code können gesetzt werden Header können gesetzt werden
URI Templates {id} -> "/customer/{id}"
Anmerkung Java Optional: Ding kann da sein oder nicht
Objektreferenzen in Repräsentationsklassen können rekursiv zu Problemen führen: daher besser ID -> Design der Repräsentation ungefähr die Grenzen der Aggregate -> macht programmierung umständlicher: anstatt foo.getCustomer() repo.findCustomerById(...)
ANMERKUNG: DDD geht es hauptsächlich darum Dinge explizit zu machen -> falls etwas implizit ist explizit machen (PLZ ist kein Integer o. String, sondern explizites Konzept)
Grenzen von BoundedContext behalten, kurzfristig mehr Aufwand, langfristig Wartbarer Konzept durch Typsystem explizit gemacht... (Beispiel)
@GetMapping: der Trick an REST: Domäne sehr fachlich orientiert, individuelle Konzepte brauchen welche Aktionen REST -> es gibt diese Resourcen und diese unterstützen alle das gleiche Superset an Operation (HTTP-Operationen) unterscheiden sich hauptsächlich in: Idempotenz Es gibt für jede Methode die entsprechende Mapping-Annotation
Idempotenz: Seiteneffekte und Wiederholbarkeit Hochgradig verteiltes System mit Netzwerk (Anfragen können nicht erfogreich sein, verloren gehen) Get-Request: Seiteneffekt frei, Resource verändert den Zustand nicht durch Request (Log o. zählen ist möglich) Seiteneffekt bezieht sich immer auf die Resource selbst Idempotenz: "egal wie oft Operation ausgeführt wird der Effekt ist der selbe als wenn die erste Operation erfolgreich gewesen wäre" Idempotenz heißt nicht es muss das gleiche passieren, sondern das Ergebnis muss das selbe sein. DELETE /customer/4711 - nicht angekommen DELETE /customer/4711 - 202 - No Content DELETE /customer/4711 - 404 - Not Found --> Ergebnis ist immer das gleiche Resource ist weg Dagegen: POST /customers {"firstname" : "Ollie"} - 201 Created [Header: Location: /customers/4712] kann nicht mehrfach gemacht werden um das selbe Ergebnis zu erhalten: 2ter Request würde evtl. Duplikat erzeugen => POST ist nicht idempotent GET ist Seiteneffektfrei im Bezug auf die Resource, bei absichtlicher Verletzung (Zähler o. ä.) gehen andere davon aus, dass die Standards eingehalten werden
Unterschied Rest - HTTP: Rest sagt nur es gibt für eine Resource die gleichen Standadisierten Methoden für die festgelegt ist ob diese Idempotent sind (nicht zwingend HTTP) Http festgelegte Methden Webdav gleiche HTTP Methoden + weitere (aber eben nicht HTTP)
Request ist Zustandlos
!Langlaufender Prozess: initialer Post erzeugt Request Put Update ... Abschluss Delete Request
restalk-patterns.org/conditional-update-large-resource.html <- u. a. Patterns
REPRÄSENTATIONS DESIGN:
laut REST hat können wir Resourcen identifizieren mit URIs - Mechanismus besteht? Wie passen IDs da hinein -> Vermeiden das IDs (z. B. DB PK) in die Repräsentation leaken -> Welche Felder sollen in der Repräsentation enthalten sein: 2 Ansätze: -> Einführung von Datentransferobjekten, Beschreibt Repräsentation, Daten werden aus Resource hineinkopiert, dann wird Datentransferobjekt serialisiert -> Annotation @JsonIgnore -> Framework spart Id bei repräsentation aus.
IDs zu entfernen gute Sache: Warum? Updatefall (PUT) 1. Resource holen (GET), Felder anpassen, Zurückschicken (PUT) -> Problem geändertes ID Feld, potentielles Mismatch [ PUT /customer/1 - { "id":2 } ]
Beziehung zw. Order und Customer Jackson rendert Baum soweit es sehen kann (fundamental, im Detail beeinflussbar) -> zw. Aggregaten lieber nur Id
Schlecht -> Baum { date : "20181102" customer : { firstname ... } }
Okay kein Baum, aber schlecht anderes Identifikationskonzept interne ID { id : 12 date : "20181102" customerId: 4712 }
Anmerkung: Hemdsärmeliger aber falscher Ansatz: URIs selbst im Client zusammenbauen, URI Blackbox für Client, der Client kann sie irgendwo erhalten oder eben nicht Client trifft keine Annahme über die Struktur der URIs
=> statisches Hypermedia Spring Data REST, Dynamisches Hypermedia (Suche mit Link auf einzelnes Objekt) Idee: { id : 12 date : "20181102" customerId: /customer/4712 }
Bisher nur JSON als Repräsentationsformat Problem ähnlich wie XML: Ohne zusätzliches Wissen über das Applikationsformat nicht anwendbar Date nur String usw.
Referenzen zu anderen Resourcen: JSON kennt kein "Link"-Konzept viele verschiedene Wege um Links in JSON zu beschreiben Verbreitetes Format: HAL - Hypertext Application Language (JSON/XML)
_links: { "customer" : { <- Link Relations "href" : "/customer/4712" } } Objekt _links mit Objekt für FeldKonvention: Link Relations vordefiniert iana.org/assignments/link-relations/link-relations.xhtml
{ id : 12 date : "20181102" lineItems: [ { productName: "iPad" price: 799 } ] _links: { "customer" : { <- Link Relations "href" : "/customer/4712" } } }
Wie zwischen unterschiedlichen Systemen? Ein System entdeckt die Resourcen { id : 12 date : "20181102" orderNumber : "1234" lineItems: [ { productName: "iPad" price: 799 } ] _links: { "customer" : { <- Link Relations "href" : "/customer/4712" } } }
und erhält dann vom Server die Möglichkeit eine Order anhand ihrer Nummer zu finden (keine Datenbankid, auch wenn unique)
Möglichkeit Bestellung anhand orderNumber zu finden:
Unterschied zw.: /orders + $id <- Selbstgebaut Risiko orders/{id} -> expand ("id": 4711) <- Server forder explizit auf, Änderungen besser möglich
Verwendung von Server URIs ermöglicht einfaches aufsplitten der API (z. B. Monolith auf mehrere Systeme) -> der Client muss nicht geändert werden da URIs nur Blackbox -> der Client geht nicht kaputt
Links als fundamentales statisches Integrationskozept!
Constraints und ihre Effekte auf die Gesamtarchitektur, wie kann der Wert von Objekten eingeschätzt werden: Frage: In welchem Kontext bin ich.
Haupttreiber für Microservices: Monolitische Architekturen führen zu gegenseitiger Behinderung durch die Teams Versuch: anstatt ein großes Monolitisches System mehrere kleine Anzahl und Größe ergeben sich aus DDD und Bounded Ctx, Versuch die logischen Gegebenheiten in Software widerzuspiegeln
Es soll erreicht werden das unabhängig von allen anderen neue Versionen von einzelnen Systemen ausgeliefert werden können Möglichst wenig kommunikation der Teams untereinander Problem: Kommunikation (API) Was bedeutet eine Änderung am API Wechsel von /orders/{id} auf /orders?id={id} Kopplung sollte so lose sein das jedes einzelne System unabhängig so weiterentwickelt werden kann, dass andere Systeme mindestens nicht kaputt gemacht werden in Monolitischen System Refactoring einfach, da Änderung an alle Stellen durchgereicht wird in losem System schwer (andere werden nicht automatisch geändert)API nach REST-Prinzip soll "evolvable" sein -> d. h. veränderbar ohne Clients zu zerstören
MS-Architektur funktioniert fundamental wie das Web, Systeme kommen, Systeme gehen Prinzipien wie das Inet funktioniert in das API-Design aufnehmen
Wie? Was für ein Abstraktionslevel bietet eine API? Abstraktionslevel: Wie Domänenspezifisch wird das API Terminologie (ubiqutos languge) soll im Code wiederfindbar sein -> wichtig dafür wo Geschäftslogik lebt
Unterschiede: Data <-> CRUD via HTTP/JDBC in disguise [Nachdenken über die Daten] Domain Logic <-> Spring Data REST/Re-usable, but low-level [Nachdenken über den Geschäftsvorfall] Process Flow <-> Useful and specific Presentation
Wo wird eine REST-API angesetzt
Idee -> CRUD über HTTP => Geschäftslogik lebt im Client (bzw. wird in den Client repliziert, da überprüfungen trotzdem erforderlich sind) In der Praxis gibt es viele REST-APIs die so aussehen, weil es einfach und schnell erzeugbar ist
=> API muss gut Designt sein, da diese nicht immer einfach änderbar ist, daher Problem wenn die API das Designproblem ist
Evolvability Nachdenken darüber wie die Technologie änderbar, erweiterbar ist
Umso generischer die API, umso mehr Kopplung entsteht, weil Geschäftslogik zw. Server und Client repliziert wird.
Gegenmaßnahme: Entitäten und Aggregatgrenzen definieren, Resourcen Verknüpfen SpringDataRest schaut sich Domänenmodell an und liefert API, hilfreich behebt aber grunsätzliches Problem nicht: richtiges API muss auf Process Flow Lvl.
Wenn Abstraktsionsniveau Data ist dann lebt die Geschäftslogik zum Teil auf Server und repliziert auf Client
Möglichkeit um mehr Domänenlogik auf den Server zu bekommen, weniger auf den Client: Dynamisches Hypermedia
Dynamisches Hypermedia: Serving Data and Navigation Information at the same time:
Hypermedia as the engine of application state zusätzlich zu links dem Client sagen welche Links es gibt, wenn es sie gibt das diesen Links gefolgt werden kann, wenn es den Link nicht gibt, das nichts angeboten werden soll Je nach Zustand der betrachteten Resource REST state transfer -> Link ist nachvollziehen eines State Transfers
Buch: Rest in Practice
RESTBUCKS: überlegung was löst Zustandsübergänge aus
Aber: manchmal sind bestimmte Zustandsübergänge nur in bestimmten Fällen erlaubt Wie kriegt der Client raus wann stornieren oder cancel angezeigt werden kann und wann nicht?
1. Idee: Payload inspizieren -> Problem Payload häufig hoch volatil, bestehende Clients können bei Änderung nicht mehr ihre Aufgaben ausführen 2. Idee: Hypermedia inspizieren -> dem Client wird weniger Dokumentation zur Verfügung gestellt (da vieles implementierungsdetails sind, die der Client nicht wissen soll) Der Client soll Links verstehen können und ihm wird nur mitgeteilt das er bei bestimmten Links verschiedene Dinge tun kann.{"_links":{ "cancel" : { "href" : ... } ... } Komplexität für Client auf simples Ja/Nein reduzieren, Logik wird im Server gemacht
Zwei verschiedene Formen von Wissen werden gegeneinander aufgewogen
Non-hypermedia based System Hypermedia based System viel Domänenwissen im Client wenig Domänenwissen im Client wenig Protokollwissen im Client viel Protokollwissen im Client viel Kopplung an den Server wenig Kopplung an den Server
Domänenwissen ändert sich schnell Protokollwissen ändert sich selten/wenig
Daher: Vorteil wenn Domänenwissen im Client durch Protokollwissen ersetzt wird
ANMERKUNG: In der Microservice Welt ist mein Hauptanliegen das ich Änderungen an der API/Geschäftslogik vornehmen kann ohne die Clients zu brechen Traum: Der Client kann neue Features lernen
Github: spring-restbucks
Polling: HTTP ETag Gib mir die Bestellung wenn sie nicht im Zustand ist wie vorher (ETag) Anfragen können so effizient mehrfach gestellt werden
SpringData: API -> in der Entität können Events aufgesammelt werden es gibt funktionalität im Repository, wenn die Entität gespeichert wurde werden die Events publiziert und können von anderen aufgesammelt werden
-
- REST- Oliver Drotbohm
- REWE digital - Ansgar Brauner
- Containerisierung- Axel Burghof
- Eventing - Sebastian Gauder
- UI - Wolf Schlegel, Niko Hellwig
- Brownfield zu Greenfield - Christian Nockemann
- Resilience - Marion Bruns, Komal Ahluwalias
- UI bei REWE digial - Christian Fröhlingsdorf
- 8 things developers should know about microservices - Wolf Schlegel, Laura Ionescu, Felix Hammerl