tag:quandyfactory.com,2012-1-29:/2012129 2012-1-29T12:00:00Z Quandy Factory Newsfeed - Blog Quandy Factory is the personal website of Ryan McGreal in Hamilton, Ontario, Canada.. http://quandyfactory.com/blog/91/services-first:_a_better_way_to_build_a_web_application_(draft) 2012-02-02T12:00:00Z Services-First: A Better Way to Build a Web Application (DRAFT) <h3>Introduction</h3> <blockquote> <p>The best way to find yourself is to lose yourself in the service of others.</p> <p>-- Mahatma Gandhi</p> </blockquote> <p>When you're building a web application, it's a powerful design heuristic to build a REST web service API, and build your application on top of your service.</p> <p>The functionality your application needs to perform should all be accessible or at least exposable as web services. This enforces a responsible design approach that will pay real dividends in maintainability, extendability and reduced technical debt.</p> <p>It may also pay unexpected dividends in generating value over and above the principal goal of your web application. By thinking about your application design as a web service, you open up the possibility that the real value you deliver is not what you thought your service was going to be, but some subsidiary or extraneous solution you develop that turns out to be a) useful to others and b scalable/profitable.</p> <h3>The Infamous Yegge Platform Rant</h3> <p>Last October, Google engineer Steve Yegge posted a long, thoughtful rant on his public <a href="https://plus.google.com/110981030061712822816">Google+ page</a> in which he argued that Google "does everything right" - except platforms, which he argues Google still doesn't really understand. Rather, Google provides products, not services.</p> <p>Yegge's argument was directed internally at his fellow Googlers, and he took the essay down after realizing that he had accidentally posted it publicly. Luckily for us, copies are <a href="https://plus.google.com/112678702228711889851/posts/eVeouesvaVX">still available</a> with Yegge's blessing.)</p> <p>Yegge contrasted his former employer, Amazon, which overwhelmingly adopted a services-first approach after founder and CEO Jeff Bezos ordered every department in the company to start exposing its functions to the rest of the company as a web service. According to Yegge:</p> <blockquote> <p>[T]he first big thing Bezos realized is that the infrastructure they'd built for selling and shipping books and sundry could be transformed an excellent repurposable computing platform. So now they have the Amazon Elastic Compute Cloud, and the Amazon Elastic MapReduce, and the Amazon Relational Database Service, and a whole passel' o' other services browsable at aws.amazon.com. </p> </blockquote> <p>According to Yegge, this services-first approach has allowed Amazon to serve as a platform on which other companies build their businesses.</p> <h4>Dogfooding</h4> <p>Operating as a platform drastically increases the accessibility of a company's products and increases that likelihood that someone will build a "killer app" on the platform.</p> <p>In Amazon's case, the killer app is Amazon's own business, which runs on the AWS platform (though a number of third party companies have also built their applications on top of the AWS platform).</p> <p>Yegge stresses the vital importance of building on your own platform: </p> <blockquote> <p>The Golden Rule of Platforms, "Eat Your Own Dogfood", can be rephrased as "Start with a Platform, and Then Use it for Everything."</p> </blockquote> <p>Yegge points out that it might be painful to maintain the discipline to eat your own dogfood, but it is <em>far</em> more painful and expensive to turn a monolithic application into a platform after the fact.</p> <blockquote> <p>If you delay it, it'll be ten times as much work as just doing it correctly up front. You can't cheat. You can't have secret back doors for internal apps to get special priority access, not for ANY reason. You need to solve the hard problems up front.</p> </blockquote> <p>According to Yegge, Amazon went through the painful exercise of transforming itself from a product company into a platform company because of necessity:</p> <blockquote> <p>[I]t took an out-of-band force to make Bezos understand the need for a platform. That force was their evaporating margins; he was cornered and had to think of a way out. But all he had was a bunch of engineers and all these computers... if only they could be monetized somehow... you can see how he arrived at AWS, in hindsight.</p> </blockquote> <h4>Amazon Web Services</h4> <p>Here is how Bezos himself explained the strategy in his latest annual letter to shareholders:</p> <blockquote> <p>Our technologies are almost exclusively implemented as services: bits of logic that encapsulate the data they operate on and provide hardened interfaces as the only way to access their functionality. This approach reduces side effects and allows services to evolve at their own pace without impacting the other components of the overall system. Service-oriented architecture - or SOA - is the fundamental building abstraction for Amazon technologies.</p> </blockquote> <p>Amazon launched its first web service - Elastic Compute Cloud (EC2) - in 2006. Since then, it has followed up with a long list of additional services:</p> <ul> <li>CloudFront - low latency global CDN</li> <li>DevPay - accounts receivable service</li> <li>DynamoDB and SimpleDB - schemaless nosql key/value store</li> <li>ElastiCache - scalable in-memory cache</li> <li>Elastic MapReduce - distributed mapreduce processing for huge datasets</li> <li>Flexible Payment Service (FPS) - digital payment service</li> <li>Mechanical Turk - on-demand human intelligence market</li> <li>Relational Database Service (RDS) - relational database</li> <li>Simple Email Service (SES) - bulk and transactional email service</li> <li>Simple Notification Service (SNS) - send notifications</li> <li>Simple Storage Service (S3) - data storage and retrieval</li> <li>Route 53 - Domain Name System service</li> </ul> <p>None of these service are central to Amazon's core business, which is selling books and other consumer products directly to consumers. However, they all began life internally as essential peripheral or supporting functions. </p> <p>Today, the company can sell these web services publicly because Bezos had the foresight to insist that the company conduct its internal affairs via web services.</p> <p>The company is secretive about how much revenue it earns directly from its suite of web services, but the best estimates are that revenue reached $500 million in 2010 and $750 million in 2011. Web service revenue is expected to reach $1 billion in 2012 and $2.6 billion in 2015.</p> <h3>Services-First</h3> <p>No matter what you build, you will develop expertise in a number of related, peripheral functions that support your primary product. If you develop your product in a services-first fashion, you allow for the possibility to expose those peripheral functions to third parties.</p> <p>No initial product idea survives first contact with the market. Every successful business is successful because it was able to adjust - sometimes wildly - its product and business strategy in response to real-world feedback. Startup gurus call this "pivoting", and a founder's ability to pull it off is decisive in the survival of the business.</p> <p>Pivoting doesn't mean throwing away what you've built and starting over. Rather, it means <em>re-purposing</em> what you've built to reach a more promising market. </p> <p>Many successful startup products started out as components of larger, less focused products. In his book <em>The Lean Startup</em>, entrepreneur Eric Ries calls this the "zoom-in pivot". Other businesses started out assuming they would be selling to one market and ended up attracting interest from a different sector altogether.</p> <p>A services-first approach decouples your product from the platform it's built on, allowing you more flexibility to change it as required. It also allows you to provide the platform itself to clients as an additional source of value.</p> <h3>Designing Your Web Application</h3> <p>So when building your web application, it's a powerful design heuristic to build a web service first, and then build the application on top of it.</p> <h4>Why a Heuristic?</h4> <p>Application design is non-deterministic. There is not one "right" way to do things. Ten different designers given the same problem will create ten different solutions - and any number of them can be 'good enough'.</p> <p>When you can't reason your way to a proof, you have to fall back on a less certain, more exploratory approach. A heuristic is a method of approaching the problem that tends to help you produce good solutions but falls short of being a solution itself. </p> <p>Think of a heuristic as a general rule of thumb rather than a specific prescription.</p> <h4>Design Goals</h4> <p>For the end user, a web service should be:</p> <ul> <li><strong>Useful</strong> - The most elegant web service in the world won't interest anyone if it doesn't solve real problems.</li> <li><strong>Usable</strong> - Similarly, the most useful web service won't be adopted if people can't figure out how to use it.</li> <li><strong>Explorable</strong> - The easier it is to navigate and discover what a service does and how to use it, the more it will be adopted.</li> <li><strong>Accessible</strong> - In the case of a web service, accessibility means a variety of clients can use and interact with it.</li> <li><strong>Combinable</strong> - The purpose of web APIs is so clients can combine data and functionality from a variety of sources to build new things.</li> <li><strong>Flexible</strong> - Your own product is not the only thing that can be built with your platform. Your API should be open enough to allow for use cases you couldn't imagine.</li> </ul> <p>For the web service developer, a web service should offer and/or encourage:</p> <ul> <li>Clean, consistent organization of data and functionality</li> <li>Separation of different layers in the application stack</li> <li>Flexibility to change the application built on it</li> <li>Clarity of abstractions</li> </ul> <p>Finally, the developer is also an end user and will also benefit from usefulness, usability, discoverability and so on.</p> <h4>Design Considerations</h4> <p>With these goals in mind, I propose the following design considerations. Again, since design is non-deterministic, these considerations can help you to make decisions in the open-ended design process that will tend to point you toward a better final product.</p> <h5>Design for Adoption</h5> <p>The entire purpose of an API is for people to use it. If you're not sure how to design something, use this question as a guide: <strong>What will make it easier for users to understand how this works and how to use it?</strong></p> <p>If you force yourself to be a user, you will be more inclined to look at the API from the user's perspective.</p> <h5>Design for Maintainability</h5> <p>Developers spend more time fixing, refactoring and modifying existing code than they spend creating new code. Your API should be designed in such a way that it is easy to dive in and work with existing code. One way to achieve this is through what the Rails developers call <a href="http://en.wikipedia.org/wiki/Convention_over_configuration">convention over configuration</a>. A architecture that builds on established standards will be easier to maintain.</p> <h3>Representational State Transfer (REST)</h3> <p>REST is a style of web service architecture based around clients and servers that closely models the way HTTP works. In a RESTful system, a client makes a request for a resource on a server, and the server issues a response that includes a representation of the resource.</p> <p>The concept was formalized in 2000 by <a href="http://roy.gbiv.com/">Roy Fielding</a>, one of the architects of Hypertext Transfer Protocol (HTTP), in his <a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm">doctoral dissertation</a>. It is not surprising, then, that REST and HTTP mesh very smoothly.</p> <p>A RESTful client-server system is <strong>stateless</strong>, meaning each request against the server contains all the information the server needs to process it; and <strong>cacheable</strong>, in that the server can specify whether and for how long resource representations can be cached either locally on the client or on intermediate servers between the client and the server.</p> <h4>HTTP</h4> <p>Hypertext Transfer Protocol (HTTP) is a stateless protocol based on a client requesting a resource across a network and the server providing a response. As such, an HTTP transaction entails a request and a response. The request goes from the client to the server, and the response goes from the server back to the client.</p> <h4>HTTP Requests and Responses</h4> <p>In HTTP, the client makes an <strong>HTTP request</strong> to the server and the server issues an <strong>HTTP response</strong>. </p> <h5>Requests</h5> <p>An HTTP request has three parts: </p> <ol> <li><p>The request line, which includes the HTTP method, the URL and the HTTP version: <code>GET /users/1 HTTP/1.1</code></p></li> <li><p>One or more optional HTTP headers, which are key/value pairs with metadata about the data being requested and/or provided.</p></li> <li><p>An optional message body, which is data being sent from the client to the server as part of the request. E.g. in a POST request, the message body will include the data that the server should use to create a new resource.</p></li> </ol> <h5>Response</h5> <p>An HTTP response also has three parts:</p> <ol> <li><p>The HTTP <strong>Status Code</strong>, which indicates the status of the requested resource: <code>HTTP/1.1 200 OK</code></p></li> <li><p>One or more optional HTTP headers, which are key/value pairs with metadata about the response being provided.</p></li> <li><p>An optional message body, which is a representation of the resource that was requested (hence the name "Representational State Transfer").</p></li> </ol> <h4>Idempotence</h4> <p>This funny-looking word is really important: a request is <strong>idempotent</strong> if making it multiple times has the same effect as making it just once. Read that again if you have to.</p> <p>A request is <em>not</em> idempotent if issuing it more than once has a different effect than issuing it once. For example, a POST request to add a comment to a document is not idempotent: issuing the POST request twice adds the comment twice, so that the document contains two comments with unique URLs. </p> <h4>URLs and Resources</h4> <p>In a RESTful architecture, each URL represents a <strong>resource</strong>. This is vitally important: a resource is a noun, an object, and not the action performed on it. </p> <p>A RESTful web service has multiple endpoints - one for each resource. (Contrast SOAP, which has only one endpoint and puts everything else - objects, methods, parameters, etc. - into the XML payload.)</p> <p>If you find yourself creating URLs like <code>/create_user</code>, you're doing it wrong. Instead, create a URL like <code>/users</code> and map your user object to that URL. </p> <p>Remember: a resource is a noun, not a verb.</p> <h4>HTTP Methods</h4> <p>If a resource is a noun, the <strong>HTTP Method</strong> is the verb. What you do to the resource with your request depends on what <strong>method</strong> you use. If a URL is an object, the HTTP method is the action you execute on that object. </p> <table> <caption>HTTP Methods</caption> <thead> <tr> <th>Method</th> <th>Action</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>Retrieve a resource</td> </tr> <tr> <td>POST</td> <td>Create a new resource</td> </tr> <tr> <td>PUT</td> <td>Update an existing resource</td> </tr> <tr> <td>DELETE</td> <td>Delete an existing resource</td> </tr> </tbody> </table> <p>In our user example, execute an HTTP POST request on <code>/users</code> to create a new user. The server should respond with the specific URL of the user you created: <code>/users/1</code>. </p> <p>To view a representation of that resource, issue an HTTP GET request on the user's URL: <code>/users/1</code>. </p> <p>To update the user, issue an HTTP POST request on the user's URL with the new user data in the request body. </p> <p>To delete the user, issue an HTTP DELETE request on the user's URL.</p> <h5>GET Method</h5> <p>To retrieve a resource, issue an HTTP GET request. GET requests are idempotent (see below), which means making a GET request multiple times does not cause any change in the resource that is requested.</p> <p>GET requests do not include a message body, but GET responses usually do.</p> <h5>POST Method</h5> <p>To submit data to be processed, issue an HTTP POST request. POST requests require a message body, i.e. the data to be processed.</p> <p>For example, if there is a resource called <code>/articles</code> and you want to add a new article, issue a POST request to <code>/articles</code> with the content. The server should create a new subsidiary URI under <code>/articles</code> - for example, <code>/articles/1</code> - and assign that URL to the content you sent with your POST request.</p> <p>Important note: POST requests are <em>not</em> idempotent, meaning multiple POST requests will create multiple resources with unique identifiers.</p> <h5>PUT Method</h5> <p>To update the resource at an existing URL, issue an HTTP PUT request on that URL. For example, if there is a URL <code>/articles/1</code> and you want to replace the content served at that URL, issue a PUT request to that URL with the new content.</p> <p>Important note: PUT requests <em>are</em> idempotent, so issuing 2 or 5 or 50 identical PUT requests will have the same effect on the resource as issuing just one PUT request. Like POST requests, PUT requests include a message body (the resource to be placed at the URL).</p> <h5>DELETE Method</h5> <p>To remove a resource (and remove its accompanying URL), issue an HTTP DELETE request. DELETE requests should be idempotent, i.e. issuing 1 or 2 or 5 or 50 identical DELETE requests will delete exactly one resource. DELETE requests do not require a message body.</p> <h4>HTTP Headers</h4> <p>Both HTTP requests and responses can include optional <strong>HTTP Headers</strong>, or key/value pairs that supply meta-data about the request and the response. </p> <h5>Accept Header</h5> <p>An HTTP request can include an <code>Accept</code> header that specifies what media types the client will accept in a response.</p> <p>In a REST web service, the request should include an <code>Accept</code> header with the preferred media type - e.g. <code>application/json</code> - and the server should attempt to fulfill the client's preference in its response, given its capabilities.</p> <p>If you are willing or required support multiple formats (e.g. JSON, XML, YAML), the best way for the client to specify what format they prefer is via the <code>Accept</code> header. </p> <p>Here are some examples of Accept headers:</p> <ul> <li><p>JSON: <code>Accept: application/json</code></p></li> <li><p>XML: <code>Accept: application/xml</code></p></li> <li><p>YAML: <code>Accept: application/x-yaml</code></p></li> </ul> <p>Note: YAML has an <code>x-</code> prefix because it is not a formalized MIME type. For thoroughness, you could accept all four possibilities:</p> <pre><code>application/x-yaml, application/yaml, text/x-yaml, text/yaml </code></pre> <h4>HTTP Status Codes</h4> <p>HTTP has a great way of telling the client whether the request was successful or an error has occurred: HTTP status codes. Every HTTP response includes a <code>status</code> header with an <strong>HTTP status code</strong> that indicates the status of the request. </p> <p>This tells the client whether the request was successful or not, and what to expect by way of a response. It's important to send the right status code.</p> <p class="image"><img src="https://farm8.staticflickr.com/7148/6508023065_8dae48a30b.jpg" title="401 Unauthorized (Image Source: Flickr)"><br>401 Unauthorized (Image Source: <a href="https://secure.flickr.com/photos/girliemac/sets/72157628409467125/with/6508022985/">Flickr</a>)</p> <p>I also include HTTP status codes and error descriptions in the response body. A REST purist might call this overkill, but remember that you want to make your API as self-documenting and easy to use as possible. </p> <p>For the purposes of your web service, there are three categories of HTTP status codes you need to worry about: success codes, client error codes, and server error codes.</p> <h5>Success Codes</h5> <p>HTTP success status codes provide useful information about successful requests:</p> <ul> <li><strong>200 OK</strong> - A GET request was successful at retrieving a resource.</li> <li><strong>201 Created</strong> - A POST request was successful at creating a resource.</li> <li><strong>202 Accepted</strong> - This means the request was accepted but the response is not ready. Useful for queued or otherwise deferred processes.</li> </ul> <h5>Client Error Codes</h5> <ul> <li><strong>400 Bad Request</strong> - The request data could not be parsed properly, or a value was missing or invalid.</li> <li><strong>401 Unauthorized</strong> - The user requested an access-restricted resource but authentication either failed or was not provided.</li> <li><strong>404 Not Found</strong> - The user tried to request a resource that does not exist.</li> <li><strong>405 Method Not Allowed</strong> - User tried to execute an HTTP method on a resource that does not allow it. E.g. a POST request against <code>/users/charlie</code> instead of <code>/users</code>.</li> <li><strong>406 Not Acceptable</strong> - The request came with an <code>Accept</code> header for a media type that the server cannot provide in the response.</li> <li><strong>410 Gone</strong> - A previously available resource has been removed permanently.</li> <li><strong>429 Too Many Requests</strong> - Useful if you've rate-limited your API for a given user.</li> </ul> <h5>Server Error Codes</h5> <ul> <li><strong>500 Internal Server Error</strong> - Try to avoid this one. It generally means your web service is broken.</li> <li><strong>501 Not Implemented</strong> - User has tried to send a request method that your server doesn't recognize.</li> <li><strong>503 Service Unavailable</strong> - This tells the client the service disruption is temporary.</li> </ul> <h3>Designing a RESTful Web Service</h3> <h4>Don't Reinvent the Wheel</h4> <p>HTTP was invented to allow clients to request resources from servers across the internet and for those servers to respond with representations of those resources. The entire internet runs on it. It's flexible, powerful and offers a clean abstraction model based on resources and methods.</p> <p>Don't re-implement what HTTP does in an ad-hoc way on top of HTTP. Just use HTTP. </p> <p>Many web services only use HTTP as a network tunneling protocol, and then pass objects, methods and parameters inside an 'envelope' that contains all the information. This type of web service architecture includes SOAP and other Remote Procedure Call (RPC) formats.</p> <p>Web services that actually use HTTP the way it was designed are called <strong>Representative State Transfer</strong> (REST) web services. Unfortunately, REST is widely misunderstood and a lot of web services that call themselves "RESTful" aren't.</p> <h4>Discoverable API</h4> <p>In a RESTful web service, a GET request on a base API URL returns a list of resources available in the API. Critically, each resource has a direct URL. This makes it very easy for developers to step into your API and figure out what is available.</p> <p>Now, you may be thinking you've seen this pattern before. You're right: it's exactly how every single website works. Go to the homepage, and what do you find? Links to the other resources on the website! </p> <p>A RESTful approach makes your URLs hackable. If your API user is looking at a URL like: <code>/cats/miss_mew</code>, they should be able to lop off the <code>/miss_mew</code> and do a GET request against <code>/cats</code> to return the base collection of cats (each with its own URL, of course).</p> <h4>REST Resource/Method Matrix</h4> <p>At a conceptual level, a RESTful web service API is a matrix of resources and methods that exposes the functionality of the service to third party applications. Below is an example of what that matrix might look like.</p> <p>Again, I will repeat that actions are not mapped to URLs. A resource is an object - a noun - and the action inheres to the HTTP method - a verb - not to the URL. </p> <p>As a result, the same resource URL can serve different responses (corresponding with different actions) depending on the HTTP method used.</p> <table> <caption>REST Resource/Method Matrix</caption> <thead> <tr> <th colspan="4">Request</th> <th colspan="3">Response</th> <th rowspan="2">Idempotent</th> </tr> <tr> <th>Resource</th> <th>URL</th> <th>Method</th> <th>Request Data</th> <th>Server Action</th> <th>Response Body</th> <th>Status Code</th> </tr> </thead> <tbody> <tr> <td>Users</td> <td>/users</td> <td>GET</td> <td></td> <td>retrieves list of users</td> <td>list of users and associated URLs</td> <td>200 OK</td> <td class="green">Yes</td> </tr> <tr> <td>Users</td> <td>/users</td> <td>POST</td> <td>user details</td> <td>creates a new user</td> <td>new URL and user representation</td> <td>201 Created</td> <td class="red">No</td> </tr> <tr> <td>User 9001</td> <td>/users/9001</td> <td>GET</td> <td></td> <td>retrieves details for user 9001</td> <td>user representation</td> <td>200 OK</td> <td class="green">Yes</td> </tr> <tr> <td>User 9001</td> <td>/users/9001</td> <td>PUT</td> <td>new user details</td> <td>updates user details</td> <td>updated user representation</td> <td>200 OK</td> <td class="green">Yes</td> </tr> <tr> <td>User 9001</td> <td>/users/9001</td> <td>DELETE</td> <td></td> <td>deletes user 9001</td> <td>returns status</td> <td>200 OK</td> <td class="green">Yes</td> </tr> <tr> <td>User 9001</td> <td>/users/9001</td> <td>GET</td> <td></td> <td>checks that user 9001 does not exist</td> <td>Not found error message</td> <td>404 Not Found</td> <td class="green">Yes</td> </tr> </tbody> </table> <h4>Use JSON</h4> <p>JavaScript Object Notation (JSON) is a data serialization format that delivers an optimal combination of the following criteria:</p> <ul> <li><strong>Lightweight</strong> - less boilerplate means less data transfered across the network</li> <li><strong>Readable</strong> - Humans can easily see and understand the content of a JSON object</li> <li><strong>Flexible</strong> - allows various nestable data types: object (dictionary), array (ordered list), string, integer, float, boolean</li> <li><strong>Interoperable</strong> - every programming language and framework under the sun can generate and consume JSON.</li> <li><strong>Convertable</strong> - JSON can be converted into a native object more easily than XML.</li> </ul> <p>Here's an example of a response to an API base URL GET request, represented in JSON:</p> <pre><code>{ "ok": true, "status_code": 200, "resources": [ { "url": "/authors", "resource": "authors" }, { "url": "/articles", "resource": "articles" }, { "url": "/blogs", "resource": "blogs" }, { "url": "/comments", "resource": "comments" }, { "url": "/events", "resource": "events" } ] } </code></pre> <p>Unless you've got a compelling business or technical reason for using a different media type, stick with JSON.</p> <h4>Search</h4> <p>Since grammatically, "search" can be a noun as well as a verb, it's okay to have a URL called <code>/search</code>. Arguably, the best way to filter the response a server delivers to a GET request on <code>/search</code> is to use a querystring:</p> <pre><code>/search?doctype=article&keyword=hello%20world </code></pre> <p>It is possible to do this without a querystring - something like:</p> <pre><code>/search/doctype/article/keyword/hello%20world </code></pre> <p>But that's silly and pedantic - and arguably wrong, anyway. It's harder for your users to guess and it's harder for you to parse.</p> <h4>Pagination</h4> <p>When your user does a GET request on <code>/api/cats</code>, do they really want a list of 7 million cats? Do you really want to have to serve that and support the processing and bandwidth involved?</p> <p>Again, I don't think there's anything wrong with using querystrings here:</p> <pre><code>/api/cats?offset=101&limit=50 </code></pre> <p>Similarly, I don't think there's anything wrong with using terminology that is familiar to developers who work with databases. </p> <h4>Versioning</h4> <p>Your API is a contract you have made with your users. If you plan to introduce a change to your API that breaks backwards compatibility, you need to do so in such a way that it doesn't break existing clients.</p> <p>There is no easy, obvious way to do this that will make everyone happy, satisfy REST and satisfy the goal of making your API usable and discoverable.</p> <p>There are a few different ways you can version your API, all of which have pros and cons.</p> <h5>Versioning in URL</h5> <p>Many popular web APIs do this: <code>/api/v1/cats</code>. If you roll out a version 2 of your API that would break clients using version 1, you expose it at <code>api/v2/cats</code>.</p> <ul> <li>Pro: it's easy for users to understand and doesn't break existing clients.</li> <li>Con: results in multiple URLs for the same resource; locks versioning into the URL; multiplies API maintenance issues.</li> </ul> <h5>Versioning in Querystring</h5> <p>Some APIs do this: <code>api/cats?v=1</code>.</p> <ul> <li>Pro: default URL doesn't need to include version (defaults to newest version); one URL for each resource.</li> <li>Con: clients that don't explicitly specify a version will break when the API is updated.</li> </ul> <h5>Versioning in Accept header</h5> <p>REST purists recommend putting the version in a custom Accept header: <code>Accept: application/vnd.company.app-v1+json</code></p> <ul> <li>Pro: default URL doesn't need to include version; one URL for each resource; changes do not break clients; version is a formatting issue so it's the "right" place.</li> <li>Con: more esoteric and difficult for developers to discover how it works.</li> </ul> <h4>Use Secure HTTP</h4> <p>I don't want to say too much about security (though I do recommend the <a href="https://www.owasp.org/index.php/REST_Security_Cheat_Sheet">REST Security Cheat Sheet</a> - Draft document from the Open Web Application Security Project (OWASP) as a handy reference), but I will say a quick word in support of Secure HTTP - or HTTPS - rather than plaintext HTTP.</p> <p>HTTPS is a bit slower, but on a web service, you're not making multiple requests to pull down javascripts, CSS, image files and so on, so you can afford to take a small hit for an encrypted connection. </p> <p>The payback in client security is worth it.</p> <h4>Summary of Benefits</h4> <p>It's extremely easy to lose yourself in theory and get sucked into the purity movement (remember XHTML?). If REST doesn't deliver practical results, it doesn't matter how elegant, pure or canonical it claims to be.</p> <p>In short, a RESTful API is discoverable and navigable. With no background knowledge, a client can do a GET request on the root URL to get a list of resources with URLS. It's easy to dive right in and quickly figure out what is available.</p> <h5>Better URLs</h5> <p>Stable, persistent URLs allow for resource bookmarking and better search indexing. </p> <p>Sane, well-named URLs are more descriptive than arcane or arbitrarily named URLs. </p> <p>Hackable URLs allow for better resource discovery.</p> <h5>Developers Understand HTTP</h5> <p>REST is essentially just HTTP, and we already understand HTTP (with an important caveat, below). </p> <p>The API developer doesn't need to waste time and energy (badly) reinventing/re-implementing what HTTP does on top of HTTP.</p> <p>The client doesn't need to waste time and energy learning a bunch of ad hoc RPC protocols.</p> <h5>Our Tools Understand HTTP</h5> <p>Just about every programming language can natively perform an HTTP request and parse the response (with an important caveat, below).</p> <h3>Problems with REST</h3> <p>REST comes with its own issues, of course, and it would be disingenuous to gloss over them.</p> <h4>Requires Proper Understanding of HTTP</h4> <p>Much as I wrote earlier that REST is just HTTP and we all understand HTTP, the honest fact is that we don't understand HTTP <em>all that well</em>.</p> <p>It's true we've all been using it for 20 years now, but for various historical, cultural and educational reasons, there's a constant tension between understanding and using our protocols the way they were designed and knowing just enough to <em>build something that gets the job done</em>.</p> <p>That latter imperative lowers the barrier to entry into web development, but it also leads to the same avoidable mistakes being made over and over again in applications that require a more professional approach: SQL injection, XSS, hacked cleartext passwords, and so on.</p> <p>We've all heard horror stories about web applications that didn't understand HTTP and blew up when a search engine bot innocently crawled a GET request to <code>/delete_user.php?username=Ryan</code> and thereby wiped out the entire database.</p> <p>For a newbie developer who doesn't really understand HTTP very well, a web service with URLs like <code>/api/create_user</code> is more immediately obvious than a web service with URLs like <code>/api/users</code> that require a POST request to create a user.</p> <p>However, this is changing as the benefits of REST become more widely known and more developers start to take advantage of its defining characteristics.</p> <p>One thing is for certain: once you've developed and built on a REST web service, you'll finally understand HTTP inside and out. </p> <h4>Tooling</h4> <p>SOAP advocates argue that mature tooling makes it easy for a service provider to deploy a WSDL and for clients to consume it and execute web service methods as if they were local function calls.</p> <p>Of course, the reality is less rosy: leaky abstractions, incompatible data types, lack of interoperability between, say, a Java WSDL and a C# SOAP client, and so on. </p> <p>In far too many cases, I've had to hand-roll an ad-hoc SOAP client and build my own XML templates when a supposedly push-button WSDL turned out to be platform-dependent. Instead of saving time by consuming a WSDL, I was stuck painstakingly reverse-engineering the RPC formatting details that the interface tried unsuccessfully to hide.</p> <p>Still, SOAP has that enterprise-friendly turnkey appeal, even if the delivery doesn't fulfill the promise. </p> <p>REST, on the other hand, is sorely lacking in push-button tooling - on both the framework development and client side.</p> <p>Even tools that ought to know better tend to abstract away the HTTP methods from the developer and generically treat every request as a GET request - unless it comes with form data or hits a kludgy <code>/process_form</code> kind of an URL.</p> <p>We've come a long way from the <code>cgi-bin</code> form processing scripts of the '90s, but we still have a way to go. The good news is that this changing as developers come to recognize the benefits that come from understanding and using HTTP the way it was designed.</p> <h5>Frameworks</h5> <p>Most web application frameworks weren't developed with HTTP in mind, but rather with the minimal subset of HTTP that that is comprised of viewing web pages and filling out forms. Data transfer and processing, even in web applications designed for human users, tends unconsciously to follow an RPC model.</p> <p>There are already a few that have embraced the full functionality of HTTP more deeply. </p> <p>I'm not that familiar with Ruby, but I understand <a href="http://rubyonrails.org/">Rails 3</a> is designed to <a href="http://guides.rubyonrails.org/v2.3.8/routing.html">work RESTfully</a> right out of the box with its routing DSL. </p> <p>Similarly, the lightweight <a href="http://www.sinatrarb.com/">Sinatra</a> framework understands resources and methods natively and simply:</p> <pre><code>require 'sinatra' get '/users' do # show users end get '/users/1337' do # show a user end post '/users' do # create a user end put '/users/1337' do # update a user end delete '/users/1337' do # delete a user end </code></pre> <p>In Python, <a href="https://www.djangoproject.com/">Django</a> is not RESTful by design has some good <a href="http://django-rest-framework.org/">REST plugins</a>. </p> <p>The lightweight Python framework I use, <a href="http://webpy.org/">web.py</a>, is REST-friendly at its core: resources map to classes and HTTP methods map to class methods.</p> <pre><code>import web app = web.application(urls, globals()) urls = ('/users/(.*)', 'User') class User(object): def GET(self, name): # get a resource def POST(self, name): # create a resource def PUT(self, name): # update a resource def DELETE(self, name): # delete a resource </code></pre> <p>In PHP, you've got <a href="http://framework.zend.com/manual/en/zend.rest.server.html">Zend_REST</a>, <a href="http://www.recessframework.org/">Recess</a>, <a href="http://peej.github.com/tonic/">Tonic</a> and others that understand and support RESTful API design.</p> <p>The .Net framework has <a href="http://msdn.microsoft.com/en-us/netframework/aa663324">Windows Communications Foundation</a> (WCF). Java has <a href="http://www.playframework.org/">Play</a> <a href="http://www.restlet.org/">RESTlet</a>, <a href="https://jersey.dev.java.net/">Jersey</a> and so on.</p> <h5>Client Tools</h5> <p>Nearly every language understands HTTP, but too many default HTTP modules understand HTTP in a crippled, semi-literate way that reflects how most browsers use HTTP: fetch pages or fill out forms.</p> <p>In Python, there are no less than three HTTP libraries that ship with the language: <code>urllib</code>, <code>urllib2</code> and <code>httplib</code> - and none of them make the full expressiveness of HTTP particularly accessible.</p> <p>Fortunately, third party developers have stepped in with libraries like <a href="https://code.google.com/p/httplib2/">httplib2</a> and <a href="https://code.google.com/p/httplib2/">requests</a>, which do a much better job of exposing all the HTTP methods in a sane, consistent manner.</p> <p>Similarly, REST-aware clients are appearing in other languages as well. </p> <p>If you're a .net shop, you've got tools like <a href="http://restsharp.org/">RestSharp</a>. </p> <p>Of course, JavaScript understands HTTP natively, perhaps better than some other, more "mature" and "full-featured" languages - possibly because JavaScript lives in browsers and breathes HTTP.</p> <pre><code>var xmlhttp=new XMLHttpRequest(); // GET request xmlhttp.open("GET", "/users", true); xmlhttp.send(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { alert('GET request successful'); // response is in xmlhttp.responseText } } // POST request var jsonObject = {"username": "ryan", "fullName": "Ryan McGreal"}; var jsonString = JSON.stringify(jsonObject); xmlhttp.open("POST", "/users", true); xmlhttp.setRequestHeader("Content-Type","application/json"); xmlhttp.send(jsonString); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { alert('POST request successful'); // response is in xmlhttp.responseText } } // PUT request var jsonObject = {"username": "ryan", "fullName": "Ryan G. McGreal"}; var jsonString = JSON.stringify(jsonObject); xmlhttp.open("PUT", "/users/ryan", true); xmlhttp.setRequestHeader("Content-Type","application/json"); xmlhttp.send(jsonString); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { alert('PUT request successful'); // response is in xmlhttp.responseText } } // DELETE request xmlhttp.open("DELETE", "/users/ryan", true); xmlhttp.send(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { alert('DELETE request successful'); // response is in xmlhttp.responseText } } </code></pre> <p>JavaScript also has a panoply of frameworks and libraries that make HTTP requests even easier:</p> <pre><code>// yay jQuery! $.get("/users", function(response){ alert('GET request successful'); }); $.post("/users", data, function(response){ alert('POST request successful'); }); $.put("/users/ryan", data, function(response){ alert('PUT request successful'); }); $.delete("/users/ryan", function(response){ alert('PUT request successful'); }); </code></pre> <p>So the tools for creating and consuming REST web services are steadily improving, while the tools for creating and consuming SOAP web services are over-engineered and IMHO over-rated.</p> <p>In the meantime, REST makes up in discoverability and navigability what it lacks in automated tooling.</p> <h5>Browsers</h5> <p>Of all our tools for working with HTTP, the most universal is the browser. It's the easiest way to browse a web service API, particularly if it works like the web itself. After all, the URL is just a resource on a server accessible via HTTP, and browsers are specifically designed to make HTTP requests and present the responses to users.</p> <p>However, like other established HTTP-based tools, most browsers are only good at GET and POST requests, not PUT or DELETE requests. Even then, POST requests generally require an HTML form that the user can fill in and submit. </p> <p>In addition, most browsers are not good at rendering representations in formats other than HTML or XML.</p> <p>I use Firefox as my main browser (yes, I'm old-fashioned), and I've discovered a few add-ons that make exploring web APIs a lot easier:</p> <ul> <li><a href="https://addons.mozilla.org/en-US/firefox/addon/httpfox/?src=search">HttpFox</a> - an HTTP analyzer.</li> <li><a href="https://addons.mozilla.org/en-US/firefox/addon/jsonview/?src=search">JSONView</a> - pretty-prints JSON response objects in the browser window.</li> <li><a href="https://addons.mozilla.org/en-US/firefox/addon/modify-headers/?src=search">Modify Headers</a> - Add, modify and filter the HTTP request headers sent to a web server.</li> <li><a href="https://addons.mozilla.org/en-US/firefox/addon/restclient/?src=search">RESTClient</a> - Visit and test REST/WebDav services.</li> </ul> <h4>REST is a Spectrum</h4> <p>Ultimately, it makes sense to talk about an API being <em>more</em> or <em>less</em> RESTful and you probably shouldn't kill yourself to reach 100.00000%.</p> <p>Like anything else, REST purity is subject to diminishing returns. If you get the important stuff correct - URLs are resources, methods are actions, representations include URLs - you'll get to enjoy the main benefits of a REST design approach without tearing your hair out. </p> <p>For each choice, ask yourself whether the benefit of going more RESTful justifies the cost. Keep in mind that your goal is to make your API as usable and discoverable as possible.</p> <h3>References</h3> <ul> <li><p><a href="http://nordsc.com/ext/classification_of_http_based_apis.html">Classification of HTTP-based APIs</a></p> <p><a href="http://nordsc.com/ext/classification_of_http_based_apis.html">http://nordsc.com/ext/classification_of_http_based_apis.html</a></p></li> <li><p><a href="http://martinfowler.com/articles/richardsonMaturityModel.html">Richardson Maturity Model</a> - A model (developed by Leonard Richardson) that breaks down the principal elements of a REST approach into three steps. These introduce resources, http verbs, and hypermedia controls.</p> <p><a href="http://martinfowler.com/articles/richardsonMaturityModel.html">http://martinfowler.com/articles/richardsonMaturityModel.html</a></p></li> <li><p><a href="http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven">REST APIs must be hypertext-driven</a> - Roy Fielding's attempt to get people to understand what he was talking about when he defined REST.</p> <p><a href="http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven">http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven</a></p></li> <li><p><a href="https://www.youtube.com/apigee#p/c/5/R8SIxZVaai4">RESTful API Design - Pragmatic REST</a> - Webinar by Apigee.</p> <p><a href="https://www.youtube.com/apigee#p/c/5/R8SIxZVaai4">https://www.youtube.com/apigee#p/c/5/R8SIxZVaai4</a></p></li> <li><p><a href="https://www.owasp.org/index.php/REST_Security_Cheat_Sheet">REST Security Cheat Sheet</a> - Draft document from the Open Web Application Security Project (OWASP)</p></li> <li><p><a href="https://www.ibm.com/developerworks/webservices/library/ws-restful/">RESTful Web Services: The Basics</a> - IBM developerWorks article</p> <p><a href="https://www.ibm.com/developerworks/webservices/library/ws-restful/">https://www.ibm.com/developerworks/webservices/library/ws-restful/</a></p></li> <li><p><a href="http://stackoverflow.com/a/1619677/682547">General Principles for Good URI Design</a> - StackOverflow answer</p> <p><a href="http://stackoverflow.com/a/1619677/682547">http://stackoverflow.com/a/1619677/682547</a></p></li> </ul> Ryan McGreal 2 http://quandyfactory.com/blog/89/python_string_formatting_with_dictionaries 2012-01-04T12:00:00Z Python String Formatting With Dictionaries <p>My mind is duly blown. I never realized that the traditional printf-style string formatting in Python - the kind that uses the % operator - supports the use of a dictionary as well as a tuple.</p> <p>The following works:</p> <pre><code>&gt;&gt;&gt; data = { 'title': 'Python String Formatting With Dictionaries', 'author': 'Ryan McGreal', 'summary': 'In Python, you can use dictionaries instead of tuples to populate values via classic string formatting.', 'content': '&lt;p&gt;My mind is duly blown...', 'author_bio': 'Ryan McGreal lives in Hamilton with his family and works as a programmer and writer.', } &gt;&gt;&gt; template = """&lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;title&gt;<span style="color: red">%(title)s</span>&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;article&gt; &lt;header&gt; &lt;hgroup&gt; &lt;h1&gt;<span style="color: red">%(title)s</span>&lt;/h1&gt; &lt;h2&gt;By <span style="color: red">%(author)s</span>&lt;/h2&gt; &lt;h3&gt;<span style="color: red">%(summary)s</span>&lt;/h3&gt; &lt;hgroup&gt; &lt;/header&gt; <span style="color: red">%(content)s</span> &lt;footer&gt; <span style="color: red">%(author_bio)s</span> &lt;/footer&gt; &lt;/article&gt; &lt;/body&gt; &lt;/html&gt;""" &gt;&gt;&gt; print template % data </code></pre> <p>As an ultra-lightweight templating engine, this is pretty sweet: it's fast, simple, supports Unicode, and has no third-party dependencies.</p> Ryan McGreal 2 http://quandyfactory.com/blog/65/designing_a_restful_web_application 2011-09-30T12:00:00Z Designing a RESTful Web Application <h3>Introduction</h3> <p>I'm working on a couple of projects that involve building a web service, and I decided early on that because of our business constraints - having to communicate with a variety of different systems of varying levels of sophistication - it made sense to keep the web service as simple and accessible as possible.</p> <p>That pointed me toward designing a RESTful web service that transmits data in a simple format over straight HTTP. After all, just about any programming language imaginable can make an HTTP request. I also decided to go with JSON for the data format, in part because I've been <a href="/blog/50/couchdb_working_notes">experimenting lately with CouchDB</a> and appreciate both the simplicity and flexibility of JSON and the fact that you can find a JSON parser for any language.</p> <p>This blog entry is my attempt to get all the concepts of RESTful web service design straight. There's a good chance that some of this information is wrong; and if you notice something, please <a href="mailto:ryan@quandyfactory.com">let me know about it</a>. I'll investigate your argument and update the essay as applicable.</p> <p>With that in mind, here we go.</p> <h3>Representational State Transfer</h3> <p>Representational State Transfer, or REST, is a model for designing networked software systems based around clients and servers. In a RESTful system, a client makes a <strong>request</strong> for a <strong>resource</strong> on a server, and the server issues a <strong>response</strong> that includes a representation of the resource.</p> <p>The concept was formalized in 2000 by Roy Fielding, one of the architects of Hypertext Transfer Protocol (HTTP), about more which below, in his doctoral dissertation. It is not surprising, then, that REST and HTTP mesh very smoothly.</p> <p>A RESTful client-server system is <strong>stateless</strong>, meaning each request against the server contains all the information the server needs to process it; and <strong>cacheable</strong>, in that the server can specify whether and for how long resource representations can be cached either locally on the client or on intermediate servers between the client and the server.</p> <h3>Hypertext Transfer Protocol</h3> <p>Hypertext Transfer Protocol (HTTP) is a stateless protocol based on a client requesting a resource across a network and the server providing a response. As such, an HTTP transaction entails a <strong>request</strong> and a <strong>response</strong>. The request goes from the client to the server, and the response goes from the server back to the client.</p> <h4>HTTP Requests</h4> <p>An HTTP request has three parts:</p> <ol> <li><p>The <strong>request</strong> line, which includes the HTTP method (or "verb"), the uniform resource identifier (URI), and the HTTP version. E.g.</p> <p><code>GET /article/1/ HTTP/1.1</code></p></li> <li><p>One or more optional <a href="http://en.wikipedia.org/wiki/List_of_HTTP_headers">HTTP headers</a>, which are key/value pairs that characterize the data being requested and/or provided.</p></li> <li><p>An optional <strong>message body</strong>, which is data being sent from the client to the server as part of the request.</p></li> </ol> <h4>HTTP Responses</h4> <p>An HTTP response also has three parts:</p> <ol> <li><p>The <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">HTTP status code</a>, indicating the status of the requested URI, e.g.</p> <p><code>HTTP/1.1 200 OK</code></p></li> <li><p>One or more optional <strong>HTTP headers</strong>, which are key/value pairs that characterize the data being provided.</p></li> <li><p>An optional <strong>message body</strong>, which is the data being returned to the client in response to the request.</p></li> </ol> <h3>Resources</h3> <p>HTTP deals in <strong>resources</strong>. Each URI points to a resource on the server. Think of URIs as nouns, not verbs, with one URI for each resource. </p> <p>For example, if you want to add the ability to create an article, it might be tempting to create a URI called <code>/create_article</code>. This is wrong, because it conflates the object (the resource) and the action (creation). Instead, it makes more sense to have a resource called <code>/article</code> and a <strong>method</strong> that lets you create articles.</p> <h3>HTTP Methods</h3> <p>HTTP defines several methods, or "verbs", to execute on a resource: <code>HEAD</code>, <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, <code>TRACE</code>, <code>OPTIONS</code>, <code>CONNECT</code>, and <code>PATCH</code>. However, the following four are most commonly used in web services:</p> <h4>GET Method</h4> <p>To retrieve a resource, issue an HTTP <strong>GET</strong> request. GET requests are <em>idempotent</em> (see below), which means making a GET request multiple times does not cause any change in the resource that is requested. </p> <p>GET requests do not include a message body, but GET responses usually do.</p> <h4>POST Method</h4> <p>To submit data to be processed, issue an HTTP <strong>POST</strong> request. POST requests require a message body, i.e. the data to be processed. </p> <p>For example, if there is a resource called <code>/article</code> and you want to add a new article, issue a POST request to <code>/article</code> with the content. The server will create a new <em>subsidiary</em> URI under <code>/article</code> - for example, <code>/article/9001</code> - and assign that URI to the content you sent with your POST request.</p> <p>Important note: POST requests are <em>not</em> idempotent (see below), meaning multiple POST requests will create multiple resources with unique identifiers.</p> <h4>PUT Method</h4> <p>To place content at an existing resource, issue an HTTP <strong>PUT</strong> request. For example, if there is a URI <code>/article/9001</code> and you want to replace the content served at that URI, issue a PUT request to that URI with the new content.</p> <p>Important note: PUT requests <em>are</em> idempotent, i.e. issuing 1 or 5 or 50 identical PUT requests will have the same effect on the resource. <strong>PUT</strong> requests must include a message body (the resource to be placed at the URL). </p> <h4>DELETE Method</h4> <p>To remove a resource (and remove its accompanying URI), issue an HTTP <strong>DELETE</strong> request. DELETE requests should be idempotent, i.e. issuing 1 or 5 or 50 identical DELETE requests will delete exactly one resource. DELETE requests do not require a message body.</p> <h4>Idempotence</h4> <p>This funny-looking word is crucial to designing an effective web service. A request is <strong>idempotent</strong> if issuing it more than once does not change the resource state beyond issuing it just once. Read that again if you have to.</p> <p>For example, a DELETE request is idempotent if the first request deletes a resource at a URI, and the second request does nothing because the resource at that URI is already deleted. </p> <p>For another example, a PUT request is idempotent if the first request updates a resource at a URI, and the second request updates the same resource in the same way at the same URI.</p> <p>A request is <em>not</em> idempotent if issuing it more than once <em>does</em> change the resource. For example, a POST request to add a comment to a document is not idempotent, if issuing the POST request twice adds the comment twice (so that the document contains two identical comments with separate URLs). </p> <h4>PUT vs. POST</h4> <p>My original understanding of HTTP methods was that you would use PUT to create a resource and POST to update it. This seems in keeping with common sense, but it breaks down when you apply the all-important filter of idempotence.</p> <p>An interesting discussion on Stack Overflow <a href="http://stackoverflow.com/questions/630453/put-vs-post-in-rest">tackles this issue</a>, but what convinced me to change my mental model was this observation:</p> <blockquote> <p>POST creates a child resource, so POST to <code>/items</code> creates a resources that lives under the <code>/items</code> resource. Eg. <code>/items/1</code>.</p> <p>PUT is for creating or updating something with a known URL. </p> </blockquote> <p>This collision between the inclination to regard PUT as creating and POST as updating is a significant source of confusion about how to well-design a RESTful system, and deserves more attention.</p> <p>Furthermore, it's tempting to assume that the HTTP verbs line up precisely with the SQL CRUD verbs, but while they're superficially similar, they're not identical. Treating them as such leads to this kind of gotcha. </p> <p>It's important to keep the logic of HTTP methods separate from the logic of SQL queries, and to develop specialized appropriate logic between the two domains that ensures the data processing on the server produces responses that satisfy the requirements of the HTTP methods (particularly in respect to idempotence).</p> <h3>Conceiving the Web Service: A Resource/Method Table</h3> <p>At a conceptual level, a RESTful web service API is a matrix of resources and methods that exposes the functionality of the service to third party applications. Below is an example of what that matrix might look like. </p> <p>Again, note well that actions are not mapped to URIs. A resource is an <em>object</em>, a <em>noun</em>, and the action inheres to the HTTP Verb, not to the URI. As a result, the same resource URI can serve different responses (corresponding with different actions) depending on the HTTP Verb.</p> <table> <caption>REST Resource/Action Matrix</caption> <thead> <tr> <th colspan="4">Request</th> <th rowspan="2">Server Action</th> <th rowspan="2">Response</th> <th rowspan="2">Idempotent</th> </tr> <tr> <th>Resource</th> <th>Parameters</th> <th>Method</th> <th>Data</th> </tr> </thead> <tbody> </tr> <tr> <td>/article</td> <td></td> <td>POST</td> <td>article details</td> <td>creates a new article</td> <td>returns confirmation and id</td> <td class="red">No</td> </tr> <tr> <td>/article</td> <td>/id</td> <td>GET</td> <td></td> <td>gets article details</td> <td>returns article details</td> <td class="green">Yes</td> </tr> <tr> <td>/article</td> <td>/id</td> <td>PUT</td> <td>new article details</td> <td>updates article details</td> <td>returns confirmation and updated article details</td> <td class="green">Yes</td> </tr> <tr> <td>/article</td> <td>/id</td> <td>DELETE</td> <td></td> <td>deletes an article </td> <td>returns confirmation of deleted article</td> <td class="green">Yes</td> </tr> </tbody> </table> <p>This is the RESTful way to organize a web service: URIs are objects and HTTP Verbs are actions performed on those objects.</p> <h3>HTTP Response Data Formats</h3> <p>Every web server is also a RESTful web service, accepting GET and POST requests to particular resources, and then performing actions and serving data in response.</p> <p>A conventional web server delivers its data in HTML format (<code>text/html</code>), with related Javascript (<code>text/jss</code>), CSS (<code>text/css</code>) and image files. HTML is an excellent format for marking up textual data for human use, but it has very limited expressive power for structuring data beyond simple documents.</p> <p>The most common formats used to transmit structured data across HTTP are <strong>XML</strong> and <strong>JSON</strong>, with an honourable mention for <strong>YAML</strong>.</p> <h4>XML</h4> <p><a href="http://www.w3.org/XML/">XML</a>, or eXtensible Markup Language, is a markup (i.e. tag) based syntax based on SGML for formatting structured, text-based data. XML is a format in which to create domain specific markup languages that define particular data structures. </p> <p>For example, <a href="http://en.wikipedia.org/wiki/RSS">RSS</a> and <a href="http://en.wikipedia.org/wiki/Atom_%28standard%29">Atom</a> are XML language standards defined to structure documents published to websites so that the documents can be 'syndicated' to feed readers and third party sites for display.</p> <p>Likewise, the default underlying structure of Microsoft Office documents since Office 2007 is an XML language called <a href="http://en.wikipedia.org/wiki/Office_Open_XML">OOXML</a>.</p> <p>XML structures data by defining elements, properties, data types and allowable nesting rules in an XML schema called a <a href="http://en.wikipedia.org/wiki/Document_Type_Definition">Document Type Definition</a>, or DTD. </p> <p>A given XML document specifies which DTD schema should define its structure with a <a href="http://en.wikipedia.org/wiki/Document_Type_Declaration">Document Type Declaration</a>, or DOCTYPE.</p> <p>A given XML document can reference multiple DTDs by using namespaces.</p> <p>Here is a sample XML file containing contact information about a person, taken from <a href="http://en.wikipedia.org/wiki/JSON">Wikipedia</a>.</p> <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;Person&gt; &lt;firstName&gt;John&lt;/firstName&gt; &lt;lastName&gt;Smith&lt;/lastName&gt; &lt;age&gt;25&lt;/age&gt; &lt;address&gt; &lt;streetAddress&gt;21 2nd Street&lt;/streetAddress&gt; &lt;city&gt;New York&lt;/city&gt; &lt;state&gt;NY&lt;/state&gt; &lt;postalCode&gt;10021&lt;/postalCode&gt; &lt;/address&gt; &lt;phoneNumber type="home"&gt;212 555-1234&lt;/phoneNumber&gt; &lt;phoneNumber type="fax"&gt;646 555-4567&lt;/phoneNumber&gt; &lt;/Person&gt; </code></pre> <p>XML must be: </p> <ul> <li><p><strong>Well-formed</strong> - elements are properly nested, tags are properly closed and match case, special characters are escaped, and Unicode characters are encoded; and </p></li> <li><p><strong>Valid</strong> - its elements and attributes match the rules defined in the DTD.</p></li> </ul> <p>An XML Language called XSLT can be used to map XML documents into other markup languages, e.g. HTML.</p> <p>XML is in wide use in applications that transfer structured data over the internet.</p> <h4>JSON</h4> <p><a href="http://json.org/">JSON</a>, or "JavaScript Object Notation", is a lightweight data format introduced in 2001 by Douglas Crockford.</p> <p>Pronounced "Jason", JSON is based on JavaScript object literal notation, a syntax for creating objects in JavaScript by literally describing their properties and methods. Here is the JSON equivalent to the XML code in the previous section.</p> <pre><code>{ "firstName": "John", "lastName": "Smith", "age": 25, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021" }, "phoneNumber": [ { "type": "home", "number": "212 555-1234" }, { "type": "fax", "number": "646 555-4567" } ] } </code></pre> <p>While the XML above contained 367 characters (not including indentation), the equivalent JSON contains only 272 characters - only three-quarters as large.</p> <p>JSON supports <strong>lists</strong> (ordered sets of values) and <strong>dictionaries</strong> (unordered collections of key/value pairs) with arbitrary nesting and various data types: <strong>number</strong>, <strong>string</strong>, <strong>boolean</strong>, <strong>list<em>, *</em>object</strong> and <strong>null</strong>. </p> <p>Like XML, JSON is also in wide use in applications that transfer structured data over the internet.</p> <p>A major advantage over XML is that the syntax is much simpler and less verbose, which makes it lighter across networks as well as more human-readable.</p> <p>Mature JSON parsers are available for a wide range of programming languages in addition to JavaScript. Python, for example, <a href="http://docs.python.org/library/json.html">includes a json parser</a> as part of its standard library (as of version 2.6; earlier versions can use the third-party <a href="http://pypi.python.org/pypi/simplejson/">simplejson</a> library). </p> <p>A decent JSON parser converts an object back and forth between the programming language's native data types and their JSON equivalents. That way, an application can receive a JSON object, convert it into a native object, process it natively, and then convert the final result back to JSON to be dispatched elsewhere.</p> <h4>YAML</h4> <p><a href="http://www.yaml.org/">YAML</a>, pronounced to rhyme with "camel", is a recursive acronym meaning "YAML Ain't Markup Language". Whereas JSON is a more minimal data format than XML, YAML takes minimalism to an extreme, eschewing quotation marks, brackets and curly braces altogether in favour of significant indentation and line breaks.</p> <p>The YAML equivalent to the XML and JSON contact examples above would be:</p> <pre><code>firstName: John lastName: Smith age: 25 address: streetAddress: 21 2nd Street city: New York state: NY postalCode: 10021 phoneNumber: - type: home number: 212 555-1234 - type: fax number: 646 555-4567 </code></pre> <p>That works out to just 239 characters - including the significant white space.</p> <h3>REST Best Practices</h3> <p>A RESTful web API ought to be <em>discoverable</em> by its users. One crucial way to do that is by making sure that each resource in your API includes URLs to subsidiary URLs.</p> <p>Here is an example, using JSON:</p> <pre><code>{ "articles": [ { "title": "My First Baguette", "description": "After a month of reading about how to make baguettes, I finally took the plunge today.", "date_published": "2011-07-11", "url": "http://quandyfactory.com/blog/81/my_first_baguette" }, { "title": "Tenn. Passes Controversial Lawnmower Theft Bill", "description": "The lawnmowing industry has successfully lobbied the Tennessee State Government to pass a groundbreaking law making it a criminal offence to loan your lawnmower to a neighbour.", "date_published": "2011-06-02", "url": "http://quandyfactory.com/blog/78/tenn_passes_controversial_lawnmower_theft_bill" } ] } </code></pre> <p>When you do this, you make it easy for API users to discover your API structure and functionality without having to keep referring to obscure documentation.</p> <hr /> <p><em>Special thanks to <a href="http://socialtech.ca/ade">Adrian Duyzer</a> for reading a draft of this essay and setting me straight on the respective roles of PUT and POST.</em></p> Ryan McGreal 2 http://quandyfactory.com/blog/81/my_first_baguette 2011-07-11T12:00:00Z My First Baguette <p>I got up at 5:00 AM this morning to make baguette. First of all, I made a <a href="http://en.wikipedia.org/wiki/Pre-ferment">poolish</a>, which is a pre-ferment made from flour, yeast and water. I made this batch and left it on the printer in the basement to rise slowly over the day.</p> <p class="image"> <img src="/static/images/baguette_01_poolish.jpg" alt="Fresh poolish set to ferment on our basement printer" title="Fresh poolish set to ferment on our basement printer"><br> <em>Fresh poolish set to ferment on our basement printer</em></p> <p>I came home after work to a mature bowl of poolish that had the consistency of the sticky goo that kids get in an egg-shaped plastic container and which invariably ends up getting ground into the carpet, staining the cushions and stuck to the cat's hair.</p> <p class="image"> <img src="/static/images/baguette_02_poolish_fermented.jpg" alt="The poolish after 12 hours of fermenting" title="The poolish after 12 hours of fermenting"><br> <em>The poolish after 12 hours of fermenting</em></p> <p class="image"> <img src="/static/images/baguette_03_poolish_fermented_closeup.jpg" alt="Close-up of the fermented poolish" title="Close-up of the fermented poolish"><br> <em>Close-up of the fermented poolish</em></p> <p>I mixed up a batch of dough using the poolish, more flour, water, yeast and salt, and kneaded it for around 15 minutes. I coated the dough in oil, put it in a covered bowl and left it to rise for an hour or so.</p> <p>Once it had risen, I split it into four pieces, pre-shaped them into ovals, wrapped them in cellophane and left them for another twenty minutes.</p> <p>Then I unwrapped them, rolled them out into baguettes, and arranged them in a makeshift couche, or "bed", which is supposed to be a heavy linen cloth that keeps the loaves in shape as they undergo their second fermentation.</p> <p class="image"> <img src="/static/images/baguette_04_couche.jpg" alt="My makeshift couche, a red hand towel that turned out to be rather on the sticky side" title="My makeshift couche, a red hand towel that turned out to be rather on the sticky side"><br> <em>My makeshift couche, a red hand towel that turned out to be rather on the sticky side</em></p> <p>I ran into trouble getting the loaves out of the couche and onto the pan, as they tended to stick to the material, even though I had floured it beforehand. As a result, one of the loaves ended up looking rather like that bulbous <a href="http://lotr.wikia.com/wiki/Gothmog_%28Orc%29">Orc Lieutenant</a> from the Return of the King movie.</p> <p>In any case, I cranked the oven to 450 degrees and added a pan of water for steam. As the oven heated up, I wondered what the hell I was thinking baking in the middle of July - "hell" being the operative word. (Note to self: next time, hold off on learning to bake a new kind of bread until September.)</p> <p>I baked the loaves for around 25 minutes. When they came out, the one looked like an orc but the best looking one looked almost like an actual baguette.</p> <p class="image"> <img src="/static/images/baguette_05_cooked.jpg" alt="Four cooked baguettes, at least one of which actually looks like a baguette" title="Four cooked baguettes, at least one of which actually looks like a baguette"><br> <em>Four cooked baguettes, at least one of which actually looks like a baguette</em></p> <p>Finally, the baguettes cooled and I was ready for my first taste test. I served myself a slice with brie de meaux and some red pepper confit. It did not taste quite right, but is certainly better than I expected.</p> <p class="image"> <img src="/static/images/baguette_06_served.jpg" alt="The taste test" title="The taste test"><br> <em>The taste test: fresh baguette with brie and red pepper confit</em></p> <p>More experiments to follow in the near future!</p> Ryan McGreal 2 http://quandyfactory.com/blog/78/tenn_passes_controversial_lawnmower_theft_bill 2011-06-02T12:00:00Z Tenn. Passes Controversial Lawnmower Theft Bill <p>From <a href="http://news.yahoo.com/s/ap/20110601/ap_on_hi_te/us_password_sharing_crackdown">Yahoo! News</a>:</p> <p>The lawnmowing industry has successfully lobbied the Tennessee State Government to pass a groundbreaking law making it a criminal offence to loan your lawnmower to a neighbour. </p> <p>The practice of lawnmowersharing is claimed, according to an industry-funded study, to cost the industry $10 billion a year in lost revenue.</p> <p>"Now that everyone who wants to enjoy a mowed lawn has to come clean and buy or rent their own lawnmower, we can finally put an end to the harmful piracy that has been driving the lawnmowing industry to the brink of collapse," said Dr. Lawrence Angelo, an industry spokesperson.</p> <p>The barbecue industry is watching this closely as it attempts to secure passage of a law that would uphold barbecue terms-of-use restrictions preventing barbecue owners from flagrantly cooking food for dinner guests without a multi-user licence.</p> Ryan McGreal 2 http://quandyfactory.com/blog/69/how_to_write_a_blog_post 2010-07-26T12:00:00Z How to Write a Blog Post <p>The most basic guideline to writing a blog is this: <strong>Provide value for your readers.</strong> Valuable writing is personal, informative, instructional, revelatory, entertaining, and engaging. The object of your blog is not to promote your organization but to serve the interests of your readers. </p> <p>Don't think of it as an alternative channel for press releases or other marketing activities; people can see a sales pitch coming a mile away. Instead, think of it as a way to build relationships with readers by sharing your expertise in a way that helps them and encourages them to share your articles more widely and come back for more.</p> <h3>Topics</h3> <p>There's no predicting which topics will generate the most interest, but variety of topics and approaches will give your blog the broadest set of opportunities to engage readers. </p> <p>If you stick with it, blogging becomes a habit that integrates itself into your workflow and actually helps you by engaging readers to discuss, expand, scrutinize and clarify your own thoughts.</p> <ul> <li><p><strong>Relax.</strong> Think of blogging as a conversation, not an assignment. A blog entry is closer in tone to a personal correspondence than a term paper. Think about subjects, issues or events that you would enjoy discussing with your peers.</p></li> <li><p><strong>Write for yourself.</strong> Write about things that interest or challenge you. The single most-read article I've ever written is a technical guide on <a href="/blog/65/designing_a_restful_web_application">designing web applications</a>, which I wrote to clarify the underlying principles so I would understand them more fully.</p></li> <li><p><strong>Try to surprise.</strong> Most nonfiction writing is goal-oriented and persuasive, but the term "essay" comes from the French verb <em>essayer</em>, which means "to try". An <em>exploratory</em> essay is a formalized attempt to understand an issue rather than defend a conclusion. Exploratory essays often lead in <a href="http://www.paulgraham.com/essay.html">surprising</a> directions, which makes for a compelling read.</p></li> <li><p><strong>Solve a problem.</strong> Your day is filled with problems and challenges that are similar to those faced by other people. Share some of the innovative ways you have solved particular problems and invite readers to share theirs. Another popular blog entry I've written was a summary of lessons I've learned trying to deal with <a href="/blog/54/shared_awareness_a_better_way_to_manage_comment_trolls">comment trolls</a>.</p></li> <li><p><strong>Find and explore a niche.</strong> The internet is a big enough medium that even seemingly narrow subjects, if well-developed, can attract lively reader communities. This also provides opportunities to transfer ideas and practices from one niche to another.</p></li> <li><p><strong>Explain a statistic.</strong> Modern citizens are bombarded with statistics delivered out of the blue and out of context. You can humanize statistics by explaining what they mean in concrete terms and by sharing anecdotes that sketch the human faces behind the numbers.</p></li> <li><p><strong>Share your experience.</strong> Your value to your organization goes beyond a narrow job description and encompasses all the experiences, values and connections you bring with you to work. Tap into those so your writing reflects your broader competence.</p></li> <li><p><strong>Entertain.</strong> Good blog writing is like good conversation: refreshing, invigorating, and entertaining as well as informative. Feel free to have a bit of fun.</p></li> <li><p><strong>Revisit older posts.</strong> Over time, with new information and greater experience, our opinions often change and evolve. It's worth going back to older posts periodically to reassess them - what is still true, and what has changed. Obviously you won't be able to do this right away.</p></li> </ul> <h3>Writing and Tone</h3> <ul> <li><p><strong>Write in first person.</strong> You are writing as a <em>person</em> as well as on behalf of an organization. Allow your writing to reflect your personality and your personal voice. Don't be afraid to refer to yourself as "I" if you're writing about something you've done.</p></li> <li><p><strong>Use the active voice.</strong> Your blog is written <em>by</em> people, <em>about</em> people and <em>for</em> people. Don't drop the people out of your writing by relegating them to the tail-end of your sentences. Figure out who is the active agent in a sentence and move that person or entity forward into the subject.</p> <p><em>Passive</em>: "Ideas were gathered from community residents on how to use the vacant land." <br /> <em>Active</em>: "Community residents shared their ideas on how to use the vacant land."</p></li> <li><p><strong>Have an opinion.</strong> Readers know you are a human and expect you to have opinions. Good opinions are <em>reasonable</em> and <em>defensible</em> but <em>encourage discussion</em>. </p></li> <li><p><strong>Add value.</strong> Don't just re-post a link to something else. At the very least, add your own commentary that expands, clarifies, corrects or otherwise enhances the linked content.</p></li> <li><p><strong>Vary the length of your posts.</strong> Sometimes a short piece with a pithy observation or bit of commentary can stand on its own. Other issues deserve a more in-depth treatment. Both are welcome and keep the blog interesting.</p></li> <li><p><strong>Leave some room.</strong> Your article doesn't need to be exhaustive or anticipate every possible critique. A well-written article covers an important aspect of an issue but leaves room for readers to add to the conversation by expanding, clarifying, and challenging what you've written. </p></li> </ul> <h3>Editing</h3> <ul> <li><p><strong>Always check spelling and grammar.</strong> Typos and other flagrant errors look sloppy. Aim for the sweet spot that feels like conversational English - the terrain that lies between the strictures of formal writing and the morass of split infinitives and grocers' apostrophes.</p></li> <li><p><strong>Edit for clarity and brevity.</strong> Stephen King recently shared the simplest advice he ever received for revising: <em>Draft 2 equals draft 1 minus 10 percent.</em> He also advises, "Kill your darlings" - by which he means sentences that serve only to display their own cleverness.</p></li> <li><p><strong>Remove noise words.</strong> We all habitually pepper our writing with such tics as "to be sure", "in order to", "bottom line", "liaise", "going forward", "in the loop", "two cents", "for what it's worth", "in my [humble] opinion", "ducks in a row", "outside the box", "at the end of the day", "vis-a-vis", and a profusion of gratuitous modifiers (e.g. "<em>very</em> unique") that communicate nothing but hyperbole. Get rid of them.</p></li> <li><p><strong>Define Jargon.</strong> Some business-speak is a necessary evil. If you have to use industry-specific terminology, write the terms and acronyms out in full and define them for readers.</p></li> </ul> <h3>Presentation</h3> <ul> <li><p><strong>Write a Punchy Title.</strong> Clear, descriptive, humorous titles catch the reader's attention and help search engines determine the article's content. Warning: don't go overboard. A hyperbolic title that oversells the content will leave readers feeling betrayed.</p></li> <li><p><strong>Keep paragraphs short.</strong> Big walls of text are hard to scan on a computer screen. Keep paragraphs short - between one and three sentences - and discrete in terms of their contents.</p></li> <li><p><strong>Include subtitles.</strong> Subtitles within an article, particularly a longer one, break up the text into manageable chunks.</p></li> <li><p><strong>Use appropriate layout techniques.</strong> If you want to draw attention to a key word or phrase, make the text <strong>bold</strong>. If you have a list of items, use lists (like the lists in this guide). Use bullet-point lists for your items unless they need to be in a specific order, in which case use numbered lists. </p></li> <li><p><strong>Summarize.</strong> After writing your article, write a one- or two-sentence summary and post the summary at the top, just beneath the title. Combined with a punchy title, a clear summary will help readers decide whether your article is worth reading.</p></li> </ul> Ryan McGreal 2 http://quandyfactory.com/blog/59/ubuntu_1004_first_thoughts 2010-05-01T12:00:00Z Ubuntu 10.04 First Thoughts <p>Last night I upgraded my Acer Aspire One from Ubuntu 9.10 to 10.04 (I use the regular edition, not the netbook edition). 10.04 is a Long-Term Support (LTS) release, meaning Canonical promises to support the desktop edition for three years and the server edition (which comes without a GUI layer) for five years.</p> <p>It took all night to download the upgrade (watching the <em>time remaining</em> on the download status dialog brought me back to the halcyon days of the Windows 95 file-transfer time remaining status) but the upgrade itself was painless.</p> <p>So far, here's what I think:</p> <h3>Boot Time</h3> <p>This is the most immediate, obvious improvement in 10.04. True to their commitment, the Ubuntu developers have made big strides in shaving as much as they can off startup and shutdown times.</p> <p>Startup is visibly faster than 9.10, which didn't really improve much on 9.04. From the power button through the kernel selection to the login screen runs a cool 35-40 seconds. </p> <p>Add another six or seven seconds after logging in to bring up the desktop, and another two or three seconds to auto-connect to the local wifi network.</p> <p>One interesting note: 8.10 and 9.04 displayed long lists of command-line messages during bootup, while 9.10 eliminated that for a splash screen. Now I get command-line messages again, albeit only about eight lines and for only a second or two.</p> <h3>Shutdown</h3> <p>Shutdown is even nicer: six seconds flat. </p> <p>I have only two small gripes with the shutdown process:</p> <ol> <li><p>The indicator applet on the panel splits the user menu and shutdown menu into two separate dropdowns, but they still sit right beside each other as if they were a single item. It will take a little getting used to.</p></li> <li><p>The default behaviour for shutdown is to pop up a confirmation dialog, but like 9.10 you can't right-click on it to access preferences and turn off the dialog. The easiest way to fix this is to open up a terminal and start <code>gconf-editor</code>. (If you don't have it installed, fire up the Ubuntu Software Centre and install Configuration Editor.) Expand "Apps" in the tree menu on the left, select the "indicator-session" setting on the right side and check "suppress_logout_restart_shutdown" to turn off shutdown confirmations.</p></li> </ol> <h3>Appearance</h3> <p>It's not brown. Really, that's the main thing. Beyond that, if you've upgraded after already customizing the appearance of 9.10, most of your settings remain intact (with one notable exception, below).</p> <p>I tried out the new default Ambiance theme and didn't like it. It's just a little too purple and dreary for me. But as before, you can customize the hell out of your window styles via System -> Preferences -> Appearance.</p> <h3>Window Controls</h3> <p>In fairness, the window titlebar button positions are less bad than they were when Canonical first proposed moving them to the left side of the window chrome. In the first iteration, the buttons were in the order <code>[-][_][X]</code>, but in the release version they're in the order <code>[X][_][-]</code> with the close button on the very left. </p> <p>Yet I just don't see a benefit to moving them to the left side that justifies the aggravation of having to unlearn a habit that goes back nearly 20 years. After all, I originally chose Ubuntu in part because it seemed to be the easiest transition from a background in Windows.</p> <p>Luckily there's an easy way to fix it. Open a terminal and run <code>gconf-editor</code>. In the tree menu on the left click on Apps -> metacity -> general. Double-click on the "button_layout" setting on the right side and replace the text with: <code>menu:minimize,maximize,close</code> to restore the previous behaviour.</p> <p>A word of warning: if you go into System -> Preferences -> Appearance and change the theme, the window buttons will reset to the left side again.</p> <h3>Wifi</h3> <p>Ubuntu 8.10 and 9.04 were a bit painful to get wifi working on an Aspire One. The <em>only</em> thing that worked for me was to download madwifi and compile from source. Every time a major system update installed, I had to reinstall madwifi (<a href="https://help.ubuntu.com/community/CheckInstall">checkinstall</a> didn't work for me).</p> <p>This changed with 9.10, in which wifi Just Worked, and it still works in 10.04. In fact, it seems to connect considerably faster now - and the little connection icon in the NetworkManager applet looks prettier than the previous one. (With the icon in 9.10, I always had to double-check whether I was looking at the wifi or battery status.)</p> <h3>Ubuntu One</h3> <p>I decided to try out Ubuntu One, Canonical's cloud storage offering. Roughly equivalent to significant players (e.g. Dropbox), Ubuntu One offers 2 GB of cloud storage for free with the option to buy 50 GB for $10 per month. Also like Dropbox, you can share individual files and folders publicly.</p> <p>Note: you access Ubuntu One through your Me menu, not through System -> Administration or System -> Preferences.</p> <p>Setting up an account is easy enough, though the web interface felt slow and unstable. Actual syncing seems pretty smooth, though I haven't yet had a chance to try and modify files using different systems at the same time. More to come as I play with this.</p> <p>The syncing options seem fairly impressive. You can even install a Firefox addon to sync your bookmarks.</p> <p>Ubuntu One also comes with a music store provided by 7Digital. The selection is passable if nowhere near exhaustive, and the prices are not bad. I'd rather see songs in the $0.25-$0.50 range - for me, that's the break-even point against the opportunity cost of downloading free music - but we're moving in the right direction.</p> <p>The music store also provides MP3s, which will grate Free Software purists but serves the rest of us just fine.</p> <p>The store integrates closely with the Rhythmbox music player, which suffers most of the aggravations of a full-featured music player but at least can boast that it's not nearly as bloated or clumsy to use as iTunes.</p> <p>For iPod users who are sick of iTunes and/or itching to run Linux instead of Mac or Windows, you can actually use Rhythmbox to load and sync your iPod.</p> <h3>Firefox</h3> <p>There was a hullabaloo when Canonical announced that they were switching the default search engine on Ubuntu Firefox from Google to Yahoo, but they switched back before the final release. Hey, money talks when you're giving your product away for free.</p> <p>As for Firefox itself, Ubuntu 10.04 ships with version 3.6.3. The lag between Firefox releases and the version available through Ubuntu's package manager was a fairly long-standing aggravation, which led to workarounds like Ubuntuzilla to get newer versions working. </p> <p>I hope this is a sign that Canonical is committed to staying on top of new Firefox releases.</p> <h3>Problems</h3> <p>I only had two real software failures.</p> <p>Brasero flat-out stopped working, returning an "unknown error" on any attempt to burn a CD or DVD. It looks like a lot of people are having this problem. Sooner than try to fight my way to a fix, I just started using Gnomebaker instead.</p> <p>Gtk-Gnutella also stopped working. The version in the Lucid repository is two points behind the current version and throws an "ancient version detected" alert; it also fails to connect to the filesharing network. </p> <p>My attempts to compile the newest version from source (I tried both checkinstall and a straight compilation) produced an application that would crash seconds after opening. I finally gave up and switched to Frostwire.</p> <p>Otherwise, everything continued to work as expected.</p> <h3>Summary</h3> <p>There's certainly a lot more to explore before I've gotten to the bottom of this new edition, but so far I'm impressed. It's fast, solid and stable to use, the ratio of Just Works to Needs Hacking is approaching 1:0, the upgrade retained most of my previous settings without breaking anything, and several gripes I previously had with Ubuntu have been resolved. </p> <p>Aside from a few annoyances that are fairly easy to work around, I have no major complaints.</p> Ryan McGreal 2 http://quandyfactory.com/blog/54/shared_awareness:_a_better_way_to_manage_comment_trolls 2010-04-15T12:00:00Z Shared Awareness: A Better Way to Manage Comment Trolls <h3>Trolling</h3> <p>As everyone who has spent more than five minutes on the internet knows, in the absence of personal accountability, some people inevitably turn into assholes.</p> <p class="image"> <img src="/static/images/internet_dickwad_theory.gif" alt="John Gabriel's Greater Internet Dickwad Theory" title="John Gabriel's Greater Internet Dickwad Theory"><br> <a href="http://www.pennyarcademerch.com/pat070381.html"> John Gabriel's Greater Internet Dickwad Theory </a> </p> <p>Not many, mind you, but even one determined troll on an internet forum can be enough to bring the entire affair crashing down.</p> <p>The term "troll" comes originally from the fishing method of dangling a shiny lure out the back of a boat while putting along just quickly enough to grab the interest of a fish. The fish that can't resist the lure is snared on the hook and eaten.</p> <p>(Of course, the term also connotes ugly, hairy brutes who lurk under bridges waiting to snatch unwary passers-by.)</p> <p>Trolls posting anonymously on message boards absolutely <em>love</em> to provoke outrage. The troll's objective is not to change anyone's mind; rather, it's to write in such a way as to elicit a sequence of increasingly angry, frustrated replies that drag the discussion further and further off-topic until the original thread is in shambles.</p> <p>Crude trolls subsist on the meager attention given to their vulgarity; but sophisticated trolls can keep a debate going for days by dancing on the fine line between feigned reasonableness and deliberate obtusity. They are by far the more damaging to online discourse.</p> <p>What makes trolls disruptive is not the trollish comments themselves, but the chain of outraged replies they manage to elicit. </p> <p class="image"> <img src="/static/images/xkcd_duty_calls.png" alt="Duty Calls" title="What do you want me to do? LEAVE? Then they'll keep being wrong!"><br> <a href="http://xkcd.com/386/"> Duty Calls </a> </p> <p>Successfully managing trolls requires understanding why this happens and breaking the chain of replies.</p> <h3>Shared Awareness</h3> <p>Trolls attract replies by exploiting the general absence of <strong>shared awareness</strong> on most mediated communications platforms. Shared awareness is a special state of meta-knowledge among collaborators that allows them to act cooperatively on the knowledge.</p> <p>If I know something and you know something but I don't know that you know it and you don't know that I know it, we can't build on that knowledge even though we both know the same thing.</p> <p>Shared awareness is not reached until: I know something, you know something, I know that you know, you know that I know, I know that you know that I know, and you know that I know that you know. (Whew!)</p> <p>Once we all know that we all know the thing, we can then act on it. Without that shared awareness, our ability to put the information to use is sharply curtailed.</p> <p>Now, sometimes the absence of shared awareness is a blessing. There are many potentially socially awkward situations - say, someone in a room full of people passes wind - in which the absence of shared awareness of who passed wind means everyone can take a position of <a href="http://en.wikipedia.org/wiki/Plausible_deniability">plausible deniability</a>. </p> <p>Not only does the culprit avoid embarrassment, but also the other parties don't have to deal with the discomfort of having to address the incident. After all, most people don't like confrontations.</p> <h3>Someone is Wrong on the Internet!</h3> <p>In a conventional online comment system, several participants may each know something, but they do not necessarily have shared awareness about it (everyone knows that everyone knows).</p> <p>When a troll posts a comment, for example, other participants may decide independently that the comment is inappropriate and/or wrong and/or offensive, but they have no way of knowing whether anyone else feels the same way.</p> <p>A conscientious forum-goer could just ignore the troll and hope for the best, but <em>silence is affirmation</em>, and a comment left to stand unchallenged starts to look like it represents the prevailing view of the forum participants.</p> <p>Hence, the conscientious forum-goer feels no choice but to reply to the troll, if only to assure others that at least someone else disagrees with it. </p> <p>Now the fish is hooked, and a skillful troll can keep that fish on the line for a long time. </p> <h3>Collateral Damage</h3> <p>In the meantime, other fish get scared away by the thrashing. </p> <p>Pointless, unending debates between trolls and earnest opponents do two things:</p> <ol> <li>They disrupt and crowd out legitimate discussion; and</li> <li>They deter other potential commenters from getting involved, lest they be trolled as well.</li> </ol> <p>Eventually, the community starts to dissipate altogether. At this point, the troll - really, a parasite (if you'll forgive the switched metaphor) - abandons the dying host and sets off in search of a fresh victim.</p> <p>The important thing to remember is that the damage takes place not only because the troll posts a comment <em>but also because an earnest commenter decides to reply</em>. </p> <p>In turn, earnest commenters get into it with trolls because in the absence of shared awareness, they have no other way to know whether the community as a whole accepts or rejects the troll's arguments. </p> <p>Worst of all, the very dialogue with the troll, undertaken for the purpose of invalidating the troll's position, actually deters others from sharing their own positions and clearing up the matter.</p> <h3>Three Points of Attack</h3> <p>The three necessary conditions for a troll disrupting a forum are:</p> <ol> <li>An absence of shared awareness;</li> <li>A troll; and</li> <li>A well-meaning opponent to debate with the troll.</li> </ol> <p>It's safe to generalize that any online forum will have both trolls and well-meaning opponents willing to debate with them. Since most online forums also don't have shared awareness, that means all three necessary conditions are likely to be present and disruption is likely to follow.</p> <p>Since all three conditions are <em>necessary</em> for disruption to take place, we have three points of attack in trying to prevent it:</p> <ol> <li>Establish shared awareness;</li> <li>Block the trolls; or</li> <li>Block the well-meaning replies.</li> </ol> <p>Of the three options, #1 is the most promising. If you can establish a shared awareness that the troll is inappropriate - i.e. everyone knows that everyone knows that the troll is not representative - then well-meaning opponents no longer feel obliged to reply. </p> <p>In turn, since the objective of a troll is to provoke replies, a troll who can't get anyone to take the bait will eventually give up and move on to another forum.</p> <p>So how do we go about establishing shared awareness on a text-based communications forum?</p> <h3>Clear Understanding of Rules</h3> <p>The first step to shared awareness is for everyone on the forum to have a clear understanding of the rules and a clear set of expectations on now to behave and how others should behave. It's not enough just to say of trolling that <a href="http://en.wikipedia.org/wiki/I_know_it_when_I_see_it">we know it when we see it</a></p> <p>The rules should be simple, straightforward, fair, and agreeable to the members. Ideally, they should participate in determining what the rules will be. That way, the members have a sense of investment and ownership in the policies that govern their actions on the forum.</p> <p>However, the rules should probably include some variation on the following:</p> <ul> <li>Maintain a civil and polite tone, even when disagreeing with someone.</li> <li>Argue from evidence and cite your sources.</li> <li>Do not use rude or insulting language.</li> <li>Do not post comments that are needlessly inflammatory, seek to provoke an emotional reaction from others, are attempts to disrupt and derail the discussion, or abuse evidence and reasoning to defend an unjustifiable conclusion.</li> <li>Do not post comments that are off-topic.</li> </ul> <p>The important thing is that the participants feel invested in community standards that define them as civil, reasonable and responsible. Again, most people - even on the internet - like to think of themselves in these terms and actually value the personal exchanges they get to have on socially functional online forums.</p> <p>A pseudonymous screen name might be less personal than a real name, but it still inheres to a real personal identity, and people can still form relationships while communicating on the internet - <em>if</em> their community is successful in establishing and maintaining a shared awareness of their values.</p> <h3>Communicate Without Posting a Reply</h3> <p>By themselves, codes of conduct are only useful insofar as people see them being applied fairly and consistently. </p> <p>For inappropriate comments, there needs to be a mechanism for members of the community to <em>flag them as inappropriate</em> without actually posting replies; and that flag must be <em>visible to everyone</em> so that an individual looking at the comment can see how many other people have already flagged it.</p> <p>If you have used <a href="http://reddit.com">reddit</a>, <a href="http://digg.com">Digg</a>, or <a href="http://news.ycombinator.com">Hacker News</a>, you may have already have experienced such a mechanism at work. In these three link sharing sites, registered users can up-vote or down-vote both submitted articles and user comments. </p> <p>While each site works a little bit differently, a given article's or comment's score reflects some combination of the number of upvotes, the number of downvotes, the 'reputations' of the users who assigned upvotes and downvotes, and the amount of time that has passed since the article or comment was posted. </p> <p>Some sites introduce an additional constraint in that registered users do not gain the ability to downvote comments until after they have first attained a minimum threshold of reputation based on how other users have upvoted and downvoted <em>their</em> articles and comments.</p> <p>Hacker News also starts fading the text colour of comments with net negative scores. The benefit to this is that it sends a strong visual message about how the community feels about the comment.</p> <h3>Remove the Incentive</h3> <p>So what happens after you implement this form of community moderation? Earnest, well-meaning commenters can see for themselves that the community has rejected a troll and hence no longer feel obliged to set the record straight in a reply. </p> <p>Eventually, the troll gets bored at not being able to elicit any more replies and moves on.</p> <p>This is the key to successful community moderation: <strong>establishing shared awareness removes the incentive to reply to trolls, which in turn removes the incentive to troll</strong>.</p> <p>To borrow a phrase from <a href="http://en.wikiquote.org/wiki/John_Gilmore">John Gilmore</a>, the other forum members learn to treat the trolls as damage and route around them. Eventually the trolls give up.</p> <p>It might take a while to establish shared awareness, but the forum admins must be patient and remain steadfast in the face of what is likely to be frenetic opposition from the trolls before they finally quit. </p> <p>Have no doubt: the trolls will immediately understand what is happening, and will fight to undermine it with every rhetorical tool at their disposal, accusing you of censorship, tyranny, megalomania and anything else they can think of. They will fling a litany of abuse at you in the hope that some of it will stick.</p> <h3>Free and Voluntary</h3> <p>The best way to fend off these attacks is to ensure that the moderation is free, voluntary, and non-censorious. The following guidelines seem to work well:</p> <ol> <li><p>Regularly reinforce community standards by having the policy displayed prominently on the site and reminding users of their important role in maintaining those standards.</p></li> <li><p>Troll comments can visually represent the negative judgments of their peers - for example by fading in colour or shrinking in size in proportion to the negativity of their score - but they should not disappear completely. (Note: it's reasonable to make an exception for obvious spam and/or libelous content and just delete it.)</p></li> <li><p>Moderation ought to apply directly to comments, not to the users making them. People respond to incentives, and some trolls actually stop trolling and start participating responsibly when their trolls are voted down but their fair posts are voted up.</p></li> <li><p>Similarly, blacklisting trollish users generally doesn't work. As long as it's easy to create <a href="http://en.wikipedia.org/wiki/Sockpuppet_%28Internet%29">sockpuppet accounts</a> with throwaway email addresses, forums behave like badly applied wallpaper: when you push a bubble down, it just pops up somewhere else.</p></li> </ol> <h3>Censorship</h3> <p>Finally, a few words on censorship as an alternative method of blocking trolls:</p> <p>Censorship is a slippery slope. Deciding which comments to delete is ultimately a judgment call, and people who are emotionally invested in the success of a forum are notoriously poor at remaining objective and applying standards consistently. </p> <p>There's a risk that legitimate comments will get the axe. Even worse, there's a risk that an atmosphere of censorship will deter people from participating - the very problem that moderation was introduced to solve!</p> <p>Finally, censorship gives the trolls strong rhetorical ammunition to accuse you of heavy-handedness and elicit sympathy from other participants. The perceived lack of transparency over which comments are acceptable puts everyone into an oppositional stance, and the trolls will be watching carefully for examples of hypocrisy.</p> <p>Both philosophically and practically, censorship is at least as harmful as its targets. It depends naively on the consistent objectivity and benevolence of the moderators, introduces a <a href="http://en.wikipedia.org/wiki/Chilling_effect_%28term%29">chilling effect</a> on participants who may hold legitimate beliefs that break with the majority, and arms trolls with a credible claim that their rights are being violated.</p> Ryan McGreal 2 http://quandyfactory.com/blog/50/couchdb_working_notes 2010-04-09T12:00:00Z CouchDB Working Notes <p>Note: this article is very much a work in progress. It functions mainly as a place where I can document what I learn about CouchDB as I play around with it and get a progressively better sense of what it does and how it works.</p> <h3>Summary</h3> <p>CouchDB is a non-relational database server built in Erlang that stores its data as JSON and communicates RESTfully over HTTP.</p> <h3>Install CouchDB</h3> <p>First, you need to install CouchDB. If you're using a civilized operating system with a package manager, this won't be difficult.</p> <pre><code>ryan@home ~$ sudo apt-get install CouchDB </code></pre> <p>The package manager will install CouchDB and all its dependencies. The installation script will finish with this hopeful line:</p> <pre><code> * Starting database server CouchDB </code></pre> <p>That's it. Couch is up and running.</p> <h3>RESTful API</h3> <p>The first thing to know about CouchDB is that it that you connect to it over HTTP using the standard HTTP "verbs" like GET, PUT, POST and DELETE - the very same protocol that browsers use to connect to web servers. </p> <p>This is important to understand: you don't need database drivers or clients or anything else to interact with CouchDB. You just need to be able to send an HTTP request to the CouchDB server and receive the response.</p> <p>You can prove this by opening your browser and navigating to <a href="http://localhost:5984">http://localhost:5984</a>. It should return something like this:</p> <pre><code>{"CouchDB": "Welcome", "version": "0.10.0"} </code></pre> <p>So far, so good. But what are you looking at?</p> <h3>JSON</h3> <p>If you've built any Ajax web apps in the past few years, the output from the CouchDB web server should be familiar to you. It's <a href="http://json.org/">JSON</a>, or JavaScript Object Notation, the lightweight data format introduced by Douglas Crockford. </p> <p>(BTW if you write JavaScript but haven't read Crockford's book <cite><a href="http://www.amazon.ca/exec/obidos/ASIN/0596517742/wrrrldwideweb">JavaScript: The Good Parts</a></cite>, I highly recommend it. Chances are you're Doing It Wrong today.)</p> <p>JSON is based on JavaScript object literal notation, a method of creating objects in JavaScript by literally describing their properties and methods:</p> <pre><code>myObject = { name: "Ryan McGreal", age: 36, emails: ["ryan@quandyfactory.com", "editor@raisethehammer.org"], doSomething: function() { alert("This is a useful method!"); } } </code></pre> <p>JSON supports arrays and dictionaries with nesting and various data types (number, string, boolean, array, object and null). It also supports schemas. </p> <p>A major advantage over XML is that the syntax is much simpler and less verbose, which makes it lighter across networks as well as more human-readable.</p> <p>While it's possible to evaluate JSON as JavaScript using <code>eval()</code>, it's not recommended (for what I hope are obvious reasons). It's much better to use a JSON parser. </p> <p>The good news is that there are already JSON parsers for wide range of programming languages in addition to JavaScript. Python, for example, <a href="http://docs.python.org/library/json.html">includes the <code>json</code> module</a> as part of the standard library (as of version 2.6). There are also libraries for PHP, Ruby, Perl, Java, Scala, Erlang, Common Lisp, and Clojure, among others.</p> <h3>Create a CouchDB Database</h3> <p>Given that you connect to CouchDB over HTTP, it should not surprise you to learn that you execute actions against CouchDB using the HTTP request method 'verbs': GET, PUT, POST and DELETE. </p> <p>As a result, any programming language that can communicate over HTTP can connect to a CouchDB server.</p> <p>Let's create a new database. For simplicity's sake I'll connect to the CouchDB server on the command line using <a href="http://curl.haxx.se/">curl</a>. The -X command line argument specifies which HTTP request method to use.</p> <pre><code>ryan@home ~$ curl -X PUT http://localhost:5984/mydb </code></pre> <p>CouchDB returns the following response (in JSON format, or course):</p> <pre><code>{"ok": true} </code></pre> <p>You now have a CouchDB database called <code>mydb</code>. Prove it by running a GET request against the URL for your database:</p> <pre><code>ryan@home ~$ curl -X GET http://localhost:5984/mydb </code></pre> <p>Check it out: your database has a URL!</p> <p>CouchDB will return a summary of the database properties (I've added whitespace to the CouchDB JSON responses to aid readability):</p> <pre><code>{ "db_name": "mydb", "doc_count": 0, "doc_del_count": 0, "update_seq": 0, "purge_seq": 0, "compact_running": false, "disk_size": 79, "instance_start_time": "1276950495049303", "dis_format_version": 4 } </code></pre> <p>Notice the <code>doc_count</code> property of 0. We should fix that by adding a document. </p> <h3>Add a Document</h3> <p>You add documents to a database using the HTTP POST method (the same method HTML forms use to send form data to the server).</p> <pre><code>ryan@home ~$ curl -X POST http://localhost:5984/mydb \ --header 'Content-Type: application/json' \ --data '{"title": "My First CouchDB Document", "author": "Ryan McGreal", \ "date_posted": "2010-04-09 11:38:26", "section": "blog", \ "content": "This is my first-ever CouchDB document. Niiice." }' </code></pre> <p>CouchDB returns a response like the following:</p> <pre><code>{ "ok": true, "id": "bf58234234aed2389dcb23423", "rev": "1-4ac239847fea987bd2340" } </code></pre> <p>Let's take a look at what just happened. </p> <p>We executed an HTTP POST request with a Content-Type header of "application/json" (since we're sending the data in JSON format) and a JSON object for the posted data. The JSON object includes the following keys and values:</p> <ul> <li><strong>title</strong>: My First CouchDB Document</li> <li><strong>author</strong>: Ryan McGreal</li> <li><strong>date_posted</strong>: 2010-04-09 11:38:26</li> <li><strong>section</strong>: blog</li> <li><strong>content</strong>: This is my first-ever CouchDB document. Niiice. </li> </ul> <p>CouchDB accepted the JSON object, parsed it to ensure that it's valid JSON, and then created a document in the mydb database with the content you provided. </p> <p>It also issued the document with an ID and a revision ID, which it included in the response so that you can access the document later. </p> <p>The revision ID is additionally beneficial: you've got version control built into your database. CouchDB also serves revision numbers as HTTP ETags, so you can take advantage of caching.</p> <h3>View a Document</h3> <p>Let's take a look at the document we just created.</p> <pre><code>ryan@home ~$ curl -X GET http://localhost:5984/mydb/bf58234234aed2389dcb23423 </code></pre> <p>Your document has a URL, too!</p> <p>CouchDB returns the following response:</p> <pre><code>{ "ok": true, "id": "bf58234234aed2389dcb23423", "rev": "1-4ac239847fea987bd2340", "title": "My First CouchDB Document", "author": "Ryan McGreal", "date_posted": "2010-04-09 11:38:26", "section": "blog", "content": "This is my first-ever CouchDB document. Niiice." } </code></pre> <p>There it is. You can access its properties and values just like you would a recordset. </p> <p>The difference is that the CouchDB data is formatted in JSON and can be nested - i.e. an object's property could be an array or another object with its own keys and values - rather than forced into a flat table.</p> <h3>Add Another Document</h3> <p>Let's try adding a document with nested data:</p> <pre><code>ryan@home ~$ curl -X POST http://localhost:5984/mydb \ --header 'Content-Type: application/json' \ --data '{"title": "Some Handy Links", "author": "Ryan McGreal", \ "links": ["http://quandyfactory.com", "http://raisethehammer.org", \ "http://hamilton350.com"]}' </code></pre> <p>CouchDB responds:</p> <pre><code>{ "ok": true, "id": "e6ed2389dcb26100caeg3423", "rev": "1-7c21a0fd39fea987bd2340" } </code></pre> <p>Now we have two documents in our database with different sets of fields - you're not forced to map your data into a standardized set of properties. </p> <p>If we tried to do this in a traditional RDBMS, our documents table would have to have a <code>links</code> column with a Null value in the first document, as well as a <code>content</code> column with a Null value in the second document.</p> <p>With CouchDB, you can give your documents arbitrary fields and values depending on what's applicable for each document.</p> <h3>Update a Document</h3> <p>[Coming Soon]</p> <h3>Data Views</h3> <p>[Coming Soon]</p> <h3>Misc.</h3> <p>Let's create another database.</p> <pre><code>ryan@home ~$ curl -X PUT http://localhost:5984/mydb </code></pre> <p>Wait a minute; didn't we just create a database called <code>mydb</code>?</p> <p>CouchDB returns this response:</p> <pre><code>{ "error": "file_exists", "reason": "The database could not be created, the file already exists." } </code></pre> <p>Whew, dodged a bullet there. CouchDB doesn't let you accidentally overwrite an existing database.</p> <p>At any time you can get an array of all the databases on your server with the following GET request:</p> <pre><code>ryan@home ~$ curl -X GET http://localhost:5984/_all_dbs ["mydb"] </code></pre> <p>There's plenty more, but this should be enough to get you started thinking about how you might use CouchDB in projects whose data models don't necessarily lend themselves to strict relational structures.</p> <h3>Utils Web Interface</h3> <p>One last thing: CouchDB also provides a web interface with some handy utilities for navigating around the server and its databases. </p> <p>Just load <a href="http://localhost:5984/_utils">http://localhost:5984/_utils</a> in your browser to see it.</p> Ryan McGreal 2 http://quandyfactory.com/blog/46/mpaa_and_piracy 2010-02-26T12:00:00Z MPAA and Piracy <p>When I was a kid, I had a <em>Fat Albert</em> book in which one of the Cosby Kids (for details of who did what I'm going on memory, but I think it was Bucky) gets upset about something and runs away. </p> <p>The other kids all search the projects for him, but evening is coming on and it's getting harder to see.</p> <p>At one point, Weird Harold is walking from streetlamp to streetlamp, peering into the pool of light under each pool. Rudy walks up and asks Harold what he's doing. Harold says he's looking for Bucky.</p> <p><em>But why are you looking under the streetlamps?</em> asks Rudy.</p> <p>Harold answers, <em>That's the only place I can see.</em></p> <p>Which brings us to:</p> <p><img src="http://www.geek.com/wp-content/uploads/2010/02/piratedvd.jpg" alt="What pirates get vs. what paying customers get" /></p> <p>(<a href="http://www.geek.com/wp-content/uploads/2010/02/piratedvd.jpg">source</a>)</p> Ryan McGreal 2