View
546
Download
8
Category
Tags:
Preview:
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.
Citation preview
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
Recommended