16 Jan

API-Design mit Symfony2 – Teil 1

von SymfonyKeine Kommentare

In den letzten Wochen und Jahren ging der Trend immer stärker von klassischen serverseitigen Webseiten hin zu Single Page Applications (SPA), die zu großen Teilen in JavaScript geschrieben sind. Dabei übernimmt die SPA die komplette Darstellung der Webseite und der auf ihr vorhandenen Daten, die Datenhaltung an sich jedoch findet nach wie vor auf Serverseite statt. Mit dieser strikten Trennung und der damit verbundenen Auslagerung des View-Layers auf die Clientseite, ergibt sich für den Backend-Entwickler die Möglichkeit, sich auf das Wesentliche zu konzentrieren.

Statt dass die Generierung des beim Client anzuzeigendes HTML-Codes mit Hilfe einer Template-Engine im Backend der Applikation geschieht, muss sich der gemeine Entwickler nun nur noch darum kümmern, dass die SPA mit den richtigen Daten versorgt wird. Hier hat sich die Implementierung einer Application Programming Interface (API) durchgesetzt. In diesem Artikel erkläre ich, wie man mit Hilfe des Symfony2-Frameworks auf einfache Art und Weise eine API implementieren kann.

Die erste Frage, die wir uns stellen müssen, ist die folgende: in welchem Datenformat tauschen wir die Daten zwischen Backend (in diesem Fall also die Symfony2-Applikation) und SPA aus? Das Datenformat sollte bestenfalls standardisiert sein und von möglichst allen Komponenten unterstützt werden. Da die SPA in JavaScript geschrieben ist, bietet sich direkt JSON als Datenformat an. Es lohnt sich aber auch weiter zu denken: vielleicht möchte man eine Smartphone-App schreiben, auf denen typisierte Programmiersprachen dominieren, die eventuell keine gute Unterstützung für JSON anbieten. Wir sollten uns also eine Möglichkeit offen halten, die Daten auch in einem anderen Format, wie z.B. XML, ausgeben zu können.

Serialisierung

Glücklicherweise haben sich schon sehr viele Menschen darüber Gedanken gemacht, wie man diesen Prozess der Serialisierung implementieren kann. Bereits das Symfony2-Framework enthält die Serializer-Komponente, mit dem JMS Serializer Bundle gibt es eine gute Alternative, die meiner Meinung nach noch ein bisschen einfacher zu nutzen ist. Das Bundle übernimmt für uns – mit ein wenig Konfiguration – die Serialisierung und Deserialisierung unserer Daten in andere Datenformate bzw. von anderen Datenformaten.

Nehmen wir nun an, dass wir Posts zwischen Backend und Frontend austauschen wollen. Dazu dient diese kleine Klasse:

Damit das Serializer-Bundle mit dieser Klasse umgehen kann, müssen wir es mit ein paar Annotations verzieren, die aus dem Namespace  JMS\Serializer\Annotation  stammen. Der Einfachheit halber gebe ich hier nur den Typ für die einzelnen Felder an. Natürlich lassen sich mit dem JMS Serializer Bundle noch wesentlich komplexere Datenstrukturen abbilden, mehr dazu in der Dokumentation.

Nun können wir Instanzen unserer Post-Klasse einfach serialisieren und auch wieder deserialisieren, z.B. in unserem Controller:

Der obige Codeschnipsel liest und schreibt nur das JSON-Format. Um nun weitere Formate zu implementieren, wären auf den ersten Blick einige if-else-Blöcke nötig… oder auch nicht! Symfony’s Kernel-Komponente und Symfony’s Event Dispatcher-Komponente helfen hier tatkräftig aus und bieten uns Eingriffspunkte, um genau diese Dinge zu bewerkstelligen. Der Einfachheit halber fangen wir mit der Ausgabe an.

Automatisierung der Serialisierung bei der Ausgabe

Das nun folgende Prinzip sollte Symfony-Entwicklern bereits sehr bekannt vorkommen. Wir werden nun die Arbeitsweise der  @Template-Annotation imitieren, dabei aber keine Template-Engine, sondern den Serializer verwenden.

Um unsere Controller-Methode (also: die Action) als Teil unserer API zu markieren, definieren wir zunächst eine neue Annotation:

Die Klasse  APIView erweitert dabei die Klasse  ConfigurationAnnotation. Über den ControllerListener vom Framework Bundle wird die Annotation als Attribut in das Request-Objekt geschrieben, wenn diese Annotation an der derzeitigen Action hängt. Die Felder können später der Annotation als Parameter übergeben werden, hier als Beispiel die Serialisierungs-Gruppen, die man dem Serializer übergeben kann, um z.B. nur bestimmte Felder zu serialisieren (auch dazu mehr in der Dokumentation vom Serializer). Darüber hinaus geben wir der Annotation einen Namen, unter dem sie im Request-Objekt gespeichert wird und definieren mit allowArray(), dass man sie nur einmal pro Methode definieren darf.

Als nächstes kümmern wir uns um die automatische Umwandlung unserer Daten in das richtige(!) Format. Dabei soll uns ein Event Listener unterstützen, der die Rückgabewerte der Action entgegen nimmt und sie in ein Response-Objekt umwandelt, welches der Symfony Kernel dann raussenden kann:

Der Workflow ist eigentlich ganz simpel:

  1. Überprüfen, ob die APIView Annotation an der Action gesetzt war und Konfiguration evtl. speichern.
  2. Alle Dinge vorbereiten, die die einzelnen Konfigurationen beeinflussen (in diesem Fall die Serialisierungs-Gruppen)
  3. Response-Objekt vorbereiten
  4. Response-Objekt an das Event-Objekt übergeben

Der Kniff für das passende Format liegt nun in Schritt 3. An dieser Stelle machen wir uns die Informationen des Request-Objektes zu Nutze und fragen es, welches Format gewünscht ist. Dieses wird über den im HTTP-Header Content-Type übergebenen MIME-Type gemacht. Diesen Content-Type geben wir dann auch wieder zurück.

Mit der korrekten Verdrahtung in den Services ist die Ausgabe erledigt:

Fehlt eigentlich nur noch… die angepasste Controller-Action! Sie reduziert sich nun auf folgenden Code:

Wie bei den Templates gewohnt, brauchen wir nur noch die Daten zurück zu geben, die angezeigt werden sollen. Um den Rest kümmert sich unser neuer APIViewListener. Im nächsten Teil der Serie kümmern wir uns dann um die Eingabe in die API hinein.

Mehr zum Thema:

Tags: API Software-Design Symfony Webprogrammierung

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.