Langlaufende Prozesse mit REST

Unter einem langlaufenden Prozess versteht man eine Aufgabe, dessen Bearbeitungszeit hoch und eine unmitellbare Rückmeldung nicht möglich ist. Bei der Implementierung einer REST API zur Abbildung eines solchens Prozesses kann man sich folgende Fragen stellen.

  • Wie können gezielt Informationen über den Status der Durchführung übermittelt werden?
  • Wie erhält man die Möglichkeit langlaufende Prozesse abzubrechen?
  • Wie integriert man die Stärken von REST in die API?

Aufgabenstellung

Mit meinen Kollegen von neuland habe ich an Hand einer konkreten Aufgabenstellung das Problem betrachtet. Dabei war die Aufgabe die Ausführung eines Jobs, welcher per API angestoßen werden kann. Der Job beinhaltet den Download von Rohdaten, die Transformation der Daten und das abschließende Hochladen einer Datei zu einem Ziel.

Erster Entwurf: Status Created

Die Ressource job wird über die API erzeugt:

curl -X POST http://localhost:9000/jobs
HTTP/1.1 201 Created
Location: http://localhost:9000/job/1

Der Client könnte nun regelmäßig die erzeugte Ressource pollen und den Nutzer mit neuen Informationen versorgen. Betrachtet man losgelöst vom Problem die API, erwartet man gemäß RFC, dass, sobald der Status 201 an den Client gemeldet wird, die erzeugte Ressource vorhanden ist.

The origin server MUST create the resource before returning the 201 status code.

Das ist durchaus möglich, wird aber bei langlaufenden Prozessen mit hoher Wahrscheinlichkeit mit einem Timeout quittiert. Ein Abbrechen ist nicht möglich und das Offenhalten der Verbindung zwischen Client und Server ist nicht ressourcenschonend. Zudem hat der Client bei einem Abbruch, z.B. durch das Schließen des Browsers, keine Möglichkeit mehr an das Ergebnis zu gelangen.

Daraus ergeben sich die Wünsche, Erzeugung und Bereitstellung des Exports zu entkoppeln, sowie das Offenhalten der Verbindung zu verhindern. Eine Möglichkeit zum Abbrechen wäre zudem komfortabel.

Nächste Evolutionsstufe: Status Accepted

Wie wäre die Implementierung mit einem Statuscode 202? Dieser erlaubt die umgehende Rückmeldung an den Client mit dem Hinweis, dass der Request angenommen wurde, ein Erfolg aber nicht garantiert ist. Zudem könnte der Server Informationen über den Status der Anfrage mitgeben.

The entity returned with this response SHOULD include an indication of the request’s current status and either a pointer to a status monitor or some estimate of when the user can expect the request to be fulfilled.

curl -X POST http://localhost:9000/jobs -i
HTTP/1.1 202 Accepted
Location: http://localhost:9000/processing/761d91e0

Über die Resource processing ist der Client nun in der Lage selbstständig mit dem Vorgang zu interagieren.

curl http://localhost:9000/processing/761d91e0
{
  "state": "in progress",
  "tasksCompleted": ["download", "aggregation"],  
  "links": [ {
        "rel": "self",
        "href": "http://localhost:9000/processing/761d91e0/"
    } ]
}

Das Löschen der Ressource könnte den Übergang “Abbruch” dargestellen.

curl -X DELETE http://localhost:9000/processing/761d91e0/

Eine Implementierung auf diese Art und Weise würde die o.g. Punkte bereits erfüllen. Erzeugung und Bereitstellung sind entkoppelt, die Verbindung wird nicht offen gehalten und der Vorgang kann abgebrochen werden. Als netter Nebeneffekt können Client und Server noch mehr Ressourcen sparen, indem sie Conditional Requests bei der Status Abfrage nutzen.

Letzte Evolutionsstufe: weitere Entkopplung

Geht man noch einen Schritt weiter und nimmt dem Client die Bürde ab, zu ermitteln wann der Vorgang beendet ist, hat man noch eine weitere Entkopplung erreicht. Der Server meldet die Fertigstellung des Jobs über eine Weiterleitung auf die erzeugte Ressource.

curl http://localhost:9000/processing/761d91e0-5de8-4790-9e87-209b2de9237b -i -L
HTTP/1.1 301 Moved Permanently
Location: http://localhost:9000/job/761d91e0-5de8-4790-9e87-209b2de9237b
Date: Tue, 01 Mar 2016 14:25:32 GMT
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 01 Mar 2016 14:25:32 GMT
Content-Length: 202

Der Server gibt bei Beendigung der Aufgabe die Ressource job frei und der Client kann sie anzeigen (oder damit tun was er möchte).

curl http://localhost:9000/job/761d91e0-5de8-4790-9e87-209b2de9237b
{
    "job": {
        "id": {
            "value": "761d91e0-5de8-4790-9e87-209b2de9237b"
        },
        "startTime": "2016-03-01T15:25:01.642+01:00",
        "status": "RUNNING",
        "duration": 30
    },
    "endTime": "2016-03-01T15:25:32.807+01:00",
    "status": "FINISHED"
}

Beispielprojekt bei GitHub https://github.com/ralph-m/long-running-process-with-rest