<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CUEBRnY_cCp7ImA9WhNUGE8.&quot;"><id>tag:blogger.com,1999:blog-23992350</id><updated>2013-01-10T13:14:17.848Z</updated><category term="logging" /><category term="divergence" /><category term="ddunwoody" /><category term="s3" /><category term="sms" /><category term="movies" /><category term="books" /><category term="free" /><category term="development" /><category term="servername" /><category term="gavin bell" /><category term="shopping" /><category term="offline" /><category term="SIP" /><category term="last.fm" /><category term="convergence" /><category term="privacy" /><category term="ds" /><category term="threadless.com" /><category term="upgrade" /><category term="rubyonrails" /><category term="Testing" /><category term="expectations" /><category term="DxCore" /><category term="developerexpress.com" /><category term="TDD" /><category term="git" /><category term="musicovery" /><category term="expenses" /><category term="spam" /><category term="rss" /><category term="rails" /><category term="spider" /><category term="pro evo" /><category term="email" /><category term="lifestreaming" /><category term="cluetrain manifesto" /><category term="work" /><category term="confusion" /><category term="rakefile" /><category term="facebook" /><category term="tesco" /><category term="dlr" /><category term="ps3" /><category term="free our data" /><category term="RDF" /><category term="davegorman" /><category term="java" /><category term="Sainsburys" /><category term="authentication" /><category term="wifi" /><category term="bearvsshark" /><category term="chris jackson" /><category term="policy" /><category term="mojo" /><category term="gsxsw" /><category term="government" /><category term="delivery" /><category term="CallFlow" /><category term="concurrency" /><category term="SDK" /><category term="beta" /><category term="webservice" /><category term="ps2" /><category term="rest" /><category term="37signals" /><category term="new years resolution" /><category term="xpday" /><category term="uni" /><category term="friedchicken" /><category term="software as shes developed" /><category term="load balancing" /><category term="simonmcmanus.com" /><category term="dns" /><category term="Agile" /><category term="build" /><category term="flickr" /><category term="Expression" /><category term="drm" /><category term="practices" /><category term="media centre" /><category term="mac" /><category term="cache manifest" /><category term="Scrum" /><category term="marketing" /><category term="Random Thoughts" /><category term="design" /><category term="mp3" /><category term="radiohead" /><category term="ubuntu" /><category term="cruise control" /><category term="channel9" /><category term="imified" /><category term="gordon ramsey" /><category term="json" /><category term="google" /><category term="Sample App" /><category term="Quote of the Day" /><category term="barcamp barcampbrighton iphone development adobeair adobe air css html javascript" /><category term="sky" /><category term="ruby" /><category term="feeds" /><category term="cooking" /><category term="AOP" /><category term="springring" /><category term="nigel pepper" /><category term="support" /><category term=".Net" /><category term="land of leather" /><category term="github" /><category term="URI" /><category term="moutain biking" /><category term="FOAF" /><category term="SOA" /><category term="3g" /><category term="curseleague" /><category term="frameworks" /><category term="opensource" /><category term="jon lister" /><category term="voice" /><category term="nodejs" /><category term="tech ed" /><category term="Nassim Nicolas Taleb" /><category term="test driven development" /><category term="source control" /><category term="black swan" /><category term="heroes" /><category term="code" /><category term="mashup.starwars.com" /><category term="WLA" /><category term="london" /><category term="async" /><category term="BT" /><category term="tom loosemore" /><category term="opentec" /><category term="iplayer" /><category term="expressjs" /><category term="jamiroquai" /><category term="hack" /><category term="del.icio.us" /><category term="arsenal" /><category term="appender" /><category term="theteam" /><category term="onaswarm.com" /><category term="ldirectord" /><category term="FIFA" /><category term="manifest" /><category term="gym" /><category term="deployment" /><category term="high availability" /><category term="music" /><category term="hudson" /><category term="lifehacker" /><category term="automated" /><category term="lifestream" /><category term="libraries" /><category term="cool" /><category term="recipe" /><category term="music brainz" /><category term="latest scores" /><category term="pay per view" /><category term="telepocalyse" /><category term="wireless" /><category term="ipod" /><category term="impossibox" /><category term="gile" /><category term="Linux" /><category term="twitter" /><category term="mac mini" /><category term="redtape" /><category term="video blogging" /><category term="Tools" /><category term="vpn" /><category term="symfony" /><category term="jruby" /><category term="film" /><category term="Anonymity" /><category term="steven fry" /><category term="P2P" /><category term="digitalsigniture" /><category term="WPF" /><category term="telco" /><category term="pandora" /><category term="gmail" /><category term="university" /><category term="problem" /><category term="psd" /><category term="mocks" /><category term="galaxy" /><category term="openid" /><category term="rocky" /><category term="rewiredstate" /><category term="data mining" /><category term="cable" /><category term="dopplr" /><category term="html5" /><category term="web" /><category term="gapingvoid.com" /><category term="Woolworths" /><category term="Freedom of speech" /><category term="more is less" /><category term="crawl" /><category term="robustness" /><category term="hreview" /><category term="continious integration" /><category term="xfm lastfm last.fm scrobble scrobbler ruby" /><category term="phone" /><category term="bt vision" /><category term="validation" /><category term="c#" /><category term="poppendieck" /><category term="grails" /><category term="location" /><category term="KISS" /><category term="travel" /><category term="tom morris" /><category term="iphone" /><category term="backward capability" /><category term="css" /><category term="michael mahemoff" /><category term="hiking" /><category term="Aloha" /><category term="web21c sdk" /><category term="web 2.0" /><category term="osmosoft" /><category term="spring" /><category term="migrations" /><category term="software engineering" /><category term="aswarmofangels.com" /><category term="drpep" /><category term="sun" /><category term="Zavvi" /><category term="roles" /><category term="global microbrand" /><category term="xbox" /><category term="IAM" /><category term="blackadder" /><category term="aws" /><category term="review" /><category term="startups" /><category term="notes" /><category term="paul downey" /><category term="jp rangaswami" /><category term="xml" /><category term="mongoose" /><category term="scott hansleman" /><category term="TV" /><category term="scala" /><category term=".Net3" /><category term="ultramonkey" /><category term="refactoring" /><category term="jre" /><category term="Barry Schwartz" /><category term="universal records" /><category term="QA" /><category term="san francisco" /><category term="MCE" /><category term="VisualStudio" /><category term="freeview" /><category term="Web Services" /><category term="milan" /><category term="Nat Pryce" /><category term="FON" /><category term="alan spillane" /><category term="getsatisfaction.com" /><category term="cursebird" /><category term="david james" /><category term="links" /><category term="t4" /><category term="godaddy" /><category term="social network portability" /><category term="microformats" /><category term="isp" /><category term="scrubs" /><category term="social networks" /><category term="paris" /><category term="ironruby" /><category term="play.com" /><category term="gamma" /><category term="patience" /><category term="html" /><category term="tapas" /><category term="chinese new year" /><category term="slim" /><category term="ripplerap" /><category term="architecture" /><category term="automation" /><category term="championship manager" /><category term="request" /><category term="uncov.com" /><category term="satellite" /><category term="sxsw" /><category term="microbrand" /><category term="t" /><category term="yahoo" /><category term="xbox live" /><category term="sitemesh" /><category term="contract" /><category term="javascript" /><category term="jay fresh" /><category term="cache" /><category term="blyk" /><category term="liter" /><category term="javamail" /><category term="apple" /><category term="tablet" /><category term="leweb3" /><category term="simon mcmanus" /><category term="hansleminutes" /><category term="ipad" /><category term="easyjet" /><category term="CI" /><category term="DotNetRocks" /><category term="Security" /><category term="http" /><category term="gojko" /><category term="accelerator" /><category term="london underground" /><category term="heartbeat" /><category term="enterprise" /><category term="subscription tv" /><category term="cameramigration" /><category term="Software" /><category term="doc searls" /><category term="commercialism" /><category term="football" /><category term="intranet" /><category term="fitnesse" /><category term="guardian" /><category term="pipes" /><category term="rake" /><category term="simon willison" /><category term="ant" /><category term="platform" /><category term="vlogging" /><category term="scobleizer.com" /><category term="php" /><category term="san1t1" /><category term="broadband" /><category term="games" /><category term="Toyota Production System" /><category term="simple" /><category term="monitoring" /><category term="bbc" /><category term="inner dialog" /><category term="mongodb" /><category term="book" /><category term="samsung" /><category term="digital tv" /><category term="log4j" /><category term="call" /><category term="clr" /><category term="no.de" /><category term="project darkstar" /><category term="abstraction" /><category term="customer experience" /><category term="paul foster" /><category term="google reader" /><category term="microsoft" /><category term="authorisation" /><category term="SDP" /><category term="ndistro" /><category term="data" /><category term="db" /><category term="leweb" /><title>Robbie Clutton</title><subtitle type="html">Robbie ramblings on traveling, technical thoughts, short stories and some more too.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.iclutton.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.iclutton.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>172</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/RobbieClutton" /><feedburner:info uri="robbieclutton" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CkYAQHs5cSp7ImA9WhdVFEk.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-4522390586219875148</id><published>2011-09-19T14:48:00.000+01:00</published><updated>2011-09-19T14:49:01.529+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-19T14:49:01.529+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="notes" /><category scheme="http://www.blogger.com/atom/ns#" term="mongodb" /><title>Notes from "Blending MongoDB and RDBMS for eCommerce"</title><content type="html">github.com/spf13&lt;br /&gt;spf13.com&lt;br /&gt;@spf13&lt;br /&gt;OpenSky eCommerce platform&lt;br /&gt;&lt;br /&gt;products in mongo using custom fields for different product types (i.e. actors in movie, tracklist for album etc)&lt;br /&gt;&lt;br /&gt;- ordered items need to be fixed at time of purchase, table inheritance bad for this&lt;br /&gt;&lt;br /&gt;## 3 things for e-commerce&lt;br /&gt;&lt;br /&gt;- optimistic concurrency (update if current, then try again if document is not current)&lt;br /&gt;- assumes environment with low data contention&lt;br /&gt;- works well for Amazon with long tail product catalogue&lt;br /&gt;- works bad for ebay, groupe, anything with flash-sales (high data contention) &lt;br /&gt;&lt;br /&gt;# Commerce is ACID in real-life&lt;br /&gt;&lt;br /&gt;purchasing something from a store deals with this without concurrency as each product can only be held by one customer&lt;br /&gt;&lt;br /&gt;MongoDB e-Commerce &lt;br /&gt;&lt;br /&gt;- each item (not sku) has it's own document&lt;br /&gt;- contains&lt;br /&gt;-- reference to sku&lt;br /&gt;-- state&lt;br /&gt;-- other meta-data (timestamp, order ref etc)&lt;br /&gt;&lt;br /&gt;cart in card action difficult, but in Mongo changing state on item makes it unavailable to other customers (e.g. if state is 'in-cart')&lt;br /&gt;&lt;br /&gt;## Blending &lt;br /&gt;&lt;br /&gt;Doctrine (OS - ORM/ODM) modelled on Hibernate&lt;br /&gt;&lt;br /&gt;in SQL&lt;br /&gt;-- product inventory, sellable inventory, orders&lt;br /&gt;&lt;br /&gt;- inventory is transient in Mongo collections&lt;br /&gt;- inventory kept in sync with listeners&lt;br /&gt;&lt;br /&gt;for financial transactions we want security and comfort of RDBMS&lt;br /&gt;&lt;br /&gt;## Playing Nice&lt;br /&gt;&lt;br /&gt;products are stored in a document&lt;br /&gt;orders are entities stored in relational DB&lt;br /&gt;store reference not relationships across two databases&lt;br /&gt;&lt;br /&gt;jwage.com/2010/08/25/blending-the-doctrine-orm-and-mongdb-odm/</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/4522390586219875148/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=4522390586219875148" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/4522390586219875148?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/4522390586219875148?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/H3xXdM5odQA/notes-from-blending-mongodb-and-rdbms.html" title="Notes from &quot;Blending MongoDB and RDBMS for eCommerce&quot;" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/09/notes-from-blending-mongodb-and-rdbms.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcBRngyeCp7ImA9WhdVFEk.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-6895394749463345652</id><published>2011-09-19T14:46:00.000+01:00</published><updated>2011-09-19T14:47:37.690+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-19T14:47:37.690+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="notes" /><category scheme="http://www.blogger.com/atom/ns#" term="mongodb" /><title>Notes from "Schema design at scale"</title><content type="html"># Eliot Horowitz&lt;br /&gt;&lt;br /&gt;## Embedding sub-documents vs separate collection&lt;br /&gt;&lt;br /&gt;i.e. blog post and comments&lt;br /&gt;&lt;br /&gt;- embedded&lt;br /&gt;-- Something like a million sub-documents is going to be unwieldy&lt;br /&gt;-- need to move whole document (expensive) if gets large&lt;br /&gt;- Not embedded&lt;br /&gt;-- Million 'comments' in separate documents means lots of reads&lt;br /&gt;- Hybrid&lt;br /&gt;-- one document for core meta-data&lt;br /&gt;-- separate document for n comments with array separated by buckets (i.e. 100 in each bucket)&lt;br /&gt;-- reduces potential seeks (if 100 in each, reduces from say 500 to 5)&lt;br /&gt;&lt;br /&gt;## Indexes&lt;br /&gt;&lt;br /&gt;- Right-balanced index access on the B-Tree&lt;br /&gt;-- only have to keep small portion in RAM&lt;br /&gt;-- time based, object id, auto-increment&lt;br /&gt;- Keep data sequential in index (covered index)&lt;br /&gt;-- create an index with just the fields you need so you can retrieve the data straight from the index&lt;br /&gt;-- index is bigger&lt;br /&gt;-- good for reads like this&lt;br /&gt;&lt;br /&gt;## Shard Key&lt;br /&gt;&lt;br /&gt;- determines how data is partitioned&lt;br /&gt;- hard to change&lt;br /&gt;- most important performance decision&lt;br /&gt;- broken into chunks by range&lt;br /&gt;- want to distribute evenly but be right balanced, like month() + md5(something)&lt;br /&gt;- if sharding for logs, need to think&lt;br /&gt;-- why do you want to scale (for write or reading)?&lt;br /&gt;-- 'want to see the last 1000 messages for my app across the system'&lt;br /&gt;-- take advantage of parallelising commands across shares (index by machine, read by app-name)&lt;br /&gt;- no right answer&lt;br /&gt;&lt;br /&gt;## Lessons&lt;br /&gt;&lt;br /&gt;Range query vs regex (that uses ^ - essentially 'starts-with') is about same performance&lt;br /&gt;If you have a genuinely unique id, use that instead of the ObjectId</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/6895394749463345652/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=6895394749463345652" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/6895394749463345652?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/6895394749463345652?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/_jSlJzz1-Jw/notes-from-schema-design-at-scale.html" title="Notes from &quot;Schema design at scale&quot;" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/09/notes-from-schema-design-at-scale.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUIFQXs6fyp7ImA9WhdVFE8.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-3662269581163647137</id><published>2011-09-19T11:17:00.000+01:00</published><updated>2011-09-19T11:18:30.517+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-19T11:18:30.517+01:00</app:edited><title>Notes from "Scaling MongoDB for Real-Time Analytics"</title><content type="html">* Scaling MongoDB for Real-Time Analytics, a Shortcut around the mistakes I've made - Theo Hultberg Chief Architect, Burt&lt;br /&gt;&lt;br /&gt;40gb, 50 million documents per day&lt;br /&gt;&lt;br /&gt;use of mongo&lt;br /&gt;- virtual memory (throwaway)&lt;br /&gt;- short time storage (throwaway)&lt;br /&gt;- full time storage&lt;br /&gt;&lt;br /&gt;why&lt;br /&gt;- sharding makes write scale&lt;br /&gt;- secondary indexes&lt;br /&gt;&lt;br /&gt;using jRuby&lt;br /&gt;&lt;br /&gt;mongo 1.8.1&lt;br /&gt;updates near 50% write lock incrementing single number&lt;br /&gt;pushing near 80% write lock&lt;br /&gt;&lt;br /&gt;mongo 2&lt;br /&gt;updates near 15% write lock&lt;br /&gt;push near 50% write lock&lt;br /&gt;&lt;br /&gt;iterations&lt;br /&gt;- 1&lt;br /&gt;-- one document per session&lt;br /&gt;-- update as new data comes along&lt;br /&gt;-- 1000% write lock!&lt;br /&gt;-- lesson: everything is about working around the global write lock&lt;br /&gt;- 2&lt;br /&gt;-- multiple documents using same id prefix&lt;br /&gt;-- not as much lock, but still not great performance, couldn't remove data at same pace as inserting&lt;br /&gt;-- lesson: everything is about working around the global write lock&lt;br /&gt;-- being more thought about designing primary key (same prefix for a group)&lt;br /&gt;- 3&lt;br /&gt;-- wrote a new collection every hour&lt;br /&gt;-- lots of complicated code, bugs&lt;br /&gt;-- enables fragmented database files on disk&lt;br /&gt;-- lesson: make sure you can remove old data&lt;br /&gt;- 4&lt;br /&gt;-- sharding&lt;br /&gt;-- higher write performance&lt;br /&gt;-- lots of problems (in 1.8) and ops time spent debugging&lt;br /&gt;-- lesson: everything is about working around the global write lock, sharding is a way around this (4 shards means 4 global write lock)&lt;br /&gt;-- lesson: sharding not a silver bullet (it's buggy, avoid it if you can)&lt;br /&gt;-- lesson: it will fail, design for failure (infrastructure)&lt;br /&gt;- 5&lt;br /&gt;-- move things to separate clusters for different usage (high writes then drop; increment a document)&lt;br /&gt;-- lesson: one database with one usage pattern per cluster&lt;br /&gt;-- lesson: monitor everything (found replica that was 12 hours behind, useless!)&lt;br /&gt;- 6&lt;br /&gt;-- upgraded to monster servers (high memory quad extra large with AWS) (downgraded to extra large machine - 6 machines with mongod 12gb ram, 3 machines for mongo-config, 3 machines for arbiters across three availability zones writing 2000 documents per second, reading 4000 documents per secondc)&lt;br /&gt;-- lesson: call the experts when you're out of ideas&lt;br /&gt;- 7&lt;br /&gt;-- partitioning again and pre-chunking (need to know your data and range of your keys)&lt;br /&gt;-- partition by database, new db each day&lt;br /&gt;-- decrease size of documents (less in RAM)&lt;br /&gt;-- no more problems removing data&lt;br /&gt;-- lesson: smaller objects means smaller documents&lt;br /&gt;-- lesson: think about your primary key&lt;br /&gt;-- lesson: everything is about working around the global write lock&lt;br /&gt;&lt;br /&gt;Q&amp;A&lt;br /&gt;- would you recommend?&lt;br /&gt;-- best for high read load, high write load is not a solved problem yet&lt;br /&gt;- EC2&lt;br /&gt;-- you have replicas, why also have EBS?&lt;br /&gt;-- use ephemeral disk, it comes included and is predictable performance (with caveats like spreading across availability zones to avoid data centre loss)&lt;br /&gt;-- use RAID 10 if using EBS&lt;br /&gt;- monitoring&lt;br /&gt;-- mongostat&lt;br /&gt;-- plugins that are available&lt;br /&gt;-- mms from 10gen&lt;br /&gt;-- server density (here at Mongo con)&lt;br /&gt;- map reduce&lt;br /&gt;-- moved away to be more real-time (but was using hadoop)</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/3662269581163647137/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=3662269581163647137" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/3662269581163647137?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/3662269581163647137?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/tXjsJhExJRc/notes-from-scaling-mongodb-for-real.html" title="Notes from &quot;Scaling MongoDB for Real-Time Analytics&quot;" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/09/notes-from-scaling-mongodb-for-real.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ADRnw4eCp7ImA9WhZUEkQ.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-6407612055788254419</id><published>2011-06-05T18:37:00.003+01:00</published><updated>2011-06-05T18:42:57.230+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-05T18:42:57.230+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="scala" /><title>My first scala app</title><content type="html">Well I say first, but that’s not quite the whole story.  I’ve worked on existing Scala code bases but recently got the opportunity to start a new project and I got to see how the experiences on those projects and conversations with colleges influenced design decisions.  I wanted to note down some of the tricks I’d picked up.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Tools&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;We start with the tools and first up is &lt;a href="https://github.com/harrah/xsbt/"&gt;SBT&lt;/a&gt;, version 0.7.x.  We depend on the &lt;a href="https://github.com/mpeltonen/sbt-idea"&gt;IDEA plugin&lt;/a&gt; to generate IDEA project files.  We also use the &lt;a href="https://github.com/olim7t/sbt-scalariform"&gt;Scaliform&lt;/a&gt; plugin which formats code every compile within SBT and the &lt;a href="https://github.com/OlegYch/sbt-sources-plugin"&gt;Sources plugin&lt;/a&gt; which when run downloads the source Jars without having to stipulate the ‘withSources’ directive on the dependencies. &lt;br /&gt;&lt;br /&gt;The IDEA and Sources plugin are run as processors and are not checked into the SBT project file, whereas the Scalariform plugin is.  The distinction is that a developer might choose to use a different IDE or not download the sources, but we should always try and conform to the same coding practices and being lazy, having a plugin do it for you is one less thing to worry about.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Inversion of control / Dependency injection&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;One of the interesting developments early on was the choice of the dependency injection frameworks.  Would it be Spring, Guice or something custom?  Actually, we decided not to have any and just use plain old servlets through our routing framework, &lt;a href="https://github.com/scalatra/scalatra"&gt;Scalatra&lt;/a&gt;.  I think this has actually lead us to write better code, for instance we have taken advantage of the companion objects for classes instead of relaying on repository classes that would need to be injected.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Routing&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;We started with the routing framework, &lt;a href="https://github.com/scalatra/scalatra"&gt;Scalatra&lt;/a&gt; which is similar to Sinatra and other simple routing frameworks.  It provides a clean way of exposing your application to the web and combined with the following, enables all of the methods to be only a few very focused lines of code.  We used &lt;a href="http://scalate.fusesource.org/"&gt;Scalate&lt;/a&gt; and &lt;a href="http://scalate.fusesource.org/documentation/ssp-reference.html"&gt;SSP&lt;/a&gt; for the template rendering which can feel a little verbose with the variable declarations at the top, but when you can pre-compile the templates you can understand why those are required.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Databasing&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;All new projects are using &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt; and we’ve been using the Scala driver, &lt;a href="http://api.mongodb.org/scala/casbah/2.0/"&gt;Casbah&lt;/a&gt;, for a while but this gave us an opportunity to try out &lt;a href="https://github.com/novus/salat"&gt;Salat&lt;/a&gt; the case class wrapper.  This is probably as close as I’ve seen ORM like features in Scala while maintaining the MongoDB query sugar.  As mentioned, using companion objects instead of repository classes allowed us to do tidy things like:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;case class Artist(_id: ObjectId = new ObjectId, name: String) {&lt;br /&gt; def save() = Artist.saveArtist(this)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;object Artist extends SalatDAO[Artist, ObjectId](...mongo config...) {&lt;br /&gt; def saveArtist(artist: Artist) = update(Map(“_id” -&amp;gt; artist._id, artist&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;And use them like:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;val artist = Artist(“My new favourite band”)&lt;br /&gt;artist.save()&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This meant we were using a repository like model, but hiding it behind the case class and the companion object.  It makes for very neat code further upstream.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Webifying&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The app has to call out to other web APIs and for this we use &lt;a href="http://dispatch.databinder.net/Dispatch.html"&gt;dispatch&lt;/a&gt; which wraps the Apache HttpClient project with a bow making it really simple to get resources and convert them to string, XML or JSON.  We were already familiar with lift-json so we would get the response as a string and use case classes to parse the JSON into.  Once again, XML is almost a joy to work with in Scala compared to other languages, but lift-json still wins in simplicity so we use JSON formatted APIs where we can.&lt;br /&gt;&lt;br /&gt;What’s next&lt;br /&gt;&lt;br /&gt;I’ve got my eye on &lt;a href="http://liftweb.net"&gt;Lift&lt;/a&gt; and &lt;a href="http://akka.io/"&gt;Akka&lt;/a&gt;.  I’ve had a play with Akka, but I feel I’d like to understand it a little more before using it in a project.  Other than that, I think I’m generally getting my head around Scala and functional programming all the more and I’m rather enjoying it.  I still feel like I’m just scratching the surface, but it is my first app remember?</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/6407612055788254419/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=6407612055788254419" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/6407612055788254419?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/6407612055788254419?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/7g04jq3UfaQ/my-first-scala-app.html" title="My first scala app" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/06/my-first-scala-app.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkADSXs4fSp7ImA9WhZRFE4.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-927283600945750708</id><published>2011-04-10T13:24:00.002+01:00</published><updated>2011-04-10T13:32:58.535+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-10T13:32:58.535+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ndistro" /><category scheme="http://www.blogger.com/atom/ns#" term="curseleague" /><category scheme="http://www.blogger.com/atom/ns#" term="no.de" /><category scheme="http://www.blogger.com/atom/ns#" term="expressjs" /><category scheme="http://www.blogger.com/atom/ns#" term="nodejs" /><category scheme="http://www.blogger.com/atom/ns#" term="twitter" /><category scheme="http://www.blogger.com/atom/ns#" term="cursebird" /><title>Building Curseleague.com</title><content type="html">&lt;p&gt;&lt;br /&gt;I really wanted an excuse to play with NodeJS once again and after a conversation with &lt;a href="http://twitter.com/blagdaross"&gt;blagdaross&lt;/a&gt; in the office I had my idea.  I had also been looking for an excuse to stretch the legs of Joyents &lt;a href="http://no.de"&gt;no.de&lt;/a&gt; hosting service for NodeJS.  I had all of the pieces needed:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;&lt;a href="http://dev.twitter.com/"&gt;Twitter API&lt;/a&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;a href="http://cursebird.com/help/api"&gt;Cursebird API&lt;/a&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;a href="https://no.de/"&gt;no.de hosting&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Once again I turned to the trusty toolset of &lt;a href="http://expressjs.com/"&gt;ExpressJS&lt;/a&gt; for routing and EJS for template rendering.  I wanted to build straight out of Twitter lists, so first thing was to hit Twitter to get the members of a list.  As this is an open call, no oAuth handshaking has to take place, which is nice.  However, getting the list of members returns a whole bunch of metadata about each member on the list.  I didn't need any of this information, and I couldn't find all of the members without pagination.  This is where I started to have some fun with writing asynchronous code for very much a synchronous web request.  &lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Once I had the list of members of a list, I could do a lookup on Cursebird for the points they have given those members.  I have to say the Cursebird API is just fantastic!  Talk about the API being the website, just stick .json on the end of any page you visit and get the data you need.  No API key nonsense for open data is also a great move.  Well done to Cursebird on their efforts.  After sorting the list according to points, all it takes is rendering.  &lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;I recognised that crunching all of this data with potentially a few hundred server side calls to Twitter and Cursebird would be slow, so I introduced a really dumb key-value pair cache and use that cache at every level, from caching a single request to Twitter or Cursebird, to caching a whole dataset for a webpage.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Onto hosting, and no.de has this really interesting deployment technique where you do a git push into a repository you're given on the virtual server you have.  From there a post-push hook is fired which deploys your app and runs a named file (server.js).  The no.de real time analytics are really impressive, although it'd be great to get historical information out of that too.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;The one on-going grievance I'm dealing with is locally I deploy on port 3000 but in production I deploy on port 80, and currently I'm changing this on commits as a work.  I had a way of doing this through a node module I'd built a while ago to &lt;a href="https://github.com/theteam/node-properties"&gt;get properties from a json file&lt;/a&gt;, although up until now I'd been using &lt;a href="https://github.com/visionmedia/ndistro"&gt;nDistro&lt;/a&gt; to manage my dependencies, but with no.de I'd have to do this a little differently.  I could push the properties project into the &lt;a href="http://npmjs.org/"&gt;NPM&lt;/a&gt; repositories but I'm also thinking of building a node module that can read nDistro files and add dependencies at run-time.  It's food for thought.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Finally a big thank you to &lt;a href="http://twitter.com/tregoning"&gt;JT&lt;/a&gt; for working up the styling.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Cheers,&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Robbie&lt;br /&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/927283600945750708/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=927283600945750708" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/927283600945750708?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/927283600945750708?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/yv3rFyhSThA/building-curseleagecom.html" title="Building Curseleague.com" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>3</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/04/building-curseleagecom.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEDQHgycCp7ImA9Wx9UFkQ.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7322635818303928523</id><published>2011-02-14T14:37:00.005Z</published><updated>2011-02-14T15:17:51.698Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-14T15:17:51.698Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="last.fm" /><category scheme="http://www.blogger.com/atom/ns#" term="music brainz" /><category scheme="http://www.blogger.com/atom/ns#" term="rewiredstate" /><category scheme="http://www.blogger.com/atom/ns#" term="gsxsw" /><category scheme="http://www.blogger.com/atom/ns#" term="sxsw" /><category scheme="http://www.blogger.com/atom/ns#" term="hack" /><category scheme="http://www.blogger.com/atom/ns#" term="guardian" /><title>Guardian SXSW Hack Review</title><content type="html">&lt;h2&gt;What I built and why&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;I really wanted to scratch my own itch at the hack weekend with SXSW in mind.  There is going to be over a thousand bands playing at the music festival, and many of those will be trying to break through and make it.  This means, there’s a fair chance I won’t have heard of many of the bands.  Also, the sheer number of bands playing means it’ll be difficult for me to do any quick research about those bands to find out who I should go and see play.&lt;br /&gt;&lt;br /&gt;I’ve used Last.FM for nearly four years and have something like approaching 20,000 scrobbled tracks in their dataset, so they have a good impression of the type of music and artists I like listening too and I wanted to tap into that data.  Bearing in mind what I just said about not knowing any of the bands playing, I needed a different way to look at the data. Using &lt;a href="http://threechords.org/"&gt;Matt Andrew’s&lt;/a&gt; band listing API, I used the Last.FM API to find around 20 similar artists for each band playing at the festival, leaving me with a dataset of about 20,000 artists that are like those playing at the festival.  My thinking here was there would be a good chance of bigger bands would be amongst this dataset and I might have more of a chance of finding a match.&lt;br /&gt;&lt;br /&gt;Now, using the top artists from the Last.FM API, I could do an intersection of the artists I like and artists similar to those playing at SXSW.  I could then do a look up to see what bands are similar to bands I like, helping me discover new music at the festival without investing too much time doing any research on all of the artists.  Yes, it gave me a bit of a headache too.&lt;br /&gt;&lt;br /&gt;I only really got this working by lunch time on the second day, and although I had intended to build a simple HTML layer on the data I’d built, I just didn’t have the energy.  I went for a coffee, a chat and a nice sit down instead.  I don’t think it helped tremendously with the presentation not having any visuals, but I think a few people saw the potential of the application and the data behind it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What’s next&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The hack day is done, but I want to push this project on a little more.  The code certainly needs a lot of love and I wasn’t using a complete dataset over the weekend, so I need to import that to get more comprehensive results.  I’d also like to make this more generic for any festival, be it SXSW or Glastonbury.  &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Lessons, observations and stories&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The hack weekend was only my second hack event, and the first one I went too was only for the first day of a two day event, so I suppose this was my first full hack event.  I’ve been to plenty of small meet-ups, larger Bar Camp events and mammoth conferences, but a hack day definitely has a different and more intimate vibe too it.  The number of attendees seemed pretty optimal and it was certainly a good mix of developers, designers and journalists although this only really shone through in the final presentations.&lt;br /&gt;&lt;br /&gt;Although having an idea of what I was intending to build at the event, and even doing some thinking about how the application might work (but no coding before the pistol on Saturday morning, as that’s cheating!) it still took me a while to get started.  I probably would have coded my idea up in Node.JS as it’s quickly becoming my language of choice in the ‘get something done fast’ category, but as I was building something that I hoped would eventually be brought into the Guardian for festival coverage I thought I should build the application in the fast becoming language of choice at the Guardian, scala.  &lt;br /&gt;&lt;br /&gt;Scala, although fantastically awesome on one hand, still runs on the JVM and still requires Java like setup with web.xml files and the like. This isn’t a problem in itself, but it means some non-trivial time is spent just setting up a project the way you like it.  &lt;br /&gt;&lt;br /&gt;This leads nicely into lesson one:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;If intending to use a language that requires some investment in setup, look for a way of reducing this, perhaps by having a library of “hello world” applications pre-configured for repeat use.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Keeping with Scala, as a relative newbie to the language I know I was doing certain things in an inefficient way and that was compounded by the timescales of an event like this.  When working on a project and you suffer a setback, either you don’t know how to do something or something you thought would work just doesn’t, you generally have time to investigate, ask around and try a few things out.  However, that is a real luxury when trying to build things in hours and not weeks.&lt;br /&gt;&lt;br /&gt;Lesson Two:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;If you’re going to use a hack day to experiment with a new technology, expect frustrations and delays. Even a half hour delay can feel catastrophic.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Just to finish the Scala points, and this may be my lack of knowledge of the language, but I wish the JSON and HTTP support were a little better.  Compared to the XML support, which is an excellent xpath like implementation, the JSON support felt clunky.  I actually had to change the data I was getting (I was reading from a file, so I could do this) to remove some parts which I couldn’t get the code to parse.  As for the HTTP support, I had to bring in the Jetty HTTP client (which didn’t seem to recognise ‘utf-8’ as a character encoding), then bring in the Apache Commons HTTP client to request data from the Last.FM API.  One post on Stack Overflow I was reading while I was looking for answers to a Scala problem suggesting having a personal library for wrapping functions you wish were supported better.  &lt;br /&gt;&lt;br /&gt;Lesson Three:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Knowing how to do common things really well, and fast is essential.  In my case, using web based APIs using JSON and rendering JSON in turn.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;One technology I did use which I’m now using on a day to day basis is MongoDB and this is where knowing something about a technology really came into it’s own.  Getting stuff into and out of the database was as easy as it should be.  I used the effective but perhaps slightly noisy Casbah Scala driver to talk to MongoDB.  I was also using the Last.FM API to get information about bands and I realised that the Last.FM API was probably one of the first public web APIs I used thanks to a &lt;a href="http://blog.whatfettle.com/"&gt;Paul Downey&lt;/a&gt; workshop when I was a fresh faced graduate.  In a good way the API doesn’t look to have changed, which should be great as anything I built in that workshop might have a chance of working today.  However, the web has moved on a bit from RPC and XML and I’d like to see Last.FM offering more of a RESTful JSON based API.  That might just have influenced my decision to use Node.JS instead of Scala due to the support of JSON over XML.&lt;br /&gt;&lt;br /&gt;As this was a music project, the sensible thing was using Music Brainz ids for the bands, and although this worked in many instances some of the bands playing SXSW don’t yet have Music Brainz ids and perhaps more surprisingly the Last.FM API doesn’t seem to provide Music Brainz ids for all of the bands in it’s API, even top 100 chart bands can be without one.  The algorithm I built depended on this data, and although I could do a best effort and call Music Brainz directly, it would have been nice if this was covered off by the larger provider, Last.FM.&lt;br /&gt;&lt;br /&gt;Update: I've already found out Last.FM does actually offer JSON in it's API, that would have been handy to know, and sorta proves the lesson of know what you're doing before starting.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7322635818303928523/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7322635818303928523" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7322635818303928523?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7322635818303928523?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/gbJCO9knqPE/guardian-sxsw-hack-review.html" title="Guardian SXSW Hack Review" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>3</thr:total><feedburner:origLink>http://blog.iclutton.com/2011/02/guardian-sxsw-hack-review.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0AAQXw5fCp7ImA9Wx9RGEs.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-5645926195999154893</id><published>2010-12-20T17:20:00.001Z</published><updated>2010-12-20T17:22:20.224Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-20T17:22:20.224Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="cache manifest" /><category scheme="http://www.blogger.com/atom/ns#" term="html5" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><category scheme="http://www.blogger.com/atom/ns#" term="guardian" /><title>Cache me if you can</title><content type="html">From the producers who brought you '&lt;a href="https://github.com/robb1e/Tengo-and-Cache"&gt;Tengo and Cache&lt;/a&gt;', we present 'Cache me if you can'.&lt;br /&gt;&lt;br /&gt;The concept is all about HTML delivery into iOS devices alongside a mechanism for updating the application without going through the Apple app store.  Loading HTML from a local file is not a new idea, as a few companies and projects have sprung up around this type of mobile app development have emerged, most notably Phonegap and more recently Apparatio.  &lt;br /&gt;&lt;br /&gt;The original iOS app I had built, &lt;a href="https://github.com/robb1e/Tengo-and-Cache"&gt;Tengo and Cache&lt;/a&gt;, created a way that took this idea in a slightly different direction.  The projects above deliver HTML within an application and would rely on Javascript to update any content within the application, using local storage to persist data.  What Tengo and Cache offered was a way to download new HTML documents in their entirety, storing the files in writable areas of the file system on a device.  This worked by providing a manifest file, based on the HTML5 manifest, on the domain which was the iOS app was trying to download files from.  &lt;br /&gt;&lt;br /&gt;During a hack day for the Guardian, I extended this work to add a further downstream cache.  Although every effort is made to cache resources before loading, some files may be requested which are not included in the manifest.  Overriding the NSURLCache class, the application can intercept any calls which attempt to go out to the web.  This cache then retrieves the file, stores it, and then serves this instead of continuing down the pipeline.  The next time this file is requested, the cache serves the file on disk instead of hitting the web at all.&lt;br /&gt;&lt;br /&gt;Now that I had an iOS application that should pre-cache and intercept, I needed some content to install onto a device.  I had wanted to use a Wordpress blog, or RSS feed, but felt that this content could prove difficult.  Without knowing what content would be delivered, I couldn't build a manifest file that would capture the entire content.  Also, I thought it might prove to be bad user experience if something that would work on a web page when online might not work offline.  Even something as ubiquitous as search might look to have failed miserably.  &lt;br /&gt;&lt;br /&gt;I decided to use something I knew I had control over, the Guardian Content API.  Using a small &lt;a href="https://github.com/robb1e/Content-API-Nodejs-App"&gt;NodeJS&lt;/a&gt; application, I could retrieve a query and present that as HTML, alongside an appropriate manifest file.  Then all that was required was a small property change in the iOS app and a native application was ready to be launched.  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px" id="__ss_6262138"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/robb1e/cache-me-if-you-can" title="Cache me if you can"&gt;Cache me if you can&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse6262138" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=cache-me-if-you-can-101220111820-phpapp02&amp;stripped_title=cache-me-if-you-can&amp;userName=robb1e" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse6262138" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=cache-me-if-you-can-101220111820-phpapp02&amp;stripped_title=cache-me-if-you-can&amp;userName=robb1e" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/robb1e"&gt;Robbie Clutton&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/5645926195999154893/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=5645926195999154893" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5645926195999154893?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5645926195999154893?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/_Wd1gV7jydQ/cache-me-if-you-can.html" title="Cache me if you can" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/12/cache-me-if-you-can.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ICQ3o4eSp7ImA9Wx9TFU4.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-738347485162409491</id><published>2010-11-23T18:05:00.002Z</published><updated>2010-11-23T18:06:02.431Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-23T18:06:02.431Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="galaxy" /><category scheme="http://www.blogger.com/atom/ns#" term="tablet" /><category scheme="http://www.blogger.com/atom/ns#" term="samsung" /><category scheme="http://www.blogger.com/atom/ns#" term="review" /><category scheme="http://www.blogger.com/atom/ns#" term="phone" /><title>Samsung Galaxy Tab - Review</title><content type="html">The first thing you notice when you start using a Samsung Galaxy Tablet is that it clearly thinks it’s a phone.  Most people I show the device to also think it’s a phone as they do their best Dom Joly impersonation.  I had only intended to use the device over wifi but I’m constantly reminded that I haven’t put a SIM card in, and that the phone can only make emergency calls.  I noticed at one point that a significant portion of battery life was being put to use on phone related activities. At that point, I put the device into flight mode and then enabled wifi to try and make the battery last longer.  Trying not to use the device as a phone means many of the Samsung applications simply don’t work as they require a SIM card, although why they need the SIM card I couldn’t tell you.  &lt;br /&gt;&lt;br /&gt;One of the things I was looking forward to doing with the device when I got it was to use it around the office as I’m using a desktop and I wanted a way to take notes, look things up and do demos.  My first snag there was that the electronic keyboard is just too small for any note taking at length and when I held the device in portrait mode I was typing by thumbs alone.  I’ve found that the screen on the Galaxy is just about right to web pages, and due to the size, I can hold the device in one hand, not unlike the Kindle.  Many websites are redirecting me to their mobile version, which although can look very nice on the size of the device, it sort of deflects the purpose of having a tablet as opposed to a phone.&lt;br /&gt;&lt;br /&gt;Some of the plain oddities are exposed when trying to use Google Docs on the phone.  Having sent you to the mobile version, you can edit documents and spreadsheets, but not presentations.  When trying to change to desktop mode you get a bizarre error about the browser not supporting web word processing.  There was me thinking that all that was needed was HTML rendering, Javascript processing and an Internet connection.  &lt;br /&gt;&lt;br /&gt;I have felt I’m getting benefit when you consider just about how ‘cloud-enabled’ the device is.  I’m yet to plug the device in to my computer, apart from trying to deploy an application I’d built onto it.  For music I’ve been using Spotify, Last.FM or iPlayer for Radio 6.  Tools like Google Voice have let me find, download and play podcasts without needing desktop software like iTunes with varying success. The speakers on the device are just about good enough to carry around the house with you, or plug the headphone jack into stereos in different rooms.  I’ve used DropBox to drop video files onto the device and they just play without having to configure anything or install any codecs. &lt;br /&gt;&lt;br /&gt;I’m generally enjoying being able to think of this as more of a computer than a locked down device though, by seeing the running applications and being able to navigate the file system.  However there seem to be simple things I just can’t find, like changing the auto-lock timeout or being able to wake the device from being locked other than pushing a button at the top of the device, when my hands tend to be at the other end of the device.  &lt;br /&gt;&lt;br /&gt;I’m happy that Apple has some competition in the tablet market, but I think the experience needs to improve a lot, through both the physical device and the software that powers it.  I’d suggest a good start would be to get the device to stop thinking it’s a phone.  The Amazon Kindle has a SIM card for 3G access, but it’s knows it isn’t a phone.  It may be a small point, but I prefer my devices without the split personality.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/738347485162409491/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=738347485162409491" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/738347485162409491?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/738347485162409491?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/dqeZgQ1Vv_o/samsung-galaxy-tab-review.html" title="Samsung Galaxy Tab - Review" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/11/samsung-galaxy-tab-review.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4AQ3k9fyp7ImA9Wx5XEkw.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-435254173485791132</id><published>2010-09-11T14:21:00.008+01:00</published><updated>2010-09-11T14:49:02.767+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-11T14:49:02.767+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ndistro" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="github" /><category scheme="http://www.blogger.com/atom/ns#" term="mongoose" /><category scheme="http://www.blogger.com/atom/ns#" term="nodejs" /><category scheme="http://www.blogger.com/atom/ns#" term="theteam" /><category scheme="http://www.blogger.com/atom/ns#" term="tapas" /><title>nodejs, ndistro and git submodules</title><content type="html">On the (very early) &lt;a href="http://github.com/theteam/tapas-example"&gt;tapas&lt;/a&gt; platform at &lt;a href="http://theteam.co.uk"&gt;theteam&lt;/a&gt; I got a little stuck when mixing nDistro and git submodules and wanted to explain what I'd done to get around those issues. On the &lt;a href="http://github.com/theteam/tapas-models"&gt;tapas-models&lt;/a&gt; module, &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt; is used for data storage and tapas-models uses &lt;a href="http://github.com/LearnBoost/mongoose"&gt;Mongoose&lt;/a&gt; for MongoDB integration, but nDistro doesn't download Mongoose's downstream dependencies&lt;br /&gt;&lt;br /&gt;This is because nDistro downloads the tarball of the project from Github and that tarball doesn't include dependencies.  It might be nice for Github to do this, and I'll search the &lt;a href="http://support.github.com/"&gt;Github Support&lt;/a&gt; site to see if I can find something about that.  Anyhoo, the tarball doesn't contain it's git bindings, so I can't go in there and update the git submodules. &lt;br /&gt;&lt;br /&gt;The unfortunate thing is I've had to expose myself to some of the Mongoose inner workings to get the dependencies, but once I know them, it's a light touch for the next part.  Using nDistro as normal, I include that dependency, including the revision number in my .ndistro file.  As nDistro is executed in a bash environment, shell scripting can be used alongside the module declarations.  So, I use a Linux move command to put the dependency where Mongoose expects it and everyone's happy.&lt;br /&gt;&lt;br /&gt;It's a simple solution to what was a nagging issue and keeps me on my happy path toward &lt;a href="http://www.hawksworx.com/journal/2010/09/10/the-next-generation-stack-is-nodejs-ready-to-go-mainstream"&gt;NNNN&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/575191.js?file=ndistro%20without%20git%20submodules"&gt;&lt;/script&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/435254173485791132/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=435254173485791132" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/435254173485791132?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/435254173485791132?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/XS_Nq7HZY9U/nodejs-ndistro-and-git-submodules.html" title="nodejs, ndistro and git submodules" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/09/nodejs-ndistro-and-git-submodules.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04ESH44cSp7ImA9Wx5REkg.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-1187510738975178807</id><published>2010-08-19T20:47:00.007+01:00</published><updated>2010-08-19T22:05:09.039+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-19T22:05:09.039+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="friedchicken" /><category scheme="http://www.blogger.com/atom/ns#" term="recipe" /><category scheme="http://www.blogger.com/atom/ns#" term="guardian" /><title>Recipe: Guardian Fried Chicken</title><content type="html">This evening I made an attempt at the &lt;a href="http://www.guardian.co.uk/lifeandstyle/wordofmouth/2009/jul/24/kfc-secret-recipe-revealed"&gt;Guardian Fried Chicken&lt;/a&gt;, and although Tim Hayward gives the recipe in the associated video, I wanted to share how I had gotten on.&lt;br /&gt;&lt;br /&gt;Although the recipe is simple, it does take some time.  &lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;    &lt;li&gt;&lt;a href="http://www.flickr.com/photos/robbies/4908482426/in/set-72157624761684190/"&gt;Soak the chicken in milk overnight and cover with cling film&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;&lt;a href="http://www.flickr.com/photos/robbies/4907889305/in/set-72157624761684190/"&gt;Poach the chicken in the milk it was soaked in by bringing to the boil and then simmering for 20 minutes&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;&lt;a href="http://www.flickr.com/photos/robbies/4908486152/in/set-72157624761684190/"&gt;Meanwhile combine the spices and blend&lt;/a&gt;, &lt;a href="http://www.flickr.com/photos/robbies/4907900179/in/set-72157624761684190/"&gt;then add to plain flour&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;When the chicken is cooked, leave to cool in the milk&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;Coat the chicken in the spiced flour&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;&lt;a href="http://www.flickr.com/photos/robbies/4908453670/in/set-72157624761684190/"&gt;Return to the milk and dip again in the spiced flour&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;Place into hot (but not scalding) oil and cook for 5-6 minutes until nicely brown&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;&lt;a href="http://www.flickr.com/photos/robbies/4908457426/in/set-72157624761684190/"&gt;Pat down with kitchen paper&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I also added some &lt;a href="http://www.flickr.com/photos/robbies/4907860639/in/set-72157624761684190/"&gt;BBQ beans&lt;/a&gt; (just normal beans with brown sauce and lots of pepper) and some coleslaw for good measure.&lt;br /&gt;&lt;br /&gt;I also had to make some compromises.  I don't have a fryer, so I used a pan.  Without a temperature guide, I used a trick my Mum used to do which is to drop a s&lt;a href="http://www.flickr.com/photos/robbies/4907861897/in/set-72157624761684190/"&gt;mall piece of bread&lt;/a&gt; into the oil to get a feel for how hot the oil is.    &lt;br /&gt;&lt;br /&gt;So generally it all went well, although I had the oil too hot, so the coating became too crispy too early and when cut, the chicken looked slightly underdone.  I put it in the oven for 10 minutes to finish it off, so while the meat was cooked it also dried out the coating.  However, as I was cooking the pieces one at a time, I turned the heat down a lot so that the 5-6 minutes fried cooked the meat all the way through.&lt;br /&gt;&lt;br /&gt;The one thing that was missing from the recipe was MSG for the spices.  It might have enhanced the flavour more, it was already pretty good.  It still didn't have that KFC feel to it though.  I think I'll be trying it again though.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.flickr.com/photos/robbies/sets/72157624761684190/with/4908461652/"&gt;Check out the set for more photos.&lt;/a&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/1187510738975178807/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=1187510738975178807" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/1187510738975178807?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/1187510738975178807?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/S-2LOVldvO0/recipe-guardian-fried-chicken.html" title="Recipe: Guardian Fried Chicken" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/08/recipe-guardian-fried-chicken.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIMRX49fyp7ImA9Wx5SEE0.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-8853941708495607722</id><published>2010-08-05T10:27:00.001+01:00</published><updated>2010-08-05T10:29:44.067+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-05T10:29:44.067+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="offline" /><category scheme="http://www.blogger.com/atom/ns#" term="cache manifest" /><category scheme="http://www.blogger.com/atom/ns#" term="html5" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="manifest" /><category scheme="http://www.blogger.com/atom/ns#" term="iphone" /><category scheme="http://www.blogger.com/atom/ns#" term="ipad" /><title>Tengo and Cache</title><content type="html">This project was born from a desire to push on from some work of creating iPhone applications using only web technologies that I had done some time ago. That work can be seen http://github.com/robb1e/iWeb. This first project was a way of deploying a HTML/CSS/Javascript application onto an iOS device and have it run as a native application.&lt;br /&gt;&lt;br /&gt;The issue with that was the only way to change HTML, CSS, Javascript, images or other data was to depend on a live internet connection or do an application update. I was looking for a way to make it easier to cache those resources with a specific eye on offline experience.&lt;br /&gt;&lt;br /&gt;After some thought, the lightbulb switched on and it became an immediate choice to use the HTML5 Cache Manifest file as a basis of discovering what resources should cached. This project aims to do just that.&lt;br /&gt;&lt;br /&gt;Between the application bootstrapping and a webpage being rendered, the cache manifest is read and each file is downloaded and stored in the iOS application sandbox directory. After all files have been saved, the UIWebView will render an index.html file in that sandbox.&lt;br /&gt;&lt;br /&gt;Check it out: http://github.com/robb1e/Tengo-and-Cache</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/8853941708495607722/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=8853941708495607722" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/8853941708495607722?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/8853941708495607722?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/ZOwloVyVaxA/tengo-and-cache.html" title="Tengo and Cache" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/08/tengo-and-cache.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUQERHozeCp7ImA9WxFbFUQ.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7393513318410547071</id><published>2010-07-08T13:41:00.003+01:00</published><updated>2010-07-08T13:48:25.480+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-08T13:48:25.480+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sitemesh" /><category scheme="http://www.blogger.com/atom/ns#" term="json" /><category scheme="http://www.blogger.com/atom/ns#" term="grails" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Json and Grails</title><content type="html">Recently I was trying to return JSON data from a Grails app and I was having some issues.  Grails has some built in conversion utilities to enable code like &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;render myObject as JSON&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is all well and good, but I have a few fundamental issues with this.  First off, attributes like fully-qualified class name and the database primary key are returned.  I don't really want to expose internal information to client side applications.  It also doesn't allow any renaming of the attributes.  Say you have a String called firstName using camel case in your domain object, it returns that to the client.  What if you want the API to return first_name?&lt;br /&gt;&lt;br /&gt;The answer was a lot simpler then my searching could muster, and that is to use the template system, Sitemesh in this case, to render exactly what you want in JSON as you would if you were rendering HTML.  OK, it's a bit of a headache, but you have complete control over the content and style.  Combine that with a one liner in the controller to set the content type, and you're flying.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;response.contentType = "text/javascript"&lt;br /&gt;&lt;/pre&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7393513318410547071/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7393513318410547071" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7393513318410547071?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7393513318410547071?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/FmnaroQJCH0/json-and-grails.html" title="Json and Grails" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/07/json-and-grails.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0QDQHYzeCp7ImA9WxFUFEo.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7566655451939353196</id><published>2010-06-25T13:27:00.005+01:00</published><updated>2010-06-25T16:22:51.880+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-25T16:22:51.880+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="spider" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="automation" /><category scheme="http://www.blogger.com/atom/ns#" term="github" /><category scheme="http://www.blogger.com/atom/ns#" term="ant" /><category scheme="http://www.blogger.com/atom/ns#" term="crawl" /><category scheme="http://www.blogger.com/atom/ns#" term="monitoring" /><category scheme="http://www.blogger.com/atom/ns#" term="validation" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Spider and Validate</title><content type="html">One of the things we've been trying to achieve is automation is every sense.  As my old mate &lt;a href="http://twitter.com/io2"&gt;Otu&lt;/a&gt; once said "automate until it hurts!"  Well, hopefully this will make it a little less painful.  We wanted to point to a single page, have the application spider the entire site finding pages which return anything apart from 200 OK, and validate all those who do return 200 OK.  &lt;br /&gt;&lt;br /&gt;By combining two PHP projects, PHPCrawl (http://sourceforge.net/projects/phpcrawl/) and the frontend-test-suite (http://github.com/NeilCrosby/frontend-test-suite) it's been possible to do just that.  Throw a little Ant build script in there too and deploy into a continuous integration container like Hudson, and you start to get a feel for the state of websites in development, or even in production as part of a monitoring tool.  &lt;br /&gt;&lt;br /&gt;To make use of this, edit the build.xml file to change the SITE_URL to be the endpoint which you want to test and run 'ant validate'.  This will do an Ant copy with filtering to create a file called test.php.  Ant then runs this and then captures the output from this.  It collects which pages may have returned a 500, a 404 and 200 OK pages.  It then passes an array of 200 OK pages into the frontend-test-suite which uses PHPUnit to report on HTML validation.&lt;br /&gt;&lt;br /&gt;There's still plenty of work to do, mainly introducing the ability to supply your own W3C validator endpoint and introducing CSS validation.  It'll come though, so keep an eye out!&lt;br /&gt;&lt;br /&gt;The result of this is over at Github - http://github.com/robb1e/Validator&lt;br /&gt;&lt;br /&gt;As a side note, to get frontend-test-suite as a submodule on Github I followed instructions from &lt;a href="http://twitter.com/FND/statuses/17009478723"&gt;FND&lt;/a&gt;, thanks!</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7566655451939353196/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7566655451939353196" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7566655451939353196?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7566655451939353196?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/r_AAtn0pk-M/spider-and-validate.html" title="Spider and Validate" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/06/spider-and-validate.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IAQXs5cCp7ImA9WxFUEUU.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-2489135801922755449</id><published>2010-06-22T07:44:00.004+01:00</published><updated>2010-06-22T07:52:20.528+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-22T07:52:20.528+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="dns" /><category scheme="http://www.blogger.com/atom/ns#" term="URI" /><category scheme="http://www.blogger.com/atom/ns#" term="servername" /><category scheme="http://www.blogger.com/atom/ns#" term="request" /><category scheme="http://www.blogger.com/atom/ns#" term="grails" /><category scheme="http://www.blogger.com/atom/ns#" term="css" /><title>Grails multiple web sites, same web application</title><content type="html">We had a need recently to have one web application serving many web sites, where the same data schema was in place with data shared across multiple web sites, but we wanted to show different styling and different data depending on the URI.  In the end it turned out to be fairly simple.  With our naming conventions, it's possible to hit one of several URIs for the same site, such as:&lt;br /&gt;&lt;br /&gt;* project.testing.com&lt;br /&gt;* project.staging.com&lt;br /&gt;* project.com&lt;br /&gt;&lt;br /&gt;So I had created a domain lookup service that when passed the request URI could work out what site the request for targeting.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def targetUri = request.getServerName()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I did a lookup for that target name against an object stored in the database relating to that targetUri and passed that back, making it available for queries for the correct data to show, and passing that down into the views to render the relevant CSS and Javascript files.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/2489135801922755449/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=2489135801922755449" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/2489135801922755449?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/2489135801922755449?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/wi8iNtUmg94/grails-multiple-web-sites-same-web.html" title="Grails multiple web sites, same web application" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/06/grails-multiple-web-sites-same-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQBQ3k8cSp7ImA9WxFVF0s.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-3369589398990093423</id><published>2010-06-17T10:23:00.009+01:00</published><updated>2010-06-17T10:52:32.779+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-17T10:52:32.779+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="deployment" /><category scheme="http://www.blogger.com/atom/ns#" term="automated" /><category scheme="http://www.blogger.com/atom/ns#" term="ant" /><category scheme="http://www.blogger.com/atom/ns#" term="hudson" /><category scheme="http://www.blogger.com/atom/ns#" term="grails" /><title>Grails, Ant, Hudson and automated deployments</title><content type="html">One of the things I like about Grails is the embedded server, it makes deployment and getting things up and running nice and simple.  I've tried building a WAR and deploying into Jetty and Tomcat, but for some reason the system resources seem to spike when deployed in this fashion compared to the built in 'run-app' functions of the Grails command line tools.  &lt;br /&gt;&lt;br /&gt;With this in mind, I wanted to use the Grails command line to do automated deployment based off our &lt;a href="https://hudson.dev.java.net/"&gt;Hudson&lt;/a&gt; continuous integration container via &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; build scripts.&lt;br /&gt;&lt;br /&gt;First I created an init.d start/stop script for the server.  At &lt;a href="http://theteam.co.uk"&gt;theTeam&lt;/a&gt; we can be working on multiple projects at the same time, so we need to set some conventions about port usage so we can manage our applications easier, so this is the first step towards just that.&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/441916.js?file=Grails%20start%20script"&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;This script is simple enough, on start it checks to see if that port is used, if so it bombs out, if not it &lt;a href="http://en.wikipedia.org/wiki/Nohup"&gt;nohup's&lt;/a&gt; the Grails command, telling it which port to use, and also just for ease of management, adding the name of the site.  This means running commands like ps -ef and pipping to grep shows what's running.  Stopping searches for a process running with that port and kills it.&lt;br /&gt;&lt;br /&gt;That's the first step.  Next is the Ant script to deploy the latest code after tests and static analysis.  In this, the target folder has the latest code, we stop any server that's running, do a Grails clean and then run the server. The script then waits to see if it can open an socket to the port that the server is running on, and fails it it cannot.  This usually means the server has failed to startup.  The variables are gathered from a properties file and should be fairly obvious.  &lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/441917.js?file=Grails%20Ant%20Hudson%20auto%20deploy"&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Now, you might have noticed a few oddities in the script like setting JAVA_HOME and BUILD_ID.  First, when running in Hudson, any processes are run using the JRE and JDK that is deployed with Hudson, and I wanted to ensure it was the system Java I had installed.  The BUILD_ID is a little trick that tells Hudson not to terminate the process after the build has finished, which it had a habit of doing in earlier versions.&lt;br /&gt;&lt;br /&gt;When starting Hudson, a flag should also be set to tell it not to kill child processes.  My start command is thus:&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/441915.js?file=hudson%20without%20killing%20a%20child%20process"&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Adding this all together means it's possible to get automated deployments with Grails added into your builds.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/3369589398990093423/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=3369589398990093423" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/3369589398990093423?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/3369589398990093423?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/wdpVXAM4S9Y/grails-ant-hudson-and-automated.html" title="Grails, Ant, Hudson and automated deployments" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/06/grails-ant-hudson-and-automated.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEICQHc4fip7ImA9WxFVF0s.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-680753429970131807</id><published>2010-06-17T10:21:00.001+01:00</published><updated>2010-06-17T10:22:41.936+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-17T10:22:41.936+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="s3" /><category scheme="http://www.blogger.com/atom/ns#" term="grails" /><category scheme="http://www.blogger.com/atom/ns#" term="aws" /><title>AWS S3 Integration with Grails</title><content type="html">I’ve been playing with the S3 plugin for a Grails project and wanted to share how I set it up, as the documentation on the site doesn’t cover an example.&lt;br /&gt;&lt;br /&gt;First, in my model I added a byte array field and made it transient. Transient basically means it doesn’t add the bytes to the database, but just keeps the data in memory. This means you can take advantage of all of the automatically generated controllers and view classes which are created from the model without having to really deal with the contents.&lt;br /&gt;&lt;br /&gt;    class ImageHolder{&lt;br /&gt;        byte[] image&lt;br /&gt;        String imageUri&lt;br /&gt;        static transients = ['image']&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;In the controller we use the request object to get the file and save it to disk, removing any spaces that may have crept in. Then we start using the plugin, and create a S3Asset from the file and put it into the asset service class. The assetService must also be declared at the top, and it’s injected via Spring from the plugin.&lt;br /&gt;&lt;br /&gt;    def s3AssetService&lt;br /&gt;    ....&lt;br /&gt;    def imageholder = new ImageHolder(params)&lt;br /&gt;    ....&lt;br /&gt;    def mhsr = request.getFile('image')&lt;br /&gt;    def fileName = mhsr.getOriginalFilename().replace(' ','_')&lt;br /&gt;    def ext = fileName.substring(fileName.lastIndexOf(".")+1);&lt;br /&gt;    def file = new File(fileName)&lt;br /&gt;    if (!mhsr?.empty &amp;&amp; mhsr.size &lt; 1024*200){&lt;br /&gt;  mhsr.transferTo(file)&lt;br /&gt;  def asset = new S3Asset(file)&lt;br /&gt;  asset.mimeType = ext&lt;br /&gt;  s3AssetService.put(asset)&lt;br /&gt;  imageholder.imageUri = asset.url()&lt;br /&gt;         imageholder.save()&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;What this does, is now uses the async service to put the file into S3, so your request returns very quickly. You can see that the image URI is now persisted into the domain object, but the content isn’t.&lt;br /&gt;&lt;br /&gt;One final thing is to configure AWS in config.groovy. I set the flag to not add a prefix to the bucket name, as I’m using reverse DNS/package style bucket name to ensure uniqueness.&lt;br /&gt;&lt;br /&gt;aws {&lt;br /&gt; domain="s3.amazonaws.com"&lt;br /&gt; accessKey="YOURACCESSKEY"&lt;br /&gt; secretKey="YOURSECRETKEY"&lt;br /&gt; bucketName="your.bucket.name"&lt;br /&gt; prefixBucketWithKey=false&lt;br /&gt; timeout=3000&lt;br /&gt;}</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/680753429970131807/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=680753429970131807" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/680753429970131807?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/680753429970131807?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/BXkdInsek0M/aws-s3-integration-with-grails.html" title="AWS S3 Integration with Grails" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/06/aws-s3-integration-with-grails.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcNQn85eyp7ImA9WxFUEUw.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-4643667002324936593</id><published>2010-06-16T07:11:00.009+01:00</published><updated>2010-06-21T10:21:33.123+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-21T10:21:33.123+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="html5" /><category scheme="http://www.blogger.com/atom/ns#" term="iplayer" /><category scheme="http://www.blogger.com/atom/ns#" term="iphone" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><category scheme="http://www.blogger.com/atom/ns#" term="ipad" /><category scheme="http://www.blogger.com/atom/ns#" term="apple" /><title>Apple, standards and plugins</title><content type="html">I hate to sound dramatic but with Apple sounding off about web standards over plugins with their latest products I was wondering just how much Apple was dragging he industry forward, or if it was doing the opposite.&lt;br /&gt;&lt;br /&gt;Some years ago, developing web applications meant having to know the details about the browsers your audience were using. We're still suffering from this today with discussion over different box models and questions about just when are Microsoft going to release a standards compliant browser.  One example of this is anyone who uses Exchange for email. They might know the pain of using the web client given how rich it is in Internet Explorer and how average it is in any other browser. &lt;br /&gt;&lt;br /&gt;The advent of cross browser JavaScript libraries helped reduce many of these pains and it's great to see a lot of work going into the new HTML5 specs that are taking lessons direct from these libraries.  However, whilst &lt;a href="http://www.apple.com/html5/"&gt;singing the praises of a few websites&lt;/a&gt; that are using the new HTML5 audio and video elements, Apple seem to be creating their own events for touch that I can't see anywhere in the &lt;a href="http://www.w3schools.com/html5/html5_ref_eventattributes.asp"&gt;specs&lt;/a&gt;.  It seems Mozilla are also &lt;a href="https://wiki.mozilla.org/Firefox/Projects/Multitouch_Polish/DOM_Events"&gt;playing the same game&lt;/a&gt; also and I'm worried that we're returning to a world where we have to tailor lots of code to target specific browsers.&lt;br /&gt;&lt;br /&gt;You only have to go to Nike.com or log into Gmail to see iPad websites that have orientation and swipe support. It's all very nice but how much extra work is going to have to take place to get a website to work in different browsers and different devices? What happens when a new device comes out with different events, or even as with the new iPhone and it's different screen resolutions, how will that change the work already in the wild?&lt;br /&gt;&lt;br /&gt;This raises further questions for me, especially after reports of &lt;a href="http://ajaxian.com/archives/ipad-javascript-shockingly-slow"&gt;JavaScript execution speed on the iPad&lt;/a&gt; , even though some frameworks claim this isn't so much of a problem, such as &lt;a href="http://blog.sproutcore.com/post/531215199/introducing-sproutcore-touch"&gt;Spoutcore Touch&lt;/a&gt;&lt;br /&gt; and others go further saying that &lt;a href="http://mir.aculo.us/2010/06/04/making-an-ipad-html5-app-making-it-really-fast/"&gt;no framework should be used at all to ensure performance&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Although Apple have created fairly &lt;a href="http://developer.apple.com/safari/library/technotes/tn2010/tn2262/index.html"&gt;complete documentation&lt;/a&gt; about building web content for their devices, it does beg the question why there should be differences if we are supposed to live in a standards world?  How long will it be before you hit a webpage and have it say "The website is best viewed with iPad", or any other device or browser for that matter? &lt;br /&gt;&lt;br /&gt;Should we ignore the devices ad go for standards like the &lt;a href="http://www.bbc.co.uk/iplayer/bigscreen/"&gt;BBC iPlayer&lt;/a&gt; for big screens like PS3 and iPad , use libraries to augment sites like how &lt;a href="http://www.apple.com/iphone"&gt;apple.com/iphone&lt;/a&gt; allows the user to swipe the image carousel, tailor something specific for each device such as Nike.com or should we just be building native applications?   &lt;br /&gt;&lt;br /&gt;How should we progress?  Should we let our analytics decide? Should we take the standards route or device specific route?  This will certainly be one to keep an eye on.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/4643667002324936593/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=4643667002324936593" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/4643667002324936593?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/4643667002324936593?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/PzJQ3e0piok/apple-standards-and-plugins.html" title="Apple, standards and plugins" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2010/06/apple-standards-and-plugins.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU4EQnc4fCp7ImA9WxNREk4.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-5025255071025124781</id><published>2009-09-05T12:39:00.008+01:00</published><updated>2009-09-06T11:45:03.934+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-06T11:45:03.934+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="barcamp barcampbrighton iphone development adobeair adobe air css html javascript" /><title>iPhone development using standard web technologies</title><content type="html">Fresh after giving my talk from BarCampBright4, I wanted to get my notes down from my session on iPhone development using standard web technologies.  This all started when I wanted to do an iPhone application for my &lt;a href="http://code.google.com/p/latestscorestwitter/"&gt;Latest Scores&lt;/a&gt; Twitter app.  So I bought a book and made my way through it slowly, trying to decode Objective C before coming across the part I was really after, the UIWebView class. &lt;br /&gt;&lt;br /&gt;What I ended up arriving at was the ability to load and render a HTML file with it's own resources imported such as CSS, Javascript and images.  The real magic in Objective C is this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;NSURL *url = [NSURL fileURLWithPath:[ [ NSBundle mainBundle ] pathForResource: @"index" ofType:@"html" ]];&lt;br /&gt;NSURLRequest *request = [NSURLRequest requestWithURL:url];&lt;br /&gt;[webView loadRequest:request];&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;What this is doing is creating a URL with a resource on the path called 'index' with the file extension 'html'.  Build a request from this URL and pass the request into the instance of UIWebView.  &lt;br /&gt;&lt;br /&gt;From that point, as long as all of the resources that the index page requires are bundled into the project, you can load the page and have that actually &lt;i&gt;be&lt;/i&gt; the application.   &lt;br /&gt;&lt;br /&gt;So that's all nice if you have a certain type of application which doesn't require any device interaction.  For example, if you had a blog/news site and wanted to make it into an application, you could just get the content and display.  You can also change a few settings in a file called &lt;i&gt;info.plist&lt;/i&gt; to set the icon image and application name.  &lt;br /&gt;&lt;br /&gt;That template project is available on GitHub, http://github.com/robb1e/iWeb&lt;br /&gt;&lt;br /&gt;However, if you want device interaction you'll need a richer framework, and that's where something like &lt;a href="http://www.phonegap.com"&gt;PhoneGap.com&lt;/a&gt; comes in.  With the abstracted Javascript file that works for iPhone, Blackberry and Android, functions are available to get access to the contact list, audio playback and other device features.  &lt;br /&gt;&lt;br /&gt;To play an embedded audio resource for example, you can write the following Javascript, provided you've included the PhoneGap.js file.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;new Media('beep.wav').play(); &lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;With both of these projects there is one caveat with Xcode in that it wants to compile Javascript files.  You have to make sure you set those files as resources to copy, not to compile. This can be done in the target list on the left hand side.&lt;br /&gt;&lt;br /&gt;To extend the idea of using standard web technologies whilst not building web sites, this next example uses &lt;a href"http://www.adobe.com/products/air/"&gt;Adobe's AIR&lt;/a&gt; run time and the HTML, CSS, Javascript and image from the first example.  This is also available on GitHub: http://github.com/robb1e/AirWeb&lt;br /&gt;&lt;br /&gt;By creating the application.xml file, you can tell Air to launch a file as the initial content and that will be rendered using it's &lt;a href="http://webkit.org/"&gt;WebKit&lt;/a&gt; engine.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;content&amp;gt;www/index.html&amp;lt;/content&amp;gt;&lt;br /&gt;&lt;/code&gt; &lt;br /&gt;&lt;br /&gt;To run the desktop app, install the &lt;a href="http://www.adobe.com/products/air/tools/sdk/"&gt;Adobe AIR SDK&lt;/a&gt; then run the command:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;adl application.xml&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This launches the application in debug mode.  To create an Air file, you need to compile using ADT.  &lt;br /&gt;&lt;br /&gt;This has shown that using the same technologies and languages you'd use to build a web page can be used for both mobile devices with and without device interaction and client applications that are installed on a computer.&lt;br /&gt;&lt;br /&gt;There are other ways of achieving the same result using &lt;a href="https://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html"&gt;Apple's web namespace tags&lt;/a&gt; in the markup with HTML5 tricks like &lt;a href="http://www.w3.org/TR/offline-webapps/"&gt;offline storage&lt;/a&gt;.  There's also other libraries like &lt;a href="http://www.jqtouch.com/"&gt;jQTouch&lt;/a&gt;, so check out what else is around too.&lt;br /&gt;&lt;br /&gt;The "write once, run anywhere" idea may just be coming into reality .</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/5025255071025124781/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=5025255071025124781" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5025255071025124781?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5025255071025124781?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/ddrv2BKwwmM/iphone-development-using-standard-web.html" title="iPhone development using standard web technologies" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.iclutton.com/2009/09/iphone-development-using-standard-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkMGRng6fSp7ImA9WxJRFUw.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7865437125241608793</id><published>2009-05-17T00:47:00.003+01:00</published><updated>2009-05-17T01:00:27.615+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-17T01:00:27.615+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="championship manager" /><category scheme="http://www.blogger.com/atom/ns#" term="games" /><category scheme="http://www.blogger.com/atom/ns#" term="football" /><category scheme="http://www.blogger.com/atom/ns#" term="FIFA" /><category scheme="http://www.blogger.com/atom/ns#" term="pro evo" /><title>Metadata Cloud Gaming</title><content type="html">So, what the hell am I talking about? Let me give you a concrete example, and a personal wish list of mine for many a long year. &lt;br /&gt;&lt;br /&gt;When I was growing up, one of the most capturing games was Championship Manager.  This allowed the player to experience running a club, from transfers, tactics, training and more.  However, the thing it always missed was being able to actually play the game.  In terms of actually playing the game, when I was younger, the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;FIFA&lt;/span&gt;/EA Sports combo was hard to beat, although now I prefer the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;Konami&lt;/span&gt; Pro Evolution Soccer range.&lt;br /&gt;&lt;br /&gt;I've always though that it'd be amazing to merge these two together, and I think now with Web2.0/Mashups/Cloud Computing and Web &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;APIs&lt;/span&gt; this really should be possible.  I could easily see a situation where as a gamer I could mash two of these together.  For the tactical, and running of the game, I could use &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_3"&gt;Championship&lt;/span&gt; Manager, and as a simulator for the actually games, I could use my game of choice.  In this instance it really comes down to player stats.  If I could load the game from &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_4"&gt;Championship&lt;/span&gt; Manager, with the teams, players and 'stats' to boot, I could play out the game in my simulator and then upload the result along with &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_5"&gt;accommodating&lt;/span&gt; stats into the cloud where my '&lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_6"&gt;managerial&lt;/span&gt;' game could pick them up from, and vice &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;versa&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;&lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_8"&gt;Really&lt;/span&gt;, this is ripe for the picking.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7865437125241608793/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7865437125241608793" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7865437125241608793?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7865437125241608793?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/ClKeBz5c4oo/metadata-cloud-gaming.html" title="Metadata Cloud Gaming" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2009/05/metadata-cloud-gaming.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQMQnY-fSp7ImA9WxJTF0g.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7208635218636596709</id><published>2009-04-26T16:05:00.001+01:00</published><updated>2009-04-26T16:06:23.855+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-26T16:06:23.855+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="cameramigration" /><title>The Great Twitter Camera Migration</title><content type="html">I've just started a fun little side project called the 'Great Twitter Camera Migration', check it out: http://cameramigration.blogspot.com</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7208635218636596709/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7208635218636596709" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7208635218636596709?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7208635218636596709?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/aRJqR0U45Ek/great-twitter-camera-migration.html" title="The Great Twitter Camera Migration" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2009/04/great-twitter-camera-migration.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkAGQXYzcCp7ImA9WxVWFks.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-257114131168393192</id><published>2009-02-26T16:09:00.002Z</published><updated>2009-02-26T16:12:00.888Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-26T16:12:00.888Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="xfm lastfm last.fm scrobble scrobbler ruby" /><title>XFM Bot</title><content type="html">Just put together an XFM bot to scrobble the xfm.co.uk radio station recently played tracks and push that into Last.FM. &lt;br /&gt;&lt;br /&gt;How compatible are you?&lt;br /&gt;&lt;br /&gt;http://last.fm/user/xfmbot&lt;br /&gt;&lt;br /&gt;Source code is here: http://github.com/robb1e/xfmscrobbler/tree/master</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/257114131168393192/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=257114131168393192" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/257114131168393192?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/257114131168393192?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/wQJ3FUzL0hQ/xfm-bot.html" title="XFM Bot" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.iclutton.com/2009/02/xfm-bot.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8AQng8cCp7ImA9WxVQFks.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-7006987936274026324</id><published>2009-01-30T16:55:00.006Z</published><updated>2009-02-03T11:34:03.678Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-03T11:34:03.678Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="roles" /><category scheme="http://www.blogger.com/atom/ns#" term="policy" /><category scheme="http://www.blogger.com/atom/ns#" term="platform" /><category scheme="http://www.blogger.com/atom/ns#" term="Software" /><category scheme="http://www.blogger.com/atom/ns#" term="design" /><category scheme="http://www.blogger.com/atom/ns#" term="architecture" /><category scheme="http://www.blogger.com/atom/ns#" term="authentication" /><category scheme="http://www.blogger.com/atom/ns#" term="authorisation" /><category scheme="http://www.blogger.com/atom/ns#" term="abstraction" /><title>Vendor Driven Architecture</title><content type="html">So, you have your off the shelf application driving some important business process, maybe it doesn't quite do everything you need so you think about altering the application through plugins, changing source code or otherwise.  Some of these changes may drive you further away from that off the shelf's product road map, making upgrading later difficult and expensive.  Perhaps you don't intend to alter the code, but you may think that later on there may be a better off the shelf product that suites the needs of the business and that changing solutions is the best thing to do.&lt;br /&gt;&lt;br /&gt;Depending on how these tools are used really drives the way these tools should be deployed and designed into a business process.  If you only have one client talking to this application, you probably don't need to think too hard about how to solve this problem.  However, if you have multiple clients wanting to talk to this application, you may want to give it some thought.  If you have multiple clients, accessing the application in multiple different ways, then you definitely want to think about this problem.&lt;br /&gt;&lt;br /&gt;I'm not an advocate of big design up front, but what I'm proposing isn't that exactly.  What I'm suggesting is capturing the data model and contract of interactions early and use this as a way of driving an abstraction layer between the off the shelf product and the clients using that product.  By doing this, you've isolated the application, meaning you can upgrade easier even if there are breaking changes, and you can introduce additional application logic all behind the safely of a contract and data model.  After you've done this, anyone who uses your abstraction layer, need not know what you're using underneath.  The way it should be really.  This also defines a contract for working against, meaning that applications who talk to the abstraction layer can be tested easier against known behaviours. &lt;br /&gt;&lt;br /&gt;We can start to push the boat out a little here and start thinking about roles and policy for access.  There maybe different types of clients and users who wish to use the abstraction layer, and they may have different permissions and access to different resources, so how can you control access and protect your resources?  You may be able to control that at the application layer, but what if you've added plug ins or additional application logic layers?  And what if you change the application which manages permissions, and how do you bind them to the resources at the abstraction layer?  It may be better to think in terms of the abstraction layer and data model rather than what's behind the lines of the abstraction.  This way, binding to resources will be easier to control.  While we're dealing with authorisation, authenticaion doesn't stray far, you'll want a way to authenticate the clients and users attempting to use the abstraction layer, and you may want that authentication to come from multiple sources, say a database user store, or an internal directory service.  This will make it easier for those users to gain access to the systems, and easier to administer access through roles and policies of clients and users.&lt;br /&gt;&lt;br /&gt;What we now have is a platform.  An off the shelf application with or without additional application logic, wrapped with an abstracted and understood data model which must be accessed via an authenticaion and authorisation mechanism.  Put a bow on it, you're done.&lt;br /&gt;&lt;br /&gt;Depending on your network security preferences, you could now expand this in two ways.  For every off the shelf product you employ for different business processes, you could put an abstraction layer on it with an understood data model and give access to that abstraction via the same authentication and authorisation layer you've created for the first platform, or you could replicate the authenticaion and authorisation layer and have lots of self contained, secured platforms.  It doesn't particularly matter which is employed, but what should fall out of this is a common data model across several platforms, with a uniform authentication and authorisation layer.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Pg4v3Lep7Dc/SYgjO1xugtI/AAAAAAAAATY/B5rkSe7xgyg/s1600-h/vendordrivenarchitecture.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 320px; height: 253px;" src="http://4.bp.blogspot.com/_Pg4v3Lep7Dc/SYgjO1xugtI/AAAAAAAAATY/B5rkSe7xgyg/s320/vendordrivenarchitecture.png" alt="" id="BLOGGER_PHOTO_ID_5298523699462177490" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;It does raise the question of why you'd want to be doing this in the first place, you are after all creating more work than strictly necessary.  There is a temptation to say "if we're building this abstraction layer, we may as well replace the off the shelf component itself".  Well, let's not get ahead of ourselves.  We have to remember where the business value comes from, and that's generally in products and services, not business support and operational support systems themselves.  However, by abstracting the applications used and exposing the data, the cost of integration, migration and maintenance is lowered.  This model can be applied to internal exposures and for exposing data on the web.&lt;br /&gt;&lt;br /&gt;I just want to close with what I believe is the most important aspect here, and this is the business case.  There is a business case for abstraction, but it does come at a cost.  What it brings is contractual interactions and can lead to common, agreed upon resource based exposure.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/7006987936274026324/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=7006987936274026324" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7006987936274026324?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/7006987936274026324?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/HshGAiGgy1E/vendor-driven-architecture.html" title="Vendor Driven Architecture" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_Pg4v3Lep7Dc/SYgjO1xugtI/AAAAAAAAATY/B5rkSe7xgyg/s72-c/vendordrivenarchitecture.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.iclutton.com/2009/01/vendor-driven-architecture.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08NQ304cSp7ImA9WxVTFE0.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-5437584561839451817</id><published>2008-12-27T17:41:00.003Z</published><updated>2008-12-27T18:18:12.339Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-27T18:18:12.339Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tesco" /><category scheme="http://www.blogger.com/atom/ns#" term="play.com" /><category scheme="http://www.blogger.com/atom/ns#" term="Toyota Production System" /><category scheme="http://www.blogger.com/atom/ns#" term="Woolworths" /><category scheme="http://www.blogger.com/atom/ns#" term="Zavvi" /><title /><content type="html">The fall of &lt;a href="http://news.bbc.co.uk/1/hi/business/7798973.stm"&gt;Zavvi&lt;/a&gt; along with several other soon to be notable high street absentees and the troubles the banking system has been going through has got me thinking.  &lt;br /&gt;&lt;br /&gt;First I want to tackle failure of business processes.  Zavvi's main reason for going out of business, according to the press was a domino affect from Woolworth's going into administration.  Zavvi relied heavily on Woolworth's wholesale as a source of DVDs and CDs.  From the outside in, it seems Zavvi operated with wholesale distributors of goods rather than dealing directly with manufactures.  There's nothing wrong with that of course, however, if you deal with a middle man who goes out of business than you're left scrambling looking for a replacement at the same costs.  No doubt, if you have one middle man, you've probably aligned prices closely (albeit probably a given percentage above) the distributor. &lt;br /&gt;&lt;br /&gt;Why wasn't this adequately highlighted as a risk?  Why only have one supplier? If you must have one supplier, at least have a good idea of alternatives at the drop of a hat if that risk is realised.  &lt;br /&gt;&lt;br /&gt;This comes nicely into my second point, it almost seems that Zavvi have suffered a misapplication of the &lt;a href="http://en.wikipedia.org/wiki/Toyota_Production_System"&gt;Toyota Production System&lt;/a&gt; in that they pulled stock into their store as to minimise inventory, and therefore waste. Keeping the time between ordering from the wholesaler to getting into the customers hands is a worthy goal to aim for.  After all, unsold stock is not money in the bank.  But again, this raises the risk of only having one core supplier.&lt;br /&gt;&lt;br /&gt;Moving onto Woolworth's itself, I find myself asking what Woolworth's actually sells and the only answer I can fathom is that it's a general store.  They never specialised in anything, meaning that they never had the buying power to lower their prices and pass the savings onto their customers leaving them with a breadth of stock.  In my opinion that stock wasn't a breadth of high end stock, it was a breadth of medium to low end goods, odds and ends and overly priced electrical/entertainment goods.  If I needed a new set of tea towels, brilliant, but for the floorspace they wouldn't be able to justify just selling bits and bobs like that.  &lt;br /&gt;&lt;br /&gt;This leaves me asking, who are your customers, and what differentiates you from your competitors?   &lt;br /&gt;&lt;br /&gt;Finally, how can you expect to survive charging the prices both Zavvi and Woolworth's seemed happy to charge?  In both stores I could find a product, say a DVD, from a fairly well known internet only business, play.com, cheaper that wasn't even on sale than on the highsteet in post Christmas/liquidation sales.  Play.com does advertise nationally on TV, radio and in print, and although I won't argue the brand awareness between Woolworths and Play.com, it's not like people couldn't find Play.com.  &lt;br /&gt;&lt;br /&gt;I guess after all is said and done, as a business you need to know your customers, know your competitors, know your business processes and understand your risks and how to mitigate those risks.  On top of that, I'd like to add you should always be asking these questions, always looking for improvements and changes.&lt;br /&gt;&lt;br /&gt;Just look at Tesco, boy have they changed!  They seem to know what their customers want, and are constantly changing, adding new product lines in store and online.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/5437584561839451817/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=5437584561839451817" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5437584561839451817?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/5437584561839451817?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/zTBNu-VApV8/fall-of-zavvi-along-with-several-other.html" title="" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>4</thr:total><feedburner:origLink>http://blog.iclutton.com/2008/12/fall-of-zavvi-along-with-several-other.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEAFQHs5eip7ImA9WxRaF0U.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-2269977139242527117</id><published>2008-12-20T13:49:00.003Z</published><updated>2008-12-20T14:18:31.522Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-20T14:18:31.522Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="xpday" /><title>Doing the Right thing vs Doing it right</title><content type="html">There was one lightening talk at XPDay that has stuck in my head, and I want to try to reproduce the discussion here.  I'm sure I'll get some details wrong, but you'll just have to bare with me.  The talk was on doing the right thing verses doing it right.&lt;br /&gt;&lt;br /&gt;If you look at the image below, you'll see an XY graph, along the Y axis is doing the right thing, i.e. building the right solution.  Along the X axis is doing it right, i.e. building the solution well.  There was some studies into these metrics, but I don't know what it is, so I'm just going to use pseudo numbers.&lt;br /&gt;&lt;br /&gt;In the lower left quadrant, you'll see doing the wrong thing, and doing it badly.  The cost of the project is greater than the average IT project, and the revenue is lower than expected.  In the upper left quadrant your doing the right thing, but you're doing it wrong.  Your costs are still larger than average but you are making more revenue as you're building the required product.  In the bottom right you're doing the wrong thing, but you're doing it well so your costs are lower than average, but your revenue isn't as good as expected.  In the top right, you're doing the right thing and you're doing it well.  Your costs are lower than average, and your revenue is higher than expected.&lt;br /&gt;&lt;br /&gt;Now, of course, this is abstract and to be taken with a large pinch of salt, but there are some interesting deductions to be made.  Firstly, we were told that according to the study, it's near impossible to go from building the right thing badly to building the right thing well.  Looking at the rest, it seems intuitive that if you were doing the wrong thing badly, you'd want to do the right thing badly before doing the right thing well, but in actual fact, it's less costly and more efficient to do the wrong thing well and progress to building the right thing well.  &lt;br /&gt;&lt;br /&gt;So, where's your project on the quadrant, where do you want to be and how to you want to get there?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://farm4.static.flickr.com/3027/3121959307_915d70ee02.jpg?v=1229781706"&gt;&lt;img style="cursor: pointer; width: 500px; height: 375px;" src="http://farm4.static.flickr.com/3027/3121959307_915d70ee02.jpg?v=1229781706" alt="" border="0" /&gt;&lt;/a&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/2269977139242527117/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=2269977139242527117" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/2269977139242527117?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/2269977139242527117?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/KsvuDS1zYtE/doing-right-thing-vs-doing-it-right.html" title="Doing the Right thing vs Doing it right" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>3</thr:total><feedburner:origLink>http://blog.iclutton.com/2008/12/doing-right-thing-vs-doing-it-right.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8HRn45fCp7ImA9WxRaF0U.&quot;"><id>tag:blogger.com,1999:blog-23992350.post-8551927070500982591</id><published>2008-12-20T12:34:00.003Z</published><updated>2008-12-20T13:47:17.024Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-20T13:47:17.024Z</app:edited><title>Software Development: By Way</title><content type="html">I tend to think the way libraries and frameworks are made could easily be applied to teams and projects.  When building an application, generally you focus on solving the business problem with the best tools available.  When you move onto the next problem, you might have that eureka moment where you find that there are sub parts of the new problem which overlaps with the previous problem.  The fact that you've already solved this problem, means you'd be silly not to reuse that.  Thus, you have identified a common functionality, and if you extract that into a new library, you have reusable code across more than one project.  Brilliant.  &lt;br /&gt;&lt;br /&gt;The other way that you'd come across common functionality is through conversation.  In true water cooler mannerisms, this will often be coincidental, but again, if you can identify commonality and extract that, you are deriving business value.  Not only are you reusing something, meaning time can be saved and all teams benefit from faster delivery, but with more eyes on the same code, the chances of that becoming more robust grow.  &lt;br /&gt;&lt;br /&gt;So far we mentioned code, but this could just as easily be process and tools, like build scripts, deployment practices, server provisioning and lots more too.  The key is to make discovery, and sharing easy.  I think the key here is relationships and communication.    &lt;br /&gt;&lt;br /&gt;I think this model can be applied to projects too, where new projects are spawned to develop/manage commonality, this team doesn't necessarily have to consist of full time staff, it could be part time or ad-hoc, but there should always be a project owner/lead even if the project is sleeping.  Anyone should feel they can contribute to the project, for the benefit of all the teams, even if there are full timers on the project.&lt;br /&gt;&lt;br /&gt;I think, over time, this will form a network of loosely joined teams surrounding a handful of core projects, almost like atoms around a nucleus.  I see these core projects being things like:&lt;br /&gt;&lt;br /&gt;* build scripts, including testing tools and reporting&lt;br /&gt;* logging and monitoring scripts&lt;br /&gt;* deployment processes&lt;br /&gt;* network and hardware management and provisioning&lt;br /&gt;* data access layers (especially if all projects share common relations like customers) &lt;br /&gt;* service access layers, for accessing hosted services&lt;br /&gt;* user interface asset management &lt;br /&gt;* hosted tools support, e.g. source control, software repositories, wikis, mailing lists, story tracking, continuous integration boxes etc&lt;br /&gt;&lt;br /&gt;There's probably a bunch more too.  But as I mentioned earlier, relationships are probably the most important aspect here.  Establishing a community between the project leads and another for the wider developer group across all the projects will help, but you have to support the community, let it flourish.  The more the community collaborates and communicates the more they'll drive towards commonality and reuse.  Of course, people will have different ideas and prefer different tools, and that should be supported as long as the principle of spinning out or contributing to projects carries on.</content><link rel="replies" type="application/atom+xml" href="http://blog.iclutton.com/feeds/8551927070500982591/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=23992350&amp;postID=8551927070500982591" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/8551927070500982591?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/23992350/posts/default/8551927070500982591?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/RobbieClutton/~3/I5OqJO-ce_4/software-development-by-way.html" title="Software Development: By Way" /><author><name>Robbie</name><uri>http://www.blogger.com/profile/08233833428126146585</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://farm1.static.flickr.com/55/buddyicons/87632681@N00.jpg?1146413019" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.iclutton.com/2008/12/software-development-by-way.html</feedburner:origLink></entry></feed>
