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

APIs for the REST of Us

APIs for the REST of Us

How to use APIs to reuse, refactor and rewrite legacy applications.

Davey Shafik

July 30, 2012
Tweet

More Decks by Davey Shafik

Other Decks in Programming

Transcript

  1. APIs for the REST of us
    APIs and Legacy Applications

    View Slide

  2. •Engineer at Engine Yard for
    Orchestra.io PHP Platform as a
    Service (PaaS)
    •Author of Zend PHP 5 Certification
    Study Guide, Sitepoints PHP
    Anthology: 101 Essential Tips, Tricks
    & Hacks & PHP Master: Write
    Cutting Edge Code
    •A contributor to Zend Framework,
    phpdoc, FRAPI and PHP internals
    •@dshafik
    Davey Shafik

    View Slide

  3. Legacy Code?

    View Slide

  4. Legacy Code
    20%
    80%
    Design, Building,
    QA, etc
    Maintenance

    View Slide

  5. Maintaining Code
    23%
    17% 60%
    Other
    (QA, Docs,
    Deployment)
    Bug Fixes
    New Features

    View Slide

  6. Reuse
    (What you want to)
    Refactor
    (What you can)
    Rewrite
    (What you need to)
    The 3 R’s of Software Development

    View Slide

  7. View Slide

  8. FRAPI
    • Mature: Over 2 years old
    • Current Release: 0.1.1 (7/2/2012)
    • Upcoming Release: 0.2.0 (Hustle)
    • http://getfrapi.com
    • http://github.com/frapi/frapi

    View Slide

  9. Overview

    View Slide

  10. Super Fast Router
    • Routes custom URLs to Actions
    • Custom URL parameters:
    • /a/:collection/:resource_id
    • Actions represent Collections and Resources
    • Actions support 5 HTTP methods:
    • GET,1POST,1PUT,1DELETE,1HEAD
    • Coming Soon: PATCH

    View Slide

  11. Content Negotiation
    • Correctly respects q-values
    • Request: Accept:1text/json;q=0.5,1application/json
    • Response: ContentGType:1application/json
    • Allows custom mimetypes
    • Map to any output type
    • Allows params, automatically parsed
    • application/vnd.github.:version.:param+:format
    • Responds with the correct mimetype
    • Supports mimetype params
    • application/json;version=:version;param=:param
    • Undefined params also handled (e.g. charset=utf-8)

    View Slide

  12. Abstracts Input
    • Transparently allows access to:
    • GET/POST args
    • HTTP body automatically parsed into easily accessible data structures
    [email protected]:Capplication/json,Capplication/xml,Ctext/json,Ctext/xml
    • $thisG>getParam('name');
    • Easy access to mimetype params and... mimetype
    params
    • $thisG>getAcceptParam('name');

    View Slide

  13. Abstracts Output
    • Actions always return PHP data structures (either a
    Frapi_Response object, or an array)
    • Frapi serializes to requested format on output
    • Out of the box support for:
    • JSON, JSON-P, XML, PHP serialized, PHP printr, CLI (plain text) and
    HTML (requires custom templates)
    • You can disable any you don’t want to use!
    • Create custom outputs (such as CSV or Adobe AMF)

    View Slide

  14. Admin Interface
    • Simple interface for managing actions, mimetypes,
    output types and other configuration
    • Allows for documenting your API
    • Supports markdown with syntax highlighting
    • More documentation-related features coming
    • Entire interface currently being overhauled
    • User Interface and User Experience enhancements (UI/UX++)
    Best Practices: Do not deploy admin to production

    View Slide

  15. FRAPI Admin (Current)

    View Slide

  16. FRAPI Admin (Coming Soon...)

    View Slide

  17. Using FRAPI

    View Slide

  18. View Slide

  19. classCAction_ExampleCextendsCFrapi_ActionCimplementsCFrapi_Action_Interface
    {
    C/*CAnCarrayCofCrequiredCparamsC*/
    CCCCprotectedC$requiredParamsC=Carray();
    CCCC/*CAnCarrayCofCinputCdataC*/
    CCCCprivateC$dataC=Carray();
    CCCCpublicCfunctionCtoArray()C{[email protected]>data;C}
    CCCC/*CFallbackCMethodC*/
    CCCCpublicCfunctionCexecuteAction()C{[email protected]>toArray();C}
    CCCCpublicCfunctionCexecuteGet()C{[email protected]>toArray();C}
    CCCCpublicCfunctionCexecutePost()C{[email protected]>toArray();C}
    CCCCpublic1function1executePut()C{[email protected]>toArray();1}
    CCCCpublicCfunctionCexecuteDelete()C{[email protected]>toArray();C}
    CCCCpublicCfunctionCexecuteHead()C{[email protected]>toArray();C}
    }

    View Slide

  20. classCBaseActionCextendsCFrapi_ActionCimplementsCFrapi_Action_InterfaceC{
    CCCCpublicCfunctionCtoArray()C{[email protected]>data;C}
    CCCCpublicCfunctionC__construct()C{
    CCCCCCCC/*CIfCnotCeverythingCisCprivate,CthenCcheckChereCfirstC*/
    CCCCCCCC$authC=CnewCAuth_Digest();
    CCCCCCCC$authG>authorize();
    CCCC}
    CCCCpublicCfunctionCexecuteAction()C{
    CCCCCCCCthrowCnewCFrapi_Error("MethodCNotCAllowed",C"NotCAllowed",C405);
    CCCC}
    }

    View Slide

  21. Versioning

    View Slide

  22. Versioning
    • Versioned URLs are bad
    • Mimetypes are better:
    • Accept:Capplication/vnd.myvendor.:version+:type
    • MimetypeCParamsCareCevenCbetter:
    • Accept:Capplication/json;version=:version
    • Either way, in FRAPI:
    • $thisG>getAcceptParam('version');

    View Slide

  23. namespace1MyApi;
    classCProxyC{
    CprotectedC$version;
    CpublicCfunctionC__construct($version)C{[email protected]>versionC=C$version;C}
    CpublicCfunctionCout($data)C{CreturnC$data;C}
    CpublicCfunctionCin($data)C{CreturnC$data;C}
    CpublicCfunctionCrequired()C{CCreturn1array();C}
    CpublicCfunctionCvalidate($data)C{1return1true;C}
    CstaticCpublicCfunctionC__callStatic($what,C$args)C{
    CC$verC=C$args[0];
    CCwhileC($verC>=C0)C{
    CCC$classC=C"\\MyApi\\Proxy\\v"C.$ver.C"\\"C.Cucfirst($what);
    CCCifC(class_exists($class))C{
    CCCCreturnCnewC$class($version);
    CCC}
    CCCGG$version;
    CC}
    CC$selfC=C__CLASS__;
    CCreturnCnewC$self($version);
    C}
    }

    View Slide

  24. Versioning: Proxy
    useCMyApi\Proxy;
    classCAction_ExampleCextendsCFrapi_ActionCimplementsCFrapi_Action_Interface
    {
    CCCCpublicCfunctionCexecuteGet()C{
    CCCCCCCC$proxyC=CProxy::Example($thisG>getAcceptParam('version'));
    CCCCCCCC$thisG>hasRequiredParameters($proxyG>required());
    CCCCCCCC$inputC=C$proxyG>in($thisG>getParams());
    CCCCCCCC$modelC=CnewCModel_Example();
    CCCCCCCC$exampleC=C$modelG>getById($inputG>id);
    CCCCCCCreturnC$proxyG>out($example);
    CCCC}
    }

    View Slide

  25. Coming Soon...

    View Slide

  26. Coming Soon...
    • Hustle (0.2.0)
    • Build System (PEAR or Composer)
    • New Admin Interface
    • Repository reorganization for faster deploys
    • Bump (0.3.0)
    • Better tools for:
    • HATEOAS/Hypermedia
    • Documentation
    • Automated Mocks (a la Apiary.io)
    • Tester

    View Slide

  27. Documenting Your API

    View Slide

  28. FRAPI Automated
    Documentation

    View Slide

  29. View Slide

  30. View Slide

  31. TurnAPI.com
    • Beta
    • Slow moving on features
    • Feature set is decent (versioned documentation, etc)
    • Markdown syntax with extensions
    • Free while in beta
    • Has been in beta for a long... long time.
    • Open signup

    View Slide

  32. View Slide

  33. Apiary.io
    • Beta
    • Feature set is excellent
    • Automatically creates mock API from documentation to write
    clients against
    • Debug console
    • Markdown syntax with extensions
    • Free while in beta
    • Closed signup
    • Lots of success tweeting them for invite codes: @apiaryio

    View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. Making APIs Work For
    You

    View Slide

  38. Reuse
    (What you want to)
    Refactor
    (What you can)
    Rewrite
    (What you need to)

    View Slide

  39. Reuse
    •Models
    •Business Logic
    •Libraries
    •Data sources
    •Almost anything but your views
    Pros: Quickly piggy-back off your current code. Get an
    API into production now.
    Cons: Will likely lead to REST-like (or RESTy/RESTful)
    APIs that are RPC in reality.

    View Slide

  40. Reuse: Best Practices
    • Design your API first
    • APIs are like URIs: They should be permanent
    • Don’t let bad legacy decision drive new development
    Do: Pretend your API is a re-write. Create the best API
    you can for your application. Then implement it using
    legacy code any way you can. Even if it sucks.
    Don’t: Make compromises based on old code. Instead,
    plan to later refactor the old code.

    View Slide

  41. Refactor
    • Two types of refactoring:
    • Refactor your app to use your APIs instead of legacy code.
    Then make the APIs better by:
    • Refactor the underlying implementation of your APIs to
    migrate away from legacy code
    Do: Test. Unit Tests and Integration Tests. Frisby.js is a
    great BDD integration testing system.
    Don’t: change your API! If you designed it right during
    the Reuse phase, this should be easy!

    View Slide

  42. Rewrite
    • Rewriting should be your last step
    • By the time you get here, you should have full testing in place
    • You should have separation of concerns via SOA, so you can
    rewrite piecemeal
    Do: Discard any and all bad legacy code. Treat it as a
    clean slate.
    Don’t: Rewrite just to rewrite! Legacy code isn’t
    inherently bad code. Focus on new features. Build them
    as services!

    View Slide

  43. Step-By-Step
    1. Design your API
    2. Build by Reusing legacy code (Do whatever it
    takes!)
    3. Release
    4. Refactor Application to use API instead of Legacy
    code
    5. Refactor (or Rewrite) API to not use Legacy code.
    6. Reuse API in Legacy application Rewrite (or Reuse
    in an entirely new application!)

    View Slide

  44. Testing

    View Slide

  45. Testing
    • Integration Testing
    • Test at a much higher level than Unit Testing
    • Ensures your API conforms to your design
    • Can be used for BDD — Behavior Driver Development
    • Use the excellent Frisby.js by Vance Lucas:
    http://frisbyjs.com

    View Slide

  46. Writing Tests
    varCfrisbyC=Crequire('frisby');
    frisby.create('TestCGETC/collection')
    CCCC.get('http://api.frapi/collection')
    CCCC.auth('test',C'34c285b25ac62f9472265d1e41f8a77f5d2382f6')
    CCCCCCCC.expectStatus(200)
    CCCCCCCC.expectHeaderContains('[email protected]',C'application/json')
    CCCCCCCC.expectJSON({C
    CCCCCCCCCCCCmeta:C{C
    CCCCCCCCCCCCCCCCtotal:C'N',
    CCCCCCCCCCCCCCCCdesc:C'TheCtotalCshouldCbeCtheCactiveCresourcesC
    containedCinCaCcollection/bucket.'
    CCCCCCCCCCCC},
    CCCCCCCCCCCCresources:C{C
    CCCCCCCCCCCCCCCCres1:C{Chref:C'/collection/res1',Cname:C'res1'C},
    CCCCCCCCCCCCCCCCres2:C{Chref:C'/collection/res2',Cname:C'res2'C},
    CCCCCCCCCCCCCCCCres3:C{Chref:C'/collection/res3',Cname:C'res3'C}
    CCCCCCCCCCCC}
    CCCCCCCC})
    .toss()

    View Slide

  47. Writing Tests
    frisby.create('TestCPUTC/collection')
    CCCC.put('http://api.frapi/collection')
    CCCCCCCC.expectStatus(400)
    CCCCCCCC.expectHeaderContains('[email protected]',C'application/json')
    CCCCCCCC.expectJSONTypes({
    CCCCCCCCCCCCerrors:CArray
    CCCCCCCC})
    CCCCCCCC.expectJSONTypes('errors.0',C{
    CCCCCCCCCCCC'message':CString,
    CCCCCCCCCCCC'name':CString,
    CCCCCCCCCCCC'at':CString
    CCCCCCCC})
    .toss()

    View Slide

  48. Running Tests
    $"jasmine*node"./
    ..
    Finished"in"0.092"seconds
    2#tests,#7#assertions,#0#failures

    View Slide

  49. Making Requests
    • Set Headers
    • addHeader("[email protected]",C"application/json")
    • removeHeader("[email protected]")
    • Basic HTTP Authentication
    • auth(username,Cpassword)
    • Doesn’t support Digest or other types of auth
    • Call any HTTP method
    • .get(), .post(), .put(), .delete(), .head()
    • No PATCH support

    View Slide

  50. Verifying Responses
    • Set response expectations
    • expectHeader("[email protected]",C"application/json"),
    expectHeaderContains("[email protected]",C"json")
    • expectBodyContains("sometext")
    • expectJSON({foo:C"bar"}),CexpectJSON(foo,C"bar")
    • expectJSONTypes({foo:CString}),CexpectJSONTypes("foo",CC
    String)
    • expectJSONLength(1)

    View Slide

  51. Advanced Assertions
    • Done using callbacks:
    • after(functionC(frisby,Cerror,Cresponse,Cbody)C{
    CCCexpect(something).toEqual(expression);
    })
    • afterJSON(function(frisby,Cdata)C{
    CCCexpect(data.foo).not.toMatch(/^bar$/);
    })

    View Slide

  52. Jasmine Assertions
    • toBe(): Comparison using ===
    • toEqual(): Comparison using ==
    • toMatch(): Comparison using RegExp
    • toBeDefined(), toBeUndefined(): Checks against undefined
    • toBeNull(): Checks for ===Cnull
    • toBeTruthy(), toBeFalsy(): Checks non-boolean truthfulness
    • toContain(): Checks for a value inside an Array
    • toBeGreaterThan()/toBeLessThan(): Checks < and >
    • toBeCloseTo(): Checks with a specified level of precision
    • toThrow(): Checks that a function throws an exception
    • Precede with not to invert, e.g.Cexpect(expr).not.toBeNull()

    View Slide

  53. Thank You!
    •Feedback:
    • @dshafik
    [email protected]
    • Slides:
    • http://daveyshafik.com/slides

    View Slide