Architecting Ajax Applications with Zend Framework
Upcoming SlideShare
Loading in...5
×
 

Architecting Ajax Applications with Zend Framework

on

  • 42,546 views

 

Statistics

Views

Total Views
42,546
Views on SlideShare
41,581
Embed Views
965

Actions

Likes
65
Downloads
1,304
Comments
1

16 Embeds 965

http://www.comingx.com 422
http://www.slideshare.net 336
http://gibalmeida.wordpress.com 86
http://agharthor.blogspot.com 79
https://twitter.com 16
http://zootool.com 5
http://www.e-presentations.us 5
http://blog.comingx.com 4
http://coderwall.com 4
http://a0.twimg.com 2
http://tweetedtimes.com 1
http://www.php-talks.com 1
http://feeds.feedburner.com 1
http://translate.googleusercontent.com 1
http://webcache.googleusercontent.com 1
file:// 1
More...

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Architecting Ajax Applications with Zend Framework Architecting Ajax Applications with Zend Framework Presentation Transcript

  • Architecting Ajax Applications with Zend Framework Matthew Weier O'Phinney Project Lead Zend Framework
  • Who I am
    • ZF Contributor since January 2006
    • Assigned to the ZF team in July 2007
    • Promoted to Software Architect in April 2008
    • Project Lead since April 2009
    Photo © 2009, Chris Shiflett
  • What we won't cover
    • Actual client-side code
      • Well, mostly none.
  • Why not the client side?
    • XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it
    var req = new XMLHttpRequest(); req. open ( 'GET' , '/foo' , false ); req.send( null ); if (req.status == 200) { dump(req.responseText); }
  • Why not the client side?
    • XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it
    $.get( "/foo" , function (data) { dump(data); });
  • Why not the client side?
    • XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it
    dojo.xhrGet({ url: "/foo" , load: function (data) { dump(data); } });
  • Why not the client side?
    • The real question is: how do you detect and respond to XHR requests in your Zend Framework application?
  • Topics we will cover
    • Write re-usable models that perform your application logic
    • Create standards-based services
    • Use HTTP wisely
    • Perform Context-Switching based on the requested Content-Type
    • Putting it all together: Tips and techniques
  • Writing Re-usable Models
    • Write your application logic once, but use it in many different ways
  • Why?
    • MVC is only one consumer
    • Can be called by job scripts, message systems, queues, etc.
    • Service endpoints can act as consumers (Hint: JSON-RPC is a good way to feed your Ajax applications)
  • What?
    • Domain Models
      • The individual resources and units of work in your application
      • E.g. user, team, backlog, sprint, etc.
      • E.g., object relations, transactions, queries
    • Service Layers
      • Public API of the application; i.e., the behaviors you wish to expose
      • Logic for injecting dependencies
  • Service Layers
    • The guts of your application
  • Applications are like onions … … they have layers Photo © 2008, Mike Chaput-Branson
  • Data Access Objects and Data store(s) Data Mappers Domain Models Service Layer Service Layer in Perspective
  • Benefits to Service Layers
    • Allows easy consumption of the application via your MVC layer
    • Allows easy re-use of your application via services
    • Write CLI scripts that consume the Service Layer
  • What kind of application logic?
    • Validation and filtering
    • Authentication and Authorization
    • Transactions and interactions between model entities
  • class PersonService { public function create( array $data ) { $person = new Person(); if (! $data = $this ->getValidator() ->isValid( $data ) ) { throw new InvalidArgumentException(); } $person ->username = $data [ 'username' ]; $person ->password = $data [ 'password' ]; $person ->email = $data [ 'email' ]; $this ->getMapper()->save( $person ); return $person ; } }
  • Techniques
  • Return objects implementing __toString() and/or toArray()
    • Easily converted to a variety of formats for your XHR clients
      • JSON, XML
    • Easy to cache
      • Strings and arrays serialize and deserialize easily
  • class Foo { public function __toString() { return 'foo' ; } public function toArray() { $array = array (); foreach ( $this ->_nodes as $node ) { $array [] = $node ->name; } return $array ; } }
  • if (! $data = $cache ->load( 'foo_collection' )) { $data = $this ->fooObjects->toArray(); $cache ->save( $data , 'foo-collections' ); } if (! $data = $cache ->load( 'foo-item' )) { $data = ( string ) $this ->foo; $cache ->save( $data , 'foo-item' ); }
  • Return collections as Paginators
    • Consumers do not need to be aware of data format
    • Consumers can then provide offset and limit
    • Consumers can decide how to cast
      • Paginator implements IteratorAggregate, toJson()
    • Most Paginator adapters will only operate once results are requested
    • Return paginators from Domain Objects
  • class Foo_Model_User { public function fetchAll() { $select = $this ->getDbTable()-> select () ->where( 'disabled = ?' , 0); $paginator = new Zend_Paginator( new Zend_Paginator_Adapter_DbSelect( $select ) ); return $paginator ; } }
  • $this ->users->setItemCountPerPage( $this ->numItems) ->setCurrentPageNumber( $this ->page); echo $this ->users->toJson();
  • Caching
    • Write-through caches are trivial to implement in service layers
    • Caching of result sets is trivial in service layers
      • Or, built-in with Zend_Paginator!
  • class Foo_Model_User { public function update( array $data ) { // ... update ... $cache = $this ->getCache(); $cache ->clean( Zend_Cache::CLEANING_MODE_MATCHING_TAG, array ( 'users' , 'user-' . $data [ 'username' ] ) ); $cache ->save( $data , 'user-' . $data [ 'username' ]); } }
  • Zend_Paginator::setCache( $cache ); $users ->setCacheEnabled(true);
  • Implement ACLs in Service Layers
    • Each service object is a resource
    • Once passed an ACL object, the service object defines the ACLs
    • Pass in role to the service object so it may do ACL checks
    • Throw a unique exception type for ACL failures; check for it in error handlers
    • Great way to make your RPC endpoints, such as JSON-RPC, ACL-aware
  • class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function getResourceId() { return 'team' ; } // ... }
  • class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function setAcl(Zend_Acl $acl ) { $acl ->add( $this ) // ... ->allow( 'admin' , $this , array ( 'register' , 'update' , 'list' , 'delete' )); $this ->_acl = $acl ; return $this ; } // ... }
  • class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function setRole( Zend_Role_Interface $role ) { $this ->_role = $role ; return $this ; } // ... }
  • class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function fetchAll() { if (! $this ->getAcl()->isAllowed( $this ->getRole(), $this , 'list' ) ) { throw new UnauthorizedException(); } // ... } // ... }
  • Writing Re-usable Models: Synopsis
    • MVC may be only one consumer of your application; you may want to create JSON-RPC or other RPC endpoints
    • Use return values that are not data-source dependent
    • Use Service Layers as your application's public API
    • Implement strategies such as caching, validation, and authorization in service layers
  • Create Standards-based Services
    • Standards are easier to consume
  • RPC and REST
    • RPC: Remote Procedure Call
      • Typical when wanting to invoke methods outside of standard CRUD
      • Often namespaced: “resource.method”
      • Typically, request includes a method name and typed arguments; response will be typed based on the method signature
      • Usually uses HTTP POST for all operations
  • RPC and REST (cont.)
    • REST: Representational State Transfer
      • Typically manipulating resources; e.g. CRUD
      • Payloads vary based on requested Content-Type; XML and JSON are common
      • Uses HTTP verbs to indicate operations:
        • GET: list all or retrieve individual resources
        • POST: create a new resource
        • PUT: update an existing resource
        • DELETE: delete a resource
  • RPC Services
    • Define application behaviors and expose them
  • RPC Services in Zend Framework
    • JSON-RPC: Zend_Json_Server
    • XML-RPC: Zend_XmlRpc_Server
    • SOAP: Zend_Soap_Server
    • AMF: Zend_Amf_Server
  • Common functionality
    • Mimic SoapServer API from PHP
      • Instantiate server
      • Attach one or more objects/classes, optionally with a namespace
      • Handle the request
      • Return the response
  • JSON-RPC server $server = new Zend_Json_Server(); $server ->setClass( 'App_Service_Team' , 'team' ) ->setClass( 'Scrum_Service_Backlog' , 'backlog' ) ->setClass( 'Scrum_Service_Sprint' , 'sprint' ); if ( 'GET' == $_SERVER [ 'REQUEST_METHOD' ]) { $server ->setTarget( '/jsonrpc' ) ->setEnvelope( Zend_Json_Server_Smd::ENV_JSONRPC_2 ); header ( 'Content-Type: application/json' ); echo $server ->getServiceMap(); return ; } $server ->handle();
  • $server = new Zend_XmlRpc_Server(); $server ->setClass( 'App_Service_Team' , 'team' ) ->setClass( 'Scrum_Service_Backlog' , 'backlog' ) ->setClass( 'Scrum_Service_Sprint' , 'sprint' ); $response = $server ->handle(); echo $response ; XML-RPC server
  • SOAP server if ( 'GET' == $_SERVER [ 'REQUEST_METHOD' ]) { $server = new Zend_Soap_AutoDiscover(); } else { $server = new Zend_Soap_Server( $soapUrl ); } $server ->setClass( 'App_Service_Team' ) ->setClass( 'Scrum_Service_Backlog' ) ->setClass( 'Scrum_Service_Sprint' ); $server ->handle();
  • Common RPC questions
    • What about authentication?
      • Determine method and authentication token from request object
      • Check for authentication prior to handling request
  • Authentication with RPC $request = $server ->getRequest(); if (! in_array ( $method , array ( 'user.login' ))) { $params = $request ->getParams(); $authKey = array_shift ( $params ); $adapter = new My_Auth_Adapter_Api( $authKey ); $result = Zend_Auth::getInstance() ->authenticate( $adapter ); if (Zend_Auth_Result::SUCCESS != $result ->getCode() ) { $response = $server ->fault( 'Unauthorized access' , 401); echo $fault ; return ; } }
  • Common RPC questions
    • What about ACLs?
      • Implement them in your service layer
      • Inject authentication identity when handling
      • Alternately, define ACLs based on request methods, and bail early
  • Injecting the auth identity $server ->setClass( 'Scrum_Service_Sprint' , // class 'sprint' , // namespace $auth ->getIdentity() // ctor arg );
  • Checking ACLs based on method list ( $resource , $action ) = explode ( '.' , $method , 2); if (! $acl ->isAllowed( $auth ->getIdentity(), $resource , $action ) ) { $response = $server ->fault( 'Unauthorized' , 401 ); echo $response ; return ; }
  • Common RPC questions
    • My Service Layer objects have getters and setters I don't want to expose.
      • Create a proxy object that exposes only those methods you want exposed.
      • Pass in dependencies via constructor arguments, use a service locator, or use lazy-loading with sane defaults.
  • Creating a proxy object class Foo_Service_Bar { public function setBar( Foo_Model_Bar $bar ) { } public function getBar() { } public function find( $id ) { } public function fetchAll() { } } class Foo_Service_BarProxy { public function __construct( $options = null){} protected function _setService( Foo_Service_Bar $service ) { } protected function _getService() { } public function find( $id ) { } public function fetchall() { } }
  • Injecting a service locator class Foo_Service_BarProxy { public function __construct( $serviceLocator = null ) { } } $server ->setService( 'Foo_Service_BarProxy' , 'bar' , $serviceLocator );
  • Common RPC questions
    • How do I expose services via the MVC
      • Don't.
      • Seriously, you want your services snappy; bypass the MVC when exposing RPC services.
      • Re-use Zend_Application to bootstrap dependencies
      • Alternately, extend your application Bootstrap, and override the run() method
  • Selectively bootstrapping resources $application ->bootstrap( 'auth' ); $application ->bootstrap( 'db' ); $server ->handle();
  • Overriding the application bootstrap class XmlRpcService_Bootstrap extends Bootstrap { public function run() { $server = new Zend_XmlRpc_Server(); // ... $server ->handle(); } } $application ->bootstrap()->run();
  • Common RPC questions
    • How do I use methods other than POST?
      • You don't.
      • Using the raw POST body is built into most RPC protocols
      • JSON Schema allows you to define a Service Mapping Description, which can allow you to selectively map HTTP methods to RPC methods; ZF does not yet support this in Zend_Json_Server.
  • RESTful Resources
    • Define resources and expose them
  • RESTful Resources in Zend Framework
    • Use Zend_Rest_Route to map controllers as RESTful resources
      • Maps HTTP methods to actions
      • Allows for mimicing PUT and DELETE
    • Use Zend_Rest_Controller, or implement:
      • indexAction()
      • postAction()
      • getAction()
      • putAction()
      • deleteAction()
  • What does REST have to do with Ajax?
    • It's predictable; if you have a RESTful resource, you know how to do CRUD
    • New client-side technologies such as JSON-REST automate REST operations with XmlHttpRequest
  • Creating RESTful routes // Entire application: $router ->addRoute( 'default' , new Zend_Rest_Route( $front ) ); // All controllers in a module: $router ->addRoute( 'rest' , new Zend_Rest_Route( $front , array (), array ( 'scrum' ) ) );
  • Creating RESTful routes (cont.) // Adding select controllers for a // given module $router ->addRoute( 'rest' , new Zend_Rest_Route( $front , array (), array ( 'scrum' => array ( 'backlog' , 'sprint' , ) )) );
  • RESTful controllers class Scrum_BacklogController extends Zend_Rest_Controller { public function indexAction() { } // list public function postAction() { } // create public function getAction() { } // view public function putAction() { } // update public function deleteAction() { } // delete }
  • Standardized Services: Synopsis
    • Use standard server types.
      • Wealth of existing clients that can consume them
      • Predictability == maintainability
      • Flexibility is typically built-in, if you exercise some creativity
    • Know when to use RPC and when to use REST – and what the difference is.
    • Leverage existing application setup, authentication, and ACLs whenever possible.
  • Use HTTP Wisely
    • Get to know your local HTTP protocol
  • Use HTTP Wisely
    • Inspect incoming HTTP request headers
    • Set appropriate HTTP response headers
  • Why should I care?
    • Often you can evaluate the success of an XHR request based on just the HTTP response code.
    • You can tell the server what kind of content is being provided in the request, as well as what kind of content you want returned, via HTTP headers.
      • This is a two-way street; the server needs to respond correctly!
  • Common HTTP Request Headers
    • Content-Type: the content type being sent to your application. E.g. application/json, application/xml, etc.
    • Accept: what content type the client is expecting in response. E.g., application/json, application/xml, etc.
    • Range: how many bytes or items should be returned in the response.
  • Common HTTP Response Codes
    • 201 (Created)
      • After a POST that creates a resource
    • 400 (Bad Request)
      • Failed validations
    • 401 (Unauthorized)
    • 204 (No Content)
      • Often used after successful DELETE operations
    • 500 (Application Error)
  • Common HTTP Response Headers
    • Content-Type: The content type of the response body.
    • Content-Range: the number of bytes returned and total number of bytes, OR the range of items returned, and total number of items.
    • Vary: hint to client-side caching what headers should be used to determine whether or not to cache.
  • Use HTTP Wisely: Synopsis
    • Inspect incoming HTTP request headers, and vary output and responses based on them.
    • Consider client-side caching when setting response headers.
  • Perform Context Switching based on the requested Content-Type
    • How to vary your responses based on the request
  • Context Switching
    • Inspect incoming HTTP request headers, and vary output and responses based on them.
    • Built-in support via the ContextSwitch and AjaxContext action helpers
    • … but you need to give them some help.
  • Context Switching: Basics
    • Map controller actions to “contexts”
    • A “format” request parameter will indicate which context was detected
    • When a context is detected, an additional suffix is added to the view script
    • Optionally, additional headers might be sent.
  • Mapping contexts to actions class FooController extends Zend_Controller_Action { public function init() { $switch = $this ->_helper ->getHelper( 'ContextSwitch' ); // Add "xml" context to "bar" action: $switch ->addActionContext( 'bar' , 'xml' ); // Add "json" context to "baz" action: $switch ->addActionContext( 'baz' , 'json' ); // Detect context $switch ->initContext(); } }
  • How AjaxContext differs
    • AjaxContext works just like ContextSwitch
    • … but it only triggers on XMLHttpRequest (i.e., when the “X-Requested-With: XMLHttpRequest” header is present)
    • When detecting Accept and Content-Type headers, often you can simply use ContextSwitch
  • Request Header Detection
    • When to do it?
      • Once per request
      • Front controller plugin: routeShutdown() or dispatchLoopStartup()
    • How to do it?
      • Use the request object
      • getHeader($headerName) is your friend
    • What to do?
      • Set request parameters accordingly
  • Sample Accept header detection class AcceptPlugin extends Zend_Controller_Plugin_Abstract { public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { $header = $request ->getHeader( 'Accept' ); switch (true) { case ( strstr ( $header , 'application/json' )): $request ->setParam( 'format' , 'json' ); break ; case ( strstr ( $header , 'application/xml' ) && (! strstr ( $header , 'html' ))): $request ->setParam( 'format' , 'xml' ); break ; } } }
  • Views per Context
    • When a context is detected, that context name is prepended to the view script suffix: .xml.phtml , .json.phtml , etc.
    • View scripts should set appropriate response headers
      • Controllers then do not need to be aware of current context
      • Headers are primarily the realm of the view
    • Create appropriate output for the context
  • Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml <h2>Sprints</h2> <div id= &quot;sprints&quot; ></div>
  • Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml $this ->response->setHeader( 'Content-Type' , 'application/xml' ); $xmlWriter = new Phly_Array2Xml(); echo $xmlWriter ->toXml( $this ->sprints->toArray());
  • Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml $this ->response->setHeader( 'Content-Type' , 'application/json' ); echo $this ->json( $this ->sprints->toArray());
  • Putting it all Together
    • Content-Types, Ranges, and paginators – oh, my!
  • Retrieving and decoding parameters from non-standard Content-Types
  • Parameters: The Problem
    • Non form-encoded Content-Types typically mean parameters are passed in the raw request body
    • Additionally, they likely need to be decoded and serialized to a PHP array
    • Use an action helper to automate the process
  • Retrieving parameters class Scrummer_Controller_Helper_Params extends Zend_Controller_Action_Helper_Abstract { protected $_bodyParams = array (); public function init() { $request = $this ->getRequest(); $rawBody = $request ->getRawBody(); if (empty( $rawBody )) { return ; } // ... } }
  • Retrieving parameters (cont.) // ... $contentType = $request ->getHeader( 'Content-Type' ); switch (true) { case ( strstr ( $contentType , 'application/json' )): $this ->setBodyParams( Zend_Json::decode( $rawBody )); break ; // ... }
  • Retrieving parameters (cont.) // ... switch (true) { // ... case ( strstr ( $contentType , 'application/xml' )): $config = new Zend_Config_Xml( $rawBody ); $this ->setBodyParams( $config ->toArray()); break ; // ... }
  • Retrieving parameters (cont.) // ... switch (true) { // ... default: if ( $request ->isPut()) { parse_str ( $rawBody , $params ); $this ->setBodyParams( $params ); } break ; }
  • Retrieving parameters (cont.) class Scrummer_Controller_Helper_Params extends Zend_Controller_Action_Helper_Abstract { public function getBodyParam( $name ) { } public function hasBodyParam( $name ) { } public function hasBodyParams() { } public function getSubmitParams() { if ( $this ->hasBodyParams()) { return $this ->getBodyParams(); } return $this ->getRequest()->getPost(); } public function direct() { return $this ->getSubmitParams(); } }
  • Retrieving parameters – Usage class FooController extends Zend_Rest_Controller { public function postAction() { // assuming Params helper // is registered... $params = $this ->_helper->params(); $model = $this ->getModel(); if (! $model ->create( $params )) { // ... } // ... } }
  • Detecting and Using Range Headers
  • Range: The Problem
    • Requesting all items from a RESTful resource is bad; so is returning all results
      • More results == worse performance
      • More results == more time to retrieve
      • More results often leads to exhausting memory resources
    • The Range header may be used to request a subset of the items
    • Use a plugin to detect the Range, and inject it into the request object
  • Detecting the Range header public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { if (! $range = $request ->getHeader( 'Range' )) { return ; } // Format is: Range: items=0-9 $range = explode ( '=' , $range ); list ( $start , $end ) = explode ( '-' , $range [1]); $request ->setParams( array ( 'range_start' => $start , 'range_end' => $end , )); }
  • Using range in a view script // Assuming that sprints is a Paginator object: $request = $this ->request; if ( $request ->range_start || $request ->range_end) { $start = ( int ) $request ->range_start; $end = ( int ) $request ->range_end; $count = $this ->sprints->getTotalItemCount(); $limit = (! $end ) ? $count : $end - $start ; $end = (! $end ) ? $count : $end ; $items = $this ->sprints->getAdapter() ->getItems( $start , $limit ); $this ->response->setHeader( 'Content-Range' , 'items ' . $start . '-' . $end . '/' . $count ); }
  • Leveraging REST to create CRUD applications using Dojo
  • CRUD: The Problem
    • CRUD == Create – Read – Update – Delete
    • … bo-ring!
    • REST solves CRUD on the server-side and makes it trivial
    • Leverage client-side UI toolkits to solve the problem of CRUD screens
      • dojox.data.JsonRestStore interfaces with REST endpoints
      • dojox.grid.DataGrid can consume JsonRestStore
  • Defining a Simple Mapping Description scrum.sprint.schema = {properties: { id: { type : &quot;integer&quot; }, backlog_id: { type : &quot;integer&quot; }, startDate: { type : &quot;string&quot; , &quot;default&quot; : new Date() }, endDate: { type : &quot;string&quot; , &quot;default&quot; : new Date() }, scrumTime: { type : &quot;string&quot; , &quot;default&quot; : &quot;T10:00:00&quot; } }};
  • Defining a JsonRestStore dojo.require( &quot;dojox.data.JsonRestStore&quot; ); var store = new dojox.data.JsonRestStore({ target : &quot;/sprint&quot; , idAttribute: &quot;id&quot; , schema: scrum.sprint.schema });
  • Defining a DataGrid layout var layout = [ { field: 'backlog_id' , name : 'Backlog' , editable: true }, { field: 'startDate' , name : 'Start Date' , editable: true , cellType: &quot;dojox.grid.cells.DateTextBox&quot; }, // ... { field: 'scrumTime' , name : 'Scrum Time' , editable: true , cellType: &quot;scrum.grid.TimeTextBox&quot; } ];
  • Defining a DataGrid grid var grid = new dojox.grid.DataGrid({ store: store, rowsPerPage: 10, autoheight: 5, structure: layout }, document .createElement( 'div' ));
  • Defining a DataGrid (declarative) <table id = &quot;sprintGrid&quot; dojoType = &quot;dojox.grid.DataGrid&quot; rowsPerPage = &quot;10&quot; store = &quot;store&quot; autoheight = &quot;5&quot; > <thead><tr> <th field = &quot;backlog_id&quot; width = &quot;auto&quot; editable = &quot;true&quot; > Backlog </th> <th field = &quot;startDate&quot; width = &quot;auto&quot; cellType = &quot;dojox.grid.cells.DateTextBox&quot; editable = &quot;true&quot; > Start Date </th> <!-- ... --> <th field = &quot;scrumTime&quot; width = &quot;auto&quot; cellType = &quot;scrum.grid.TimeTextBox&quot; editable = &quot;true&quot; > Scrum Time </th>
  • DataGrid in Action
  • In summary …
    • Listen to the fat guy sing …
  • Conclusions
    • Think about what behaviors you want to expose, and write models that do so.
    • Use predictable, standards-based services, and use them throughout your application.
    • Use HTTP wisely; examine request headers and send appropriate response headers.
    • Perform context switching based on the Accept header; check for the Content-Type when you examine the request.
  • Conclusions (cont.)
    • Leverage headers such as Range to reduce the size of the response, and use Vary to aid in client-side caching.
    • Leverage client-size UI widgets to simplify common CRUD tasks.
  • Most importantly …
    • Keep it simple and predictable.
  • Thank you!
    • Feedback: http://joind.in/talk/view/883
    • ZF site: http://framework.zend.com/
    • Twitter: http://twitter.com/weierophinney
    • Blog: http://weierophinney.net/matthew/