Give me some REST - RESTful APIs mit Symfony

Post on 22-May-2015

545 views 8 download

Tags:

description

Webservices werden heute in vielen Bereichen der IT zur Integration unterschiedlicher Anwendungen verwendet. Die Klasse der REST-Webservices spielt dabei eine besondere Rolle, da REST sich auf die Grundlagen von HTTP stützt, einfach verständlich ist und relativ einfach in bestehende Anwendungen zu integrieren ist. Dieser Vortrag gibt einen Überblick über die Herausforderungen einer RESTful API und zeigt, wie diese mit der Hilfe von Symfony einfach gelöst werden können. Diese Slides habe ich für meinen Vortrag auf der DWX 2014 genutzt.

Transcript of Give me some REST - RESTful APIs mit Symfony

GIVE ME SOME REST!RESTFUL APIS MIT SYMFONYVon / Paul Seiffert @seiffertp

// PAUL SEIFFERTSoftwarearchitekt bei SensioLabs Deutschland GmbH

REST?

REST!Ressourcen ~ Objekte

HTTP Verben ~ Methoden

Links ~ Assoziationen

Repräsentationen ~ Views

ADDRESSIERBARKEITJede Ressource hat eine Adresse (URI)

Beispiel:http://example.com/movies/3

UNIFORM INTERFACEMovieCollection

title: StringreleaseDate: Date

Moviecharacter: String

Role

name: stringdateOfBirth: Date

Actor

*

1

1 *

1

*

GET /moviesPOST /moviesGET /movies/1PUT /movies/1DELETE /movies/1

GET /movies

HTTP/1.1 200 OKDate: Mon, 14 Jul 2014 12:10:00 GMT

{ "movies": [ { "title": "Indiana Jones and the Temple of Doom", "uri": "/movies/1" }, { "title": "Indiana Jones and the Last Crusade", "uri": "/movies/2" }, { "title": "Indiana Jones and the Temple of the Forbidden Eye", "uri": "/movies/3" } ]}

POST /movies

POST /movies HTTP/1.1Content-Type: application/json

{ "movie": { "title": "Indiana Jones and the Kingdom of the Crystal Skull", "releaseDate": "22 May 2008" }}

POST /movies

HTTP/1.1 201 CreatedDate: Mon, 14 Jul 2014 12:15:01 GMTLocation: /movies/4

GET /movies/1

HTTP/1.1 200 OKDate: Mon, 14 Jul 2014 12:10:00 GMT

{ "movie": { "title": "Indiana Jones and the Temple of Doom", "releaseDate": "22 May 1984" }, "uri": "/movies/1"}

PUT /movies/1

PUT /movies/1 HTTP/1.1Content-Type: application/json

{ "movie": { "title": "Indiana Jones and the Temple of Doom", "releaseDate": "23 May 1984" }}

PUT /movies/1

HTTP/1.1 204 No ContentDate: Mon, 14 Jul 2014 12:15:01 GMT

DELETE /movies/1

HTTP/1.1 204 No ContentDate: Mon, 14 Jul 2014 12:20:00 GMT

REST UND SYMFONY?Symfony spricht HTTP (und somit auch REST) fließend!

ISN'T THERE A BUNDLE FOR REST??

ist super!

... für manche Projekte

FOSRestBundle

HERAUSFORDERUNGEN"REST-Syntax"

Abbildung des Domain Models auf Ressourcen

Abbildung der Domain-Logik auf das Uniform Interface

Perfektionistisch sein!

Pragmatisch sein!

MUT ZUR EINFACHEN, SAUBEREN LÖSUNG!

Request

Application

Content Negotiation

Routing

Content Retrieval / Update

SerializationResponse

Security

SECURITY

Notwendiger Weise stateless

Im einfachsten Fall HTTP Basic Authentication

CONTENT-NEGOTIATIONGET / HTTP/1.1Host: google.com

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-Encoding: gzip,deflate,sdchAccept-Language: en-US,en;q=0.8,de;q=0.6

HTTP/1.1 200 OK

Content-Type: text/htmlContent-Language: enContent-Encoding: gzip

HTTP/1.1 406 Not Acceptable

ROUTING

/moviesZeigt auf die Liste der Filme

/movies/1Zeigt auf einen bestimmten Film

ROUTING

GET /moviesGibt die Liste der Filme zurück

POST /moviesLegt einen neuen Film an

SERIALISIERUNG

Die Content-Negotiation bestimmt das Format

Das Routing bestimmt die Daten

SERIALISIERUNG$result = new MovieResult(new Movie('Star Wars: A New Hope', '25 May 1977'));

$serializedContent = $serializer->serialize($result, 'json');

echo $serializedContend;

{ "movie": { "title": "Star Wars: A New Hope", "releaseDate": "25 May 1977" }}

Request

Application

Content Negotiation

Routing

Content Retrieval / Update

SerializationResponse

Security

UND JETZT MIT SYMFONY!

Request

Application

Content Negotiation

Routing

Content Retrieval / Update

SerializationResponse

RequestListener

View Listener

Symfony Routing

Controller / Domain Logic

Security Symfony Security

SECURITYsecurity: firewalls: api: pattern: ̂/ http_basic: realm: "My Movie REST API" stateless: true

access_control: - { path: ̂/, roles: ROLE_USER }

NOCH MEHR SECURITY…

https://github.com/FriendsOfSymfony/FOSOAuthServerBundle

CONTENT NEGOTIATION

https://github.com/willdurand/Negotiation<?php

$negotiator = new \Negotiation\FormatNegotiator();

$acceptHeader = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';$priorities = array('html', 'application/json', '*/*');

$format = $negotiator->getBestFormat($acceptHeader, $priorities);// $format == html

... IN EINEM REQUEST-LISTENER:<?php

class FormatListener{ public function onRequest(GetResponseEvent $event) { $request = $event->getRequest();

$format = $this->negotiator->getBestFormat( $request->headers->get('Accept'), $this->availableFormats ); if (null === $format) { $format = $this->defaultFormat; }

$request->attributes->set('_format', $format); }}

FÜR SPRACHE UND CHARSET ANALOG.

ROUTINGmovies_list: pattern: /movies methods: GET defaults: { _controller: MoviesApiBundle:Movies:get }

movies_add: pattern: /movies methods: POST defaults: { _controller: MoviesApiBundle:Movies:post }

movie_get: pattern: /movies/{id} methods: GET defaults: { _controller: MoviesApiBundle:Movie:get }

movie_put: pattern: /movies/{id} methods: PUT defaults: { _controller: MoviesApiBundle:Movie:put }

movie_delete: pattern: /movies/{id} methods: DELETE defaults: { _controller: MoviesApiBundle:Movie:delete }

DER CONTROLLERÜbersetzt aus HTTP-Logik in Applikations-Logik

Erstellt Responses oder gibt angeforderten Daten zurück

Arbeitet (fast) format-agnostisch

Und bitte mit !DTOs

<?php

class MoviesController{ public function getAction() { return $this->movieApiService->getMovieList(); }

public function postAction(Request $request) { $movie = $this->serializer->deserialize( $request->getContent(), 'MovieDto', $request->attributes->get('_contentType') );

$this->movieApiService->addMovie($movie);

$response = new Response('', 201); $response->headers->set( 'Location', $this->generateUrl('movie_get', ['id' => $movie->getId()]) );

return $response; }}

VOM CONTROLLER ZUM MODELL

Controller Service

Domain Model

DTO Mapper

Validator

SERIALISIERUNG https://github.com/schmittjoh/serializer

<?php

$serializer = $container->get('jms_serializer');

$serializedMovie = $serializer->serialize($movie, 'json');$movie = $serializer->deserialize($serializedMovie, 'MovieDto', 'json');

SERIALIZER MAPPINGMovieDto: exclusion_policy: all properties: title: expose: true type: string releaseDate: expose: true type: Date

<?php

use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;

class ResponseSerializationListener{ public function onView(GetResponseForControllerResultEvent $event) { $request = $event->getRequest();

$content = $this->serializer->serialize( $event->getControllerResult(), $request->attributes->get('_format') );

$event->setResponse(new Response($content)); }}

FRAGEN?

DANKE!

LITERATUR

Martin Fowler -

Richardson Maturity Model

Roy Fielding's Dissertation"Architectural Styles and the Design of Network-basedSoftware Architectures"

Patterns of Enterprise ApplicationArchitecture