it-swarm.com.de

Rest API und DDD

In meinem Projekt unter Verwendung der DDD-Methodik.

Das Projekt hat das Aggregat (Entität) Deal. Dieses Aggregat hat viele Anwendungsfälle.

Für dieses Aggregat muss ich eine Rest-API erstellen.

Mit Standard: erstellen und löschen kein Problem.

1) CreateDealUseCase (Name, Preis und viele andere Parameter);

POST /rest/{version}/deals/
{ 
   'name': 'deal123',
   'price': 1234;
   'etc': 'etc'
}

2) DeleteDealUseCase (id)

DELETE /rest/{version}/deals/{id}

Aber was ist mit den restlichen Anwendungsfällen zu tun?

  • HoldDealUseCase (ID, Grund);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase (ID und viele andere Parameter);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, neuerPreis, Grund);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • etc (insgesamt 20 Anwendungsfälle) ...

Was sind die Lösungen?

1) Verben verwenden :

PUT /rest/{version}/deals/{id}/hold
{ 
   'reason': 'test'
}

Aber! Verben können nicht in der URL verwendet werden (in REST Theorie).

2) Verwenden Sie den Fertigzustand (der nach dem Anwendungsfall liegt):

PUT /rest/{version}/deals/{id}/holded
{ 
   'reason': 'test'
}

Persönlich sieht es für mich hässlich aus. Vielleicht bin ich falsch?

3) Verwenden Sie für alle Operationen eine PUT-Anforderung:

PUT /rest/{version}/deals/{id}
{ 
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

PUT /rest/{version}/deals/{id}
{ 
   'action': 'UnholdDeal',
   'params': {}
}

Im Backend ist es schwer zu handhaben. Außerdem ist es schwierig zu dokumentieren. Da 1 Aktion viele verschiedene Anforderungsvarianten hat, von denen bereits bestimmte Antworten abhängen.

Alle Lösungen haben erhebliche Nachteile.

Ich habe viele Artikel über das REST im Internet gelesen. Überall nur eine Theorie, wie kann ich mit meinem spezifischen Problem hier sein?

30
stalxed

Ich habe viele Artikel über das REST im Internet gelesen.

Basierend auf dem, was ich hier sehe, müssen Sie sich mindestens eines von Jim Webbers Vorträgen über REST und DDD ansehen

Aber was ist mit den restlichen Anwendungsfällen zu tun?

Ignorieren Sie die API für einen Moment - wie würden Sie das mit HTML-Formularen machen?

Vermutlich haben Sie eine Webseite, auf der Deal dargestellt ist, mit einer Reihe von Links. Ein Link führt Sie zum Formular HoldDeal, und ein anderer Link führt Sie zum Formular ChangePrice und so weiter. Jedes dieser Formulare hätte null oder mehr Felder, die ausgefüllt werden müssten, und die Formulare würden jeweils an eine Ressource senden, um das Domänenmodell zu aktualisieren.

Würden alle auf dieselbe Ressource posten? Vielleicht vielleicht nicht. Sie hätten alle denselben Medientyp. Wenn sie alle an demselben Webendpunkt posten, müssten Sie den Inhalt auf der anderen Seite dekodieren.

Wie implementieren Sie Ihr System bei diesem Ansatz? Nun, der Medientyp möchte json sein, basierend auf Ihren Beispielen, aber an dem Rest ist wirklich nichts falsch.

1) Verwenden Sie Verben:

Das ist gut.

Aber! Verben können nicht in der URL verwendet werden (in REST Theorie).

Ähm ... nein. REST kümmert sich nicht um die Schreibweise Ihrer Ressourcenbezeichner. Es gibt eine Reihe von bewährten URI-Methoden, die behaupten, dass Verben schlecht sind - das stimmt -, aber das folgt nicht aus REST.

Wenn die Leute jedoch so pingelig sind, benennt man den Endpunkt für den Befehl anstelle des Verbs. (dh "Halten" ist kein Verb, sondern ein Anwendungsfall).

Verwenden Sie 1 PUT-Anforderung für alle Operationen:

Ehrlich gesagt, das ist auch nicht schlecht. Sie möchten den URI jedoch nicht freigeben (aufgrund der Art und Weise, wie die PUT-Methode angegeben wird), sondern verwenden eine Vorlage, in der die Clients einen eindeutigen Bezeichner angeben können.

Hier ist das Fleisch: Sie bauen eine API auf HTTP- und HTTP-Verben. HTTP ist für die Dokumentübertragung gedacht. Der Client gibt Ihnen ein Dokument, das eine angeforderte Änderung in Ihrem Domänenmodell beschreibt, und Sie übernehmen die Änderung in der Domäne (oder nicht) und geben ein anderes Dokument zurück, das den neuen Status beschreibt.

Sie leihen sich für einen Moment aus dem CQRS-Vokabular und veröffentlichen Befehle, um Ihr Domänenmodell zu aktualisieren.

PUT /commands/{commandId}
{ 
   'deal' : dealId
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

Begründung - Sie fügen einen bestimmten Befehl (einen Befehl mit einer bestimmten ID) in die Befehlswarteschlange ein, bei der es sich um eine Sammlung handelt.

PUT /rest/{version}/deals/{dealId}/commands/{commandId}
{ 
   'action': 'HoldDeal',
   'params': {'reason': 'test'}
}

Ja, das ist auch gut.

Schauen Sie sich RESTBucks noch einmal an. Es ist ein Coffeeshop-Protokoll, aber die gesamte API verteilt nur kleine Dokumente, um die Zustandsmaschine vorzustellen.

25
VoiceOfUnreason

Entwerfen Sie Ihre Rest-API unabhängig von der Domänenebene. 

Eines der Schlüsselkonzepte des domänengesteuerten Designs ist die low Kopplung zwischen Ihren verschiedenen Softwareschichten. Wenn Sie also Ihre Ruhe-API entwerfen, denken Sie über die beste Ruhe-API nach, die Sie haben könnten. Dann ist es die Aufgabe der Anwendungsebene, die Domänenobjekte aufzurufen, um den erforderlichen Anwendungsfall auszuführen.

Ich kann Ihre Ruhe-API nicht für Sie entwerfen, weil ich nicht weiß, was Sie versuchen, aber hier sind einige Ideen.

Nach meinem Verständnis haben Sie eine Deal-Ressource. Wie Sie gesagt haben, ist das Erstellen/Löschen einfach:

  • POST/Rest/{Version}/Angebote
  • LÖSCHEN/rest/{version}/deals/{id}.

Dann wollen Sie einen Deal "halten". Ich weiß nicht, was das bedeutet, Sie müssen darüber nachdenken, was sich in der Ressource "Deal" ändert. Ändert es ein Attribut? Wenn ja, ändern Sie einfach die Deal-Ressource.

PUT/rest/{version}/deals/{id}

{
    ...
    held: true,
    holdReason: "something",
    ...
}

Fügt es etwas hinzu? Kannst du einen Deal mehrmals halten? Klingt für mich, dass "Halten" ein Substantiv ist. Wenn es hässlich ist, finde ein besseres Nomen.

POST/rest/{version}/deals/{id}/hält

{
    reason: "something"
}

eine andere Lösung: REST Theorie vergessen. Wenn Sie der Meinung sind, dass Ihre API mit der Verwendung von Verben in der URL klarer, effizienter und einfacher wäre, sollten Sie dies auf jeden Fall tun. Sie können wahrscheinlich einen Weg finden, um es zu vermeiden, aber wenn Sie nicht können, tun Sie nichts Hässliches, nur weil es die Norm ist.

Schauen Sie sich die Twitter-API an : Viele Entwickler sagen, dass Twitter eine gut entwickelte API hat. Tadaa, es verwendet Verben! Wen kümmert es, solange es cool und einfach zu bedienen ist?

Ich kann Ihre API nicht für Sie entwerfen, Sie sind der einzige, der Ihre Anwendungsfälle kennt, aber ich sage noch einmal meine beiden Ratschläge:

  • Entwerfen Sie die rest-api selbst und rufen Sie dann mithilfe der Anwendungsebene die entsprechenden Domänenobjekte in der richtigen Reihenfolge auf. Genau dafür ist die Anwendungsschicht da.
  • Folgen Sie den Normen und Theorien nicht blind. Ja, Sie sollten so gut wie möglich versuchen, bewährte Praktiken und Normen zu befolgen, aber wenn Sie es nicht können, lassen Sie sie (nach sorgfältiger Prüfung des Kurses)
14
Kaidjin

Der Artikel Anzeigen von CQRS über eine RESTful-API ist ein detaillierter Ansatz zur Lösung Ihres Problems. Sie können die Prototyp-API überprüfen. Ein paar Anmerkungen:

  • Es handelt sich um einen ausgereiften Ansatz, so dass Sie wahrscheinlich nicht alles aus dem Artikel implementieren müssen: Die Parallelität von Event Sourcing über HTTPs ETag und If-Match ist eine solche "erweiterte" Funktion
  • Es handelt sich um einen meinungsorientierten Ansatz: Der DDD-Befehlstyp wird über den Medientyp-Header gesendet, nicht über den Hauptteil. Ich persönlich finde es interessant ... aber nicht sicher, ob ich das so implementieren kann
1

Die Anwendungsfälle (UCs) trenne ich in zwei Gruppen: Befehle und Abfragen (CQRS), und ich habe 2 REST -Controller (einen für Befehle und einen für Abfragen). Die REST -Ressourcen müssen keine Modellobjekte sein, um CRUD-Operationen aufgrund von POST/GET/PUT/DELETE durchzuführen. Ressourcen können beliebige Objekte sein. In DDD sollten Sie den Controllern kein Domänenmodell zur Verfügung stellen.

(1) RestApiCommandController: Eine Methode pro Befehlsanwendungsfall. Die Ressource REST im URI ist der Name der Befehlsklasse. Die Methode ist immer POST, da Sie den Befehl erstellen und dann über einen Befehlsbus (in meinem Fall einen Vermittler) ausführen. Der Anforderungshauptteil ist ein JSON-Objekt, das die Befehlseigenschaften (die Argumente der Benutzerkontensteuerung) abbildet.

Zum Beispiel: http://localhost:8181/command/asignTaskCommand/

@RestController
@RequestMapping("/command")
public class RestApiCommandController {

private final Mediator mediator;    

@Autowired
public RestApiCommandController (Mediator mediator) {
    this.mediator = mediator;
}    

@RequestMapping(value = "/asignTaskCommand/", method = RequestMethod.POST)
public ResponseEntity<?> asignTask ( @RequestBody AsignTaskCommand asignTaskCommand ) {     
    this.mediator.execute ( asigTaskCommand );
    return new ResponseEntity ( HttpStatus.OK );
}

(2) RestApiQueryController: Eine Methode pro Abfrage-Anwendungsfall. Hier ist die Ressource REST im URI das DTO-Objekt, das die Abfrage zurückgibt (als Element einer Sammlung oder nur als eines). Die Methode ist immer GET, und die Parameter der Abfrage-UC sind Parameter im URI.

Zum Beispiel: http://localhost:8181/query/asignedTask/1

@RestController
@RequestMapping("/query")
public class RestApiQueryController {

private final Mediator mediator;    

@Autowired
public RestApiQueryController (Mediator mediator) {
    this.mediator = mediator;
}    

@RequestMapping(value = "/asignedTask/{employeeId}", method = RequestMethod.GET)
public ResponseEntity<List<AsignedTask>> asignedTasksToEmployee ( @PathVariable("employeeId") String employeeId ) {

    AsignedTasksQuery asignedTasksQuery = new AsignedTasksQuery ( employeeId);
    List<AsignedTask> result = mediator.executeQuery ( asignedTasksQuery );
    if ( result==null || result.isEmpty() ) {
        return new ResponseEntity ( HttpStatus.NOT_FOUND );
    }
    return new ResponseEntity<List<AsignedTask>>(result, HttpStatus.OK);
} 

HINWEIS: Mediator gehört zur DDD-Anwendungsschicht. Es ist die UC-Grenze, sucht nach dem Befehl/der Abfrage und führt den entsprechenden Anwendungsdienst aus.

0
choquero70