<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>John P. Wood</title>
	
	<link>http://johnpwood.net</link>
	<description>collection of thoughts...</description>
	<lastBuildDate>Wed, 10 Mar 2010 14:32:07 +0000</lastBuildDate>
	
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/johnpwood" /><feedburner:info uri="johnpwood" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Standup Timer 1.2 Released</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/o-ibBIXSAiw/</link>
		<comments>http://johnpwood.net/2010/03/10/standup-timer/#comments</comments>
		<pubDate>Wed, 10 Mar 2010 14:30:32 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[Standup Timer]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=786</guid>
		<description><![CDATA[Standup Timer version 1.2 has just been released.  Per popular demand (2 requests :)), I have added support for meetings of any length, with any number of participants.  Prior to this release, Standup Timer restricted you to meetings 5, 10, 15 or 20 minutes long, and a maximum of 20 participants.  These [...]]]></description>
			<content:encoded><![CDATA[<p>Standup Timer version 1.2 has just been released.  Per popular demand (2 requests :)), I have added support for meetings of any length, with any number of participants.  Prior to this release, Standup Timer restricted you to meetings 5, 10, 15 or 20 minutes long, and a maximum of 20 participants.  These restrictions are still enabled by default, but can be disabled via the application&#8217;s settings.</p>
<p>Standup Timer is free and open source. The source code can be found at <a href="http://github.com/jwood/standup-timer">http://github.com/jwood/standup-timer</a>. Standup Timer can be found in the Android Market.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/o-ibBIXSAiw" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2010/03/10/standup-timer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2010/03/10/standup-timer/</feedburner:origLink></item>
		<item>
		<title>Thoughts on Android Development</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/G7tPSW0xx8c/</link>
		<comments>http://johnpwood.net/2010/02/23/thoughts-on-android-development/#comments</comments>
		<pubDate>Tue, 23 Feb 2010 14:25:15 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[Standup Timer]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=700</guid>
		<description><![CDATA[Now that Standup Timer has a few releases under its belt, I thought it would be a good time to reflect on my experience with the Android SDK, and Android development in general.  But before I begin, I should mention a few points about my background, to help you understand my perspective.
First, Standup Timer [...]]]></description>
			<content:encoded><![CDATA[<p>Now that <a href="/standup-timer">Standup Timer</a> has a few releases under its belt, I thought it would be a good time to reflect on my experience with the Android SDK, and Android development in general.  But before I begin, I should mention a few points about my background, to help you understand my perspective.</p>
<p>First, Standup Timer was my first mobile app.  Aside from a <strong>very</strong> small amount of time I&#8217;ve spent playing around with the Blackberry development kit (really too small to even mention), I have had no prior experience developing software for a mobile device.  My background is largely web development and distributed applications development.  Second, I have been doing Java development for quite some time, so I am very comfortable working in Java.  Third, if you&#8217;re not familiar with Standup Timer, it is a very simple application.  It only interacts with a few components of the Android platform, and only consists of a few screens.  No GPS, no web, no multi-touch, etc.  So, my tour of the Android SDK was far from complete.</p>
<h2>Dealing with multiple devices</h2>
<h3>Supporting multiple screen sizes</h3>
<p>The topic of supporting multiple screen sizes has been a popular one, especially when comparing Android development to iPhone development.  <strong>Supporting multiple screen sizes in your application does add a little complexity to the development process, but for my app (and I&#8217;d imagine most apps), it is very manageable.</strong></p>
<p><img src="http://johnpwood.net/wp-content/uploads/2010/02/screen_sizes-300x154.png" alt="" title="screen_sizes" width="300" height="154" class="alignleft size-medium wp-image-714" />This issue did not take Google by surprise.  They knew that if Android was to be successful, it would need support devices with a wide range of physical characteristics, including screen size.  So, from the beginning, they made it possible for applications to support multiple screen sizes with little effort.  The Android platform contains many features to help with this, including the ability to pre-scale images for phones with different resolutions, allowing you to specify your screen component sizes in density independent pixels (dips), and allowing you to easily center and stretch your components to fill the screen.  These features, along with a set of best practices for supporting multiple screen sizes, are documented very well at <a href="http://developer.android.com/guide/practices/screens_support.html">http://developer.android.com/guide/practices/screens_support.html</a>.</p>
<p>For applications that do not use the standard Android view components, or do custom graphics, it may be a different story.  I have read blog posts from some Android game developers saying it is a larger issue, and some who say it is not.  So, I&#8217;m not sure.  However, for the vast majority of applications, the standard view components work just fine, making supporting multiple screen sizes a very manageable issue.</p>
<p>One thing that did bite me regarding multiple screen sizes is the fact that views will not scroll by default if they happen to flow off the screen.  You need to anticipate this, and wrap any views that may flow off the screen with a <code>ScrollView</code> to enable scrolling behavior.</p>
<h3>Supporting multiple devices</h3>
<p>To me, this issue is much larger than the multiple screen size issue.  Android is an open source project, which gives anybody the ability to modify the code however they wish.  Google prevents carriers and cell phone manufacturers from abusing this by holding back a few key applications from the open source release, including the Android Market application.  Without the Android Market application, phones would not have access to the 20,000 apps currently available for the Android platform.</p>
<p><strong>However, phone manufacturers do tweak the platform to work for their specific hardware.</strong>  Android 2.1 on one device is not necessarily the same as Android 2.1 on another device.  For an example of this, <a href="http://www.engadget.com/2010/02/08/motorola-droids-next-update-to-be-android-2-1-includes-multito/">compare 2.1 on the Motorola DROID with 2.1 on the Nexus One</a>.  <strong>These seemingly minor modifications can be very troublesome for application developers.</strong>  The web has been <a href="http://www.dailyfinance.com/story/too-many-androids-variety-of-phone-models-may-chase-app-makers/19265188/">flooded</a> <a href="http://www.switched.com/2009/11/20/android-explosion-poses-a-problem-for-developers/">with</a> <a href="http://www.wired.com/gadgetlab/2009/11/android-fragmentation/">reports</a> of developers growing frustrated with the complexity matrix of Android versions and the exploding number of phones running them, all potentially containing their own tweaks to the platform.  Browsing through the change logs of applications I have installed on my Motorola DROID, I can see that several apps have made changes to fix issues on specific devices.</p>
<p>This issue is forcing developers to not only test on every device they plan on supporting, but also to write device specific code to work around any known issues for a device.  As Android continues to grow, this level of support will be unsustainable.  Developing for a common platform should mean that your application will run fine on any device running that platform.  But, because of these device specific tweaks, this is quickly turning out not to be the case for Android.  I worry that if Google doesn&#8217;t find some way to control this, developers will continue to abandon the platform.</p>
<p>I have even run into what appear to be device related issues with Standup Timer, which is miniscule compared to the size and complexity some of the other apps available in the Android Market.  Standup Timer uses an Android API that prevents the user&#8217;s screen from blacking out while a timer is in progress, letting you always see how much time is remaining for a meeting.  Just the other day somebody left a comment in the Market indicating that this was not working on their device.  So far, all of the other reviews have been positive, leading me to believe that this could be an issue with that user&#8217;s particular device.  The commentator didn&#8217;t provide any information about the device they were using, or how they could be contacted.  So, I&#8217;m not sure if I&#8217;ll ever be able to track down this issue.  Even if I knew the device experiencing the issue, I&#8217;m not sure I would be able to help.  Unless you have access the device in question, reproducing these issues is practically impossible.  The Android simulator tool is great, but you cannot create a simulator for a specific Android device, running that device&#8217;s flavor of Android.  You can only create simulators running the “generic” version of an Android release.</p>
<h2>Development</h2>
<h3>Application life cycle</h3>
<p><img src="http://johnpwood.net/wp-content/uploads/2010/02/lifecycle-226x300.png" alt="" title="lifecycle" width="226" height="300" class="alignright size-medium wp-image-717" />The Android platform primarily communicates with an application through a series of life cycle events.  The platform will let the application know when an activity (a screen) has been created, paused (lost focus), resumed (regained focus), etc.  <strong>The life cycle events are easy to understand, and are specific enough so that I never need to examine the state of my application or the platform within one of these callbacks.</strong>  The fact that a specific life cycle method was called tells you what state your application is in.</p>
<p>My largest complaint about how the platform manages an application is how it destroys and re-creates the activity (the object that backs the screen) when the phone rotates from portrait mode to landscape mode, or visa versa.  It seems to me that this would best be handled by making the appropriate life cycle callbacks to the same activity instance, giving it a chance to redraw the screen.  Instead, you are forced to save all of your activity&#8217;s state, and then reload it when the new activity instance takes over.  Forgetting to save the state of a particular variable means that value will be lost when the phone is rotated.  This was the source of several bugs when writing Standup Timer.</p>
<h3>Android APIs</h3>
<p><strong>I have no major issues with the Android APIs.  They seem complete (for what I needed), and most importantly, behaved as expected.</strong>  The database API seems a bit archaic, especially in today&#8217;s age of sophisticated O/R mapping tools.  But, at least I didn&#8217;t have to catch <code>SQLException</code> after each operation, like the JDBC API.</p>
<p>However, the lack of decent documentation for some of the APIs is a problem.  There were a few instances where I was forced into using trial and error to determine the purpose of a method parameter.  I feel this level of unclarity is unacceptable for a public API.</p>
<h3>UI Creation</h3>
<p>The Android SDK offers developers two methods for creating UI screens: programatically using Java code or declaratively using XML.  I did not attempt the programatic approach, as it brought back too many nightmares of building UIs for Java applets.  <strong>The XML approach to creating screen layouts, which is the recommended approach, is very straight forward.</strong>  Java developers, especially those working in the “enterprise”, have grown to hate XML over the past few years, due to its verbosity and proliferation.  But, I think Android&#8217;s use of it is very tasteful.  At no point did I consider myself to be in XML-hell.  Looking back on the final XML that declares my user interface, it doesn&#8217;t seem overly verbose or complicated.</p>
<p>I did however struggle at times to figure out how to get the UI to look exactly the way I wanted it.  Again, this seems to be the result of poor documentation of the XML elements and their corresponding attributes.  Some more complex examples in the sample code included in the SDK would have also helped.</p>
<p>If you keep your screens simple, and don&#8217;t display too much information on any given screen, then you shouldn&#8217;t have much trouble dealing with how that screen looks in portrait or landscape mode.  Screens with a list of items, for example, will usually look just fine when viewed in either portrait or landscape mode.  When in landscape mode, the user will just see a little more blank space in the list.  For more complicated screens, the Android platform allows you to specify an alternate UI layout for landscape mode.  This allows you to completely restructure the elements on a screen to deal with the the wider, shorter screen size without affecting how the screen looks in portrait mode.  Given how I struggled to get the views to look exactly the way I wanted, I tried my best to avoid the need to specify an alternate layout file for landscape views.  Only two of the screens in Standup Timer actually needed a landscape specific layout file.</p>
<h3>adb</h3>
<p>The Android Debug Bridge (adb) is a nifty little tool that allows you to interact with an Android device or simulator from the command line.  You can install/uninstall applications, interact with files on the device, or run a shell on the device.  It also provides a series of commands that allow you to more easily create scripts to run on the device.  Not only is this tool useful by itself, but it also enables the creation of sophisticated tools for Android development.  <strong>Learning how to use adb is a wise investment of your time.</strong></p>
<h3>The Eclipse plugin</h3>
<p>The Eclipse plugin for Android development is great.  It provides tight integration between Eclipse and the Android SDK.  Code completion is available for the Android APIs as well as the different types of XML files used by the Android platform.  It has great support for Android resource files.  You can easily start your application on a simulator with the click of a button, and running your tests is a breeze.  <strong>I would not attempt to write an Android application without this plugin, or a similar plugin for a different IDE.</strong></p>
<p><img src="http://johnpwood.net/wp-content/uploads/2010/02/eclipse.png" alt="" title="eclipse" width="125" height="125" class="alignleft size-full wp-image-720" />However, I did run into a few issues regarding how Eclipse interacts with the Android simulators.  On more than a few occasions, when starting the application or running the tests, the Eclipse plugin started a simulator running a version of the Android platform that did not match my project settings (although, I have not completely ruled out user error here :) ).  Also, the Eclipse plugin seems to have a few bugs around interacting with multiple simulators.  adb allows you to specify the target device or simulator when issuing a command, which is useful when you have more than one simulator running at a time (which is almost always the case given the current state of the Android platform).  However, the Eclipse plugin doesn&#8217;t always utilize this capability.  Sometimes it will detect multiple simulators running, and ask you which one you&#8217;d like to target.  Other times it will not.  In addition to the simulator interaction issues, there are a few areas that could benefit from a bit of polish.  The UI builders, for example, are difficult to work with.  It became obvious very quickly that I would be better hand coding the XML for the UI than attempting to use the builders.</p>
<h2>Testing</h2>
<h3>Simulators are awesome</h3>
<p><strong>The Android simulators are awesome.</strong>  Once you have the proper versions of the SDK installed, it is trivial to create simulators with different screen sizes, different amounts of available storage, running different versions of the Android platform.  And, as previously mentioned, adb is great for interacting with the simulators.  While not perfect (you should always test your app on a real device before publishing it), they are pretty darn close.</p>
<p>The only thing missing, and I&#8217;m not sure how feasible this is, is the ability to create a simulator for a specific device.  For example, if I know that my app is crashing on the Nexus One for some reason, I&#8217;d love to be able to create a Nexus One simulator, so I can find and fix the issue.  Currently, debugging and fixing device specific issues is not possible without access to the device in question.  I know some large mobile shops purchase the devices they plan on supporting, for testing purposes.  And, services like <a href="http://www.deviceanywhere.com/">Device Anywhere</a> allow you virtual access to a physical device, which would satisfy this need.  But these options, especially the first one, are expensive.  A developer trying to fix a device specific issue in their free, non ad subsidized application will not have the resources for either of these options.</p>
<h3>Automated (unit and functional) Testing</h3>
<p><strong>Automated testing on the Android platform is PAINFUL.</strong>  It&#8217;s hard to decide where to even begin.  First off, the tests provided with the sample applications in the SDK are very bare bones.  Most of them simply assert that an activity has been created, and that&#8217;s it.  If there was sufficient documentation describing how to write tests for you application, this wouldn&#8217;t be that big of a deal.  But, there isn&#8217;t.  Luckily, guys like <a href="http://dtmilano.blogspot.com/">Diego Torres Milano</a> are writing blog posts and giving presentations in an attempt to fill this gaping hole in documentation.  The <a href="http://www.slideshare.net/dtmilano/testing-on-android">slides from Diego&#8217;s presentation at Droidcon 2009</a> are a great place to start.  <strong>Standup Timer, which is <a href="http://github.com/jwood/standup-timer">open source</a>, also has a complete set of unit and functional tests for you to check out if you so desire.</strong></p>
<p>Another pain point regarding automated tests for Android applications is that <strong>the tests cannot be run inside a standard Java Virtual Machine.</strong>  The implementation of the APIs in the android.jar file provided with the SDK simply throw exceptions.  That jar file is only meant for building your application, not running it.  So, all tests must be run on the device (or in a simulator).  This dramatically slows down the execution of the tests.  One <a href="https://sites.google.com/site/androiddevtesting/">alternative</a> proposed by a non-Android group inside of Google is to mock out all of the Android components using a mocking library, enabling the execution of the tests in a standard virtual machine.  Although this would speed up the test execution, it comes with its own drawbacks.  Mainly, you end up having to mock out a <strong>TON</strong> of stuff.  I think that mocking out too many components significantly reduces the value of your tests.  If you mock out everything, what are you really testing?</p>
<p>The SDK contains a few base classes that you can extend for certain types of tests.  In particular, the SDK provides support for functional tests (the testing of a particular activity, or screen) and unit tests (the testing of some underlying support code).  Writing unit tests is pretty straight forward.  With a little bit of investigation and some trial and error, I was able to create a set of tests for my database layer, which will execute against a test version of the application&#8217;s database.</p>
<p><strong>Functional tests were much more problematic.  In addition to executing much slower than unit tests, they are also more difficult to write.</strong>  For functional tests, you write code to perform the operations a user would be performing on their phone (entering text, pressing buttons, etc).  The problem is that it is not very straight forward to perform these operations via code.  It took me quite some time to figure out how to simply enter some text in a text area and press a button via code.  Even when I thought I had functional testing figured out, there was still a use case I was unable to write a test for.  Most of the issues revolve around gaining access to the components you wish to interact with.  For a simple text box or button, it is very easy.  You can simply fetch the view by its id, the same way you do in your application.  However, once you start nesting components, it becomes much more complicated.  I tried several different ways to invoke the context menu (the long press menu) of a list item, inside of a list, which is inside of a tab.  None of them worked.</p>
<p><strong>Instead of writing functional tests for activities, I found it much easier to test activities using the <code>ActivityUnitTestCase</code> class with a mocked out instance of the activity.</strong>  Test classes that extend <code>ActivityUnitTestCase</code> don&#8217;t have as much as access to the platform as functional tests.  Several API methods will throw exceptions if called from within a <code>ActivityUnitTestCase</code>, and other API methods simply do nothing.  However, this can be addressed with some simple tools that should be in every developers toolbox: mocking and stubbing.  It is trivial to contain a method call that would throw an exception during testing within a method of its own.  This method can then be overridden by a mock implementation of the activity.  The mock can then optionally set a flag when the method is called, so the test can verify that some action was performed.  One place I do this is where the activity under test tries to start another activity.  Calling <code>startActivity</code> from within a <code>ActivityUnitTestCase</code> does nothing.  So, I placed the call to <code>startActivity</code> by itself in a protected method, overrode that method in the mock activity class, set a flag in the mock method implementation, and checked to see if that flag was set in the test case to verify that the activity was started.</p>
<h2>Deployment</h2>
<h4>Publishing to the Android Market</h4>
<p><strong>Publishing an application to the Android market could not be any easier.  The process is very well documented on the Android developers website, and the Eclipse plugin makes creating and signing an installation package a point and click operation.</strong></p>
<p><img src="http://johnpwood.net/wp-content/uploads/2010/02/android_market.jpg" alt="" title="android_market" width="125" height="127" class="alignright size-full wp-image-722" />A fee of $25 is required to create a Android Developers account.  This account gives you the ability to upload your application to the Android Market using the Android Developers website.  Once published, the application can immediately be found in the Android Market.  There is no approval process.  Updating your application is just as easy.  Simply upload the new version of your application, and it is available in the Android Market immediately. </p>
<p>It should be noted that there is no fee to install the application on your phone.</p>
<h2>Summary</h2>
<p>Android is a great platform.  It is feature rich, and developer friendly.  The devices that run it are powerful and easy to use.  I also love the fact that it is built on open source technology, and that Google, for the most part, has continued this tradition of openness.  </p>
<p>But, the platform is not without problems.  Most of my complaints here are minor, and are very addressable.  Android is still a young platform, so there is plenty of room to improve.  What worries me most about the future of the Android platform is the complexity matrix I talk about in the <strong>Supporting Multiple Devices</strong> section.  </p>
<p>Android was created to be a common platform for mobile application development.  A platform where application developers could write a single application capable of running on numerous devices.  However, this dream is at risk of becoming just that, a dream.  Unless Google can find a way to minimize the impact of device specific issues, it risks losing more and more Android developers.  Last year it was alraedy known that 2010 was going to be a big year for Android.  With several new devices slated to hit the market, industry analysts are expecting its market share to jump considerably.  However, if each new device that hits the market brings with it its own set of device specific quirks, this could also cause the Android development community to quickly erode.  Applications are a very large part of the mobile experience today, and they will continue to be in the future.  But, without a development community, there will be no applications.</p>
<p>I really hope Google finds a way to address this very serious issue.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/G7tPSW0xx8c" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2010/02/23/thoughts-on-android-development/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2010/02/23/thoughts-on-android-development/</feedburner:origLink></item>
		<item>
		<title>Standup Timer 1.1 Released</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/FklncQqtpFM/</link>
		<comments>http://johnpwood.net/2010/02/08/standup-timer-1-1-released/#comments</comments>
		<pubDate>Mon, 08 Feb 2010 14:47:10 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[Standup Timer]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=686</guid>
		<description><![CDATA[Version 1.1 of Standup Timer has just been released.  This new version stores statistics for your stand-up meetings on a per-team basis.  Simply create one or more teams using the new Teams menu option, then specify the team holding the meeting when starting the timer.  To view statistics for a given team, [...]]]></description>
			<content:encoded><![CDATA[<p>Version 1.1 of Standup Timer has just been released.  This new version stores statistics for your stand-up meetings on a per-team basis.  Simply create one or more teams using the new <strong>Teams</strong> menu option, then specify the team holding the meeting when starting the timer.  To view statistics for a given team, use the <strong>Teams</strong> menu option to pull up the list of teams, and select the team you would like to see statistics for.  The <strong>Stats</strong> tab will display the average statistics for that team across all of its meetings.  For a list of the meetings that team has held, click on the <strong>Meetings</strong> tab.  Finally, to see statistics for a specific meeting, simply select the meeting from the list of meetings.</p>
<p>Standup Timer is free and open source.  The source code can be found at <a href="http://github.com/jwood/standup-timer">http://github.com/jwood/standup-timer</a>.  Standup Timer can be found in the Android Market.</p>

<div class="ngg-galleryoverview" id="ngg-gallery-5-686">


	<!-- Piclense link -->
	<div class="piclenselink">
		<a class="piclenselink" href="javascript:PicLensLite.start({feedUrl:'http://johnpwood.net/wp-content/plugins/nextgen-gallery/xml/media-rss.php?gid=5&amp;mode=gallery'});">
			[View with PicLens]		</a>
	</div>
	
	<!-- Thumbnails -->
		
	<div id="ngg-image-62" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/main.png" title=" " class="shutterset_set_5" >
								<img title="standup_timer_1.1_1" alt="standup_timer_1.1_1" src="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/thumbs/thumbs_main.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-60" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/menu.png" title=" " class="shutterset_set_5" >
								<img title="standup_timer_1.1_2" alt="standup_timer_1.1_2" src="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/thumbs/thumbs_menu.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-61" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/stats.png" title=" " class="shutterset_set_5" >
								<img title="standup_timer_1.1_3" alt="standup_timer_1.1_3" src="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/thumbs/thumbs_stats.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-59" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/meetings.png" title=" " class="shutterset_set_5" >
								<img title="standup_timer_1.1_4" alt="standup_timer_1.1_4" src="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/thumbs/thumbs_meetings.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-58" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/meeting_stats.png" title=" " class="shutterset_set_5" >
								<img title="standup_timer_1.1_5" alt="standup_timer_1.1_5" src="http://johnpwood.net/wp-content/gallery/standup-timer-1-1/thumbs/thumbs_meeting_stats.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 	 	
	<!-- Pagination -->
 	<div class='ngg-clear'></div>
 	
</div>


<img src="http://feeds.feedburner.com/~r/johnpwood/~4/FklncQqtpFM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2010/02/08/standup-timer-1-1-released/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2010/02/08/standup-timer-1-1-released/</feedburner:origLink></item>
		<item>
		<title>Know Thy Customer – Why Developers Should Do Customer Support</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/rwiZ5zYkFXs/</link>
		<comments>http://johnpwood.net/2010/01/28/know-thy-customer-why-developers-should-do-customer-support/#comments</comments>
		<pubDate>Thu, 28 Jan 2010 14:32:46 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[support]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=656</guid>
		<description><![CDATA[A couple of years ago I bought a new printer for the house; a nice 3-in-one printer/fax/copier.  I always research the electronics I purchase, and this particular model had received many very positive reviews.  So, you can bet I was surprised, and a little pissed off, when the printer started acting up just [...]]]></description>
			<content:encoded><![CDATA[<p>A couple of years ago I bought a new printer for the house; a nice 3-in-one printer/fax/copier.  I always research the electronics I purchase, and this particular model had received many very positive reviews.  So, you can bet I was surprised, and a little pissed off, when the printer started acting up just a few short months after I purchased it.  I tried like hell to figure out the issue, hoping my efforts would save me from a dreaded call to the manufacturer&#8217;s customer support hot line.  No such luck.  After a few hours of troubleshooting, I looked up the support number in the back of the users guide, dialed it, and prepared for the worst.</p>
<p><a href="http://johnpwood.net/wp-content/uploads/2010/01/support.gif"><img src="http://johnpwood.net/wp-content/uploads/2010/01/support-300x282.gif" alt="" title="support" width="300" height="282" class="alignright size-medium wp-image-678" /></a></p>
<p>We&#8217;ve all been there.  Unfamiliar with the product, we are often unable to explain in detail what is going on, especially over the phone.  And usually on the other end of the phone is an under paid customer support representative who simply reads from a script given to him by his manager.  Often these representatives know very little about the product they are supporting.  And, good luck if you happen to have a question that doesn&#8217;t appear on the script.</p>
<p>What happened next I will never forget.  On the other end of the line appeared this friendly, knowledgeable technician.  I described the problem I was having to him.  He listened patiently, and asked a couple of targeted questions to help narrow down the issue.  In no time at all, he knew exactly what was going on, and exactly how to fix it.  Step by step, he clearly instructed me on what needed to be done to get my printer back online.  Before you knew it, I was back in business. <strong> That technician, that single person, turned my experience around 180 degrees.  I was once again a satisfied customer.  Hell, I was more than satisfied.</strong></p>
<p><strong>You cannot underestimate the value of awesome customer support.</strong>  I will gladly pay more for a product or service if I know that it will come with great support, as I know it will save me from headaches and grief down the road.</p>
<p>Typically, developers don&#8217;t serve on the front lines of customer support.  However, not directly interacting with and supporting the users of your product means you are missing out on a great opportunity.  An opportunity to build customer loyalty, an opportunity to understand how a customer is using your product or service, or simply an opportunity to help somebody.</p>
<p><strong>I think developers should spend at least a few weeks a year directly supporting the users of their product.</strong>  There are several reasons why.</p>
<h3>You will learn how people use your product</h3>
<p>You think you know how people are using your product?  Think again.  You think that user interface you designed makes perfect sense?  Maybe it does to you and your co-workers, but odds are it is not as clear to your customers.  When on customer support, you&#8217;ll see first hand how customers are using your product.  <strong>If you find that your customers are fumbling around your product, thinking they are doing one thing but actually doing another, it&#8217;s a clear indication that your product isn&#8217;t as intuitive as it should be.</strong>  Don&#8217;t write off these issues as &#8220;user errors&#8221;.  Instead, get some feedback from your customer, and figure out how to make your product easier to use.</p>
<h3>It will become obvious what features are missing, what needs to be improved, or what needs to be taken out</h3>
<p><strong>Your sales team, marketing team, and product team may have some great ideas regarding what direction to take your product.  But, there is nothing quite like hearing it straight from the customer.</strong>  This doesn&#8217;t mean that you should run out and implement every feature, and make every change requested by a customer.  But, if you start to hear similar feedback while on support, it may be an indication of a legitimate need.  Take note of it!  Or, better yet, just do it!</p>
<h3>Developers can fix problems&#8230;fast</h3>
<p>Developers are in a great position to fix problems, fast.  They usually know the product more intimately than anybody else on the team, have easy access to the code, and sometimes have the ability to release a patch.  <strong>This gives developers the unique ability to provide amazing customer support.</strong>  There have been times where I&#8217;ve fixed an issue while still on the phone with a customer, or shortly thereafter.  Think about how you would feel if all of your problems were solved this quickly.</p>
<h3>Summary</h3>
<p><strong>Happy customers become loyal customers.  And, providing awesome customer support is one sure way to keep your customers happy.</strong>  Providing customer support can be very time consuming, sometimes consuming the majority of a developer&#8217;s time (which is why I think a few weeks a year is enough).  However, this should be thought of not as an expense, but an investment.  An investment in the customer <strong>and</strong> the developer.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/rwiZ5zYkFXs" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2010/01/28/know-thy-customer-why-developers-should-do-customer-support/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2010/01/28/know-thy-customer-why-developers-should-do-customer-support/</feedburner:origLink></item>
		<item>
		<title>My First Android App – Standup Timer</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/LynNTQKRpeQ/</link>
		<comments>http://johnpwood.net/2009/12/04/my-first-android-app-standup-timer/#comments</comments>
		<pubDate>Fri, 04 Dec 2009 14:32:42 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[Standup Timer]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=629</guid>
		<description><![CDATA[Putting my brand new Motorola DROID to work, I just completed my first Android application, which I&#8217;m calling Standup Timer.
Standup Timer is a very simple application that helps keep your stand-up meetings focused, and on track.  I&#8217;ve been participating in stand-up meetings of one form or another for a few years now.  One [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://johnpwood.net/wp-content/uploads/2009/11/icon-241x300.png" alt="icon" title="icon" width="241" height="300" class="alignleft size-medium wp-image-644" />Putting my brand new Motorola DROID to work, I just completed my first Android application, which I&#8217;m calling Standup Timer.</p>
<p>Standup Timer is a very simple application that helps keep your <a href="http://en.wikipedia.org/wiki/Stand-up_meeting">stand-up meetings</a> focused, and on track.  I&#8217;ve been participating in stand-up meetings of one form or another for a few years now.  One thing that is constantly an issue with stand-up meetings is that they often run longer than they are supposed to.  Instead of simply stating their status and moving on, participants will often start to ramble, or engage in conversations with other members of the team.  Those who practice any form of the Agile software development methodology know that the value of stand-up meetings drops considerably if the meeting is constantly allowed to overflow its time constraints.</p>
<p>So, I created an app for that :)  Standup Timer helps to keep your stand-up meetings on track by allotting each participant an equal share of time, and letting participants know when they are about to exceed, or have exceeded their time.  The notification comes in the form of a bell ring for the warning, and an air horn when the time has run out.  If any time is left after all participants have presented their status, then Standup Timer will keep the clock ticking, so the remaining meeting time can be used for an open discussion.</p>
<p>Standup Timer is easy to use.  Simply provide the number of participants in the meeting, the length of the meeting, and press <strong>Start</strong> to start the timer.  On the timer screen, press <strong>Next</strong> when a participant is finished presenting their status to reset the timer for the next participant.  When the meeting is over, press <strong>Finished</strong>.  Standup Timer will remember the number of participants and meeting length from the last time you used it.  It also allows you to enable/disable sounds, and to specify when (how many seconds left in their share of time) to warn participants that they are about to exceed their share of time.</p>
<p><strong>Standup Timer is free and open source.</strong>  You can find the application in the Android Market, and the source code on GitHub at <a href="http://github.com/jwood/standup-timer">http://github.com/jwood/standup-timer</a>.</p>

<div class="ngg-galleryoverview" id="ngg-gallery-4-629">


	<!-- Piclense link -->
	<div class="piclenselink">
		<a class="piclenselink" href="javascript:PicLensLite.start({feedUrl:'http://johnpwood.net/wp-content/plugins/nextgen-gallery/xml/media-rss.php?gid=4&amp;mode=gallery'});">
			[View with PicLens]		</a>
	</div>
	
	<!-- Thumbnails -->
		
	<div id="ngg-image-63" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/main.png" title=" " class="shutterset_set_4" >
								<img title="image_01" alt="image_01" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_main.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-56" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/timer.png" title=" " class="shutterset_set_4" >
								<img title="image_02" alt="image_02" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_timer.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-57" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/warning.png" title=" " class="shutterset_set_4" >
								<img title="image_03" alt="image_03" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_warning.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-52" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/individual_over.png" title=" " class="shutterset_set_4" >
								<img title="image_04" alt="image_04" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_individual_over.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-53" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/individuals_complete.png" title=" " class="shutterset_set_4" >
								<img title="image_05" alt="image_05" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_individuals_complete.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-54" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/meeting_length.png" title=" " class="shutterset_set_4" >
								<img title="image_06" alt="image_06" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_meeting_length.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-51" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/config.png" title=" " class="shutterset_set_4" >
								<img title="image_07" alt="image_07" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_config.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-66" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/menu.png" title=" " class="shutterset_set_4" >
								<img title="image_08" alt="image_08" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_menu.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-67" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/stats.png" title=" " class="shutterset_set_4" >
								<img title="image_09" alt="image_09" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_stats.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-65" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/meetings.png" title=" " class="shutterset_set_4" >
								<img title="image_10" alt="image_10" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_meetings.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-64" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/standup-timer/meeting_stats.png" title=" " class="shutterset_set_4" >
								<img title="image_11" alt="image_11" src="http://johnpwood.net/wp-content/gallery/standup-timer/thumbs/thumbs_meeting_stats.png" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 	 	
	<!-- Pagination -->
 	<div class='ngg-clear'></div>
 	
</div>


<img src="http://feeds.feedburner.com/~r/johnpwood/~4/LynNTQKRpeQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/12/04/my-first-android-app-standup-timer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/12/04/my-first-android-app-standup-timer/</feedburner:origLink></item>
		<item>
		<title>What Will It Take To Dethrone The iPhone?</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/TIrvKWxBSiY/</link>
		<comments>http://johnpwood.net/2009/11/12/what-will-it-take-to-dethrone-the-iphone/#comments</comments>
		<pubDate>Thu, 12 Nov 2009 14:14:25 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Tech Industry]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[mobile development]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=605</guid>
		<description><![CDATA[The mobile industry is exploding.  In just a few short years, everybody will have a smart phone.  A tiny, internet connected, mini computer right in their pocket.  As each month passes, we learn more and more about what the future of this industry holds, and what the mobile handset landscape will look [...]]]></description>
			<content:encoded><![CDATA[<p>The mobile industry is exploding.  In just a few short years, everybody will have a smart phone.  A tiny, internet connected, mini computer right in their pocket.  As each month passes, we learn more and more about what the future of this industry holds, and what the mobile handset landscape will look like.  If one thing is for certain, it&#8217;s that nothing is for certain.</p>
<p><img src="http://johnpwood.net/wp-content/uploads/2009/11/iphone-181x300.jpg" alt="iphone" title="iphone" width="181" height="300" class="alignleft size-medium wp-image-609" /></p>
<p>As it stands right now, Apple is king with consumers.  Though not the first ones to market with an internet connected mobile device (the Blackberry has been around for a long time, and still holds the majority of the smartphone market share), Apple appears to be the first to really understand what the average, non-corporate consumer wants.  A true mini computer.  An open device that can play music, run applications, take photos, and provide a pleasant web browsing experience.  And they did it in true Apple fashion, making the device extremely easy to use.  As a result, the iPhone has become extremely popular with consumers, and is widely considered <em>”the device to have”</em>.</p>
<p>With the majority of people still without a smartphone, much of the market remains up for grabs.  Apple&#8217;s competitors are scrambling to catch up, trying to ensure that they they get a piece of the pie.  But, one very important question lingers.  <strong>What can Apple&#8217;s competitors offer that would give the average consumer a reason to buy their device <em>instead</em> of buying an iPhone?</strong>  To me, the reasons are few, and becoming fewer.</p>
<p>(I&#8217;d love to hear your reasons in the comments.  So please, chime in.)</p>
<h3>A comparable feature set</h3>
<p>This pretty much goes without saying.  Any challenger to the iPhone crown must offer similar features to that of the iPhone.  It is very unlikely that a competing device will lure anybody away from the iPhone if it is missing a feature that is now <em>expected</em> to be there.  The device must be capable of running apps, taking photos, playing music, etc, for it even to be considered.</p>
<h3>A better network</h3>
<p>AT&#038;T&#8217;s network leaves much to be desired.  Having never been an AT&#038;T customer, I can only relay the opinions of my friends and family who are AT&#038;T customers.  However, their opinions are one in the same.  I&#8217;ve not heard a single word of praise when it comes to AT&#038;T&#8217;s network.  All of my friends and family with iPhones have expressed frustration that the device they love is frequently crippled by a network that is spotty and congested.</p>
<p>It&#8217;s no secret that Apple has an exclusive agreement with AT&#038;T, and that agreement has an expiration date.  Rumors have been circulating about a jump to another carrier, <a href="http://www.usatoday.com/tech/wireless/phones/2009-04-26-apple-verizon-iphone_N.htm">possibly Verizon</a>, sometime next year.  The more wireless carriers offering the iPhone, the less valid of a reason this will become for not purchasing one.</p>
<h3>A comparable application ecosystem</h3>
<p>Competing devices will need to have an application ecosystem that is at least comparable to the iPhone&#8217;s.  This is no small task.  There are over 100,000 applications in the App Store.  Sure, several offer the same functionality, and many are of very poor quality.  However, nobody can argue with Apple&#8217;s tag line of “There&#8217;s a app for that”.  There really is an application, in most cases many, for everything you could possibly want to do with your iPhone.</p>
<p><img src="http://johnpwood.net/wp-content/uploads/2009/11/app2.jpg" alt="app2" title="app2" width="300" height="262" class="alignright size-full wp-image-613" /></p>
<p>Given their head start, beating Apple at this game will not be easy.  Google&#8217;s Android OS currently stands the best chance of challenging Apple on this front, with over 10,000 applications already available.  The Android OS is open, and capable of running on hardware from any manufacturer.  In addition, applications written for Android are capable of running on any device that runs the OS (for the most part).  Next year is going to be a big one for Android, with several new devices coming to market from many different manufacturers.  Some analysts are even predicting that the number of Android devices in the hands of consumers <a href="http://www.computerworld.com/s/article/9139026/Android_to_grab_No._2_spot_by_2012_says_Gartner">will surpass the number of iPhones by 2012</a>.  This will no doubt attract more application developers to the platform.</p>
<p>However, Android has its own set of challenges awaiting.  The fact that manufacturers are free to run Android on devices with very different hardware specifications (screen size, input controls, etc) poses a major challenge for application developers.  Perhaps the risk of rendering thousands of existing Android applications useless by releasing a device with dramatically different hardware specs will be enough to convince manufacturers not to do it.  Perhaps Google will provide a set of Android APIs that can help application developers deal with this issue.  Perhaps a set of best practices will emerge as a guide for developers looking to tackle this issue.  Perhaps we&#8217;ll see something similar to the PC application market in the mid-late 90&#8217;s (and the Blackberry application market today), where only certain devices will be capable of running certain applications.  Only time will tell if these issues will prevent the development of the Android application ecosystem.</p>
<h3>A killer feature</h3>
<p>One wild card that is always in play is the killer feature.  Apple&#8217;s competitors are only one, innovative, killer feature away from stealing the spotlight for themselves. <img src="http://johnpwood.net/wp-content/uploads/2009/11/android.png" alt="android" title="android" width="150" height="150" class="alignleft size-full wp-image-616" style="padding-top: 10px" /> By <em>“killer feature”</em>, I mean a feature so awesome that when you see it in action, you say to yourself, “I <strong>need</strong> one of those!”.</p>
<p>Version 2.0 of the Android OS took a stab at this with the introduction of <a href="http://www.youtube.com/watch?v=tGXK4jKN_jY">Google Maps Navigation</a>.  A fantastic feature, Google Maps Navigation morphs your mobile device into a fully functional GPS unit, complete with a synthesized voice telling you where to go, real time traffic information, and several map overlays showing you the location of everything from ATMs to gas stations.  But, is this a <strong>killer</strong> feature?  Frankly, I&#8217;m not sure.  But, its announcement was enough to cause a significant drop in the stock price of traditional GPS manufacturers, and it certainly has potential.</p>
<h3>An incredibly easy to use device</h3>
<p>Making devices that are intuitive and easy to use has always been one of Apple&#8217;s strengths.  Look no further than the iPod for an example of this.  Competing devices will need to be as easy to use as the iPhone is to appeal to the average consumer.</p>
<p>How do I get my music onto the device?  How to I get the photos I take off?  These operations should be simple and intuitive.  Motorola&#8217;s new Android 2.0 device, the DROID, is seriously lacking in this area.  Several steps are required to store data on or pull data off of the device:</p>
<ul>
<li>Attach the device to your computer</li>
<li>Use the device&#8217;s menu system to instruct it to mount itself as an external drive</li>
<li>Locate the files on your hard drive that you would like to store on the device</li>
<li>Copy and paste the files from your hard drive onto the device</li>
<li>Unmount the device</li>
</ul>
<p>For the iPhone, the list of steps is much smaller.</p>
<ul>
<li>Attach the device to your computer, and let iTunes do the rest</li>
</ul>
<p>Are the steps required to store data on the DROID too much to handle for an experienced computer user.  No, of course not.  But, there is still a large percentage of people out there who would struggle with completing those tasks.  Believe me, I know.   Many are family and friends of mine who I help complete “simple” tasks on their computers all of the time.  These people make up a significant portion of the market.  If you want them to buy your device, then you have to make it stupid simple to use.</p>
<h3>Summary</h3>
<p>Apple has set the bar high with the iPhone, very high.  While I can think of several reasons why developers and techies would prefer a different device, I can&#8217;t think of many reasons why the average consumer would.  And, there are a lot more average consumers than there are geeks.</p>
<p>But make no mistake, Apple&#8217;s competitors have the iPhone in their sights.  The tide can shift very quickly in this market, especially since most people get a new phone every couple of years.  Will the iPhone challengers be able make a dent in the iPhone&#8217;s market share?  Or, will the iPhone be the de-facto standard for smart phones?  Only time will tell.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/TIrvKWxBSiY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/11/12/what-will-it-take-to-dethrone-the-iphone/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/11/12/what-will-it-take-to-dethrone-the-iphone/</feedburner:origLink></item>
		<item>
		<title>Using Multiple Database Models in a Single Application</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/1tLHjVOrDZ0/</link>
		<comments>http://johnpwood.net/2009/09/29/using-multiple-database-models-in-a-single-application/#comments</comments>
		<pubDate>Tue, 29 Sep 2009 12:55:35 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[nosql]]></category>
		<category><![CDATA[polyglot persistence]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=569</guid>
		<description><![CDATA[The days of the relational database being a one-stop-shop for all of your persistence needs are over.  A new class of application is beginning to emerge with requirements that exceed the capabilities of the relational database.  Some of these applications need unlimited scalability or bullet proof fault tolerance, while others may require blazing [...]]]></description>
			<content:encoded><![CDATA[<p>The days of the relational database being a one-stop-shop for all of your persistence needs are over.  A new class of application is beginning to emerge with requirements that exceed the capabilities of the relational database.  Some of these applications need unlimited scalability or bullet proof fault tolerance, while others may require blazing fast access or flexible data storage.  The relational database was simply not designed to meet the needs of this small but growing class.  Instead, a new breed of data stores are gaining momentum.  These data stores are looking at data persistence with a fresh set of eyes, diverging from the relational model considerably in order to meet these challenges.</p>
<h3>What&#8217;s wrong with the relational database?</h3>
<p>For 99% of the applications out there, absolutely nothing.  The relational database has been the industry standard for data storage over the past 30+ years for good reason.  It is an incredibly capable piece of software.  Although it may not be the <em>best</em> tool for everything it is used for, it certainly satisfies the needs of the vast majority of applications just fine.</p>
<p>However, while not new, the class of applications mentioned above are becoming more common.  These applications either handle enormous amounts of traffic, or deal with tremendous amounts of data.  The relational database falls short in a few areas when trying to meet the demands of an application like this.</p>
<p><span  class="alignleft"><br />
<a href="http://www.flickr.com/photos/45745303@N00/3677040009/" title="WordPress 2.7 Database" target="_blank"><img src="http://farm3.static.flickr.com/2631/3677040009_e1f7e1184b_m.jpg" alt="WordPress 2.7 Database" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-nc-sa/2.0/" title="Attribution-NonCommercial-ShareAlike License" target="_blank"><img src="http://johnpwood.net/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit: <a href="http://www.flickr.com/photos/45745303@N00/3677040009/" title="bioxid" target="_blank">bioxid</a></small><br />
</span></p>
<p>A single database server is usually not enough to support these requirements.  Applications like this need a true database cluster, capable of adding storage space and processing power on the fly without the application even noticing.  However, relational databases weren&#8217;t designed to operate in a cluster where all machines are capable of reading and writing data.  This is largely due to the promises they make regarding data integrity.  In order to fulfill these promises, the database needs easy, quick access to all of the data at all times to verify that duplicates aren&#8217;t being inserted, constraints aren&#8217;t being violated, etc.  This quickly becomes a bottleneck when dealing with very large amounts of data.</p>
<p>There are techniques for scaling out relational databases, but they don&#8217;t address every concern.  One popular technique is to use one or more slave databases for read requests, while continuing to funnel all write requests through the master database.  The master database constantly synchronizes with the read only databases, so the data remains consistent between databases.  This technique works great for read heavy applications, but does not help applications that perform just as many creates, updates, and deletes.  Data sharding is another popular technique, which involves splitting the data up onto several different databases based on some criteria.  But this pushes an extraordinary amount of complexity onto the application, as it is now responsible for determining which database to use for specific data sets.  Master-master replication can be used to keep multiple master databases in sync, so any database server can perform read or write operations.  However, for some applications there comes a point where the replication can&#8217;t keep up with the traffic.</p>
<p>Relational databases are also (intentionally) very strict when it comes to the structure of the data being stored.  Data must be broken up into a series of rows and columns.  Good object/relation mapping tools hide much of this awkwardness from us, but some applications deal with data that doesn&#8217;t map well into rows and columns.  A simple key/value store is usually a better fit for applications like this.</p>
<h3>How does the new breed address these problems?</h3>
<p>The new breed of data stores, called <strong>NoSQL</strong> databases, make very few promises regarding data integrity.  In this new model, data integrity becomes the application&#8217;s concern.  By not having to enforce any complex data integrity rules, NoSQL databases can scale to levels way beyond that of a relational database.  Adding more processing power or storage capacity can be as simple as adding a new machine to the cluster.  The database can then store and process the data using any machine in the cluster.</p>
<p>In this model, the data being stored is <strong>self contained</strong>, and does not rely on any other data in the database.  Therefore there is no need for one machine to know anything about the other machines in the cluster.  This approach is quite different from the relational model, where data is broken up into multiple tables to eliminate duplicate data, and joined back together when being accessed.</p>
<p>Most of these databases subscribe to a theory called <strong>eventual consistency</strong>.  In situations where duplicate information is scattered across different servers in the cluster, it is not feasible for the database to find all instances of that data and update it as a part of the original operation.  Instead, the data will be replicated to the other database servers at a later time.  Until that replication takes place, the application will be in an inconsistent state, where simultaneous queries fetching the same data could return different results.  Although this sounds terrible, it turns out that in practice it is really not too big of a deal for most applications.  Do all customers of an online retailer need to see the exact same set of product reviews 100% of the time?  Probably not.</p>
<p>Also, because there are few promises regarding data integrity, NoSQL databases can offer data storage that is much more flexible.  The database no longer has to enforce the uniqueness of a column, or ensure that the id of some referenced piece of data actually exists in the database.  Some of these databases are true key/value data stores, where you can store just about anything.  Others require a certain document format to be used (such as JSON or XML), but still allow you to freely change the contents of that document as you wish.</p>
<h3>Still no one-stop-shop for persistence</h3>
<p>Although NoSQL databases address some issues that can&#8217;t be addressed by relational databases, the opposite is true as well.  The relational database offers an unparalleled feature set.  While some of these features prevent it from serving the needs of the class of applications described above, they are absolutely required by other classes of applications.  In some domains, data integrity is the number one concern.  You need to look no further than the classic “try to withdraw money from the same account at the same time” example to justify the need for locks and transactions.</p>
<p>For the vast majority of applications out there, relational databases work great.  There are a boat load of tools and libraries that support them, and software developers are very familiar with how to use them.  It is safe to say that the relational database has secured its spot in IT departments and data centers around the world, and it isn&#8217;t going anywhere.  It is far from dead.</p>
<h3>Polyglot persistence</h3>
<p>An increasing amount of case studies are appearing that describe how real world applications are needing the data integrity offered by the relational database <strong>in addition</strong> to the benefits offered by NoSQL databases.  I believe this trend will continue, as companies are storing more data than ever, and processing that data in different ways than previously imagined.</p>
<p>To address these needs, some companies are beginning to run their relational database side-by-side with one or more of the NoSQL alternatives.  Extremely large data sets that require scalable storage space and processing power are moved to a NoSQL database, while everything else, especially data that needs its integrity kept in-check, remains in the relational database.  The term <strong><a href="http://www.infoq.com/news/2009/07/leberknight-polyglot-persistence">Polyglot Persistence</a></strong> has been used to describe the use of multiple databases within the same project.</p>
<h3>The benefits of polyglot persistence</h3>
<p>The benefits are somewhat obvious.  By running a relational database side-by-side with a NoSQL database, you get the best of both worlds.  Strict enforcement of data integrity from the relational database, and the scalability and flexibility provided by the NoSQL database.  This allows you to use the best tool for the job, depending on your use case.</p>
<p><span  class="alignright"><br />
<a href="http://www.flickr.com/photos/79538062@N00/3649334538/" title="172/365 - memory" target="_blank"><img src="http://farm4.static.flickr.com/3552/3649334538_b3c86ae2c7_m.jpg" alt="172/365 - memory" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-nc-nd/2.0/" title="Attribution-NonCommercial-NoDerivs License" target="_blank"><img src="http://johnpwood.net/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit: <a href="http://www.flickr.com/photos/79538062@N00/3649334538/" title="jypsygen" target="_blank">jypsygen</a></small><br />
</span></p>
<p>There are a few scenarios where I&#8217;ve seen systems take advantage of polyglot persistence.  The first scenario involves the need to perform some set of complex calculations on an extremely large data set.  The data is either copied/moved from the relational database to the NoSQL database, or inserted directly into the NoSQL database by the application.  The application can then use a cluster of NoSQL database servers can then divide the work, process the data, and aggregate the results.  The more machines you have in your cluster, the less time the processing will take.  The resulting data can either remain in the NoSQL database or be inserted into the relational database, depending on what needs to be done with the results.</p>
<p>The other scenario takes advantage of the schema-less nature of some NoSQL databases.  While it is certainly possible to store a serialized data structure in a single column of a relational database, interacting with that data can be a bit more challenging than if that data were in a schema-less, document oriented database.  This use case, after all, is what the documented oriented databases were designed for.  These types of databases simply treat the data as a collection of key/value pairs, identified by a unique ID.  The NoSQL databases provide ways in which you can add structure back into the document so the data inside the document can be queried.  These databases are great for storing data that can be radically different from document to document, or data whose structure changes constantly.</p>
<h3>The challenges of polyglot persistence</h3>
<p>Polyglot persistence comes with its own set of challenges.  While potentially getting the best of both worlds as far as features go, you get the complexity and hassle of dealing with not only multiple databases, but multiple databases models.</p>
<h4>Determining which database to use to store certain data</h4>
<p>With more than one database, you now have to decide where to store the data.  It&#8217;s no longer a given.  If you make the wrong decision, you could be looking at a painful migration from one database model to another as a result.  To make this decision, you need to carefully examine how the data will be used.</p>
<h4>Increased application complexity</h4>
<p>Applications also face increased complexity as they now have to interface with two different (potentially <strong>very</strong> different) data stores.  If done correctly, you should be able to isolate this complexity to the persistence layer of your application, freeing the rest of the application from having to know what database specific data is coming from.  But, interfacing to multiple data stores could greatly increase the complexity of that data persistence layer.  Your application will now need to know:</p>
<ul>
<li>How to connect to each of the databases</li>
<li>What database to use for specific sets of data</li>
<li>How to handle the different types of errors from each database</li>
<li>How to map results from each database back to your application&#8217;s object model</li>
<li>How to handle queries for information across databases</li>
<li>How to mock out the different databases for testing</li>
<li>Potentially, how to move data from one database to another</li>
</ul>
<p>Addressing these concerns could result in a bunch of new application code, and with added code usually comes added complexity, and more bugs.</p>
<h4>Increased deployment complexity</h4>
<p>In addition to the increased application complexity, you will also face increased deployment complexity.</p>
<ul>
<li>Will you need to provision new hardware to host the new database?</li>
<li>How will you backup the data in the new database?</li>
<li>How will you manage and control changes to the configuration of the new database?</li>
</ul>
<h4>Training for developers and operational staff</h4>
<p>Given that this database will likely be radically different from the relational database that your developers and operational staff are comfortable with, how will you bring them up to speed on how to use and manage this new database?  And, given that the majority of the NoSQL databases are still very young, how will you keep your developers and operational staff up to speed with the latest developments on the project?</p>
<p>This is a big issue, especially in companies with large development and operations teams, and needs to be thought through carefully.</p>
<ul>
<li>Is there an expert you can hire to help you get up and running, and mentor your staff?</li>
<li>Is there any training available that you can give to your staff?</li>
<li>Who can you turn to for support when something goes wrong in production?</li>
</ul>
<h3>Summary</h3>
<p>I&#8217;ve always been a big advocate of using the right tool for the right job.  For the past 30 years, the relational database has been the de-facto standard for persistence.  Creative people have managed to  utilize and manipulate it to serve all sorts of different use cases, quite successfully.  But just because you can fit a square peg through a round hole if you hit it with a big enough hammer doesn&#8217;t necessarily mean that you should.</p>
<p>NoSQL databases can be great tools for addressing data persistence cases that the relational database struggles with.  In addition, each NoSQL database brings its own set of strengths and weaknesses to the table.  They are becoming very important tools to have around, and I believe that our industry will see a steady increase in the adoption of these tools going forward.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/1tLHjVOrDZ0" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/09/29/using-multiple-database-models-in-a-single-application/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/09/29/using-multiple-database-models-in-a-single-application/</feedburner:origLink></item>
		<item>
		<title>Disabling sessions in Rails 2.3.4</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/RM-MYMWzSkM/</link>
		<comments>http://johnpwood.net/2009/09/04/disabling-sessions-in-rails-2-3-4/#comments</comments>
		<pubDate>Fri, 04 Sep 2009 20:06:15 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=527</guid>
		<description><![CDATA[I got a nice surprise today when upgrading our message processing application from Rails 2.3.3 to Rails 2.3.4, to pull in some important security fixes.

/opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:445:in `initialize_database_middleware': You have a nil object when you didn't expect it! (NoMethodError)
The error occurred while evaluating nil.name
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:182:in `process'
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:113:in `send'
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:113:in `run'
        [...]]]></description>
			<content:encoded><![CDATA[<p>I got a nice surprise today when upgrading our message processing application from Rails 2.3.3 to Rails 2.3.4, to pull in some important <a href="http://weblog.rubyonrails.org/2009/9/4/ruby-on-rails-2-3-4">security fixes</a>.<br />
<code style="font-size: .75em;"><br />
/opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:445:in `initialize_database_middleware': You have a nil object when you didn't expect it! (NoMethodError)<br />
The error occurred while evaluating nil.name<br />
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:182:in `process'<br />
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:113:in `send'<br />
	from /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/initializer.rb:113:in `run'<br />
        ....<br />
</code></p>
<p>Jumping into the Rails source, I found the offending line.</p>
<pre class="brush:ruby">
if configuration.frameworks.include?(:action_controller) &#038;&#038;
  ActionController::Base.session_store.name == 'ActiveRecord::SessionStore'
</pre>
<p>This code assumes that a session store is configured in your Rails app.  However, this particular application has no need for a session, so we were disabling it in the configuration by specifying the following:</p>
<pre class="brush:ruby">
config.action_controller.session_store = nil
</pre>
<p>I poked around on the web for a while, trying to find another way to disable the session.  No luck.  It appeared that the only other way to disable the session was to properly configure a session store in your <code>environment.rb</code> file, and then disable it in your <code>ApplicationController</code>.  That seemed lame.  Why should I have to configure something that I want to disable?</p>
<p>So, I coded up a simple class to act as the session store for the application that simply raises an error if anybody tries to access the session.</p>
<pre class="brush:ruby">
class NilSessionStore < ActionController::Session::AbstractStore
  def get_session(env, sid)
    raise NotImplementedError, "NilSessionStore: No session configured"
  end

  def set_session(env, sid, session_data)
    raise NotImplementedError, "NilSessionStore: No session configured"
  end
end
</pre>
<p>I then configured the application to use this class as the session store.</p>
<pre class="brush:ruby">
config.action_controller.session_store = :nil_session_store
</pre>
<p>Nice and simple, and it keeps me from having to configure something I never plan to use.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/RM-MYMWzSkM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/09/04/disabling-sessions-in-rails-2-3-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/09/04/disabling-sessions-in-rails-2-3-4/</feedburner:origLink></item>
		<item>
		<title>CouchDB: The Last Mile</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/VF1Tq6nKRRc/</link>
		<comments>http://johnpwood.net/2009/08/18/couchdb-the-last-mile/#comments</comments>
		<pubDate>Tue, 18 Aug 2009 13:32:19 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[couchrest]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=483</guid>
		<description><![CDATA[This is the 6th and final post in a series that describes our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.
 1, :per_page => 50)

This pagination code eventually made it into CouchRest.
Going live
With the remaining issues addressed, it was time to start the production migration.  One [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is the 6th and final post in a series that describes our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<p><a href="/2009/08/04/couchdb-application-changes/"><< Part 5: Application Changes</a></p>
<h3>Addressing the remaining issues</h3>
<p>We were almost there.  After <a href="/2009/08/04/couchdb-application-changes/">modifying the code to talk to CouchDB</a>, TextMe was successfully pulling data from CouchDB in our development environments.  There were just a few remaining issues that needed to be addressed before we could deploy CouchDB to production.</p>
<h4>Reducing the view sizes on disk</h4>
<p>As I mentioned in a <a href="/2009/07/21/couchdb-views-the-challenges/">previous post</a>, the amount of disk space consumed by the views was a big problem.  If we didn&#8217;t do something, we were sure to run out of disk space when migrating our 30 million row messages table to CouchDB.</p>
<p>We determined that it was not <strong>what</strong> we were emitting from our map functions that was killing us, but <strong>how many times</strong> we were emitting it.  Each of the views emitted a key/value pair for every document in the database.  At 30 million documents and 8 views, that ends up being a crap load of key/value pairs.</p>
<p>My colleagues Dave and Jerry took a detailed look at the problem, and came up with a solution.  They determined that there was simply no need to be emitting data for each document in the database.  While this would give us views that could report statistics by the second, our application only supported presenting statistics by the minute.  Even if we were able to support statistics at this level of detail, we doubted our customers would even need it.  It was simply not worth the disk space.</p>
<p>So, Dave and Jerry modified the import job described in the <a href="/2009/08/04/couchdb-application-changes/">previous post</a> to roll up several key statistics by the minute as it was building the documents.  When the job finishes processing all of the documents for that minute, it creates a summary document containing all of the rolled up statistics, and adds it to the database.  Then, they changed the map functions to only consider these summary documents.</p>
<p>This solution was able to <strong>dramatically</strong> reduce the sizes of the views on disk, while still supporting the current application functionality.  Since we are still persisting all of the original documents to CouchDB, it is possible to add a new statistic to the summary documents should we ever need to.</p>
<p>Oh, and we also picked up two new terabyte database servers, just in case :)</p>
<h4>Paginating records in CouchDB</h4>
<p>Like many Rails applications, we were using the popular <a href="http://wiki.github.com/mislav/will_paginate">will_paginate</a> gem to paginate results from the database.  Given the size of our data sets, pagination was an absolute necessity to keep from using up every last bit of memory.</p>
<p>CouchRest has a <code>Pager</code> class that paginates over view results, but it is in the CouchRest Core part of the library and doesn&#8217;t integrate too well with the object model part of the library.  It simply returns the view results as an array of hashes.  We were hoping to see a solution that would give us back an array of the corresponding <code>ExtendedDocument</code> objects.  We were also trying to keep our application from having to know about CouchDB outside of the classes described in the <a href="/2009/08/04/couchdb-application-changes/">previous post</a>.  Having completely different pagination strategies for the two databases would make that more difficult.</p>
<p>So, I decided to write some new pagination code that supported the will_paginate interface and integrated a little better with the object model part of CouchRest.  I had a quick solution that same day which fetched view results and handed back an array of the corresponding <code>ExtendedDocument</code> objects.  I then spent some time over the next two weeks modifying the code to integrate a little better with CouchRest and add support for CouchRest views, which we weren&#8217;t using.</p>
<p>With the new code in place, we can now paginate over a set of contest entries without having to know what database they are coming from.</p>
<pre class="brush:ruby">
ContestCampaignEntryDelegate.contest_campaign_entries.paginate(
  :page => 1, :per_page => 50)
</pre>
<p>This pagination code <a href="/2009/06/19/paginating-records-in-couchdb-via-couchrest/">eventually made it into CouchRest</a>.</p>
<h3>Going live</h3>
<p>With the remaining issues addressed, it was time to start the production migration.  One at a time, we manually started the jobs to move the data from MySQL to CouchDB.  When one job completed, we would start the next.  As I <a href="/2009/07/21/couchdb-views-the-challenges/">mentioned before</a>, building the views is very resource intensive.  We didn&#8217;t want to completely bog down the production machine we were using to do the migration by running multiple jobs at once.</p>
<p>Moving the archived data from MySQL to CouchDB and building all of the views took about a week (a day for this table, a couple of days for that table, etc).  Overall, it was a fairly smooth process.</p>
<p>For the initial import, we did not purge any of the data from MySQL.  Since we needed to wait until our CouchDB databases were fully populated with all views built before we could start using them, the application needed to continue working with the data in MySQL while the migration was in progress.  In anticipation of the eventual switch from MySQL to CouchDB, I added a flag in the application configuration that told the application if it should pull archived data from CouchDB.  Once all of the data had been imported and all of the views had been built, we flipped the switch.</p>
<p>With the pouring of a celebratory beer, we watched as our application began pulling data from CouchDB in production.  It was time to relax :)</p>
<h3>The results</h3>
<p>I <strong>really</strong> wish we had taken the time to record how long our “troublesome” pages were taking to load before the move to CouchDB.  Sadly, we did not.  All I can say is that <strong>pages that used to occasionally time out were now loading in a few seconds</strong>.  Since the migration, we have also implemented a few new features that would simply not have been possible without CouchDB due to database performance issues.</p>
<p>The database performance issues we set out to address seem to be a thing of the past.  If new ones pop up, I&#8217;m confident that we could once again utilize CouchDB to address them.</p>
<h3>What&#8217;s next</h3>
<p>This project was focused on addressing database related performance issues that we were facing in production.  With these issues out of the way, and our CouchDB infrastructure built-out and proven, we will soon be building even more reporting capabilities that would have simply killed our old database.  TextMe customers will soon be able to view their data in more ways than they could have imagined. </p>
<p>I am also working on a project that takes advantage of CouchDB&#8217;s schema-less nature to let our customers store and utilize data they collect from their customers.  Such a feature, which essentially lets customers define their own schema, would have been a challenge to implement in a relational database.  With CouchDB, its just a document.</p>
<h3>Thoughts about this project, and CouchDB</h3>
<p>I learned a <strong>ton</strong> while working on this project.  While vaguely familiar with “NoSQL” databases before this project, I have just recently become aware of all of the alternatives available.  With the enormous amount of data companies are beginning to collect and process, I&#8217;m sure that CouchDB and its NoSQL friends will soon become a common component in the operational environments of most companies.</p>
<p>The CouchDB community has been great.  The CouchDB and CouchRest mailing lists are extremely active, and have been very helpful.  The committers on both of these projects are active, and always eager to help.  I&#8217;d specifically like to call out <a href="http://jan.prima.de/~jan/plok/">Jan Lehnardt</a> and <a href="http://jchrisa.net">Chris Anderson</a> from the CouchDB project.  Jan has commented on a few of these posts, encouraging me to keep writing.  He also suggested a more efficient implementation of the CouchRest pagination code I wrote, which I quickly implemented.  Chris left a comment on the first post in this series thanking me for writing about CouchDB, and offering his assistance if I needed it.  I actually took Chris up on that offer when we were running into issues regarding the sizes of the views on disk.  He was quick to reply, offering several suggestions.  I&#8217;d like to thank Jan and Chris for their support and encouragement.</p>
<p>NoSQL databases are here to stay, and CouchDB is truly unique in this area.  The way it handles views, and its support for replication/synchronization set it apart from the others.  There are already several large projects, like Ubuntu One, that are relying on CouchDB to deliver what nobody else can.  Because of this, I&#8217;m sure CouchDB has a very bright future ahead of it.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/VF1Tq6nKRRc" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/08/18/couchdb-the-last-mile/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/08/18/couchdb-the-last-mile/</feedburner:origLink></item>
		<item>
		<title>Goodbye Old Friend</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/Z_qeXR5pkuw/</link>
		<comments>http://johnpwood.net/2009/08/10/goodbye-old-friend/#comments</comments>
		<pubDate>Mon, 10 Aug 2009 13:43:30 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Life]]></category>
		<category><![CDATA[bacardi]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=491</guid>
		<description><![CDATA[Last night I said goodbye to one of the best friends I’ve ever had.
My wife Beth and I always wanted a dog, and knew that as soon as we got a house, a dog would be soon to follow.  On a trip in to see my parents, we stopped by a local animal shelter [...]]]></description>
			<content:encoded><![CDATA[<p>Last night I said goodbye to one of the best friends I’ve ever had.</p>
<p>My wife Beth and I always wanted a dog, and knew that as soon as we got a house, a dog would be soon to follow.  On a trip in to see my parents, we stopped by a local animal shelter to take a look at the dogs.  We came upon this crate containing 5 puppies, the offspring of what appeared to be a German Shepherd and a Rottweiler.  I liked German Shepherds, and Beth like Rotties.  We quickly took to the sole black puppy in the litter, and started the paperwork necessary to take her home.</p>
<p>I remember the day I picked her up from the shelter like it was yesterday.  I took the day off of work to go and pick her up.  The little ball of fur weighed only 10 pounds, and was sleeping soundly in my arms as I filled out the last bits of paperwork.  The first thing we did was drive up to see Beth at work.  She whined the entire way.  We named her Bacardi.</p>
<p>Childless for the first few years of our marriage, Bacardi was our baby.  She was a first class member of the family.  We did everything with that dog.</p>
<p>A more loyal friend you could not find.  Whenever Beth was sick, Bacardi would refuse to leave her side.  Those two had a connection like I’ve never seen.  It was simply amazing to watch.  An eternal puppy at heart, Bacardi was always playful, even in her older years.  Affectionate like no other, she’d lick you until your skin came off.  Her whip-like tail would start beating against the floor the minute she saw you.  Even on my worst days, Bacardi was always able to put me in a good mood.  She was always happy to see me, and always put a smile on my face.</p>
<p>When we welcomed our first son Dylan, we were repeatedly warned by friends and family not to let Bacardi too close.  I know that many of these feelings stemmed from the fact that she was half Rottweiler, and Rottweilers don’t have the best reputation as a family dog.  But Beth and I were confident that we had raised Bacardi to be kind and gentle.  Needless to say, Bacardi proved them all wrong.  She was both a friend and a guardian to our little boy, like she was for us.  I remember one day when Dylan was lying in his bassinet, no more than a week or two old.  The bassinet was just a bit taller than Bacardi, and she tried like hell to take a look inside, to see what was making all that noise.  She was actually able to tilt the bassinet slightly with the side her head, and look at Dylan with her one eye.</p>
<p>In the following years, she could not have been a better family Dog.  She let Dylan do anything to her.  He’d ride her like a horse, lie on her side, pull her ears…everything you could imagine, and she just laid there.  If she ever had enough, she would simply walk away.  Dylan loved Bacardi as much as we did.</p>
<p>Three years later, Beth and I welcomed our daughter Chloe.  Chloe was ill quite a bit the first year of her life.  We attributed this to Dylan brining home “presents” from pre-school, but started to suspect something else when she was still getting sick over his long breaks from school.  We found out that Chloe was an asthmatic, and allergic to Bacardi.  We knew we had no choice; we had to find Bacardi a new home.  Knowing that this was the right choice didn’t make it any easier.  Luckily, Beth’s uncle who lives just 10 minutes from us agreed to take her in.  His daughter had always adored Bacardi, and we knew that she would be treated well there.  Shortly after agreeing to take her in, Beth’s uncle came to pick her up.  That was a terrible day.</p>
<p>In the few short months Bacardi lived with Beth’s uncle, we continued to see her on a regular basis.  She was always happy to see us.  Even though we knew she was being treated great, a part of us always felt terrible when we left her behind.  We wanted so badly to take her home with us, but we knew that we couldn’t.  And, it didn&#8217;t help to see that she clearly wanted to come with us.  Sometimes, it felt like losing her all over again.</p>
<p>Just this past Thursday, Beth’s cousin called to tell us that Bacardi was ill.  Beth took her to the vet on Friday, and the vet said it was either a really bad infection or cancer.  As Saturday and Sunday passed, her condition quickly got worse, and it became clear that it was not an infection.  She was in the late stages of a battle with lymphoma.  Early this morning, around 3am, we said goodbye to our friend, and put and end to her suffering.</p>
<p>In Bacardi’s 7 ½ years on this earth, she touched so many lives.  We will always cherish the memories of the times we shared with her.  She was a great friend, and one amazing dog.</p>
<p>Bacardi, I love you so much, and miss you terribly.  Goodbye old friend. </p>

<div class="ngg-galleryoverview" id="ngg-gallery-3-491">


	<!-- Piclense link -->
	<div class="piclenselink">
		<a class="piclenselink" href="javascript:PicLensLite.start({feedUrl:'http://johnpwood.net/wp-content/plugins/nextgen-gallery/xml/media-rss.php?gid=3&amp;mode=gallery'});">
			[View with PicLens]		</a>
	</div>
	
	<!-- Thumbnails -->
		
	<div id="ngg-image-50" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_6313.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_6313" alt="dcp_6313" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_6313.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-49" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_4912.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_4912" alt="dcp_4912" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_4912.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-48" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_4170.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_4170" alt="dcp_4170" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_4170.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-47" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_3568.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_3568" alt="dcp_3568" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_3568.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-46" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_3146.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_3146" alt="dcp_3146" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_3146.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-45" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_2833.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_2833" alt="dcp_2833" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_2833.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-44" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_1912.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_1912" alt="dcp_1912" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_1912.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-43" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_1911.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_1911" alt="dcp_1911" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_1911.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-42" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_1872.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_1872" alt="dcp_1872" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_1872.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 		
	<div id="ngg-image-41" class="ngg-gallery-thumbnail-box"  >
		<div class="ngg-gallery-thumbnail" >
			<a href="http://johnpwood.net/wp-content/gallery/bacardi/dcp_1856.jpg" title=" " class="shutterset_set_3" >
								<img title="dcp_1856" alt="dcp_1856" src="http://johnpwood.net/wp-content/gallery/bacardi/thumbs/thumbs_dcp_1856.jpg" width="100" height="75" />
							</a>
		</div>
	</div>
	
		
 	 	
	<!-- Pagination -->
 	<div class='ngg-clear'></div>
 	
</div>


<img src="http://feeds.feedburner.com/~r/johnpwood/~4/Z_qeXR5pkuw" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/08/10/goodbye-old-friend/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/08/10/goodbye-old-friend/</feedburner:origLink></item>
		<item>
		<title>CouchDB: Application Changes</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/7JQ-u9Gujlo/</link>
		<comments>http://johnpwood.net/2009/08/04/couchdb-application-changes/#comments</comments>
		<pubDate>Tue, 04 Aug 2009 12:45:08 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[couchrest]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=465</guid>
		<description><![CDATA[This is part 5 in a series of posts that describe our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.


>


Compared to the challenges we faced with views, modifying TextMe to interact with CouchDB was very straight forward.  This post describes how we changed the TextMe code [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is part 5 in a series of posts that describe our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<table width="100%" style="border:none; padding-top:0px; margin-top:0px">
<tr>
<td style="border:none; text-align:left; width=50%; padding-left:0px; margin-left:0px;"><a href="/2009/07/21/couchdb-views-the-challenges/"><< Part 4: Views - The Challenges</a></td>
<td style="border:none; text-align:right;"><a href="/2009/08/18/couchdb-the-last-mile/">Part 6: The Last Mile >></a></td>
</tr>
</table>
<p>Compared to <a href="/2009/07/21/couchdb-views-the-challenges/">the challenges we faced with views</a>, modifying TextMe to interact with CouchDB was very straight forward.  This post describes how we changed the TextMe code in order to use CouchDB as an archive database.  Since TextMe is a Ruby on Rails application, much of the content in this post references Ruby/Rails specific libraries and frameworks.  However, I feel the general concepts could be applied to any development platform.</p>
<h3>A quick recap</h3>
<p>Before I dive into describing how we modified our application to work with CouchDB, I&#8217;d like to quickly recap exactly what we were trying to do (see the <a href="/2009/06/15/couchdb-a-case-study/">first post in this series</a> for a more detailed overview).  TextMe is a mobile marking application.  You can use it to manage SMS powered voting campaigns, contests, subscription lists, and more.  The majority of these campaigns have a pre-determined lifespan.  Once the campaign is over, the data collected is primarily used to  calculate statistics on the campaign.  This data is very important to our customers.</p>
<p>A few of our database tables were getting quite large, and starting to affect the performance of the application.  Tuning the queries didn&#8217;t seem to help much.  So, we turned to CouchDB and its views to help us store and aggregate this large amount of data.  </p>
<p>While a campaign is still active, we do more than simply calculate statistics on the data.  For example, our contest campaign needs to ensure that a winner is properly selected.  The winner could be the Nth entry, every Nth entry, the first N entries, etc, depending on how the campaign is configured.  Selecting the wrong winner, or more winners than we are supposed to select would obviously be bad.  So, we rely on the data integrity features provided by MySQL to help us do this correctly.  However, once the campaign is over, the data is only used for statistics.</p>
<p>Based on these requirements, we decided to use CouchDB as an archive database.  When a campaign completes, we could move the data out of MySQL into CouchDB.  This would make the MySQL tables smaller and more efficient, and allow us to take advantage of CouchDB&#8217;s views to handle the statistics.  But, this also meant that our application would have to interact two databases instead of one, and for the most part, be ignorant of which database the data was coming from.</p>
<h3>Configure the application to access CouchDB</h3>
<p>Before our application can talk to CouchDB, we need to tell it a little bit about our CouchDB installation.  The <a href="http://github.com/hpoydar/couchrest-rails/tree/master">CouchRest-Rails</a> plugin aims to make this as easy as possible for Rails applications.  CouchRest-Rails provides the necessary hooks that allow you to specify your CouchDB configuration in a <code>couchdb.yml</code> file, which serves the same purpose as the default <code>database.yml</code> file used by Rails.  Simply update this file with your CouchDB connection information, and you&#8217;ll be able to easily connect to CouchDB from within your application.</p>
<p>CouchRest-Rails also provides a series of Rake tasks that help you manage your databases and views.</p>
<h3>Define the documents</h3>
<p>The very first thing you need to do when moving data to CouchDB is to figure out what your documents will look like.  I talked about this in <a href="2009/06/30/couchdb-databases-and-documents/">CouchDB: Databases and Documents</a>, so I won&#8217;t cover it again here.</p>
<h3>Write code to create documents from relational database backed data objects</h3>
<p>Once you know what the documents are going to look like, you need to write some code that will convert your RDBMS backed objects into a document, and store it in CouchDB.</p>
<p>We decided to use <a href="http://github.com/mattetti/couchrest/tree/master">CouchRest</a> to help us interact with CouchDB.  CouchRest consists of two main parts: code to interact directly with CouchDB via a set of APIs just above  CouchDB&#8217;s HTTP API (known as CouchRest Core), and code that allows you to create an object model backed by CouchDB.  The <code>ExtendedDocument</code> class is the cornerstone of the object model code.  <code>ExtendedDocument</code> is like <code>ActiveRecord::Base</code> in Rails.  It serves as the base class for CouchDB backed objects.  It provides convenient ways to define document properties, access views, define life cycle callbacks, create documents, save documents, destroy documents, paginate view results, and more.  </p>
<p>A class extending <code>ExtendedDocument</code> simply needs to define the properties that make up its document.</p>
<pre class="brush:ruby">
class ArchivedContestCampaignEntry < ExtendedDocument
  use_database :contest_campaign_entry_archive

  property :campaign_id
  property :user_id
  property :entry_number
  property :winner
end
</pre>
<p>Then, all it takes to save a document in CouchDB is to create an instance of this class, set its properties, and call the object's <code>create</code> method.</p>
<h3>Determine how data will be moved to CouchDB</h3>
<p>Now that we have code that can convert RDBMS objects into documents, we need to figure out how to actually get those documents into CouchDB.  This step will likely be dependent on how you plan on using CouchDB.  For us, we decided it would be best to “archive” records after their corresponding campaigns have been over for 48 hours or more.  So, we created a nightly cron job to fetch all non-archived campaigns that have been over for 48 hours or more, and move their corresponding entries to CouchDB.  When a campaign's entries have been moved, an “archive” flag is set on the campaign itself, so the application knows to fetch its entries from CouchDB instead of MySQL.</p>
<p>One important item to point out is that CouchDB supports a bulk save operation.  This operation allows you to save a batch of documents with a single HTTP request.  This is a big time saver, as executing one HTTP request is obviously much quicker than executing several thousand.  Our archive cron job takes advantage of this.  When archiving entries for a particular campaign, we will build one document for each entry record, and then toss that document into an array.  When that array exceeds a certain size, 2,500 in our case, a single bulk save request is sent to CouchDB with the array of documents.  This dramatically decreases the number of HTTP requests sent to CouchDB, and the amount of time required to add data to CouchDB.</p>
<p>In addition, our archive job will pause to rebuild all of the views in the database after 100,000 new documents have been inserted, as well as at the end of the job.  The final view rebuild is necessary since all of the view queries done from within the application ask for stale data, which will not trigger a view update. We never did any research to determine if this was better or worse than waiting until the end of the job, which could produce over 500,000 new documents, to rebuild all of the views.  This step was simply driven by the gut feelings of the three engineers working on the project.  I'd be interested in hearing from you if you have done any research to determine if incremental view building is more or less efficient than a big bang view rebuild after a large import has completed.  </p>
<h3>Replacing SQL queries with CouchDB views</h3>
<p>Next, we changed the application to support the substitution of SQL queries with CouchDB views.  We did this in several steps.</p>
<h4>Identify queries being performed on the data you want to move</h4>
<p>The first step in replacing SQL queries with CouchDB views is identifying all of the queries being performed on the data you plan on moving to CouchDB.  This took a few hours to do, but was not difficult by any means.  We simply searched the code for all instances of the <code>ActiveRecord</code> class name and the MySQL table name for the tables with data being moved.  We also tracked down all <code>ActiveRecord</code> associations that were made to that particular table.  We then made a note of what the queries did, and how they were used.</p>
<h4>Abstract away the query</h4>
<p>After the queries had been identified, we moved the execution of all queries to a new class.  This freed the rest of the application from having to know if the data being fetched lived in MySQL or CouchDB.  The new class would make that decision, delegating to the <code>ActiveRecord</code> class if the data was in MySQL, or the <code>ExtendedDocumenet</code> class if the data was in CouchDB.  To start off, we simply delegate to the <code>ActiveRecord</code> class since we have not yet implemented the CouchDB views.</p>
<pre class="brush:ruby">
class ContestCampaignEntryDelegate
  def self.find_all_by_campaign_id_and_winner(campaign_id, winner)
    # Delegate to the ActiveRecord object
    ContestCampaignEntry.find_all_by_campaign_id_and_winner(campaign_id, winner)
  end
end
</pre>
<h4>Build views to replace the queries</h4>
<p>Now that we have the complete list of queries performed on the data that we wish to archive, we can begin building the necessary CouchDB views to support those queries for archived campaigns.  I wrote about CouchDB views in <a href="2009/07/10/couchdb-views-–-the-advantages/">previous</a> <a href="/2009/07/21/couchdb-views-the-challenges/">posts</a>.  See those posts for more information.</p>
<h4>Add methods to the <code>ExtendedDocument</code> class to query the views</h4>
<p>CouchRest gives you a few options when it comes to creating and accessing your views.</p>
<p>One option is to use the <code>view_by</code> method available on all classes that extend ExtendedDocument.  <code>view_by</code> not only makes the views easily accessible via the code, but it will also take care of creating and storing the view in the database.  </p>
<p>In its simplest form, <code>view_by</code> will generate the necessary map function based on the parameters you specify.  This example from the CouchRest documentation shows the map function that will be generated when <code>view_by :date</code> is called inside a class named <code>Post</code>:</p>
<pre class="brush:javascript">
function(doc) {
  if (doc['couchrest-type'] == 'Post' &#038;&#038; doc.date) {
    emit(doc.date, null);
  }
}
</pre>
<p><code>view_by</code> also lets you specify compound keys (<code>view_by :user_id, :date</code>) and any parameters that you wish to be used when you query your view (<code>:descending => true</code>).  </p>
<p>If you need to do something a little more complicated, <code>view_by</code> will also let you specify the map and reduce functions to use.  Here's another example from the CouchRest documentation:</p>
<pre class="brush:ruby">
 # view with custom map/reduce functions
 # query with Post.by_tags :reduce => true
view_by :tags,
  :map =>
    "function(doc) {
      if (doc['couchrest-type'] == 'Post' &#038;&#038; doc.tags) {
        doc.tags.forEach(function(tag){
          emit(doc.tag, 1);
        });
      }
    }",
  :reduce =>
    "function(keys, values, rereduce) {
      return sum(values);
    }"
</pre>
<p>Another option for creating and accessing views is to use CouchRest Core.  CouchRest Core, as described above, is a raw, close to the metal set of APIs that let you interact with CouchDB.  These APIs let you do basically anything, including creating and accessing views.  This example from the CouchRest documentation shows how you can create and query a view using CouchRest Core:</p>
<pre class="brush:ruby">
@db = CouchRest.database!("http://127.0.0.1:5984/couchrest-test")
@db.save_doc({
  "_id" => "_design/first",
  :views => {
    :test => {
      :map =>
        "function(doc) {
          for (var w in doc) {
            if (!w.match(/^_/)) emit(w,doc[w])
          }
        }"
      }
    }
  })
puts @db.view('first/test')['rows'].inspect
</pre>
<p>For accessing our views, we decided to go with a hybrid approach.  We didn't really feel comfortable storing our map and reduce functions inside the application code.  Doing so made it less clear on how we could gracefully introduce new views or update existing views in production, keeping in mind that some of these views could take hours or days to be built for the first time.  Instead, we stored our map and reduce code outside of the application, and used CouchRest-Rails to help us get those views into the database.  This allows us to push new or updated views independent of the application, giving us time to build the views before anything tries to access them.</p>
<p>Since the views are already in the database, we decided to use CouchRest Core to access them.  We created a class called <code>ArchivedRecord</code> to make working with CouchRest Core a little easier.  <code>ArchivedRecord</code> contains methods that do type conversions, manage bulk save operations, incrementally regenerate the views, and more.  It also contains a series of methods that help with executing views with similar behavior.  For example, there are methods that will simply return the number of rows returned by a view, execute a view for a specific timeframe using the dates stored in the documents, etc.  These abstractions also handle any errors that could pop up when accessing a view.  Our application code uses the abstractions provided by <code>ArchivedRecord</code> to access the views.</p>
<h4>Change the delegate class to call the <code>ExtendedDocument</code> class for archived data</h4>
<p>Now that our views can be accessed via the application code, we can modify the delegate class to call the <code>ExtendedDocument</code> object's query method to fetch data for campaigns that have been archived.</p>
<pre class="brush:ruby">
class ContestCampaignEntryDelegate
  def self.find_all_by_campaign_id_and_winner(campaign_id, winner)
    campaign = ContestCampaign.find_by_id(campaign_id)
    if campaign.archived?
      ArchivedContestCampaignEntry.find_all_by_campaign_id_and_winner(campaign_id, winner)
    else
      ContestCampaignEntry.find_all_by_campaign_id_and_winner(campaign_id, winner)
    end
  end
end
</pre>
<h4>Deal with the <code>ActiveRecord</code> associations</h4>
<p>The last piece of the puzzle is to deal with the <code>ActiveRecord</code> associations.  <code>ActiveRecord</code> associations are magic little things that make a record's associated data accessible via methods on an instance of the <code>ActiveRecord</code> class.  For example, if I wanted to declare an association between a contest and its entries, I would simply declare the following at the top of my <code>ContestCampaign</code> class:</p>
<pre class="brush:ruby">
has_many :contest_campaign_entries
</pre>
<p><code>ActiveRecord</code> takes care of joining the <code>contest_campaign_entries</code> table with the <code>contest_campaigns</code> table, and making all of the related campaign entries available via a call to <code>some_contest_instance.contest_campaign_entries</code>.  </p>
<p>This will not work for us, as the <code>contest_campaign_entries</code> table will not contain any data for archived contests.  So, we need to handle associations differently. </p>
<p>Instead of using the above code to create the association, we use the following:</p>
<pre class="brush:ruby">
has_many :active_contest_campaign_entries,
  :foreign_key => 'contest_campaign_id',
  :class_name => 'ContestCampaignEntry'
</pre>
<p>This more verbose version tells <code>ActiveRecord</code> that we want to setup an association, named <code>active_contest_campaign_entries</code>, on the class.  Since we're circumventing the convention of naming the association after the foreign key to the associated data (which is in turned named after the associated table), we need to specify the foreign key to use, and the name of the class that backs that table.</p>
<p>To keep from breaking the existing code that uses the <code>contest_campaign_entries</code> method to obtain related entry data for a contest, we define a new method on the class with that name to fetch the associated data.  The new method simply calls the corresponding method on the delegate class, which will pull the associated entries from MySQL or CouchDB, depending on if the campaign has been archived.</p>
<pre class="brush:ruby">
class ContestCampaign
…
  def contest_campaign_entries
    ContestCampaignEntryDelegate.contest_campaign_entries(self.id)
  end
…
end

class ContestCampaignEntryDelegate
…
  def self.contest_campaign_entries(campaign_id)
    campaign = ContestCampaign.find(campaign_id)
    if campaign.archived?
      ArchivedContestCampaignEntry.find_all_entries(campaign_id)
    else
      campaign.active_contest_campaign_entries
    end
  end
…
end
</pre>
<p><code>ActiveRecord</code> supports other associations besides <code>has_many</code>.  These other associations also add methods to the class that will fetch associated data from the database.  In our case, some of this associated data is going to remain in MySQL.  CouchRest will not (and should not) automatically fetch the corresponding data from MySQL, so we needed to handle this ourselves.</p>
<p>In our documents, we store the ids of the associated data still in MySQL (see <code>campaign_id</code> and <code>user_id</code> in the document snippet below).  Because we have associations setup between the <code>ContestCampaignEntry</code> class and the <code>ContestCampaign</code> and <code>User</code> classes, <code>ActiveRecord</code> adds methods named <code>campaign</code> and <code>user</code> to <code>ContestCampaignEntry</code> that will fetch the associated objects.  We need to do the same in our <code>ExtendedDocument</code> class.</p>
<pre class="brush:ruby">
class ArchivedContestCampaignEntry < ExtendedDocument
  use_database :contest_campaign_entry_archive

  property :campaign_id
  property :user_id
  property :entry_number
  property :winner

  def user
    @user ||= User.find_by_id(user_id)
  end

  def campaign
    @campaign ||= ContestCampaign.find_by_id(campaign_id)
  end
end
</pre>
<p>The <code>user</code> and <code>campaign</code> methods in the class above will take the ids stored in the document and fetch their corresponding objects from MySQL.  In our case, these values will never change for an archived entry, so we hold on to the objects as instance variables to avoid doing additional queries when they are referenced again.</p>
<h3>Make the <code>ExtendedDocument</code> class act like the <code>ActiveRecord</code> class</h3>
<p>As I stated above, one of the goals was to make it so the application code does not need to know which database the data is coming from.  Since the data can be returned as instances of two different classes, <code>ContestCampaignEntry</code> or <code>ArchivedContestCampaignEntry</code>, we need to make sure that both of these classes implement the same methods, and behave the same way.  Failing to do so could result in hard to find bugs, or straight up exceptions.</p>
<p>One group of methods to pay extra attention to are the convenience methods that <code>ActiveRecord</code> adds to the class based on the attribute types in the database.  An example of this is the <code>attribute?</code> accessor method that is added for boolean attributes.  All attributes get an accessor named after the column in the database, but boolean attributes get an additional accessor, containing a “?” at the end.  I personally use the “?” variation of the accessor method all of the time, as I feel it makes the code easier to read and understand.</p>
<p>CouchRest on the other hand is not able to determine in advance the data types of the properties you have stored, since CouchDB is a schema-less database.  So, it is not able to do anything special for properties of a given type unless you specifically tell it what the type is.  CouchRest does allow you to specify a type when you declare the property, but the current release (version 0.32) only uses this to cast property values into their proper type after they are fetched from the database.  I've <a href="http://github.com/mattetti/couchrest/commit/d1d8da513c9a5e8782455ab7516291a45e3e4a7e">submitted a patch</a> that will generate “?” accessor methods for properties with a type specified as <code>:boolean</code>.  However, this is just one example of how your <code>ExtendedDocument</code> class could be subtlety different from the corresponding <code>ActiveRecord</code> class.</p>
<h3>Summary</h3>
<p>As I stated at the beginning of this post, changing the application to work with CouchDB was much more straightforward than getting the views to work as expected.  Perhaps this is because I'm a developer, and not a DBA.  But, great libraries like CouchRest and CouchRest-Rails certainly go a long way in helping to write clear and concise code that interacts with CouchDB.  I can only hope that other programming languages have, or soon will have, libraries like these.  The fact that CouchDB has a great API built on a protocol that everybody can talk, HTTP, certainly makes it possible.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/7JQ-u9Gujlo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/08/04/couchdb-application-changes/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/08/04/couchdb-application-changes/</feedburner:origLink></item>
		<item>
		<title>CouchDB: Views – The Challenges</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/p6w9rYJrNUo/</link>
		<comments>http://johnpwood.net/2009/07/21/couchdb-views-the-challenges/#comments</comments>
		<pubDate>Tue, 21 Jul 2009 11:47:57 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=447</guid>
		<description><![CDATA[This is part 4 in a series of posts that describe our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.


>


In the previous post, I wrote about the many features of CouchDB views.  In this post, I will describe the challenges we faced when replacing MySQL queries [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is part 4 in a series of posts that describe our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<table width="100%" style="border:none; padding-top:0px; margin-top:0px">
<tr>
<td style="border:none; text-align:left; width=50%; padding-left:0px; margin-left:0px;"><a href="/2009/07/10/couchdb-views-%e2%80%93-the-advantages/"><< Part 3: Views - The Advantages</a></td>
<td style="border:none; text-align:right;"><a href="/2009/08/04/couchdb-application-changes/">Part 5: Application Changes >></a></td>
</tr>
</table>
<p>In the <a href="/2009/07/10/couchdb-views-%e2%80%93-the-advantages/">previous post</a>, I wrote about the many features of CouchDB views.  In this post, I will describe the challenges we faced when replacing MySQL queries with CouchDB views.</p>
<h4>Map/Reduce</h4>
<p>One of the largest challenges with CouchDB views is simply wrapping your brain around the map/reduce model.  If you&#8217;ve spent any significant amount of time in the relational model, this can be quite a task.  Do not underestimate it.  Give yourself plenty of time to make this adjustment.  In my opinion, setting aside one or two full weeks to read about and experiment with views would not be excessive.  It really is a whole new world.  After several weeks, I&#8217;m still not 100% sure how to use the map/reduce model to its fullest potential.  In fact, there were a few queries that I could not figure out how to implement as views.  Because of that, I had to keep around an archive table in MySQL, and I run those few queries against that table.</p>
<h4>Javascript</h4>
<p>If you don&#8217;t know Javascript, you may want to tack on another week to the learning curve.  Javascript is an incredibly powerful language.  However, in its raw form it is fairly basic and can take some getting used to.  It does help that you don&#8217;t need to write too much Javacript to implement most map and reduce functions.  However, if you&#8217;re new to Javascript, get ready to do some research.</p>
<p>There are <a href="http://wiki.apache.org/couchdb/View_server">view servers available in other programming languages</a>, and it is easy to configure CouchDB to use them.  But, CouchDB is still young and under heavy development, and these alternative view servers are not supported by the CouchDB team.  So, use them at your own risk.</p>
<h4>SQL</h4>
<p>As I mentioned in the previous post, views are powerful and flexible.  But, views are not nearly as flexible as SQL.  SQL has been in development for <strong>decades</strong>.  Even today, it continues to evolve as a language.  You can do a ton with SQL.  As of right now, views simply cannot rival this flexibility.  The CouchDB team <a href="http://mail-archives.apache.org/mod_mbox/couchdb-commits/200905.mbox/%3C20090512213843.DC9B5238894E@eris.apache.org%3E">continues to add built-in Javascript functions</a> to help write map/reduce code, and there is even <a href="http://mail-archives.apache.org/mod_mbox/couchdb-user/200907.mbox/%3ce2111bbb0906302049g65606d3avb030e47520bbd187@mail.gmail.com%3e">talk</a> about supporting <a href="http://portal.acm.org/citation.cfm?doid=1247480.1247602">map/reduce/merge</a>.  But as of right now, the feature sets are not even close.  It is very difficult for any new technology to enter the game with the same, or even a comparable feature set to such a battle-hardened veteran.  And to be honest, I highly doubt that the CouchDB team is even trying to match SQLs feature set.  After all, CouchDB is not meant to be a replacement for the relational database.  However, this is an important point to consider if you think your current relational database backed application might be a good fit for CouchDB.</p>
<h4>Multiple views, one design document</h4>
<p>Views live in documents called “design documents”.  Views within the same design document share a B-Tree data structure.  This means that when one view in the design document is built, they all are built.  So, careful planning is required to make sure unrelated views do not live in the same design document.  You would not want the re-building of one view to delay the accessibility of another, totally unrelated view.</p>
<h4>Building/Indexing views</h4>
<p>Views can take a <strong>L&#8230;O&#8230;N&#8230;G</strong> time to build from scratch.  The view building process is also very resource intensive.  This becomes less of an issue once the views have been built, as views are updated incrementally.  It really only comes into play when you are adding many, many documents to a CouchDB database between view builds.  However, one place where this is an issue is ad-hoc queries.  Every week or two, I&#8217;ll get a request from a customer for data that is not available via our web application.  While we&#8217;ll throw that request onto the product backlog so it is eventually available via the application, it doesn&#8217;t change the fact that our customer needs that information now.  We usually satisfy such requests by firing up the MySQL client, and running one or more ad-hoc queries.  This simply is not feasible using CouchDB views, especially if you are working with a large database containing millions of documents.  CouchDB does support “temporary views”, which are ad-hoc views that you can build and execute on the fly.  However, temporary views are not recommended for production use, because they need to be built before they can get you the data you need.  This could take hours, or days depending on the size of you database and the processing power of your database server.  Temporary views are meant as a way to test new views in development which will eventually be saved into a design document, and not for running ad-hoc queries.</p>
<h4>View sizes on disk</h4>
<p>I&#8217;ve already mentioned that each design document is stored in its own B-Tree, completely separate from the B-Tree that holds the documents in the database.  These data structures can become quite large, especially if you have a ton of documents in your database.  A large database combined with several design documents can take up quite a bit of disk space.  For example, our main messaging database consisting of around 30 million documents is 20GB on disk.  The 8 design documents for that database combine for a total size of 35GB.  This brings the grand total, for the documents and the views, to 55GB.  That&#8217;s a whole lot of disk space.  CouchDB sacrifices disk space for performance, which <strong>is</strong> a good tradeoff, as disk space is cheap and virtually limitless these days.  But sadly, this is not the case for everyone.  To add enough storage capacity to handle this database, the other large databases, a mirror/backup of these databases, and still have room to grow, we were looking at an additional several hundred bucks a month in charges from our hosting company for the additional disk space.  That can be a lot to handle for a small company.  Some larger companies use SANs or similar storage devices that offer unmatched redundancy and reliability.  However, these devices will often have a fixed maximum amount of storage, and can be very expensive to upgrade one the storage capacity is maxed out.  Justifying the use of so much disk space on such an expensive resource could prove difficult.</p>
<h4>Summary</h4>
<p>Views were without a doubt the hardest part of this project to get right.  My colleagues Dave and Jerry are still hard at work trying to find creative ways to reduce the size of the views on disk.  We&#8217;re very happy with the performance boots that we&#8217;ve seen using views in our testing environment.  But unless we can find a way to reduce the disk usage, or find more affordable storage options, we may never see these performance boosts in production.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/p6w9rYJrNUo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/07/21/couchdb-views-the-challenges/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/07/21/couchdb-views-the-challenges/</feedburner:origLink></item>
		<item>
		<title>CouchDB: Views – The Advantages</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/7QIHpH-YqQ4/</link>
		<comments>http://johnpwood.net/2009/07/10/couchdb-views-%e2%80%93-the-advantages/#comments</comments>
		<pubDate>Fri, 10 Jul 2009 13:59:40 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=425</guid>
		<description><![CDATA[This is part 3 in a series of posts that describe our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.


>


Views are what attracted us to CouchDB.  If you&#8217;ve been reading the posts in this series, you already know that CouchDB is a document oriented database, and [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is part 3 in a series of posts that describe our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<table width="100%" style="border:none; padding-top:0px; margin-top:0px">
<tr>
<td style="border:none; text-align:left; width=50%; padding-left:0px; margin-left:0px;"><a href="/2009/06/30/couchdb-databases-and-documents/"><< Part 2: Databases and Documents</a></td>
<td style="border:none; text-align:right;"><a href="/2009/07/21/couchdb-views-the-challenges/">Part 4: Views &#8211; The Challenges >></a></td>
</tr>
</table>
<p>Views are what attracted us to CouchDB.  If you&#8217;ve been reading the <a href="/2009/06/15/couchdb-a-case-study/">posts</a> in this <a href="2009/06/30/couchdb-databases-and-documents/">series</a>, you already know that CouchDB is a document oriented database, and that documents themselves don&#8217;t have any official structure beyond the structure enforced by JSON.  Views are what provide the necessary structure so that you can run queries on your data.</p>
<p>CouchDB has several strong points, including its efficient B-Tree data store implementation and replication/synchronization support.  These strong points already set it apart from other, more traditional databases.  However, we came for the views, because we saw views as the potential answer to our database performance woes.</p>
<p>CouchDB builds views using a map/reduce algorithm.  When building a view, CouchDB will feed all documents that are new or have changed since the last time the view was built through a map function.  The map function selects the documents of interest for that particular view.  Then, optionally, a reduce function is run to calculate some aggregate statistics on the documents that have been selected (counts, sums, etc).  There are several places you can go on the web for more information about <a href="http://damienkatz.net/2008/02/incremental_map.html">how</a> <a href="http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views">CouchDB</a> <a href="http://wiki.apache.org/couchdb/View_collation">views</a> <a href="http://horicky.blogspot.com/2008/10/couchdb-implementation.html">work</a>.</p>
<p>A large part of the performance issues we are trying to address are being caused by repeatedly running the same database queries against very large tables, where the vast majority of the data in those tables has not changed since it was inserted.  The last part of that statement is very important.  <strong>The data has not changed since it was inserted</strong>, and due to the nature of these tables, it probably never will.  It was very wasteful for us to keep running the same calculations on that old data.  </p>
<p>This is where CouchDB views come in.  When CouchDB generates a view, it stores the result of the view on disk in a B-Tree data structure, which is very efficient to access.  CouchDB will only re-generate that view when documents that match the criteria specified in the map function are changed or added.  And, CouchDB will only need to update the view for the changed/added documents.  It will incrementally update the view&#8217;s index, so it doesn&#8217;t have to start from scratch every time.  This makes views especially ideal for large data sets.</p>
<p>Using views, we can replace all of the queries we were performing on these tables, and the calculations would be performed once, and then stored.  Accessing that data would be as simple as issuing a single HTTP request, which would efficiently pull the data from the view&#8217;s B-Tree.  In other words, it would be fast, and very efficient.</p>
<p>CouchDB views are also very flexible.  The output of the map function is a key/value pair.  That key/value pair can be anything&#8230;data from the document, hard coded values, whatever.  This flexibility allows you to create complex keys, such as a JSON array of values.  Using the view API, you can  specify ranges of keys when executing your query, fetching only the data that you want.  You also have the ability to group complex keys by the first <em>n</em> elements of the key, and run the reduce function on those groups of data.  This enables you fetch aggregate data on multiple levels, and allows you to support multiple queries with a single view.  For example, we need to calculate the number of SMS messages sent by a particular account by minute, hour, day, month, year, etc.  Using CouchDB&#8217;s view engine, we can have our map function emit a key of <code>[account_id, year, month, day, hour, minute]</code> and a value of <code>1</code> for each document in our messages database.  Our reduce function simply sums all of the values for a matching key, using the provided sum function.  Here is how simple the map/reduce code is for this view:</p>
<p><strong>Map</strong></p>
<pre class="brush:javascript">
function(doc) {
    datetime = doc.created_at_utc;
    year = parseInt(datetime.substr(0, 4));
    month = parseInt(datetime.substr(5, 2), 10);
    day = parseInt(datetime.substr(8, 2), 10);
    hour = parseInt(datetime.substr(11, 2), 10);
    minute = parseInt(datetime.substr(14, 2), 10);
    emit([doc.account_id, year, month, day, hour, minute], 1);
}
</pre>
<p><strong>Reduce</strong></p>
<pre class="brush:javascript">
function(keys, values, rereduce) {
    return sum(values);
}
</pre>
<p>Using the grouping feature of the view API, we can easily fetch message counts for this account by year, month, day, hour, or minute, by simply specifying how many levels of the key we would like to group together.  For example, to get a breakdown of messages sent for a particular account on each day in May of 2009, we would simply include the following parameters in our URL when accessing the view: <code>startkey=[1,2009,5]&#038;endkey=[1,2009,5,{}]&#038;group_level=4</code>.  These parameters tell the view that we only want to consider messages for account number 1 that were sent or received in May of 2009, and that we&#8217;d like the reduce results grouped by the 4th parameter in the key, which is the day of the month.  This would return something like:<br />
<code><br />
{"rows":[<br />
{"key":[1,2009,5,1],"value":{"OK":{"MO":11,"MT":13}}},<br />
{"key":[1,2009,5,2],"value":{"OK":{"MO":9,"MT":9}}},<br />
{"key":[1,2009,5,3],"value":{"OK":{"MO":6,"MT":10}}},<br />
{"key":[1,2009,5,4],"value":{"OK":{"MO":9,"MT":9}}},<br />
{"key":[1,2009,5,5],"value":{"OK":{"MO":11,"MT":11}}},<br />
{"key":[1,2009,5,6],"value":{"OK":{"MO":14,"MT":17}}},<br />
{"key":[1,2009,5,7],"value":{"OK":{"MO":10,"MT":12}}},<br />
{"key":[1,2009,5,8],"value":{"OK":{"MO":8,"MT":12}}},<br />
{"key":[1,2009,5,9],"value":{"OK":{"MO":14,"MT":14}}},<br />
{"key":[1,2009,5,10],"value":{"OK":{"MO":6,"MT":8}}},<br />
{"key":[1,2009,5,11],"value":{"OK":{"MO":10,"MT":12}}},<br />
{"key":[1,2009,5,12],"value":{"OK":{"MO":9,"MT":11}}},<br />
{"key":[1,2009,5,13],"value":{"OK":{"MO":9,"MT":9}}},<br />
{"key":[1,2009,5,14],"value":{"OK":{"MO":13,"MT":20}}},<br />
{"key":[1,2009,5,15],"value":{"OK":{"MO":6,"MT":7}}},<br />
{"key":[1,2009,5,16],"value":{"OK":{"MO":15,"MT":15}}},<br />
{"key":[1,2009,5,17],"value":{"OK":{"MO":5,"MT":8}}},<br />
{"key":[1,2009,5,18],"value":{"OK":{"MO":8,"MT":8}}},<br />
{"key":[1,2009,5,19],"value":{"OK":{"MO":9,"MT":13}}},<br />
{"key":[1,2009,5,20],"value":{"OK":{"MO":7,"MT":7}}},<br />
{"key":[1,2009,5,21],"value":{"OK":{"MO":6,"MT":12}}},<br />
{"key":[1,2009,5,22],"value":{"OK":{"MO":22,"MT":28}}},<br />
{"key":[1,2009,5,23],"value":{"OK":{"MO":6,"MT":8}}},<br />
{"key":[1,2009,5,24],"value":{"OK":{"MO":2,"MT":4}}},<br />
{"key":[1,2009,5,25],"value":{"OK":{"MO":2,"MT":2}}},<br />
{"key":[1,2009,5,26],"value":{"OK":{"MO":14,"MT":16}}},<br />
{"key":[1,2009,5,27],"value":{"OK":{"MO":11,"MT":15}}},<br />
{"key":[1,2009,5,28],"value":{"OK":{"MO":8,"MT":12}}},<br />
{"key":[1,2009,5,29],"value":{"OK":{"MO":7,"MT":7}}},<br />
{"key":[1,2009,5,30],"value":{"OK":{"MO":4,"MT":5}}},<br />
{"key":[1,2009,5,31],"value":{"OK":{"MO":4,"MT":6}}}<br />
]}<br />
</code></p>
<p>Views are re-built when they are accessed, and not when new documents are added to the database or existing documents are changed.  However, you <strong>do</strong> have control over when views are built.  If you specify <code>stale=”ok”</code> when accessing your view, CouchDB will not check to see if the view needs to be re-built.  It will simply return results from the last time the view was built.  We took advantage of this feature when writing the application code to access the views.  In our case, data is only added to the database once a day, and it is added by a background job.  When the job is finished inserting data into the CouchDB database, it triggers the views to re-build themselves by accessing all of the views in the database (a few at a time), without specifying the <code>stale=”ok”</code> flag.  Since this background job takes on the responsibility of updating the views after it inserts new data, the rest of our application can always specify <code>stale=”ok”</code> when accessing the views.  This keeps the queries executed by the application fast, even when views are in the process of being re-built.</p>
<p>Views are powerful, and offer a tremendous amount of flexibility.  However, they come with their own set of challenges.  In the next post, I will talk about some of the challenges we faced when attempting to replace our SQL queries against a MySQL database with CouchDB views.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/7QIHpH-YqQ4" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/07/10/couchdb-views-%e2%80%93-the-advantages/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/07/10/couchdb-views-%e2%80%93-the-advantages/</feedburner:origLink></item>
		<item>
		<title>CouchDB: Databases and Documents</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/HsX9x7POAAk/</link>
		<comments>http://johnpwood.net/2009/06/30/couchdb-databases-and-documents/#comments</comments>
		<pubDate>Tue, 30 Jun 2009 15:16:07 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=359</guid>
		<description><![CDATA[This is part 2 in a series of posts that describe our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.


>


CouchDB is a document oriented database.  A document oriented database stores information as documents of related data.  All of the data within a document is self [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is part 2 in a series of posts that describe our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<table width="100%" style="border:none; padding-top:0px; margin-top:0px">
<tr>
<td style="border:none; text-align:left; width=50%; padding-left:0px; margin-left:0px;"><a href="/2009/06/15/couchdb-a-case-study/"><< Part 1: A Case Study</a></td>
<td style="border:none; text-align:right;"><a href="/2009/07/10/couchdb-views-–-the-advantages/">Part 3: Views &#8211; The Advantages >></a></td>
</tr>
</table>
<p>CouchDB is a document oriented database.  A document oriented database stores information as documents of related data.  All of the data within a document is self contained, and does not rely on data in other documents within the database.  This can be quite a shift if you&#8217;re used to working with a relational database, where data is broken up in to multiple rows existing in multiple tables, limiting (or eliminating) the duplication of data.  Although radically different, the document oriented approach is a very good fit for many applications.  For some applications, data integrity is not the primary concern.  Such applications can work just fine without the restrictions provided by a relational database, which were designed to preserve data integrity.  Instead, giving up these restrictions lets document oriented databases provide functionality that is difficult, if not impossible to provide with a relational database.  For example, it is trivial to setup a cluster of document oriented databases, making it easier to deal with certain scalability and fault tolerance issues.  Such clusters can theoretically provide you with limitless disk space and processing power.  This is the primary reason why document oriented databases (or key/value pair databases) are becoming the standard for data storage in the cloud.</p>
<p>There are <a href="http://books.couchdb.org/relax/why-couchdb">plenty</a> <a href="http://www.readwriteweb.com/enterprise/2009/02/is-the-relational-database-doomed.php">of</a> <a href="http://thinkvitamin.com/dev/should-you-go-beyond-relational-databases/">articles</a> on the web describing the benefits of using a document oriented or key/value pair database, so I won&#8217;t re-hash any of that information here.</p>
<h3>Databases</h3>
<p>Creating a new database in CouchDB is a simple process, with no overhead.  In fact, it&#8217;s as simple as issuing a single HTTP request.<br />
<code><br />
curl -X PUT http://127.0.0.1:5984/my_database<br />
</code></p>
<p>There appears to be no “penalty” for hosting many databases within the same CouchDB server, as opposed to storing all of the documents within a single database.  We took advantage of this when migrating data into CouchDB.  Three very large tables were the focus of this migration, each containing between 3 to 50 million rows.  We decided to store the data from each table in its own database.  The data within these tables are completely unrelated, so we would never need to view data from one database combined with another.  If they were related, we would have combined the tables into a single database, as CouchDB cannot create views across multiple databases.  Also, storing each set of data in its own database provides additional flexibility.  During the migration process, there were several points where we changed the structure of the documents.  Having the ability to easily delete the affected database and re-populate it, without affecting any other document types, came in handy.  With multiple databases, we have the flexibility to change the replication schedule for one database to be different from the others.  It also makes it easy to move one or more databases to another server, should we ever choose to do so.</p>
<p>My colleague Dave made <a href="http://github.com/sakrafd/couchrest_rails/tree/master">a few changes to CouchRest Rails</a> to support connecting to multiple CouchDB databases within a single rails application.  You simply specify the database server location in the configuration files, and then each model object can specify which database it is using.</p>
<h3>Documents</h3>
<p>CouchDB documents are very flexible.  Documents are stored in JSON format, allowing you to take advantage of JSON arrays and dictionaries to represent collections of data.  There is no external force dictating how a document should be structured, or what it should contain (as long as the document is valid JSON).  Below is an example of what a document may look like for a blog post.</p>
<pre class="brush:javascript">
{
   “_id”: “CouchDB: Databases and Documents”,
   “_rev”: “1-704787893”,
   “author”: “John Wood”,
   “email”: “john_p_wood”,
   “post”: “CouchDB is a documented oriented database.  A document...”,
   “tags”: [“couchdb”, “couchdb case study”, “json”],
   “comments”: [
      {
         “email”: “joe@somewhere.com”,
         “comment”: “Thanks for the information”
      },
      {
         “email”: “kevin@xyz.com”,
         “comment”: “CouchDB sounds pretty interesting”
      }
   ]
}
</pre>
<h4>Schema-less</h4>
<p>Probably the best part about the document oriented approach is the ability to make each document different from the next.  There is no schema to enforce that a document contains specific information.  This makes CouchDB a great fit if your application needs to store data that can be wildly different between objects of the same type.  In a relational database, this is usually handled by serializing the data in some format, writing the serialized data to the database, and de-serializing the data when it is read by the application.  However, this is really nothing more than a hack.  Querying the data in such columns can be a nightmare.  And, the serialization/de-serialization process is just one more thing that can go wrong.  In a document oriented database, there is no need for such a hack.  You simply code your CouchDB views to account for the fact that certain fields may not be in the document, and act accordingly (either defaulting to some value, or simply move onto the next document in the database).</p>
<h4>Self contained</h4>
<p>The most important thing to remember about documents is that they are self contained.  All of the data representing a particular concept is right there in the document.  (This is a bit of a fabrication, as it is completely possible to establish relationships between documents by having one document store the unique id of another document.  However, these links are not directly supported by CouchDB, and can be easily broken.)  So if you are moving from a relational database to CouchDB, you should de-normalize your data as much as possible while defining the structure of your document.  JSON arrays and dictionaries can help tremendously when de-normalizing relationships.  If there is only one piece of information from the relationship worth storing in the document, then an array works great (see the “tags” property above).  For relationships with more complex data structures, an array of dictionaries fits the bill quite nicely (see the “comments” property above).</p>
<h4>The document id</h4>
<p>Another important point to consider when designing your document structure is defining what you will use as the id of the document.  The id must be unique not only in that database, but all instances of the same database if you happen to be running inside a database cluster.  CouchDB uses document ids to replicate changes between servers.  Auto-generated sequential keys are a poor fit for this.  While wildly popular in relational databases, auto-generated sequential keys throw a wrench into the gears of the replication process.  If each database in the cluster was responsible for generating its own sequential ids, it is highly likely that different documents on different servers could be assigned the same id, which would make CouchDB think that two distinct documents are the same document.  Badness would surly ensue.</p>
<p>Instead, it is recommended that you use the data&#8217;s “natural key” as the id of your document.  The natural key is some field, or combination of fields, in your document that uniquely identifies that document.  In the example above, the title of the blog post is a good fit for a natural key.  It is not very likely that I will be writing posts with the same title.  If you happen to enjoy writing about the same stuff over and over, perhaps the title of the post combined with the date and time it was created would be a better fit.  Either way, the id should be composed from data within the document.</p>
<p>If you do not provide an id, CouchDB will provide one for you.  CouchDB uses an algorithm that makes it virtually impossible for multiple CouchDB instances to generate the same id.  However, I have read <a href="http://www.atypical.net/archive/2009/05/12/couchdb-090-bulk-document-post-performance">articles</a> on the web indicating that this is a very slow operation, so you may want to avoid letting CouchDB generate an id for you.  Regardless, natural keys pulled straight from data within your document always make better ids, as they are easier to read, and more identifiable.</p>
<p>For a few of our documents, we used the sequential key generated by MySQL as the id :)  I know how stupid this sounds, given the last few paragraphs.  However, I think this was the best choice for an id in our case.  The data contained in these documents are basically a collection of ids to rows that exist, and will remain in MySQL.  None of the data within the document would be any more readable than the MySQL id.  Also, since all of the keys were originally generated in a single MySQL database, they are guaranteed to be unique.  As of right now, we always plan on creating the data in MySQL, and then &#8220;archiving&#8221; it to CouchDB at a later date, so this approach should continue to work just fine.</p>
<h4>Supporting existing functionality</h4>
<p>If you are migrating data from a relational database to CouchDB, there is another important item to think about.  If your application needs to interact with CouchDB in the same way that it did with the relational database, then you need to make sure that the CouchDB views you build will be able to replace any SQL queries that are done against that data in the relational database.  In order to make this happen, the CouchDB document will need to contain all of the necessary information for you to build views to replace those queries, if you intend on supporting the same functionality.  Remember, there are no JOINs in CouchDB.</p>
<h3>Summary</h3>
<p>I think the way CouchDB handles databases and documents is very straight forward.  Once you get used to the idea that there could be multiple instances of databases in a cluster, and that documents should be self contained, the rest is cake.  The schema-less approach has the potential to open a lot of doors.  I know that we&#8217;re already making plans to take advantage of it.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/HsX9x7POAAk" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/06/30/couchdb-databases-and-documents/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/06/30/couchdb-databases-and-documents/</feedburner:origLink></item>
		<item>
		<title>Paginating Records in CouchDB via CouchRest</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/QG5jZXlqA9M/</link>
		<comments>http://johnpwood.net/2009/06/19/paginating-records-in-couchdb-via-couchrest/#comments</comments>
		<pubDate>Fri, 19 Jun 2009 18:37:44 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchrest]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=343</guid>
		<description><![CDATA[Update: This change has been incorporated into CouchRest version 0.30
When I began looking into replacing some of TextMe&#8217;s large MySQL tables with CouchDB databases, one of the things I noticed right away was that pagination support was not quite there in CouchRest.  I say &#8220;not quite there&#8221; because CouchRest does have the ability to [...]]]></description>
			<content:encoded><![CDATA[<p><strong><em>Update: This change has been incorporated into CouchRest version 0.30</em></strong></p>
<p>When I began looking into replacing some of <a href="http://textme.net">TextMe</a>&#8217;s large MySQL tables with <a href="http://couchdb.apache.org/">CouchDB</a> databases, one of the things I noticed right away was that pagination support was not quite there in <a href="http://groups.google.com/group/couchrest">CouchRest</a>.  I say &#8220;not quite there&#8221; because CouchRest does have the ability to fetch data from the database in paginated chunks, but the current support didn&#8217;t really fit too well with way the rest of the library interacts with CouchDB views.  A helper class had to be used to fetch the data, and the data came back as hash instead of an instance of the appropriate class.</p>
<p>Pagination is a must for us, because these tables in particular are very large.  That&#8217;s one of the main reasons why we&#8217;re moving them to CouchDB in the first place.  Loading all of the data into memory at once would be troublesome to say the least.</p>
<p>CouchRest is still a very young library, currently on version 0.29.  However, despite its age, it is already fully featured and off to a great start.  So, I saw this as an opportunity to contribute to something that we have already greatly benefited from.</p>
<p>With a little inspiration from Rails, I decided to implement a proxy that would be created when a view was called to fetch data.  The proxy would defer getting data from the database until that data was actually needed.  I then implemented <a href="http://wiki.github.com/mislav/will_paginate">will_paginate</a> style <code>paginate</code> and <code>paginated_each</code> methods on the proxy object.   If either of these methods are called, only a chunk of data will be fetched from the database, and that data will be returned as an array of instances of the appropriate class.  If any other method is called on the proxy, the proxy will fetch all of the data from the view, and forward the call on to the &#8220;real&#8221; array.</p>
<p>I decided to go with will_paginate style methods because the will_paginate gem is by far the most popular pagination solution for Rails.  We use it extensively in TextMe.  So, implementing the same methods would ensure that we could continue to use our existing pagination code, and the code wouldn&#8217;t have to know if it was dealing with a collection of ActiveRecord objects or a collection of CouchRest ExtendedDocument objects. </p>
<p>The new code also throws some methods onto the class itself that lets you paginate over instances of the class without having an instance of the proxy, or a view in your CouchRest ExtendedDocument object.</p>
<p>Here are some examples, pulled from the CouchRest tests:</p>
<p>Paginating using instance methods:</p>
<pre class="brush:ruby">
articles = Article.by_date :key => Date.today
articles.paginate(:page => 1, :per_page => 3).size.should == 3

articles = Article.by_date :key => Date.today
articles.paginated_each(:per_page => 3) do |a|
  a.should_not be_nil
end
</pre>
<p>Paginating via class methods:</p>
<pre class="brush:ruby">
articles = Article.paginate(:design_doc => 'Article',
  :view_name => 'by_date', :per_page => 3, :descending => true,
  :key => Date.today, :include_docs => true)
articles.size.should == 3

options = { :design_doc => 'Article', :view_name => 'by_date',
  :per_page => 3, :page => 1, :descending => true,
  :key => Date.today, :include_docs => true }
Article.paginated_each(options) do |a|
  a.should_not be_nil
end
</pre>
<p>Currently, the forked version of CouchRest containing this feature can be found on GitHub, at <a href="http://github.com/jwood/couchrest/tree/master">http://github.com/jwood/couchrest/tree/master</a>.  I&#8217;ve submitted a request to have this pulled into the main CouchRest repository.</p>
<p>Hopefully this will be helpful to others.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/QG5jZXlqA9M" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/06/19/paginating-records-in-couchdb-via-couchrest/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/06/19/paginating-records-in-couchdb-via-couchrest/</feedburner:origLink></item>
		<item>
		<title>CouchDB: A Case Study</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/0n5Utpp8O2g/</link>
		<comments>http://johnpwood.net/2009/06/15/couchdb-a-case-study/#comments</comments>
		<pubDate>Mon, 15 Jun 2009 13:40:17 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[couchdb]]></category>
		<category><![CDATA[couchdb case study]]></category>
		<category><![CDATA[textme]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=337</guid>
		<description><![CDATA[This is part 1 in a series of posts that describe our investigation into CouchDB as a solution to several database related performance issues facing the TextMe application.
Part 2: Databases and Documents >>
The wall was quickly approaching.  After only a few short years, several of our database tables had over a million rows, a [...]]]></description>
			<content:encoded><![CDATA[<p><strong>This is part 1 in a series of posts that describe our investigation into <a href="http://couchdb.apache.org/">CouchDB</a> as a solution to several database related performance issues facing the <a href="http://textme.net">TextMe</a> application.</strong></p>
<p style="text-align:right"><a href="/2009/06/30/couchdb-databases-and-documents/">Part 2: Databases and Documents >></a></p>
<p>The wall was quickly approaching.  After only a few short years, several of our database tables had over a million rows, a handful had over 10 million, and a few had over 30 million.  Our queries were taking longer and longer to execute, and our migrations were taking longer and longer to run.  We even had to disable a few customer facing features because the database queries required to support them were too expensive to run, and were causing other issues in the application.</p>
<p>The nature of our business requires us to keep most if not all of this data around and easily accessible in order to provide the level of customer support that we strive for.  But, it was becoming very clear that a single database to hold all of this information was not going to scale.  Besides, it is common practice to have a separate, reporting database that frees the application database from having to handle these  expensive data queries, so we knew that we&#8217;d have to segregate the data at some point.</p>
<p>Being a young company with limited resources, scaling up to some super-powered server, or running the leading commercial relational database was not an option.  So, we started to look into other solutions.  We tried offloading certain expensive queries onto the backup database.  That helped a little, but the server hosting the backup database simply didn&#8217;t have enough juice to keep up with the load.  We also considered rolling up key statistics into summary tables to save us from calculating those stats over and over.  However, we realized that this was only solving part of the problem.  The tables would still be huge, and summary tables would only replace some of the expensive queries.</p>
<p>It was about this time that my colleague Dave started looking into <a href="http://couchdb.apache.org/">CouchDB</a> as a possible solution to our issues.  Up until this point, I had never heard of CouchDB.  CouchDB is document oriented, schema-free database similar to <a href="http://aws.amazon.com/simpledb/">Amazon&#8217;s SimpleDB</a> and <a href="http://labs.google.com/papers/bigtable.html">Google&#8217;s BigTable</a>.  It stores data as JSON documents and provides a powerful view engine that lets you write Javascript code to select documents from the database, and perform calculations.  A RESTful HTTP/JSON API is used to access the database.  The database boasts other features as well, such as robust replication, and bi-directional conflict detection and resolution.</p>
<p>The view engine is what peeked our interest.  Views can be “rebuilt” whenever we determine it is necessary, and can be configured to return stale data.  “Stale data?  Why would I want stale data?”, you may be asking yourself.  Well, one big reason comes to mind.  Returning stale data is fast.  When configured to return stale data, the database doesn&#8217;t have to calculate anything on the fly.  It simply returns what it calculated the last time the view was built, making the query as fast as the HTTP request required to get the data.  The CouchDB view engine is also very powerful.  CouchDB views use a map/reduce approach to selecting documents from the database (map), and performing aggregate calculations on that data (reduce).  The reduce function is optional.  CouchDB supports Javascript as the default language for the map and reduce functions.  However, this is extensible, and there is support out there for writing views in several other languages.</p>
<p>In our case, we are planning to use CouchDB as an archive database that we can move “old” data to once a night.  Once the data is moved to the CouchDB database, it would no longer be updated, and would only be used for calculating statistics in the application.  Since we would only be moving data into the database once a day, we only need to rebuild the views once a day.  Therefore, all queries could simply ask for (and get) stale data, even when the views were in the process of being rebuilt.  Also, moving all of the old data out of the relational database would dramatically reduce the size of the specific tables, improving the performance of the queries that hit those tables.</p>
<p>I&#8217;m really looking forward to this partial migration to CouchDB.  The ability to add new views to the database without affecting existing views gives us the flexibility we need to grow the TextMe application to provide better, more specific, and more relevant statistics.  In marketing, statistics are king.  Since TextMe is a mobile marketing tool, we want it to be able to provide all of the data that our customers are looking for, and more.  I feel that by moving to CouchDB, we will not only be able to re-activate those features that we had to disable due to database performance, but also add more features and gather more statistics that would have otherwise been impossible with our previous infrastructure.</p>
<p>The migration to CouchDB was not always straight forward.  We faced several challenges, and learned many lessons over the past month.  All of those challenges will be addressed here.</p>
<p>In the coming posts, I plan to talk about:</p>
<ul>
<li>Structuring your CouchDB databases, and the documents within them.</li>
<li>More details about CouchDB views.</li>
<li>The application code necessary to talk to CouchDB.</li>
<li>Migrating parts of an existing application from a relational database backed by ActiveRecord to CouchDB.</li>
<li>How the CouchDB security model differs from a traditional relational database.</li>
</ul>
<p>Stay tuned!</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/0n5Utpp8O2g" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/06/15/couchdb-a-case-study/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/06/15/couchdb-a-case-study/</feedburner:origLink></item>
		<item>
		<title>Strive to Limit Integration Points</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/Wn2VevaDEH8/</link>
		<comments>http://johnpwood.net/2009/04/28/strive-to-limit-integration-points/#comments</comments>
		<pubDate>Tue, 28 Apr 2009 13:01:46 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[queues]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=329</guid>
		<description><![CDATA[Last week, I was working on a new feature of TextMe that required a call to one of our external service providers for some data.  The call in particular was to lookup the carrier for a given mobile number.  Sounds simple enough.  However, we already had code that integrated with this provider [...]]]></description>
			<content:encoded><![CDATA[<p>Last week, I was working on a new feature of <a href="http://www.textme.net">TextMe</a> that required a call to one of our external service providers for some data.  The call in particular was to lookup the carrier for a given mobile number.  Sounds simple enough.  However, we already had code that integrated with this provider in one component of our architecture, and I needed to make this call from another component.</p>
<p>A couple of options jumped out at me.  I could pull the code I needed to use into a library that could be shared between the components, or implement some form of inter-process communication that would enable me to invoke the service from the one component, and have it processed by the component that already integrated with the service provider.</p>
<p>Pulling the code into a library would be the easier of the two to implement for sure.  Like any project of reasonable size, we were already doing this for several other shared pieces of code.  Adding one more to the list would be a piece of cake.  The second option would require a bit more work.  The component that integrates with the service provider runs as a daemon process, so using something straightforward like HTTP to handle the interprocess communication was out of the question.  Instead, I&#8217;d likely have to utilize the queuing framework that we already had in place.  What makes it more difficult is that the queuing library we use only handles asynchronous calls, and this would need to be a synchronous call.  Not the end of the world by any means, but without a doubt more complicated than simply sucking the code into a library.</p>
<p>Even though option one was easier to implement, having two components in the architecture integrate with a 3rd party seemed like a bad idea.  Sprinkling integration points throughout your application is usually a recipe for failure.  Largely because it is only a matter of time before an integration point fails.  </p>
<p>If we went with option one, we could have the library handle the failures.  However, even if handled properly, failures like this usually have other consequences.  For example, if the service never responded, it could cause requests to back up in the given component.  Even if we implemented a timeout, it is likely that the timeout would be greater than the average response time, which means our system would take longer to process each request.  If you had to deal with a lot of incoming requests at the time of the failure, you could be in for a world of hurt, especially if you had multiple components suffering from this issue.</p>
<p>With option two, we have a bit more control over the situation.  First off, we would know there was one, and only one spot in our architecture that integrated with that particular service.  This would allow us to better understand the potential impact of the failure, and the steps that needed to be taken to address it.  Second, it would allow us to more easily implement a circuit beaker to prevent the failure from rippling across the system.  If the circuit breaker was tripped, we could return an error, some sort of filler data, or queue the request up for processing at a later time.  Third, we could potentially add resources to account for the situation.  Since the work was being done in a completely different component, if it was simply a matter of increased latency on the part of our service provider, we could always spin up a few more instances of that component to account for the fact that some of the requests may be starting to back up.</p>
<p>In his fantastic book, <a href="http://www.pragprog.com/titles/mnee/release-it">Release It</a>, Michael Nygard talks about integration points, along with a host of other topics regarding the deployment and support of production software.  Any developer who writes code that will eventually be running in a production environment (which I hope is EVERY developer) should read this book.  Regarding integration points, Michael says the following:</p>
<ul>
<li>Integration points are the number-one killer of systems.</li>
<li>Every integration point will eventually fail in some way, and you need to be prepared for that failure.</li>
<li>Integration point failures take several forms, ranging from various network errors to semantic errors.</li>
<li>Failure in a remote system quickly becomes your problem, usually as a cascading failure when your code isn&#8217;t defensive enough.</li>
</ul>
<p>However, even though integration points can be tough to work with, system&#8217;s without any integrations points are usually not that useful.  So, integration points are a necessary evil.  Our best tools to keep them in line are defensive coding, being smart about where you place the integration points in your system, and limiting the integration points in the system.</p>
<p>With the help of my colleague Doug Barth, we (mostly Doug) whipped up a synchronous client for the <a href="http://groups.google.com/group/ruby-amqp">Ruby AMQP library</a>.  I then used this code to implement the synchronous queuing behavior I needed to keep the integration point where it belonged.  Those interested can find the code in GitHub, at <a href="http://github.com/dougbarth/amqp/tree/bg_em">http://github.com/dougbarth/amqp/tree/bg_em</a>.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/Wn2VevaDEH8" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/04/28/strive-to-limit-integration-points/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/04/28/strive-to-limit-integration-points/</feedburner:origLink></item>
		<item>
		<title>Increase Design Flexibility by Separating Object Creation From Use</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/-W55YzaJXmE/</link>
		<comments>http://johnpwood.net/2009/02/18/increase-design-flexibility-by-separating-object-creation-from-use/#comments</comments>
		<pubDate>Wed, 18 Feb 2009 14:05:27 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[oo]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=321</guid>
		<description><![CDATA[I just finished reading Emergent Design, by Scott Bain.  Overall, I thought it was a pretty good book that touched on some important concepts in software design.  I&#8217;ve read about one particular concept covered in the book a few times before, but the value of it didn&#8217;t sink in until I read Emergent [...]]]></description>
			<content:encoded><![CDATA[<p>I just finished reading Emergent Design, by Scott Bain.  Overall, I thought it was a pretty good book that touched on some important concepts in software design.  I&#8217;ve read about one particular concept covered in the book a few times before, but the value of it didn&#8217;t sink in until I read Emergent Design.  This concept states that code that creates an object should be separate from code that uses the object.</p>
<p>Separating code that creates an object from the code that uses the object results in a much more flexible design, which is easier to change.  Creating this separation is also very easy to do.  By simply avoiding a call to the <code>new</code> operator in the &#8220;client&#8221; code for the particular object you wish to instantiate, you are able to evolve your code to adjust to a variety of changes, most of which require no changes in the code that uses the object.  Let&#8217;s walk through an example.</p>
<p>Let&#8217;s say we have a logging class, named <code>Logger</code>, that we use to log messages from our application.  The class is pretty simple, and looks something like this.</p>
<pre class="brush:java">
public class Logger {
    private static final String logFileName = "application.log";
    private FileWriter fileWriter;
    private Class<?> from;

    public Logger(Class<?> from) {
        this.from = from;

        try {
            fileWriter = new FileWriter(logFileName, true);
        } catch (IOException e) {
            throw new RuntimeException("Log file '" + logFileName +
                    "' could not be opened for writing.", e);
        }
    }

    public void log(String message) {
        try {
            fileWriter.write(
                from.getCanonicalName() + ": " + message + "\n");
            fileWriter.flush();
        } catch (IOException e) {
            System.err.println("Writing to the log file failed");
            e.printStackTrace();
        }
    }
}
</pre>
<p>In our application, we would typically use the <code>Logger</code> class like this:</p>
<pre class="brush:java">
Logger logger = new Logger(MyClass.class);
logger.log("Some message");
</pre>
<p>I think this is pretty typical, and seems to be the default pattern.  Create the object that you need, and then use it.  Simple and straightforward.  However, the simplicity comes at the price of limited flexibility.  For example, what if I wanted to limit the <code>Logger</code> class to only having one instance?  Or, what if I wanted to start logging some messages to the database, and some to the file system?  By combining the code that creates the object with the code that uses the object, we&#8217;ve greatly limited the ways in which we can evolve our design without affecting existing &#8220;client&#8221; code.  Sure, we can work our way out of it, but since the <code>Logger</code> is a very popular class used by almost every other class in the system, it will require a lot of work to change.</p>
<p>So, how can we avoid this?  How can we effectively encapsulate the creation of the object from the code that uses it?  The very first &#8220;tip&#8221; in Effective Java, by Joshua Bloch, is to prefer static builder methods over constructors.  Joshua suggests this for the same reasons Scott suggests separating code that creates the object from code that uses the object in Emergent Design.  Instead of making your clients use the <code>new</code> operator to create instances of your object, provide them with a static builder method to do so.  </p>
<pre class="brush:java">
    public static Logger getInstance(Class<?> from) {
        return new Logger(from);
    }

    protected Logger(Class<?> from) {
        this.from = from;

        try {
            fileWriter = new FileWriter(logFileName, true);
        } catch (IOException e) {
            throw new RuntimeException("Log file '" + logFileName +
                    "' could not be opened for writing.", e);
        }
    }
</pre>
<p>Note that I changed the scope of <code>Logger</code>&#8217;s constructor from public to protected.  This will discourage other classes outside of the logging package from using it, while leaving the <code>Logger</code> class open for subclassing.  With this new method in place, users of this class can now create an instance by doing the following.</p>
<pre class="brush:java">
Logger logger = Logger.getInstance(MyClass.class);
logger.log("Some message");
</pre>
<p>It seems silly to provide a method that simply calls <code>new</code>.  But, doing so adds so much flexibility to the design, that Scott considers it a &#8220;practice&#8221;, or something he does every time without even thinking about it.  Abandoning the constructor also opens a few doors.  You are no longer required to return an instance of that specific class, giving you the freedom return any object of that type.  You don&#8217;t always have to return a new instance, allowing you to implement a cache, or a singleton.  You can use this flexibility to your advantage when evolving your design.  Let&#8217;s see how.</p>
<p>Let&#8217;s say we get a request from our accounting department to log messages from code that deals with financial transactions (conveniently located in the <code>net.johnpwood.financial</code> package) to the database.  This sounds like the birth of a new type of logger.  Because clients are not using the <code>new</code> operator to create new instances of the <code>Logger</code> class, we can easily evolve <code>Logger</code> into an abstract class, keeping the static <code>getInstance()</code> method as the factory method for the <code>Logger</code> class hierarchy.  After we have the abstract class, we can create two new subclasses to implement the individual behavior.  All of this with no change to how the client uses the logging functionality.</p>
<p>Because the filesystem logger and the database logger don&#8217;t have too much in common, the <code>Logger</code> class has been slimmed down quite a bit.  What remains is the interface for the <code>Logger</code> subtypes, defined via the abstract <code>log()</code> method, and a factory method to create the proper logger, which is implemented in <code>getInstance()</code>.</p>
<pre class="brush:java">
public abstract class Logger {

    public static Logger getInstance(Class<?> from) {
        if (from.getCanonicalName().startsWith(
                "net.johnpwood.financial")) {
            return DatabaseLogger.getInstance(from);
        } else {
            return FilesystemLogger.getInstance(from);
        }
    }

    protected Logger() {}
    public abstract void log(String message);
}
</pre>
<p>We now have two distinct classes that handle logging transactions.  <code>FilesystemLogger</code>, which contains most of the old <code>Logger</code> code, and <code>DatabaseLogger</code>.  <code>FilesystemLogger</code> should look pretty familiar.</p>
<pre class="brush:java">
public class FilesystemLogger extends Logger {
    private static final String logFileName = "application.log";
    private FileWriter fileWriter;
    private Class<?> from;

    public static FilesystemLogger getInstance(Class<?> from) {
        return new FilesystemLogger(from);
    }

    protected FilesystemLogger(Class<?> from) {
        this.from = from;

        try {
            fileWriter = new FileWriter(logFileName, true);
        } catch (IOException e) {
            throw new RuntimeException("Log file '" + logFileName +
                    "' could not be opened for writing.", e);
        }
    }

    @Override
    public void log(String message) {
        try {
            fileWriter.write(
                from.getCanonicalName() + ": " + message + "\n");
            fileWriter.flush();
        } catch (IOException e) {
            System.err.println("Writing to the log file failed");
            e.printStackTrace();
        }
    }
}
</pre>
<p><code>DatabaseLogger</code> is also pretty simple, since I didn&#8217;t bother to implement any of the hairy database code (doesn&#8217;t help to illustrate the point&#8230;and I&#8217;m lazy).</p>
<pre class="brush:java">
public class DatabaseLogger extends Logger {
    private Class<?> from;

    public static DatabaseLogger getInstance(Class<?> from) {
        return new DatabaseLogger(from);
    }

    protected DatabaseLogger(Class<?> from) {
        this.from = from;
        establishDatabaseConnection();
    }

    @Override
    public void log(String message) {
        LoggerDataObject dataObject =
            new LoggerDataObject(from, message);
        dataObject.save();
    }

    private void establishDatabaseConnection() {
        // Connect to the database
    }
}
</pre>
<p>We&#8217;ve significantly changed how the <code>Logger</code> works, and the client is totally oblivious to the changes.  The client code continues to use the <code>Logger</code> as it did before, and everything just works.  Pretty sweet, eh?  </p>
<p>As you can imagine, there are many other ways you can evolve your design if you have this separation of creation and use.  If we need to create a <code>MockLogger</code> for testing purposes, it can be created in <code>Logger.getInstance()</code> along with the other <code>Logger</code> implementations.  The client would never know that it is using a mock.  If we ended up creating 10 different loggers, it would be trivial to have <code>Logger.getInstance()</code> delegate the creation of the proper <code>Logger</code> instance to a factory, moving the creation logic out of the <code>Logger</code> class.  Again, no changes to the client.</p>
<p>Separating creation from use also allows you to easily evolve your class into a singleton (or any other pattern that controls the number of instances created).  This doesn&#8217;t make much sense for <code>Logger</code>, since each unique <code>Logger</code> instance contains state.  However, it does make sense for some classes.  Evolving your class into a singleton simply requires a static instance variable on the class containing the instance of the singleton object, and an implementation of <code>getInstance()</code> that returns the singleton instance.  If clients have already been using the <code>getInstance()</code> method to get an instance of the class, then no change would be required on their end.  Here&#8217;s an example:</p>
<pre class="brush:java">
public class SomeOtherClass {
    private static SomeOtherClass instance = new SomeOtherClass();

    public static SomeOtherClass getInstance() {
        return instance;
    }

    private SomeOtherClass() {}
}
</pre>
<p>It is worth pointing out that static builder methods are not the only way to achieve this separation.  Dependency injection frameworks like Spring and Guice do all of this for you.  They take on the responsibility of creating the objects, and getting the instances to the code that uses them.  If you are a disciplined developer, and never &#8220;cheat&#8221; by instantiating the objects directly, then all of the same benefits outlined above apply when using a dependency injection framework.</p>
<p>Like everything in life, there are cons that go along with the pros.  Separating the code that creates an object from the code that uses the object is not the default pattern.  It is not the norm.  It will take time for you and your co-workers to get comfortable with this pattern.  API documentation tools don&#8217;t &#8220;call out&#8221; static builder methods like they do constructors.  This could have an effect on anybody using your library.  Dependency injection frameworks take the creation of objects completely out of your code, moving it to some magical, mysterious land where things just happen, somehow.  This also can take some time to get used to, especially for those new to the concept.</p>
<p>However, I feel that the benefits of separating creation from use far outweigh the drawbacks.  </p>
<p>In our field, change is a constant.  As a profession, we&#8217;re gradually learning to stop fighting change, and to start accepting it.  This means designing for change.  Doing so makes everybody&#8217;s life easier, from the customer to the developer.  Separating creation from use is one, quick way we can increase the flexibility of our design, with very little up front cost.</p>
<p>Thanks to <a href="http://mmurthy.com">Mahesh Murthy</a> for reviewing this post.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/-W55YzaJXmE" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/02/18/increase-design-flexibility-by-separating-object-creation-from-use/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/02/18/increase-design-flexibility-by-separating-object-creation-from-use/</feedburner:origLink></item>
		<item>
		<title>Build Your Own Sandbox Application</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/L0-0wX2JBGs/</link>
		<comments>http://johnpwood.net/2009/01/19/build-your-own-sandbox-application/#comments</comments>
		<pubDate>Mon, 19 Jan 2009 14:23:44 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[career development]]></category>
		<category><![CDATA[continued education]]></category>
		<category><![CDATA[personal projects]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=268</guid>
		<description><![CDATA[Sandboxes are fun.  A simple box full of sand, a bucket, and a shovel somehow opens the imagination like nothing else.  You can build anything you want, keep it around for a while if you like it, add to it, subtract from it, or crush it in Godzilla like fashion if you so [...]]]></description>
			<content:encoded><![CDATA[<p>Sandboxes are fun.  A simple box full of sand, a bucket, and a shovel somehow opens the imagination like nothing else.  You can build anything you want, keep it around for a while if you like it, add to it, subtract from it, or crush it in Godzilla like fashion if you so choose.  You can experiment with your creation in any way imaginable, without consequence.  Creating things in such a carefree environment can be refreshing, and rewarding.  </p>
<p>I think that it is a great idea for developers to have such an environment for themselves where they can try out new technologies, techniques, or processes.  Setting up a sandbox now-a-days is pretty easy.  Machines are cheap, or free if you&#8217;re not picky and keep your eyes and ears open.  That 4 year old PC that your cousin is throwing away because he got a brand new Dell for Christmas will fit the bill just fine.  Linux is free, runs great on older computers, and has the power and flexibility to host virtually any application.  Sign up for a free DNS service such as <a href="http://www.dyndns.com/">DynDNS</a> and poof, you&#8217;ve got yourself a little server that you can reach over the Internet.  My sandbox is a dusty old dual Pentium III with 512 MB of RAM, which is running <a href="http://www.ubuntu.com">Ubuntu Linux</a>.  It fits the bill quite nicely if you ask me.</p>
<p>But a sandbox is only half the equation.  For developers, we need an application that we can play with in the sandbox.  Something we can poke and prod.  A <strong>sandbox application</strong> so to speak.  There are several reasons why you many want to create a sandbox application. </p>
<h4>Platform for playing with new technologies</h4>
<p>Perhaps the biggest reason for creating and maintaining your own sandbox application is that it can serve as a platform for trying out new technologies.  Doing this at work can be tough.  Your boss or client may not be thrilled to hear that you completely re-wrote part of the application to use the bleeding edge release of some hot new framework because you <em>&#8220;thought it was cool&#8221;</em>.  But, there&#8217;s nothing stopping you from doing it with your own application.  Even if you rely heavily on your application, there&#8217;s no reason why you can&#8217;t fork your code, and give the new technology a try on a separate branch.  If it works out you can merge the code into the main branch, and if not you can always abandon the changes.  Now, this approach will only work if you have your application under source control (which you should).  If you don&#8217;t mind giving the world access to your code, <a href="http://www.github.com">GitHub</a> will host your code for free.  Otherwise, it&#8217;s very easy to setup your own source control system in your sandbox.  </p>
<h4>Material to blog about</h4>
<p>Trying out new technologies, techniques, or processes can also give you plenty of material to blog about.  If the technology/technique/process you are tinkering with is hot, it is very likely that many people will be interested in reading about your experience.  If you blog frequently about topics that people are interested in, you&#8217;ll steadily increase readership.  This could be very good for your career, as well known programmers generally don&#8217;t have a hard time finding work.  Work usually finds them.</p>
<h4>Create something that you will use</h4>
<p>What&#8217;s the point in going through all of this trouble to create something if you never use it?  I&#8217;ve created <a href="http://johnpwood.net/projects/webapps/diners-club">a</a> <a href="http://johnpwood.net/projects/webapps/karate-journal">few</a> <a href="http://johnpwood.net/projects/webapps/addressbook">applications</a> that I use on a regular basis.  Not only did these applications address some need that I was currently facing, but having a sandbox application that you actually use means that you will be more likely to maintain and enhance it.</p>
<h4>Looks great on a resume</h4>
<p>Employers love to hire people who show an interest in their field outside of work.  I&#8217;ve found that people passionate about their field are usually better at their jobs than those who show up at 9, work on the same ol&#8217; stuff for 8 hours, and go home.  Having a sandbox application shows people that you love what you do so much that you have dedicated time outside of work to create something that you care about.  This especially holds true if you&#8217;ve put serious thought into your application, and are excited to show it off to anybody who asks to see it.</p>
<h4>Release it, and potentially help others</h4>
<p>One of my colleagues once said,</p>
<blockquote><p>Your parents lied to you.  You are <strong>not</strong> special.  There&#8217;s millions of people out there <strong>just like you</strong>.</p></blockquote>
<p>He wasn&#8217;t trying to be mean, this time :)  He was simply pointing out that you are not alone.  If you are facing a problem, odds are there are hundreds or thousands (or more) of people out there who are facing that same problem.  Releasing your application could be helping all of these people.  Perhaps it would help them so much that they would be willing to pay for it.  Wouldn&#8217;t that be nice?</p>
<h4>Open source your application</h4>
<p>Open sourcing your application can be great for several reasons.  Perhaps you&#8217;ve just finished migrating your application to the latest version some framework.  Not only can you blog about your experience, but you can share the code with others so that they can see exactly how you did it.  This could potentially help others looking to migrate their applications to the same framework.  You could get feedback from the community about something you could be doing better, and learn something new.  People looking at your code could spot a bug, giving you the opportunity to fix it before it affects you (especially if you use your application).  Some employers ask to see source code samples as part of the interview process.  What sort of reaction do you think you would get if you immediately spit out several repositories that you owned on GitHub for the prospective employer to browse at their leisure?  I&#8217;m not sure about you, but I&#8217;d be pretty impressed.</p>
<p>However, there is a potential drawback in releasing your code to the world.  Ironically, it&#8217;s the same as one of the benefits.  <strong>The world can see your code</strong>.  This can be a bad thing if your code is full of bugs, or sloppy.  So if you plan on <a href="http://johnpwood.net/2008/03/26/release-your-code/">releasing your code</a>, take the extra care necessary to ensure that the code reflects your best effort.  Your code, and you as a developer, will benefit from the extra TLC.</p>
<h4>The next big thing</h4>
<p>You never know which crazy, off the wall idea will turn into the next big thing.  Who would of thought that there was a real need for <a href="http://www.twitter.com">an application</a> that lets you tell the world what you&#8217;re doing right now.  If your idea for a sandbox application turns into something with real business value, then you never know where it will end up.  Large companies will often spend big bucks to buy great ideas.  I&#8217;d imagine it would be pretty cool to be on the receiving end of one of those deals.</p>
<h4>Summary</h4>
<p>Creating a sandbox, and a sandbox application is something that every serious developer should do.  If nothing more, it will give you a place to tinker with new technologies and grow as a developer.  There is little to no cost to set it up, and your imagination is the only limit.</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/L0-0wX2JBGs" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/01/19/build-your-own-sandbox-application/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/01/19/build-your-own-sandbox-application/</feedburner:origLink></item>
		<item>
		<title>Trastel Accepted as Official Service Level Testing Tool @ Orbitz</title>
		<link>http://feedproxy.google.com/~r/johnpwood/~3/VkuzFLtEk-Q/</link>
		<comments>http://johnpwood.net/2009/01/07/trastel-accepted-as-official-service-level-testing-tool-orbitz/#comments</comments>
		<pubDate>Wed, 07 Jan 2009 23:33:00 +0000</pubDate>
		<dc:creator>John Wood</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[dsl]]></category>
		<category><![CDATA[orbitz]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://johnpwood.net/?p=258</guid>
		<description><![CDATA[Trastel, the DSL I created to help with the automated testing of our services at Orbitz, has been accepted as the QA team&#8217;s official testing language.  Yippee!
]]></description>
			<content:encoded><![CDATA[<p><a href="http://johnpwood.net/2008/10/29/falling-in-love-with-dsls/">Trastel</a>, the DSL I created to help with the automated testing of our services at Orbitz, has been accepted as the QA team&#8217;s official testing language.  Yippee!</p>
<img src="http://feeds.feedburner.com/~r/johnpwood/~4/VkuzFLtEk-Q" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://johnpwood.net/2009/01/07/trastel-accepted-as-official-service-level-testing-tool-orbitz/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://johnpwood.net/2009/01/07/trastel-accepted-as-official-service-level-testing-tool-orbitz/</feedburner:origLink></item>
	</channel>
</rss>
