2015 WordCamp Maine Keynote


Published on

WP Rest API, The New York Times, and The New WordPress
WordCamp Maine Portland, Maine - May 16, 2015

Published in: Internet

2015 WordCamp Maine Keynote

  1. 1. WP Rest API, The New York Times, and The New WordPress WordCamp Maine Portland, Maine - May 16, 2015
  2. 2. Scott Taylor • Core Developer, WordPress open source project • Release Lead for 4.4 • Sr. Software Engineer, The New York Times • @wonderboymusic on Twitter/ Instagram/Swarm • I like Music, NYC, and Mexican food
  3. 3. Blogs at The NYTimes • Multisite • 200+ blogs over the course of time • The 00s were the glory days for Blogs and WordPress • The NYT used WordPress very early • NYT was an early investor in Automattic
  4. 4. Legacy Blogs Codebase • Separate from the rest of the codebase (NYT4 is PHP, but not WordPress) • Global NYTimes CSS and JS, CSS for all Blogs, custom CSS per-blog • A universe that assumed jQuery AND Prototype were loaded on the page in global scope
  5. 5. <html> <head> <script src=“prototype.js”</script> <script src=“jquery.js”</script> </head> <script> jQuery(‘#good-times’).click(…) $$(‘.prototype’).whatever() </script> Somewhere else in the markup:
  6. 6. What could possibly go wrong here?
  7. 7. • Inline HTML from 2008 that assumes Prototype will still be a thing in 2015, stored in post_content • Widgets and inline code that add their own version of jQuery/Prototype, because YOLO • Even better: widgets/modules from other teams that use a different version of jQuery … at times there could be 4 jQuerys on the page (and 4 different versions at that) Things Like ….
  8. 8. <script src=“http://graphics8.nytimes.com/js/app/ lib/jquery/jquery-1.6.2.min.js”></script> <script src=“http://js.nyt.com/js/app/lib/jquery/ jquery-1.7.1.min.js"></script> <script src="http://js.nyt.com/video/vhs/build/ vhs-2.x.js" ></script> <script> NYTD.jQuery(document).ready(function ($) { ... }); </script> All the jQuerys
  9. 9. No shared modules • Code/HTML markup can get out of sync with other projects regularly: header, footer, navigation • The CSS and JS files split across multiple SVN repos - changes to global assets can affect us without us knowing. Fixing the code requires scouring through multiple repos.
  10. 10. <script src="/js/common.js"></script> <script src="/js/app/lib/NYTD/0.0.1/tabset.js"></script> <script src="/js/blogs_v3/nyt_universal/js/common.js"></script> <script src="/js/blogs_v3/nyt_universal/js/memberTools.js"></script> <script src="/js/common/screen/modifyNavigationDisplay.js"></script> <script src="/js/blogs_v3/nyt_universal/tabsetoverlayrevealer.js"></ script> <script src="/js/common/sharetools/2.0/shareTools.js"></script> <script src="/js/app/save/crossPlatformSave.js"></script> <script src="/js/blogs_v3/nyt_universal/js/blogscrnr.js"></script> Nightmare* * I took these today from the markup for TMagazine, only remaining blog on the “old” code/theme (nyt_universal)
  11. 11. At the NYT, we don’t use … • WordPress Comments: There is an entire team that deals with these for the site globally, in a different system called CRNR • Media: There is another CMS at the Times, Scoop, which stores the images, videos, slideshows, etc • WordPress native post-locking: This only landed in WordPress core in version 3.6, we have yet to reconcile the differences • There is layer for Bylines which is separate from Users: Our users are employees authenticated via LDAP, most post authors don’t actually enter the content themselves
  12. 12. 2008: Live Blogs at the Times • A Blog would create a post and check “Start Live Blogging” • the updates related to the post were stored in custom tables in the database • the APIs for interacting with these tables duplicated tons of WordPress functionality • Custom Post Types didn’t exist until WordPress 3.0 (June 2010) - the NYT code was never rewritten to leverage them (would have required porting the content as well)
  13. 13. Live (actual) Blogs: Dashboards/Dashblogs • A Live Blog would be its own blog in the network, its own set of tables • A special dashboard theme that had hooks to add custom JS/CSS for each individual blog, without baking them into the theme • Making an entirely new site in the network for a 4-hour event is overkill • For every 10 or so new blogs that are added, you are adding 100 new database tables - gross!
  14. 14. 2013: The New Frontier My arrival at the New York Times coincided with the NYT5 project, already in progress
  15. 15. NYT5 • NYT5 “apps” are Git repos that are transformed via Grunt into a new directory structure. You can’t run your repo as a website: it has to be built • Impossible to create a “theme” this time with shared JS and CSS - CSS is SASS, JS is Require. • PHP for shared components has Composer dependencies and uses namespaces - the directories are expanded via Grunt in accordance with PSR-0 Autoloading Standard
  16. 16. require( [‘jquery’], function ($) { $(‘#cool-link’).click(...); } ); require( [‘jquery/1.9’], function ($) { $(‘#cool-link’).click(...); } ); require( [‘jquery/2.0’], function ($) { $(‘#cool-link’).click(...); } ); Require.js
  17. 17. NYT5 Dealbreakers • We can’t just point at WordPress on every request and have our code figure out routing. Routing happens in Apache in NYT5 - most requests get piped to app.php • Because PHP Namespaces are used, WP has to load early and outside of them (global scope) • On the frontend, WP cannot exit prematurely before hitting the framework, which returns the response to the server via 
  18. 18. $wp_query = new WP_Query(); $GLOBALS[‘wp_query’] = ... 
 function wp_thing() { global $wp_query; . . . } GLOBALS
  19. 19. Namespaces namespace ScottCodeFun; class Cool { } function cooler() {} In another file: use ScottCodeFun as Fun; new FunCool(); Funcooler();
  20. 20. Apache NYT5: app.php Route to NYT5 Blogs app - Load initial files
 - Bootstrap WP - Capture WP content - WP complete
  21. 21. NYT5 Advantages • “shared” modules - we inherit the “shell” of the page, which includes: navigation, footer, login, etc. • our nyt5 theme doesn’t need to produce an entire HTML document, just the “content” portion • With WP in global scope, all of its code is available even when we hit the MVC parts of the NYT5 framework. • WP output is captured via an output buffer on load - it’s accessible downstream when the app logic is running.
  22. 22. • We can use Varnish instead of Batcache • our [nytmedia] shortcodes can just output “markers” • The NYT5 logic for articles already knows to replace markers with markup from shared modules • we can lean on code from the NYT5 foundation and shared repos
  23. 23. Cool, we made it work, but …
  24. 24. Bad News for Blogs • Blogs were duplicating Section Fronts, Columns:
 Mark Bittman has column in the paper.
 The column also exists on the web as an article. 
 He contributes to the Diner’s Journal blog. 
 There is a section front for dining. 
 He also has his own NYTimes blog. Why? • Blogs and WordPress were combined in everyone’s mind. So whenever WordPress was mentioned as a solution for anything, the response was: aren’t blogs going away? #dark
  25. 25. First Draft Lens Live Coverage
  26. 26. What if… • Instead of custom tables and dupe’d API code, new object types: events and updates! • To create a new “Live Blog”: create an event, then go to a Backbone- powered screen to add updates • If WP isn’t desired for the front end, it could be the backend for anything that wants a JSON feed for live event data • Using custom post types, building a Live Event UI that looks like the NYT5 theme would be nominal
  27. 27. • Built an admin interface with Backbone to quickly produce content - which in turn could be read from JSON feeds • When saving, the updates post into a service we have called Invisible City • Our first real foray into using the REST API • Our plan was just to be an admin to produce data via self-service URLs What we did
  28. 28. Live Events, the new Live Blogs: Complete Rewrite of 2008 code • nytimes.com/live/{event} and nytimes.com/live/{event}/ {update} • Brand new admin interface: Backbone app that uses the WP-API. Constantly updated filterable stream - Backbone collections that re-fetch on Heartbeat tick • Custom JSON endpoints that handle processes that need to happen on save • Front end served by WordPress for SEO, but data is received by web socket from Invisible City and rendered via React
  29. 29. Responsive on Mobile
  30. 30. Some REST API gotchas…
  31. 31. Most plugins only handle POST • WP-API and Backbone speak REST • REST will send you requests via 
  32. 32. $hook = add_menu_page( ... ); add_action( “load-$hook”, ‘custom_load’ ); function old_custom_load() { if ( ‘POST’ !== $_SERVER[‘REQUEST_METHOD’] ) { return; } ... } function new_custom_load() { if ( ‘GET’ === $_SERVER[‘REQUEST_METHOD’] ) { return; } ... }
  33. 33. HTTP is time-consuming • It is easy to lose track of how many things are happening on the ‘save_post’ hook • Admin needs to be fast • The front end is typically cached, but page generation shouldn’t be bogged down by HTTP requests • Anything which is time-consuming should be offloaded to a separate “process” or request who response you don’t need to handle
  34. 34. wp_remote_post( $url, wp_parse_args( array( ‘timeout’ => 0.01, ‘blocking’ => false ), $args ) ); Fire and Forget* * Stolen from Mark Jaquith’s nginx cache invalidation technique: wp_remote_get( $url, array( ‘timeout’ => 0.01, ‘blocking’ => false, ‘headers’ => array( ‘X-Nginx-Cache-Purge’ => ‘1’ ) ) );
  35. 35. Custom JSON Endpoints for POST • Use fire-and-forget technique on ‘save_post’, instead of waiting for responses inline. You can still log/handle/re-try responses in the separate request. • Most things that happen on ‘save_post’ only need to know $post_id for context, the endpoint handler can call get_post() from there
  36. 36. Register a route: $routes[ '/live-events/(?P<post_id>d+)/sns' ] = array( array( array( $this, 'live_event_sns' ), WP_JSON_Server::CREATABLE ), ); $routes[ '/async-cream-invalidation' ] = array( array( array( $this, 'async_cream' ), WP_JSON_Server::CREATABLE ), );
  37. 37. Handle the route: public function async_cream() { $urls = array_map( 'stripslashes', $_POST['urls'] ); if ( $urls ) { nyt_cream_invalidation( $urls ); } exit(); }
  38. 38. Trigger the process: NYT_Admin_JSON::async_request( '/async-cream-invalidation', array( ‘body’ => array( 'urls' => $urls ) ) );
  39. 39. Custom JSON Endpoints for GET • We do not hit these endpoints on the front-end • We have a storage mount that is fronted via Varnish and Akamai • JSON feeds can show up on the homepage of the NYT to dynamically render “promos” - these have to massively scale
  40. 40. An “Interactive Promo” on an article page
  41. 41. Questions?