Fördern RESTful-APIs anämische Domänenmodelle?

34

Ich arbeite an einem Projekt, in dem wir versuchen, sowohl domänenbasiertes Design als auch REST auf eine serviceorientierte Architektur anzuwenden. Wir sorgen uns nicht um 100% REST-Konformität. Es ist wahrscheinlich besser zu sagen, dass wir versuchen, ressourcenorientierte HTTP-APIs zu erstellen ( Stufe 2 von Richardsons REST-Reifegradmodell). Wir versuchen jedoch, die Verwendung von HTTP-Anforderungen im RPC-Stil zu vermeiden , dh wir versuchen, unsere HTTP-Verben gemäß RFC2616 zu implementieren, anstatt POSTdies IsPostalAddressValid(...)beispielsweise zu tun .

Eine Betonung hierauf scheint jedoch auf Kosten unseres Versuchs zu gehen, ein domänengetriebenes Design anzuwenden. Mit nur GET, POST, PUT, DELETEund ein paar anderen selten verwendeten Methoden, neigen wir cruddy Dienste zu bauen, und cruddy Dienste sind in der Regel anämische Domänenmodelle haben.

POST: Empfangen Sie die Daten, validieren Sie sie, speichern Sie sie in den Daten. GET: Daten abrufen, zurückgeben. Keine wirkliche Geschäftslogik da. Wir verwenden auch Nachrichten (Ereignisse) zwischen den Diensten, und es scheint mir, dass der größte Teil der Geschäftslogik darauf aufbaut.

Sind REST und DDD auf einem gewissen Niveau angespannt? (Oder verstehe ich hier etwas falsch? Tun wir vielleicht etwas anderes falsch?) Ist es möglich, ein starkes Domänenmodell in einer serviceorientierten Architektur zu erstellen, während HTTP-Aufrufe im RPC-Stil vermieden werden?

Kazark
quelle
1
POST wurde absichtlich so konzipiert, dass es "absichtlich vage" ist. Das Ergebnis eines POST ist implementierungsspezifisch. Was hindert Sie daran, das zu tun, was Twitter und andere API-Designer tun, und jede POST-Methode im Nicht-CRUD-Teil Ihrer API gemäß Ihren spezifischen Anforderungen zu definieren?
Robert Harvey
@RobertHarvey Wir haben POST als eine Kreation ausgelegt. Wenn Sie noch einmal auf die Norm schauen, ist das vielleicht zu simpel. Denken Sie zum Beispiel, dass POST zu diesem IsPostalAddressValid(...)Zweck zum Thema "Bereitstellen eines Datenblocks, z. B. des Ergebnisses der Übermittlung eines Formulars, für einen Datenverarbeitungsprozess" passen würde?
Kazark
Das liegt daran, dass es kein CREATE-Verb (und auch kein UPDATE-Verb) gibt. Ich behaupte, dass diese Verben im Standard fehlen (aus welchem ​​Grund auch immer), weshalb Sie POST für "alles andere" kooptieren müssen. Ihr "Datenverarbeitungsprozess" ist in diesem Fall der Prozess, der die Postanschrift überprüft und einen Wert zurückgibt, der dem Ergebnis dieser Analyse entspricht.
Robert Harvey
1
@RobertHarvey: Ich glaube, dass POST und PUT / PATCH einfach das von Ihnen gewünschte CREATE- und UPDATE-Verb sind. Es ist nur anders benannt, so dass das Verb auch in nicht-RESTful-Design noch Sinn macht.
Lie Ryan
@LieRyan: Das werde ich dir gewähren. Ich denke nur, dass CRUD per Definition anämische Datenmodelle impliziert. Sie können ein gewisses Verhalten mit sich führen, wenn Sie sich beispielsweise im M von MVC befinden, aber sicher nicht über heterogene Systeme hinweg. Für alles andere als CRUD benötigen Sie POST.
Robert Harvey

Antworten:

38

Martin Fowlers erstes Gesetz über verteilte Systeme: "Verteilen Sie Ihre Objekte nicht!" Remote-Schnittstellen sollten grobkörnig und interne Schnittstellen feinkörnig sein. Häufig gilt das Rich Domain-Modell nur in einem begrenzten Kontext .

Die REST-API trennt zwei verschiedene Kontexte, die jeweils eigene interne Modelle haben. Die Kontexte kommunizieren über eine grobkörnige Schnittstelle (REST-API) unter Verwendung von "anämischen" Objekten (DTO).

In Ihrem Fall klingt es so, als würden Sie versuchen, einen Kontext über eine Grenze zu verteilen, die eine REST-API ist. Dies kann zu einer feinkörnigen Remote-Schnittstelle oder einem anämischen Modell führen. Abhängig von Ihrem Projekt kann es ein Problem sein oder auch nicht.

Simoraman
quelle
1
Fowler hat viele gute Gedanken, aber vergessen wir nicht, was für eine Katastrophe die ursprünglichen EJB-Spezifikationen und -Implementierungen waren. Erst danach stellten sie fest, dass Methodenaufrufe auf niedriger Ebene für jede kleinere Operation wie getName () ein Netzwerk- / Lade-Albtraum waren. Grobkörnige Interfaces wurden der Weg und damit das Konzept, dass ganze Entity-Graphen / Nachrichten im Verb + Nomen-Kontext gesendet und empfangen wurden.
Darrell Teague
9

POST wurde absichtlich so konzipiert, dass es "absichtlich vage" ist. Das Ergebnis eines POST ist implementierungsspezifisch. Was hindert Sie daran, das zu tun, was Twitter und andere API-Designer tun, und jede POST-Methode im Nicht-CRUD-Teil Ihrer API gemäß Ihren spezifischen Anforderungen zu definieren? POST ist das Sammelverb. Verwenden Sie es, wenn keines der anderen Verben gut zu der Operation passt, die Sie ausführen möchten.

Anders ausgedrückt, Ihre Frage könnte genauso lauten: "Fördern 'intelligente' Objekte das Design im RPC-Stil?" Selbst Martin Fowler (der den Begriff "anämisches Domänenmodell" geprägt hat) räumt ein, dass reine DTOs einige Vorteile haben:

Das Einfügen von Verhalten in Domänenobjekte sollte nicht dem soliden Ansatz widersprechen, die Domänenlogik mithilfe von Ebenen von Dingen wie Persistenz und Präsentationsverantwortung zu trennen. Die Logik, die sich in einem Domänenobjekt befinden sollte, ist die Domänenlogik - Validierungen, Berechnungen, Geschäftsregeln - wie auch immer Sie sie nennen möchten.

In Bezug auf das Richardson-Reifegradmodell können Sie Level 3 erreichen, ohne sich jemals um "Anämische Domänenmodelle" zu kümmern. Denken Sie daran, dass Sie das Verhalten niemals an den Browser übertragen werden (es sei denn, Sie planen, Javascript durch Ihre Modelle zu injizieren).

Bei REST geht es hauptsächlich um Maschinenunabhängigkeit. Implementieren Sie das REST-Modell in dem Maße, in dem Ihre Endpunkte Ressourcen darstellen sollen, und damit die Benutzer Ihrer API auf diese Ressourcen auf einfache Weise zugreifen und sie auf standardmäßige Weise verwalten können. Wenn das anämisch erscheint, dann soll es so sein.

Siehe auch
Ich brauche mehr Verben

Robert Harvey
quelle
Ich denke, das spricht sicherlich die RFC2616-Seite der Frage an. Was ist mit der Tatsache, dass wir versuchen, ressourcenorientiert zu sein, dh zumindest Level 2 im Richardson-Reifegradmodell für REST zu erreichen?
Kazark
1
Ich las durch martinfowler.com/articles/richardsonMaturityModel.html . Sie können Level 3 erreichen, ohne sich jemals um "Anämische Domänenmodelle" zu kümmern. Denken Sie daran, dass Sie das Verhalten niemals an den Browser übertragen werden (es sei denn, Sie planen, Javascript durch Ihre Modelle zu injizieren).
Robert Harvey
4

Die REST-API ist nur eine Art von Präsentationsebene. Es hat nichts mit dem Domain-Modell zu tun.

Die Frage, die Sie gestellt haben, stammt aus Ihrer Verwirrung darüber, dass Sie sich irgendwie aneinander anpassen müssen. Das tust du nicht.

Sie ordnen Ihr Domain-Modell Ihrer REST-API so zu, wie Sie Ihr Domain-Modell einem RDBMS über ein ORM zuordnen - es muss diese Zuordnungsebene geben.

Domäne ← ORM → RDBMS-
Domäne ← REST-Zuordnung → REST-API

Elnur Abdurrakhimov
quelle
3

Ich glaube nicht, dass sie dazu neigen, anämische Domänenmodelle (ADMs) zu fördern, aber sie erfordern, dass Sie sich etwas Zeit nehmen und die Dinge durchdenken.

Zunächst einmal denke ich, dass das Hauptmerkmal von ADMs ist, dass sie wenig bis gar kein Verhalten aufweisen. Das soll nicht heißen, dass das System kein Verhalten aufweist, nur, dass es sich normalerweise um eine Art Service-Klasse handelt (siehe http://vimeo.com/43598193 ).

Und wenn das Verhalten im ADM nicht vorhanden ist, was dann? Die Antwort sind natürlich die Daten. Und wie ordnet diese REST-API zu? Nun, vermutlich sind die Daten dem Inhalt der Ressource und das Verhalten den HTTP-Verben zugeordnet.

Damit Sie über alles verfügen, was Sie zum Erstellen eines umfangreichen Domänenmodells benötigen, müssen Sie nur in der Lage sein, die Zuordnung der HTTP-Verben zu den Domänenvorgängen für die Daten zu überprüfen und diese Vorgänge dann denselben Klassen zuzuordnen, in denen Ihre Daten enthalten sind.

Ich denke, dass die Leute Probleme bekommen, wenn sie Schwierigkeiten haben zu sehen, wie die HTTP-Verben ihrem Domain-Verhalten entsprechen, wenn das Verhalten jenseits von einfachem CRUD liegt, dh wenn es Nebenwirkungen in anderen Teilen der Domain jenseits von gibt Ressource, die von der HTTP-Anforderung geändert wird. Eine Möglichkeit, dieses Problem zu lösen, sind Domain-Ereignisse ( http://www.udidahan.com/2009/06/14/domain-events-salvation/ ).

RibaldEddie
quelle
3

Dieser Artikel ist ziemlich themenbezogen und ich glaube, er beantwortet Ihre Frage.

Ein Kernkonzept, von dem ich denke, dass es Ihre Frage sehr gut beantwortet, ist im folgenden Absatz des erwähnten Artikels zusammengefasst:

"Es ist sehr wichtig, zwischen Ressourcen in der REST-API und Domänenentitäten in einem domänengetriebenen Design zu unterscheiden. Das domänengetriebene Design bezieht sich auf die Implementierung (einschließlich der API-Implementierung), während Ressourcen in der REST-API das API-Design und den Vertrag bestimmen. API-Ressource Die Auswahl sollte nicht von den zugrunde liegenden Details der Domänenimplementierung abhängen. "

Majix
quelle
1

Einige recht erfolgreiche Implementierungen, die ich gesehen / gebaut habe, beantworten die Frage, wie sie die Verb + Nomen-Metapher mit grobkörnigen, geschäftsfreundlichen Methoden mischen, die auf die Entitäten einwirken.

Legen Sie also anstelle der (zum Scheitern verurteilten) getName()Methode / des (zum Scheitern verurteilten) Dienstes Folgendes offen getPerson(): Übergeben Sie einen Bezeichner vom Typ / ID, und geben Sie die gesamte PersonEntität zurück.

Da das Verhalten der Entität Person in einem solchen Kontext nicht angemessen vermittelt werden kann (und möglicherweise auch nicht in einem datenzentrierten Kontext sein sollte), ist es durchaus sinnvoll, ein Datenmodell (versus Objekt) für die Anforderungs- / Antwortpaare von zu definieren Die Dienste.

Die Dienste und definierten Verben selbst fügen einige domänenzulässige Verhaltensweisen, Steuerelemente und sogar Zustandsübergangsregeln für die Entitäten hinzu. Zum Beispiel würde es eine domänenspezifische Logik geben, was beim transferPerson()Serviceaufruf passiert , aber die Schnittstelle selbst würde nur die Ein- / Ausgabe-Entitäten / Daten definieren, ohne IHR internes Verhalten zu definieren.

Ich würde Autoren widersprechen, die sagen würden, dass beispielsweise eine Implementierung eines Übertragungsverbs zur Klasse Person gehört oder mit einem personenbezogenen Dienst verknüpft ist. In der Tat wäre die Methode der Übertragung für a Personund Optionen davon (in diesem einfachen Beispiel) besser durch a definiert Carrier, wobei die Personmöglicherweise nicht einmal wissen, welche Übertragungsmethoden verfügbar sind oder wie die Übertragung überhaupt stattfindet (wer weiß, wie Düsentriebwerke funktionieren) sowieso).

Macht das die PersonEntität anämisch? Ich glaube nicht

Es kann / sollte eine Logik zu personenbezogenen Dingen geben, die personenintern sind, wie z. B. ihr Gesundheitszustand, die nicht von einer externen Klasse definiert werden sollte.

Abhängig von den Anwendungsfällen ist es jedoch durchaus akzeptabel, dass eine Entitätsklasse in bestimmten Systemen kein wichtiges / relevantes Verhalten aufweist, z. B. bei einem Sitzzuweisungsservice in einem Transportsystem. Ein solches System kann durchaus REST-basierte Dienste implementieren, die sich mit Personeninstanzen und zugeordneten Kennungen befassen, deren internes Verhalten jedoch niemals definieren / implementieren.

Darrell Teague
quelle
Gute Punkte - das bringt wirklich eine neue Perspektive, die andere Antworten noch nicht hatten.
Kazark
0

Ist Ihr Problem, dass Sie versuchen, Ihr Modell in die grundlegenden Verben zu packen, indem Sie POST so oft wie möglich verwenden?

Es ist nicht notwendig - ich weiß, dass REST für die meisten Leute POST, GET, PUT und DELETE bedeutet, aber der http rfc sagt:

Die allgemeinen Methoden für HTTP / 1.1 sind nachstehend definiert. Obwohl dieser Satz erweitert werden kann, kann nicht davon ausgegangen werden, dass zusätzliche Methoden dieselbe Semantik für separat erweiterte Clients und Server verwenden.

Und Systeme wie SMTP verwenden den gleichen Stil verbbasierter Methoden, jedoch mit einem völlig anderen Satz.

Es gibt also keinen Grund, warum Sie diese verwenden müssen, Sie können jeden Satz von Verben verwenden, den Sie mögen (obwohl Sie wirklich feststellen werden, dass Sie mit ein wenig Nachdenken alles tun können, was Sie in den Basic 4 brauchen). Was REST von den anderen Mechanismen unterscheidet, ist seine zustandslose und konsistente Art, diese Verben zu implementieren. Sie sollten nicht versuchen, ein Nachrichtenübermittlungssystem zwischen den Ebenen zu implementieren, da Sie dann im Grunde kein REST ausführen. Stattdessen führen Sie einen Nachrichtenübermittlungs-, RPC- oder Nachrichtenwarteschlangenmechanismus aus, durch den Sie zweifellos die Vorteile von REST (d. H Einfachheit, die es wirklich gut über eine http-Verbindung funktioniert).

Wenn Sie ein komplexes Messaging-Protokoll mit allen Funktionen möchten, erstellen Sie dieses (wenn Sie dies über das Web tun können, gibt es einen Grund, warum REST so beliebt ist), aber versuchen Sie ansonsten, sich an das Architekturdesign von REST zu halten.

gbjbaanb
quelle