Give me some REST - RESTful APIs mit Symfony
-
Upload
paul-seiffert -
Category
Software
-
view
545 -
download
8
description
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??
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