Architecture REST : On ne va pas se mentir (BlendWebMix)

Architecture REST : On ne va pas se mentir (BlendWebMix)

Après vous avoir convaincu que vous n’avez probablement jamais créé d’APIs « REST » de votre vie, je vous propose un tour d’horizon des outils disponibles pour concevoir de vraies APIs REST en PHP (et oui !) grâce à l’écosystème du framework Symfony.

Online slides: http://slides.williamdurand.fr/rest-apis-php-and-symfony/
Sources: https://github.com/willdurand-slides/rest-apis-php-and-symfony

F59d2f1ed66b8d9c6ceebea5a748494b?s=128

William Durand

October 30, 2014
Tweet

Transcript

  1. Architecture REST : On ne va pas se mentir. William

    Durand - October 30th, 2014 (Cette conférence n'est pas et ne sera jamais sponsorisée par iTélé)
  2. Introduction to REST

  3. Everyone who has ever talked about REST, at some point,

    has said something idiotic about REST. (Except for maybe Roy Fielding)
  4. REpresentational State Transfer REST is the underlying architectural principle of

    the web. It is formalized as a set of constraints, described in Roy Fielding's dissertation.
  5. Richardson Maturity Model http://martinfowler.com/articles/richardsonMaturityModel.html

  6. Level 0 - The Swamp of POX HTTP as a

    tunneling mechanism RPC style system (SOAP, XML-RPC)
  7. Level 1 - Resources Individual resources (URIs) Notion of object

    identity
  8. Level 2 - HTTP Verbs Client uses specific HTTP verbs

    Server uses HTTP status codes
  9. HTTP Verbs Method Safe? Idempotent? GET yes yes HEAD yes

    yes POST no no PUT no yes DELETE no yes (*) ... no no Safe : means cacheable. Idempotent : result independent on the number of executions.
  10. (*) What should be the status code for a DELETE

    request performed on a non existent resource?
  11. HTTP Status Codes Code range Description Example 1xx Information 100

    - Continue 2xx Successful 201 - Created 3xx Redirection 301 - Moved Permanently 4xx Client Error 404 - Not Found 5xx Server Error 501 - Not Implemented
  12. http://httpstatus.es/

  13. Level 3 - Hypermedia Controls Service discovery via link relations

    Hypermedia formats (ATOM, HAL, JSON-LD, etc.)
  14. Level 3 = Content Type Negotiation + HATEOAS

  15. Content Type Negotiation Content Type Negotiation is the principle of

    finding appropriate response formats based on client requirements. No standardized algorithm available Apache algorithm is documented though Also covers encoding (Accept-Encoding) and language (Accept-Language) negotiation mod_negotiation
  16. Example Accept: application/json, application/xml;q=0.9, text/html;q=0.8, text/*;q=0.7, */*;q=0.5 Priority Description q=1.0

    application/json q=0.9 application/xml q=0.8 text/html q=0.7 text/* (ie. any text) q=0.5 */* (ie. any media type)
  17. HATEOAS Hypermedia As The Engine Of Application State. It means

    that hypermedia should be used to find your way through the API. It is all about state transitions. Your application is just a big state machine. <?xml version="1.0" encoding="UTF-8"?> <collection page="1" limit="10" pages="1"> <user id="123"></user> <user id="456"></user> <link rel="self" href="/api/users?page=1&amp;limit=10" /> <link rel="first" href="/api/users?page=1&amp;limit=10" /> <link rel="last" href="/api/users?page=1&amp;limit=10" /> </collection>
  18. Let's Summarize

  19. 1 Identify the "things" you want to expose with your

    API.
  20. Collections & Resources GET /programmers { "programmers": [ { "nickname":

    "willdurand", "powerLevel": 5 } ] } GET /programmers/willdurand { "nickname": "willdurand", "powerLevel": 5 }
  21. 2 Provide a well-know way of manipulating these "things".

  22. Creating Resources POST /programmers HTTP/1.1 Content-Type: application/x-www-form-urlencoded nickname=ubermuda HTTP/1.1 201

    Created Location: /programmers/ubermuda Content-Type: application/json { "nickname": "ubermuda", "powerLevel": 0 }
  23. Updating Resources PUT /programmers/ubermuda HTTP/1.1 Content-Type: application/json { "nickname": "ubermuda",

    "powerLevel": 1 } HTTP/1.1 200 OK Content-Type: application/json { "nickname": "ubermuda", "powerLevel": 1 }
  24. Deleting Resources DELETE /programmers/ubermuda HTTP/1.1 Idempotent HTTP/1.1 204 No Content

    Not idempotent HTTP/1.1 404 Not Found
  25. 3 Link these "things" together so you can navigate from

    one to another.
  26. HAL+JSON GET /programmers { "programmers": [ { "nickname": "willdurand", "powerLevel":

    5, "_links": { "self": { "href": "/programmers/willdurand" }, } } ], "_links": { "self": { "href": "/programmers?page=2&limit=1" }, "first": { "href": "/programmers?page=1&limit=1" }, "last": { "href": "/programmers?page=5&limit=1" }, "next": { "href": "/programmers?page=3&limit=1" } } }
  27. State Machine

  28. 4 STATELESS!!!

  29. REST is the web!

  30. How to build REST APIs with PHP/Symfony?

  31. In order to comply with RMM Level 1, I want

    to turn business objects/data to resources.
  32. JMS Serializer ® schmittjoh/serializer

  33. In A Nutshell Enables (de) serialization of object graphs Implements

    visitor pattern to enable flexibility Fully leverage native JSON and XML Custom exclusion strategies to determine what to serialize Quite easy to extend
  34. Usage use JMS\SerializerBundle\Annotation as Serializer; /** @Serializer\XmlRoot("response") */ class MyResponse

    { /** * @Serializer\XmlList(inline=true, entry="article") */ protected $articles; /** * @Serializer\XmlAttribute() */ protected $page; public function __construct(Collection $articles, $page) { $this->articles = $articles; $this->page = $page; } }
  35. JSON { "articles": [ "bim", "bam", "bingo" ], "page": "2"

    }
  36. XML <response page="2"> <article>bim</article> <article>bam</article> <article>bingo</article> </response>

  37. In order to comply with RMM Level 2, I want

    to use proper HTTP verbs and status codes .
  38. But I need to understand what the client wants and

    says. And I also need to rely on a serializer. And I will have to deal with filters, versioning, etc. I need a framework!
  39. FOSRestBundle (+ Symfony) ® FriendsOfSymfony/FOSRestBundle

  40. In A Nutshell Toolbox of services and listeners to build

    RESTful APIs Generate HTML, XML, and JSON from a single action Automatic generation of routes from actions GET parameter parsing and validation Integration with Symfony serializer and JMS Serializer Accept header negotiation (thx to the lib) Request body decoding API versioning Negotiation
  41. Usage class RestController { /** * route name: liip_hello_rest_get_articles *

    pattern: /liip/hello/rest/articles.{_format} * http method requirement: GET * * @View() * @QueryParam(name="page", requirements="\d+", default="1") */ public function getArticlesAction($page) { //$page = $request->query->get('page'); //$page = preg_match('\d+', $page) ? $page : 1; $articles = [ 'bim', 'bam', 'bingo' ]; return new \Acme\MyBundle\MyResponse($articles, $page); } }
  42. HTML <html> <body> <div> <div>bim</div> <div>bam</div> <div>bingo</div> </div> <div>page: 2</div>

    </body> </html>
  43. Message Decoding Decode request body from XML, JSON, etc. into

    the request Integration with converters to turn parameters to objects GET parameter validation and normalization POST /users HTTP/1.1 Accept: application/json,*/*;q=0.8 Content-Type: application/json {"name":"Don Johnson"} public functon postAction(Request $request) { $name = $request->request->get('name'); }
  44. Error/Exception Handling Return correct HTTP status code Determine for which

    exception to expose the exception message Automatically extract errors from a Form instance fos_rest: exception: codes: 'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404 'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT messages: 'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
  45. Want A Smarter Mapping? ® TheBigBrainsCompany/rest-util

  46. Usage $invalidArgumentExceptionMapping = new ExceptionMapping(array( 'exceptionClassName' => '\InvalidArgumentException', 'factory' =>

    'default', 'httpStatusCode' => 400, 'errorCode' => 400101, 'errorMessage' => null, 'errorExtendedMessage' => 'Extended message', 'errorMoreInfoUrl' => 'http://api.my.tld/doc/error/400101', )); $exceptionMap = new ExceptionMap(); $exceptionMap->add($invalidArgumentExceptionMapping); $errorResolver = new ErrorResolver($exceptionMap); $error = $errorResolver->resolve( new \InvalidArgumentException('This is an invalid argument exception.') );
  47. Symfony Bundle Configuration tbbc_rest_util: error: exception_mapping: InvalidArgumentException: class: "InvalidArgumentException" factory:

    default http_status_code: 400 error_code: 400101 error_message: ~ extended_message: "Extended message" more_info_url: "http://api.my.tld/doc/error/400101"
  48. Example print_r($error->toArray()); Array ( [http_status_code] => 400 [code] => 400101

    [message] => This is an invalid argument exception. [extended_message] => Extended message [more_info_url] => http://api.my.tld/doc/error/400101 ) echo json_encode($error->toArray()); { "http_status_code": 400, "code": 400101, "message": "This is an invalid argument exception.", "extended_message": "Extended message", "more_info_url": "http:\/\/api.my.tld\/doc\/error\/400101" }
  49. In order to comply with RMM Level 3, I want

    to HATEOAS ALL THE THINGS!
  50. Hateoas ® willdurand/Hateoas

  51. In A Nutshell Leverages the JMS Serializer library Relies on

    the Symfony2 ExpressionLanguage component Supports JSON and XML Allows to configure links and embedded resources in XML, YAML, PHP, or Annotations Dynamic relations (relation providers) Exclusion strategies
  52. Usage use JMS\Serializer\Annotation as Serializer; use Hateoas\Configuration\Annotation as Hateoas; /**

    * @Serializer\XmlRoot("user") * * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())") */ class User { /** @Serializer\XmlAttribute */ private $id; private $firstName; private $lastName; public function getId() {} }
  53. JSON $hateoas = HateoasBuilder::create()->build(); $json = $hateoas->serialize(new User(123, 'John', 'Doe'),

    'json'); { "id": 123, "first_name": "John", "last_name": "Doe", "_links": { "self": { "href": "/api/users/123" } } }
  54. XML $hateoas = HateoasBuilder::create()->build(); $xml = $hateoas->serialize(new User(123, 'John', 'Doe'),

    'xml'); <user id="123"> <first_name><![CDATA[John]]></first_name> <last_name><![CDATA[Doe]]></last_name> <link rel="self" href="/api/users/123"/> </user>
  55. In order to [make humans happy|dominate the world|whatever], I want

    to create documentation for my API.
  56. Swagger UI ® wordnik/swagger-ui

  57. NelmioApiDocBundle ® nelmio/NelmioApiDocBundle

  58. In A Nutshell Designed for Symfony Generates documentation for your

    REST APIs Gathers information from PHPDoc Supports FOSRestBundle, SensioFrameworkExtraBundle, JMSSerializerBundle and JMSSecurityExtraBundle Supports your own annotations and you own parsers Sandbox (Killer Feature 쁥) Swagger compliant
  59. Usage /** * List all notes. * * @ApiDoc( *

    resource = true, * statusCodes = { 200 = "Returned when successful" } * ) * @QueryParam( * name="offset", requirements="\d+", nullable=true, * description="Offset from which to start listing notes." * ) * @QueryParam( * name="limit", requirements="\d+", default="5", * description="How many notes to return." * ) */ public function getNotesAction() {}
  60. None
  61. ng-admin ® marmelab/ng-admin

  62. None
  63. Demo?

  64. ® willdurand/Propilex

  65. ® gimler/symfony-rest-edition

  66. Thank You. Questions? ¾ ® ¬ williamdurand.fr github.com/willdurand twitter.com/couac

  67. https://knpuniversity.com/screencast/rest

  68. Improving REST in Symfony http://symfony.com/blog/improving-rest-in-symfony https://groups.google.com/forum/#!forum/resting-with-symfony Ä Join us!

  69. Further Reading http://www.ietf.org/rfc/rfc2616.txt http://martinfowler.com/articles/richardsonMaturityModel.html http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven http://www.slideshare.net/Wombert/designing-http-interfaces-and-restful-web-services-dpc2012-20120608 http://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-way/ http://www.mnot.net/blog/2012/12/04/api-evolution http://knpuniversity.com/blog/what-the-rest http://knpuniversity.com/blog/rest-revisited

    http://timelessrepo.com/haters-gonna-hateoas http://blog.liip.ch/archive/2013/10/28/resting-with-symfony2.html http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/ https://blog.apigee.com/detail/restful_api_design_plural_nouns_and_concrete_names
  70. Like all great movies, that's not the real end!

  71. In order to make my API more fancy self-documenting, I

    want to use URI Templates.
  72. RFC 6570: URI Template A compact sequence of characters for

    describing a range of URIs through variable expansion. URIs URI Template http://example.com/~fred/ http://example.com/~ {username}/ http://example.com/~mark/
  73. TemplatedUriRouter ® hautelook/TemplatedUriRouter

  74. Usage demo_route: pattern: /demo/{page} $templateLink = (new Rfc6570Generator($routes)) ->generate('demo_route', array(

    'page' => '{page}', 'sort' => '{sort}', 'filter' => array('{filter}'), )); /demo/{page}?{&sort}{&filter%5B%5D*} q Leverages the . Symfony Routing component
  75. In order to not burn too many servers, I want

    to add and manage a caching layer.
  76. FOSHttpCache ® FriendsOfSymfony/FOSHttpCache

  77. In A Nutshell Set path-based cache expiration headers via app

    config Set up an invalidation scheme without writing PHP code Tag your responses and invalidate cache based on tags Send invalidation requests with minimal performance impact ( Varnish and Nginx supported out of the box) Differentiate caches based on user type (e.g. roles) Easily implement HTTP cache client
  78. In order to provides client applications a secure delegated access

    to my API on behalf of my users, I want to use OAuth .
  79. What Is OAuth? OAuth is an open protocol to allow

    secure authorization in a simple and standard method from web, mobile, and desktop applications. It is an authorization framework that enables a third-party application to obtain limited access to an HTTP service. http://edu.williamdurand.fr/security-slides/#slide85
  80. HWIOAuthBundle Client-side implementation Support 20+ different providers Supports both OAuth1.0a

    and OAuth2 ® hwi/HWIOAuthBundle
  81. FOSOAuthServerBundle Server-side implementation of OAuth2 Supports Doctrine ORM|ODM, Propel Highly

    configurable Thank you for helping us! Alan Gabriel Bem ® ® (OAuth1.0a) FriendsOfSymfony/FOSOAuthServerBundle willdurand/BazingaOAuthServerBundle
  82. The real end.