Entwicklung & Design

RESTful entwickeln mit TYPO3 Flow: Der Weg zum eigenen Webservice

Seite 2 / 2

Implementierung von Best Practice: Drei Beispiele

Im Laufe der Zeit haben sich einige Best Practices für den Entwurf von REST-APIs herauskristallisiert, deren Gebrauch in der Regel sinnvoll ist. Brauchbare Quellen sind etwa hier und hier zu finden. Im folgenden Abschnitt geht es darum, wie einige dieser Empfehlungen mit TYPO3 Flow umgesetzt werden können.

Versionierung

In der Praxis entwickelt sich das Fachmodell einer Applikation im Lauf der Zeit weiter. Ein Webservice sollte jedoch möglichst stabil bleiben und sich nicht verändern, sobald er einmal öffentlich ist; schließlich weiß man nicht, wer die angebotene API überhaupt nutzt und sich darauf verlässt, dass sie funktioniert. Um die Abwärtskompatibilität zu erhalten, empfiehlt sich daher eine Versionierung der API. So lassen sich neue Funktionen oder nicht abwärtskompatible Änderungen einfach in einer neuen Version der API implementieren. Clients können dann (etwa über die URL) die zu nutzende Version der API angeben:

Versionierung der API

/api/v1/products/12345

Listing 4

Idealerweise berücksichtigt man von Anfang an, dass es einmal mehrere Versionen des Webservices geben wird. Eine mögliche Herangehensweise ist, das eigentliche Domänenmodell und die REST-API in verschiedene Flow-Packages zu entkoppeln; kommen später neue Versionen der API hinzu, können einfach weitere Pakete hinzugefügt werden.

Soll die Schnittstelle stabil bleiben, ist es problematisch, zum Property Mapping direkt die Objekte des Fachmodells zu nutzen, da sich dieses prinzipiell ändern kann. Eine Lösung besteht darin, Hilfsklassen in Form einfacher Data Transfer Objects einzuführen. Diese werden vom Property Mapper abgebildet (1) und lassen sich später nutzen, um (über eine Mapper-Klasse, die man dann selbst implementieren muss) die eigentlichen Fachobjekte zu erstellen (2).

Data Transfer Objects

public function createAction(ProductDto $productDto) { /* (1) */
	$product = $this->productMapper->createProduct($productDto); /* (2) */
	$this->productRepository->add($product);
}

Listing 5

Vorteil dieser Lösung ist, dass die vom Property Mapper genutzten Transfer-Klassen unverändert bleiben. Ändert sich das Fachmodell, kann die Abbildungslogik innerhalb des Webservices angepasst werden, die Schnittstelle nach außen bleibt gleich. Eine Implementierung dieses Musters findet sich im Beispielquelltext am Ende dieses Artikels).

Über das Routing ist es zudem einfach, die API-Version über die URL zu spezifizieren. Dies geschieht durch den Import der Subrouten aus den jeweiligen Paketen in der globalen Routing.yaml und die Einbindung mit einem URL-Präfix:

Import von Subrouten

- name: Products API v1
  uriPattern: api/v1/<ProductsV1Subroutes>
  subRoutes:
    ProductsV1Subroutes: {Package: Helmich.ProductsApiV1}
- name: Products API v2
  uriPattern: api/v2/<ProductsV2Subroutes>
  subRoutes:
    ProductsV2Subroutes: {Package: Helmich.ProductsApiV2}

Listing 6

Status-Codes

Der Teil eines HTTP-Responses, auf den Clients wahrscheinlich als erstes achten, ist der HTTP-Statuscode. RFC 7231 spezifiziert eine ganze Reihe Statuscodes, mit deren Auswertung man Anwender über Erfolg (oder Misserfolg) einer HTTP-Anfrage informieren kann. Diese sollten von einer guten REST-API auch genutzt werden.

Vieles übernimmt hierbei bereits Flow. Es sendet beispielsweise bei einem nicht korrekt interpretierbaren Request einen 400-Bad-Request-Response, beim Fehlen eines benötigten Domänenobjekt einen 404-Not-Found-Response. Darüber hinaus kann der Status-Code auch direkt im Controller verändert werden (1):

Status-Code im Controller verändern

public function createAction(Product $product) {
	$this->productRepository->add($product);
	$this->response->setStatus(201); (1)
}

Listing 7

Cache-Control-Header

Genau wie der Statuscode lassen sich auch Response-Header direkt im Controller setzen – zum Beispiel für Cache-Header:

Response Header im Controller

public function showAction(Product $product) {
	$this->response->setHeader('Cache-Control', 'public, max-age=86400');
	$this->response->setHeader('Last-Modified',
		$product->getModificationTime()->format('r'));
	$this->response->setHeader('Etag',
		sha1($product->getModificationTime()->getTimestamp()));
	$this->view->assign('product', $product);
}

Listing 8

Aus architektonischer Sicht ist hier jedoch Vorsicht geboten: Auch wenn sich im Controller relativ einfach verschiedene Cache-Header setzen lassen, verletzt man hier sehr schnell das Single Responsibility Principle. Eine „saubere“ Lösung könnte hier beispielsweise darin bestehen, die Möglichkeiten zur aspektorientierten Programmierung zu nutzen und die Caching-Logik in einen eigenen Caching-Aspekt auszugliedern.

Zusammenfassung

Einen tatsächlich im ursprünglichen Sinne RESTful Webservice zu entwickeln, ist gar nicht so einfach. Mit dem REST Maturity Model gibt es mittlerweile sogar Ansätze, um zu beurteilen, wie RESTful ein Webservice überhaupt ist. Auch in TYPO3 Flow ist zuweilen noch Handarbeit nötig, um eine echt REST-konforme API zu konstruieren. Dennoch bekommen Entwickler in TYPO3 Flow ein gutes Werkzeug an die Hand, nicht zuletzt wegen der guten HTTP-Implementierung.

Beispiel-Quelltext zum Artikel
Die Code-Beispiele finden sich (zusammen mit weiterführenden Beispielen) unter diesem Link auf GitHub.
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

4 Kommentare
Werner
Werner

.. wäre ja nicht verkehrt gewesen, den Namen Rails im Artikel mal zu erwähnen..

Controller sieht da so aus:

class ArticlesController < ApplicationController

before_action :set_article, only: :show

def index
@articles = Article.online
end

def show
end

private
def set_article
@article = Article.find(params[:id])
end

end

Wenn ich das nun mit dem Typo code vergleiche..ist klar
warum ich Rails bevorzuge.

Murks
Murks

Ich sehe kein Typo3/Flow ich sehe nur ein Symfony erweitert um völligen Murks…
Dann doch lieber was gescheites anstatt sowas…