{
  "attachments": [],
  "title": "Firefox Test Pilot: The Flattening",
  "tags": [
    "mozilla",
    "testpilot",
    "webdev",
    "django",
    "gulp"
  ],
  "thumbnail": "/uploads/2016/tp-flat.png",
  "year": "2016",
  "month": "09",
  "day": "26",
  "isDir": false,
  "slug": "testpilot-static",
  "type": "entry",
  "date": "2016-09-26T19:00:00.000Z",
  "postName": "2016-09-26-testpilot-static",
  "html": "<p><strong>TL;DR</strong>: Firefox Test Pilot is becoming a statically-generated site from content in flat files. We're moving away from Django and PostgreSQL, and it's been a bit of a journey.</p>\n<!--more-->\n\n\n\n<img class=\"fullwidth\" src=\"/uploads/2016/tp-header.png\" width=\"\" height=\"\">\n\n<nav role=\"navigation\" class=\"table-of-contents\"></nav>\n\n<p>I've been working on <a href=\"https://testpilot.firefox.com/\">Firefox Test Pilot</a> for over a year, but I haven't written about it here before now. Mostly because I've been busy and lazy and <a href=\"/2016/08/31/yak-shaving-habits/\">busily shaving yaks</a>.</p>\n<p>But, there have been big things afoot lately, and I figured they were worth writing about - if only because they're invisible, behind-the-scenes things that nonetheless took a lot of work to accomplish.</p>\n<h2 id=\"be-prepared---but-for-what\">Be prepared - but for what?</h2>\n<p>When we started building Test Pilot last summer, we based the server-side on Django &amp; PostgreSQL. We had assumptions about the future: </p>\n<ul>\n<li><p>We'd need to collect measurements from experiments. </p>\n</li>\n<li><p>We thought experiments would need some active server-side resources provided by the mothership.</p>\n</li>\n<li><p>We'd need to manage user profiles &amp; preferences, so we required sign-in with a Firefox Account.</p>\n</li>\n</ul>\n<p>A year later, these assumptions didn't quite pan out: </p>\n<ul>\n<li><p>Rather than reinvent the wheel by collecting &amp; analyzing measurements ourselves, we took advantage of Google Analytics and the efforts of <a href=\"https://wiki.mozilla.org/Telemetry\">the Firefox Telemetry team</a>.</p>\n</li>\n<li><p>We found it's best to stay out of the way of teams building Test Pilot experiments - let them manage their own services as necessary, rather than be tied to the delivery cadence of the core project.</p>\n</li>\n<li><p>The sign-in requirement turned away many potential users. But, we didn't need accounts to facilitate experiment participation anyway. Our metrics are anonymous and a Firefox add-on manages opt-in.</p>\n<p>Accounts ended up being private data we had to keep secure, but only used for email notifications. We have better ways to manage email subscriptions across Mozilla - so one less wheel to reinvent!</p>\n</li>\n</ul>\n<h2 id=\"didnt-need-that-server-anyway\">Didn't need that server anyway</h2>\n<p>There was just one last reason to use Django &amp; PostgreSQL on Test Pilot: A web-based content management system to update the site without heavyweight server deployments &amp; database migrations. </p>\n<p>But, wait a minute: If the other reasons for a server dropped away - why do we need complex deployments?</p>\n<p>Furthermore, why maintain content <a href=\"https://indieweb.org/database-antipattern\">in a database</a> at all?</p>\n<p>The whole Test Pilot team knows their way around text editors and GitHub - so let's make that our CMS. We can <a href=\"https://github.com/mozilla/testpilot/blob/master/frontend/tasks/pages.js\">bake the whole site</a> from <a href=\"https://github.com/mozilla/testpilot/tree/master/content-src/experiments\">flat files</a>. Deployment is <a href=\"https://github.com/mozilla/testpilot/blob/master/docs/development/deployment.md#producing-a-static-build\">running a build script</a> and <a href=\"https://github.com/mozilla/testpilot/blob/master/circle.yml#L71\">uploading the result</a> to a web server. We get revision control &amp; collaboration along with the rest of the project. And as a security bonus, we stop shipping the tools to change the site along with the deployed site itself.</p>\n<p>None of this is revolutionary. Aaron Swartz's \"<a href=\"http://www.aaronsw.com/weblog/000404\">Bake, Don't Fry</a>\" is over 14 years old: Why fry up a new web page for every visit when you can pre-bake the whole site ahead of time? I used <a href=\"http://blosxom.sourceforge.net/\">Bloxsom</a> back in the day and <a href=\"https://blog.lmorchard.com/2014/10/20/static-blog-generation-with-gulp/\">Gulp bakes this blog now</a>. Static site generators <a href=\"https://www.staticgen.com/\">are numerous &amp; popular</a> - GitHub itself offers <a href=\"https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/\">GitHub Pages</a> powered by <a href=\"https://github.com/jekyll/jekyll\">Jekyll</a>.</p>\n<p>It sounds obvious in retrospect, but it took awhile to realize our site could be stripped down to so little. We assumed we needed all those moving parts - or would need them someday. But, it appears that we can get away with being nearly serverless. And if someday a feature requires more, we can stand up some loosely-coupled microservices - or better yet, find that another team at Mozilla has already solved the problem.</p>\n<h2 id=\"the-show-must-go-on\">The show must go on</h2>\n<p>But, having realized all of this, we couldn't just burn down the site and start over. Because we're working on a vehicle in motion, we've been doing this in increments over the summer: </p>\n<ul>\n<li><p>A couple of months ago, <a href=\"https://github.com/mozilla/testpilot/issues/1035\">we removed the Firefox Accounts requirement</a> and then <a href=\"https://github.com/mozilla/testpilot/issues/1034\">deleted the user data</a> once we were sure we weren't going back.</p>\n</li>\n<li><p>We switched data sources for displaying the number of folks participating in experiments from our own Django API to <a href=\"https://github.com/mozilla/testpilot/issues/1039\">a Telemetry-based resource</a>.</p>\n</li>\n<li><p>I wrote a task to <a href=\"https://github.com/mozilla/testpilot/blob/master/frontend/tasks/content.js#L22\">import content from the current Django API</a>. Then, I wrote <a href=\"https://github.com/mozilla/testpilot/blob/master/frontend/tasks/content.js#L16\">a task to generate JSON</a> from those imported files - a direct static replacement for the Django API.</p>\n</li>\n<li><p>Next, I implemented <a href=\"https://github.com/mozilla/testpilot/blob/master/testpilot/experiments/views.py#L50\">a feature flag in Django</a> to substitute static JSON for content from the database. Thus, we can start managing content in YAML now, maintaining our current infrastructure until we work out a new stripped-down deployment process.</p>\n</li>\n<li><p>Soon, we'll be able to update the site by pushing to the appropriate branch on GitHub. We've got <a href=\"https://github.com/mozilla/testpilot/blob/master/frontend/tasks/pages.js\">tasks to generate stub pages</a> for all the front end app routes. We're also looking into enforcing a requirement to <a href=\"http://micropipes.com/blog//2016/08/31/signing-your-commits-on-github-with-a-gpg-key/\">sign our commits and tags</a> on the way to release.</p>\n</li>\n<li><p>After that, <a href=\"https://github.com/mozilla/testpilot/issues/1306\">we plan to go even further with static site generation</a>. Test Pilot is currently a single page app that pulls content from JSON. But, we can do better by pre-rendering those HTML pages in our build process ahead of time.</p>\n</li>\n</ul>\n<h2 id=\"whats-next\">What's next?</h2>\n<p>There's a funny thing about all of this: If we're successful, no one visiting the site should notice anything different. We're developing some new features &amp; experiments - but all this work to rid our infrastructure of Django &amp; PostgreSQL should ideally be a non-event for anyone visiting the site. This is the least glamorous sort of work one occasionally has to do on a software project - change everything, but don't break anything.</p>\n<p>The real benefit will be that we're able to do a lot of things faster and more easily. For instance, there are now fewer places that need changes to display a new piece of information on a page. We don't have to monitor as many third-party dependencies - <a href=\"https://github.com/mozilla/testpilot/issues/1116\">which we weren't doing very well to begin with</a>.</p>\n<p>Our development stack shrinks from Docker containers with Django &amp; PostgreSQL &amp; Node.js - down to just Node.js v6.2.0. <a href=\"https://github.com/mozilla/testpilot/blob/master/docs/development/quickstart.md\">The whole system has gotten simpler and more direct</a>.</p>\n<p>But, wait, there's more: Along with totally changing our server-side infrastructure, <a href=\"https://github.com/mozilla/testpilot/issues/1307\">we've also rewritten the front end of the site to switch from Ampersand to React &amp; Redux</a>. It should make static site generation easier. It's also eased development on a handful of new features in the past week or so.</p>\n<p>It's a big deal - and another thing that, in retrospect, seems more obvious now than it did a year ago. But, I'm going to save writing about that for my next post.</p>\n<!-- vim: set wrap linebreak nolist wrapmargin=0 textwidth=0 syntax=markdown formatoptions-=t: -->\n",
  "body": "**TL;DR**: Firefox Test Pilot is becoming a statically-generated site from content in flat files. We're moving away from Django and PostgreSQL, and it's been a bit of a journey.\n\n<!--more-->\n\n<img class=\"fullwidth\" src=\"/uploads/2016/tp-header.png\" />\n\n<nav role=\"navigation\" class=\"table-of-contents\"></nav>\n\nI've been working on [Firefox Test Pilot][testpilot] for over a year, but I haven't written about it here before now. Mostly because I've been busy and lazy and [busily shaving yaks](/2016/08/31/yak-shaving-habits/).\n\nBut, there have been big things afoot lately, and I figured they were worth writing about - if only because they're invisible, behind-the-scenes things that nonetheless took a lot of work to accomplish.\n\n## Be prepared - but for what?\n\nWhen we started building Test Pilot last summer, we based the server-side on Django & PostgreSQL. We had assumptions about the future: \n\n* We'd need to collect measurements from experiments. \n\n* We thought experiments would need some active server-side resources provided by the mothership.\n\n* We'd need to manage user profiles & preferences, so we required sign-in with a Firefox Account. \n\nA year later, these assumptions didn't quite pan out: \n\n* Rather than reinvent the wheel by collecting & analyzing measurements ourselves, we took advantage of Google Analytics and the efforts of [the Firefox Telemetry team][telemetry].\n\n* We found it's best to stay out of the way of teams building Test Pilot experiments - let them manage their own services as necessary, rather than be tied to the delivery cadence of the core project.\n\n* The sign-in requirement turned away many potential users. But, we didn't need accounts to facilitate experiment participation anyway. Our metrics are anonymous and a Firefox add-on manages opt-in.\n  \n  Accounts ended up being private data we had to keep secure, but only used for email notifications. We have better ways to manage email subscriptions across Mozilla - so one less wheel to reinvent!\n\n## Didn't need that server anyway\n\nThere was just one last reason to use Django & PostgreSQL on Test Pilot: A web-based content management system to update the site without heavyweight server deployments & database migrations. \n\nBut, wait a minute: If the other reasons for a server dropped away - why do we need complex deployments?\n\nFurthermore, why maintain content [in a database][database] at all?\n\nThe whole Test Pilot team knows their way around text editors and GitHub - so let's make that our CMS. We can [bake the whole site][sitebake] from [flat files][]. Deployment is [running a build script][buildscript] and [uploading the result][uploadweb] to a web server. We get revision control & collaboration along with the rest of the project. And as a security bonus, we stop shipping the tools to change the site along with the deployed site itself.\n\nNone of this is revolutionary. Aaron Swartz's \"[Bake, Don't Fry][baked]\" is over 14 years old: Why fry up a new web page for every visit when you can pre-bake the whole site ahead of time? I used [Bloxsom][] back in the day and [Gulp bakes this blog now][gulp]. Static site generators [are numerous & popular][staticgen] - GitHub itself offers [GitHub Pages][pages] powered by [Jekyll][].\n\nIt sounds obvious in retrospect, but it took awhile to realize our site could be stripped down to so little. We assumed we needed all those moving parts - or would need them someday. But, it appears that we can get away with being nearly serverless. And if someday a feature requires more, we can stand up some loosely-coupled microservices - or better yet, find that another team at Mozilla has already solved the problem.\n\n## The show must go on\n\nBut, having realized all of this, we couldn't just burn down the site and start over. Because we're working on a vehicle in motion, we've been doing this in increments over the summer: \n\n* A couple of months ago, [we removed the Firefox Accounts requirement][fxareq] and then [deleted the user data][deletedata] once we were sure we weren't going back.\n\n* We switched data sources for displaying the number of folks participating in experiments from our own Django API to [a Telemetry-based resource][counts].\n\n* I wrote a task to [import content from the current Django API][import]. Then, I wrote [a task to generate JSON][generatejson] from those imported files - a direct static replacement for the Django API.\n\n* Next, I implemented [a feature flag in Django][featureflag] to substitute static JSON for content from the database. Thus, we can start managing content in YAML now, maintaining our current infrastructure until we work out a new stripped-down deployment process.\n\n* Soon, we'll be able to update the site by pushing to the appropriate branch on GitHub. We've got [tasks to generate stub pages][stubs] for all the front end app routes. We're also looking into enforcing a requirement to [sign our commits and tags][signreq] on the way to release.\n\n* After that, [we plan to go even further with static site generation][staticsite]. Test Pilot is currently a single page app that pulls content from JSON. But, we can do better by pre-rendering those HTML pages in our build process ahead of time.\n\n## What's next?\n\nThere's a funny thing about all of this: If we're successful, no one visiting the site should notice anything different. We're developing some new features & experiments - but all this work to rid our infrastructure of Django & PostgreSQL should ideally be a non-event for anyone visiting the site. This is the least glamorous sort of work one occasionally has to do on a software project - change everything, but don't break anything.\n\nThe real benefit will be that we're able to do a lot of things faster and more easily. For instance, there are now fewer places that need changes to display a new piece of information on a page. We don't have to monitor as many third-party dependencies - [which we weren't doing very well to begin with][dependencies].\n\nOur development stack shrinks from Docker containers with Django & PostgreSQL & Node.js - down to just Node.js v6.2.0. [The whole system has gotten simpler and more direct][quickstart].\n\nBut, wait, there's more: Along with totally changing our server-side infrastructure, [we've also rewritten the front end of the site to switch from Ampersand to React & Redux][reactrewrite]. It should make static site generation easier. It's also eased development on a handful of new features in the past week or so.\n\nIt's a big deal - and another thing that, in retrospect, seems more obvious now than it did a year ago. But, I'm going to save writing about that for my next post.\n\n[dependencies]: https://github.com/mozilla/testpilot/issues/1116\n[quickstart]: https://github.com/mozilla/testpilot/blob/master/docs/development/quickstart.md\n[testpilot]: https://testpilot.firefox.com/\n[buildscript]: https://github.com/mozilla/testpilot/blob/master/docs/development/deployment.md#producing-a-static-build\n[uploadweb]: https://github.com/mozilla/testpilot/blob/master/circle.yml#L71\n[sitebake]: https://github.com/mozilla/testpilot/blob/master/frontend/tasks/pages.js\n[stubs]: https://github.com/mozilla/testpilot/blob/master/frontend/tasks/pages.js\n[signreq]: http://micropipes.com/blog//2016/08/31/signing-your-commits-on-github-with-a-gpg-key/\n[reactrewrite]: https://github.com/mozilla/testpilot/issues/1307\n[staticsite]: https://github.com/mozilla/testpilot/issues/1306\n[featureflag]: https://github.com/mozilla/testpilot/blob/master/testpilot/experiments/views.py#L50\n[generatejson]: https://github.com/mozilla/testpilot/blob/master/frontend/tasks/content.js#L16\n[import]: https://github.com/mozilla/testpilot/blob/master/frontend/tasks/content.js#L22\n[counts]: https://github.com/mozilla/testpilot/issues/1039\n[deletedata]: https://github.com/mozilla/testpilot/issues/1034\n[fxareq]: https://github.com/mozilla/testpilot/issues/1035\n[flat files]: https://github.com/mozilla/testpilot/tree/master/content-src/experiments\n[telemetry]: https://wiki.mozilla.org/Telemetry\n[gulp]: https://blog.lmorchard.com/2014/10/20/static-blog-generation-with-gulp/\n[bloxsom]: http://blosxom.sourceforge.net/\n[jekyll]: https://github.com/jekyll/jekyll\n[pages]: https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/\n[baked]: http://www.aaronsw.com/weblog/000404\n[database]: https://indieweb.org/database-antipattern\n[staticgen]: https://www.staticgen.com/\n\n<!-- vim: set wrap linebreak nolist wrapmargin=0 textwidth=0 syntax=markdown formatoptions-=t: -->\n",
  "parentPath": "./content/posts/archives/2016",
  "path": "2016/09/26/testpilot-static",
  "summary": "TL;DR: Firefox Test Pilot is becoming a statically-generated site from content in flat files. We're moving away from Django and PostgreSQL, and it's been a bit of a journey.",
  "needsBuild": true,
  "prevPostPath": "2016/09/11/poke-the-mongo",
  "prevPostTitle": "Poking the Mongo on Pico-8",
  "nextPostPath": "2016/11/11/heartbroken",
  "nextPostTitle": "Heartbroken"
}