Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

William Durand

October 30, 2014
Tweet

More Decks by William Durand

Other Decks in Programming

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é)

    View Slide

  2. Introduction to REST

    View Slide

  3. Everyone who has ever talked about REST, at
    some point, has said something idiotic about REST.
    (Except for maybe Roy Fielding)

    View Slide

  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.

    View Slide

  5. Richardson Maturity Model
    http://martinfowler.com/articles/richardsonMaturityModel.html

    View Slide

  6. Level 0 - The Swamp of POX
    HTTP as a tunneling mechanism
    RPC style system (SOAP, XML-RPC)

    View Slide

  7. Level 1 - Resources
    Individual resources (URIs)
    Notion of object identity

    View Slide

  8. Level 2 - HTTP Verbs
    Client
    uses specific HTTP
    verbs
    Server
    uses HTTP
    status codes

    View Slide

  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.

    View Slide

  10. (*)
    What should be the status code for a DELETE request
    performed on a non existent resource?

    View Slide

  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

    View Slide

  12. http://httpstatus.es/

    View Slide

  13. Level 3 - Hypermedia Controls
    Service discovery via link relations
    Hypermedia formats (ATOM, HAL, JSON-LD, etc.)

    View Slide

  14. Level 3
    =
    Content Type Negotiation
    +
    HATEOAS

    View Slide

  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

    View Slide

  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)

    View Slide

  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.








    View Slide

  18. Let's Summarize

    View Slide

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

    View Slide

  20. Collections & Resources
    GET /programmers
    {
    "programmers": [
    {
    "nickname": "willdurand",
    "powerLevel": 5
    }
    ]
    }
    GET /programmers/willdurand
    {
    "nickname": "willdurand",
    "powerLevel": 5
    }

    View Slide

  21. 2
    Provide a well-know way of
    manipulating these "things".

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  24. Deleting Resources
    DELETE /programmers/ubermuda HTTP/1.1
    Idempotent
    HTTP/1.1 204 No Content
    Not idempotent
    HTTP/1.1 404 Not Found

    View Slide

  25. 3
    Link these "things" together so you
    can navigate from one to another.

    View Slide

  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" }
    }
    }

    View Slide

  27. State Machine

    View Slide

  28. 4
    STATELESS!!!

    View Slide

  29. REST is the web!

    View Slide

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

    View Slide

  31. In order to comply with RMM Level 1,
    I want to turn business objects/data to resources.

    View Slide

  32. JMS Serializer
    ® schmittjoh/serializer

    View Slide

  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

    View Slide

  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;
    }
    }

    View Slide

  35. JSON
    {
    "articles": [
    "bim",
    "bam",
    "bingo"
    ],
    "page": "2"
    }

    View Slide

  36. XML

    bim
    bam
    bingo

    View Slide

  37. In order to comply with RMM Level 2,
    I want to use proper
    HTTP verbs
    and
    status codes
    .

    View Slide

  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!

    View Slide

  39. FOSRestBundle
    (+ Symfony)
    ® FriendsOfSymfony/FOSRestBundle

    View Slide

  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

    View Slide

  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);
    }
    }

    View Slide

  42. HTML



    bim
    bam
    bingo

    page: 2


    View Slide

  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');
    }

    View Slide

  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

    View Slide

  45. Want A Smarter Mapping?
    ® TheBigBrainsCompany/rest-util

    View Slide

  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.')
    );

    View Slide

  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"

    View Slide

  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"
    }

    View Slide

  49. In order to comply with RMM Level 3,
    I want to HATEOAS ALL THE THINGS!

    View Slide

  50. Hateoas
    ® willdurand/Hateoas

    View Slide

  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

    View Slide

  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() {}
    }

    View Slide

  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"
    }
    }
    }

    View Slide

  54. XML
    $hateoas = HateoasBuilder::create()->build();
    $xml = $hateoas->serialize(new User(123, 'John', 'Doe'), 'xml');





    View Slide

  55. In order to [make humans happy|dominate the world|whatever],
    I want to create documentation for my API.

    View Slide

  56. Swagger UI
    ® wordnik/swagger-ui

    View Slide

  57. NelmioApiDocBundle
    ® nelmio/NelmioApiDocBundle

    View Slide

  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

    View Slide

  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() {}

    View Slide

  60. View Slide

  61. ng-admin
    ® marmelab/ng-admin

    View Slide

  62. View Slide

  63. Demo?

    View Slide

  64. ® willdurand/Propilex

    View Slide

  65. ® gimler/symfony-rest-edition

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  70. Like all great movies, that's
    not the real end!

    View Slide

  71. In order to make my API more fancy self-documenting,
    I want to use URI Templates.

    View Slide

  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/

    View Slide

  73. TemplatedUriRouter
    ® hautelook/TemplatedUriRouter

    View Slide

  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

    View Slide

  75. In order to not burn too many servers,
    I want to add and manage a caching layer.

    View Slide

  76. FOSHttpCache
    ® FriendsOfSymfony/FOSHttpCache

    View Slide

  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

    View Slide

  78. In order to provides client applications a
    secure delegated access
    to my API on behalf of my users, I want to use
    OAuth
    .

    View Slide

  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

    View Slide

  80. HWIOAuthBundle
    Client-side implementation
    Support 20+ different providers
    Supports both OAuth1.0a and OAuth2
    ® hwi/HWIOAuthBundle

    View Slide

  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

    View Slide

  82. The real end.

    View Slide