<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CUACRH8-fSp7ImA9WxNUFUk.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010</id><updated>2009-11-06T15:42:45.155-06:00</updated><title>過労死 Coderoshi</title><subtitle type="html">Death by Overcoding</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.coderoshi.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.coderoshi.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>155</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="license" type="text/html" href="http://creativecommons.org/licenses/by-nc-sa/2.0/" /><link rel="self" href="http://feeds.feedburner.com/coderoshi" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;A0IMQn48cSp7ImA9WxNRFko.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8090868339365118797</id><published>2009-09-11T09:12:00.012-05:00</published><updated>2009-09-11T09:33:03.079-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-11T09:33:03.079-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="seo" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><title>Subdomains on Github</title><content type="html">I've been using &lt;a href="http://github.com"&gt;Github&lt;/a&gt; for a while, and it didn't really occur to me to write about it. It's just something that I assume everyone uses, but don't talk much about. Like toilet paper.&lt;br /&gt;&lt;br /&gt;But I just discovered some two-ply goodness that I had to share. Github &lt;a href="http://www.netlingo.com/word/link-juice.php"&gt;link juice&lt;/a&gt;; or more correctly - instant SEO credibility by owning a github subdomain. If you visit &lt;a href="http://coderoshi.github.com"&gt;http://coderoshi.github.com&lt;/a&gt;, you'll be redirected to this site. But how?&lt;br /&gt;&lt;br /&gt;There are two way of going about it. If you visit a subdomain that hasn't been used you'll get this page:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img.skitch.com/20090911-x7yx62yjycqm5hm5s5stqj7wes.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 750px; height: 536px;" src="http://img.skitch.com/20090911-x7yx62yjycqm5hm5s5stqj7wes.jpg" border="0" alt="" /&gt;&lt;/a&gt;Try it: &lt;a href="http://alice.github.com/"&gt;http://alice.github.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;That's most of the trick. Create a new public repository (don't worry, it's free) of "yourusername.github.com" (don't forget the ".github.com part). Add an index.html file and push to master. Viola.&lt;br /&gt;&lt;br /&gt;(for you non-git people, when you create the repository, github gives you clearer instructions than I could here)&lt;br /&gt;&lt;br /&gt;But there's more. To do a simple redirect, just paste in some html with a meta refresh tag, or to keep the nice subdomain url to mask your GeoCities site (look it up, kids) - just upload a page with a blank iframe, which contains your site.&lt;br /&gt;&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;html&amp;gt;&lt;br /&gt;&amp;lt;style&amp;gt;body,html{padding:0px;margin:0px;}&amp;lt;/style&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;iframe src="http://www.coderoshi.com" width="100%" height="100%"&lt;br /&gt;  style="border:0px" marginheight="0" marginwidth="0" frameborder="0"&amp;gt;&lt;br /&gt;  &amp;lt;a href="http://www.coderoshi.com"&amp;gt;Coderoshi - Death By Overcoding&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/iframe&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;/pre&gt;&lt;br /&gt;But there's more! If you don't want something so sloppy, and merely want a URL redirect to your site, instead of an index.html file, upload a file named CNAME, which contains your domain. Mine looks like this:&lt;pre&gt;coderoshi.com&lt;/pre&gt;That's it! Instant credibility from google (you're a real subdomain of the very popular github.com domain).&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8090868339365118797?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CQne6gRxzY8:ZvPxJVelIVo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CQne6gRxzY8:ZvPxJVelIVo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=CQne6gRxzY8:ZvPxJVelIVo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CQne6gRxzY8:ZvPxJVelIVo:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CQne6gRxzY8:ZvPxJVelIVo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=CQne6gRxzY8:ZvPxJVelIVo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8090868339365118797" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8090868339365118797?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8090868339365118797?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/CQne6gRxzY8/subdomains-on-github.html" title="Subdomains on Github" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/09/subdomains-on-github.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEGQXYzfip7ImA9WxNSGEg.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8045889115112388266</id><published>2009-09-01T19:48:00.003-05:00</published><updated>2009-09-01T19:50:20.886-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-01T19:50:20.886-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="git" /><title>Codeoshi on Github</title><content type="html">Here's the link: &lt;a href="http://github.com/coderoshi"&gt;http://github.com/coderoshi&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Sure, it's been there for a while - it just recently occurred to me that I never linked to it.&lt;br /&gt;&lt;br /&gt;Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8045889115112388266?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=wnYn0ph3lPA:nU-GLS4c7bE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=wnYn0ph3lPA:nU-GLS4c7bE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=wnYn0ph3lPA:nU-GLS4c7bE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=wnYn0ph3lPA:nU-GLS4c7bE:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=wnYn0ph3lPA:nU-GLS4c7bE:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=wnYn0ph3lPA:nU-GLS4c7bE:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="related" href="http://github.com/coderoshi" title="Codeoshi on Github" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8045889115112388266" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8045889115112388266?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8045889115112388266?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/wnYn0ph3lPA/codeoshi-on-github.html" title="Codeoshi on Github" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/09/codeoshi-on-github.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcEQH0zfip7ImA9WxNTFkg.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-2542560923353499342</id><published>2009-08-19T00:00:00.000-05:00</published><updated>2009-08-19T00:00:01.386-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-19T00:00:01.386-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 52</title><content type="html">Like one cast away on a desert island,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;the rolling tide keeps his head.&lt;br /&gt;Like one working on the dark prairie plains,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;the chirping insects keep him company.&lt;br /&gt;Listen to the hum of the machine, for that is the coder’s solitude,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;that keeps his company ahead.&lt;br /&gt;&lt;br /&gt;The elite understands his value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;does not lord it over his subordinates,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;does not embellish it to his superiors.&lt;br /&gt;&lt;br /&gt;His opportunity flies like a gentle bee’s.&lt;br /&gt;Alights on the first flower it sees,&lt;br /&gt;And leaves just as suddenly, without a trace&lt;br /&gt;But benefits the flower in endless ways.&lt;br /&gt;&lt;br /&gt;The elite has no home but Code,&lt;br /&gt;And because of this, is never homeless.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-2542560923353499342?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GqL3WzaGmIk:ywKSZNbX5uE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GqL3WzaGmIk:ywKSZNbX5uE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GqL3WzaGmIk:ywKSZNbX5uE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GqL3WzaGmIk:ywKSZNbX5uE:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GqL3WzaGmIk:ywKSZNbX5uE:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GqL3WzaGmIk:ywKSZNbX5uE:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=2542560923353499342" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2542560923353499342?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2542560923353499342?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/GqL3WzaGmIk/code-te-ching-verse-52.html" title="Code Te Ching - Verse 52" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/08/code-te-ching-verse-52.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08NQXo_cSp7ImA9WxNTFU4.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8785787745101543305</id><published>2009-08-17T10:29:00.012-05:00</published><updated>2009-08-17T12:58:10.449-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-17T12:58:10.449-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><title>Ruby is Java and Java is C</title><content type="html">Remember 10 years ago? As "Java 2" (jdk 1.2) exploded in popularity throughout 1999, scores of developers dropped their perl/c CGI web server-side methods in favor of the new hotness Java. And why not? The language was maturing, the simplicity of ASP was ported to Java as JSPs in June, and the servlet spec was hammered out with the advent of WARs in August. Those of us lucky enough to get paid to be on this forefront found plenty of work still needed to be done - by way of tooling and optimization.&lt;br /&gt;&lt;br /&gt;But with this new fervor of excitement rose an equal amount of riffing from the "real" server-side language: C/C++. "It may seem like you can write apps faster", they'd say, "but there's no way you can beat our tools - look at our libraries! Look at the power of our pre-processor, compile optimizations, and emacs! And - you'll be sorry when you have to spend all that time optimizing, and eventually re-write your code in C anyway." It was a fair argument, and there were certainly anecdotes over the years of companies pulling the plugin on their Java projects to revert back to C (hell, I worked on one at Cerner as late as 2004). Some Javanistas attempted to argue back - but most of us quietly hammered on, bending Java to our will - content in the knowledge that hardware would close the performance gap, and our tools would do nothing but grow. Yet still they scorned - for a while. Ten short years later, you rarely hear from that camp anymore concerning web server-side productivity or reliability. We clearly - emphatically - won. (It's a shame, really, since I kind of miss hacking C.)&lt;br /&gt;&lt;br /&gt;But times have changed. Despite incessant claims that Moore's law is dead - it has held strong, driving forward. Hardware is cheap. Disks are cheap. Memory is cheap. And in many systems, bandwidth is cheap. Yet now, a short 10 years later, the Ruby camp suffers the slings and arrows of a myopic community - in this case Java steps into the role of C. The old, general purpose stand-by refuses to fade away in favor of it's lighter, easier, friendlier replacement. The arguments levied against Ruby these days are similar to the old arguments against Java. It's slower. It doesn't have powerful tools. It doesn't concern the needs of the "enterprise". "Real" programmers understand the difference between interfaces and abstract classes - and the value of each being distinct (I still don't know). Yadda yadda yadda...&lt;br /&gt;&lt;br /&gt;So I'll make two predictions here. Not so bold now, but in fairness to me, I've been saying it for 4 years: Ruby will win this battle for the web over Java. Java is just too clunky of a language to survive, with too much baggage to match the agility of Ruby - or even Python for that matter.&lt;br /&gt;&lt;br /&gt;Java - desperate to remain relevant - has at least learned something from the past. Java, as a language will die, but hopefully the JVM - arguably the greatest VM ever created (not so much by design, but by sheer effort alone) won't fade into that dark night. Hopefully &lt;a href="http://www.engineyard.com/blog/2009/engine-yard-adds-jruby-support/"&gt;EngineYard's recent investment into JRuby &lt;/a&gt;is just the right medicine - lest it completely drop from the web server-side market. And that really would be a shame.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8785787745101543305?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=KTbwylx6I1k:EK3BW2zTI_k:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=KTbwylx6I1k:EK3BW2zTI_k:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=KTbwylx6I1k:EK3BW2zTI_k:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=KTbwylx6I1k:EK3BW2zTI_k:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=KTbwylx6I1k:EK3BW2zTI_k:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=KTbwylx6I1k:EK3BW2zTI_k:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8785787745101543305" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8785787745101543305?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8785787745101543305?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/KTbwylx6I1k/ruby-is-java-and-java-is-c.html" title="Ruby is Java and Java is C" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/08/ruby-is-java-and-java-is-c.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUABRnc6eSp7ImA9WxNTF0o.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-5787629701016715040</id><published>2009-07-29T08:55:00.040-05:00</published><updated>2009-08-20T09:15:57.911-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-20T09:15:57.911-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="startup" /><category scheme="http://www.blogger.com/atom/ns#" term="google app engine" /><title>Build a Business with Google</title><content type="html">Hi, I'm Eric. A C++ coder turned Java zealot turned Ruby on Rails adopter turned Django turned... whatever. They're all fine. I'm not here to talk about superior architecture, or the best language, or design or whatever. I'm here to talk about getting your web app idea out the fucking door. This is exactly what we did with &lt;a href="http://www.microtasking.net"&gt;MicroTasking.net&lt;/a&gt;. A year ago this was hard, but could be done for free. Two years ago it could be done cheaply. Five years ago you needed some funding and a lot more time. This is today - so I'll talk about the lowest barrier to entry technical stack on the market: Google.&lt;br /&gt;&lt;br /&gt;Note - I don't work for Google, I don't get kickbacks, I don't even own any stock. But I have &lt;a href="http://www.sonatype.com"&gt;bootstrapped&lt;/a&gt; &lt;a href="http://www.vontoo.com"&gt;several&lt;/a&gt; &lt;a href="http://www.thefutureis.mobi"&gt;companies&lt;/a&gt;, &lt;a href="http://www.microtasking.net"&gt;products&lt;/a&gt;, &lt;a href="http://www.cerner.com"&gt;enterprises&lt;/a&gt;, &lt;a href="http://www.snipsnipe.com"&gt;forgettable&lt;/a&gt; &lt;a href="http://gigaring.heroku.com"&gt;services&lt;/a&gt;, &lt;a href="http://www.vocmsg.com/"&gt;and&lt;/a&gt; &lt;a href="http://apache.maven.org"&gt;open&lt;/a&gt; &lt;a href="http://mojo.codehaus.org/"&gt;source&lt;/a&gt; &lt;a href="http://plexus.codehaus.org/"&gt;products&lt;/a&gt;. I also make &lt;a href="http://www.ziggypod.com/"&gt;iPhone apps&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Are you afraid of "putting all of your eggs in one Google basket"? Don't. When speaking of risk assessment, the largest risk of a startup is failing to launch. Google going away isn't your biggest concern. Scaling isn't either. Just push it out the door - then worry about those things. Once you have customers, you'd be amazed all of the problems you can pay to have solved. Like migrating to dedicated machines, if it makes you feel better.&lt;br /&gt;&lt;br /&gt;So what do you need, from the technical side, to start a web application company? A domain name. You need a server that doesn't suck. Email/office software is nice. A blog is good. A phone number to call. What else? You also need &lt;span style="font-style:italic;"&gt;customers&lt;/span&gt;. You get them through advertising and campaign optimization (which you achieve via tracking). They also need to be able to make purchases. But first thing's first:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Domain Registration/Host/Email/Office Apps&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Let's start with the basics. Register a domain and get a host through &lt;a href="http://www.google.com/a/cpanel/domain/new"&gt;Google Apps signup&lt;/a&gt;. Click "I want to buy a domain name" and search for one. It costs $10/year, which is more than Go Daddy. What they give is worth the extra price. You'll automatically be set up with a standard version of GA - which  provides you with 50 domain-specific email accounts integrated with the Calendar, a sandboxed office suite with IM/Video Chat, Documents, Slideshows, Spreadsheets - also integrated Forms, which you can put on your Site or email, and collect results as a spreadsheet. Oh, also, they provide a website CMS.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Money down: $10&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Phone Management&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Sign up for &lt;a href="http://www.google.com/voice"&gt;Google Voice&lt;/a&gt;. It's not public yet - but it will be soon enough. I use it daily... it has damn near everything you need for a small company PBX. I live in Indianapolis, but my business number is San Francisco - which forwards to my cellphone. Twofold benefit. 1) you don't want to give out your private number, and 2) you don't want to give out your private number. It's free for now. You can even pay extra for a toll-free (800, 888, 866) number.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;More on Email&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is a nice trick mentioned by Tim Ferriss in the &lt;a href="http://www.fourhourworkweek.com/"&gt;4-Hour Work Week&lt;/a&gt;. Since you're eventually going to grow and outsource all of your work (right?) it's not a bad idea to segment your communications in advance. Your company may just be you and a buddy for now, but you want the illusion of a larger team. Sales. Info. Webmaster. Whatever. You can do this on the Google Apps dashboard by clicking "Users and Groups" on the top, then choosing "Groups". Create as many groups as you need and just have them forward to the appropriate people. As you grow, it's easy to reroute the traffic - much easier than changing a well-known email address. Our list for MicroTasking.net is:&lt;br /&gt;&lt;br /&gt;contact@microtasking.net, help@microtasking.net, info@microtasking.net, refund@microtasking.net, sales@microtasking.net&lt;br /&gt;&lt;br /&gt;They all forward to me and Jim.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Blog&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Get a blog via &lt;a href="http://www.blogger.com"&gt;Blogger&lt;/a&gt;. Just sign-up with the Google Account you made above. Follow the steps to create a blog. That's easy enough, eh? What you probably want here though, is not a blog URL http://mynewblog.blogspot.com , but rather http://blog.mynewcompany.com. This is a two-step process.&lt;br /&gt;&lt;br /&gt;First, create the blog subdomain. In the Google Apps dashboard, click "Domain Settings" above. Then click "Domain Names", then "Advanced DNS settings". Sign into enom. "Edit Host Records" and "Add New" CNAME named "blog" (or whatever). Set the address to "ghs.google.com." (don't forget the period at the end). That's it for the DNS routing table.&lt;br /&gt;&lt;br /&gt;Next log back into Blogger. Click "Settings" and then "Publishing". Fill out Your Domain with the new subdomain (eg. http://blog.mynewcompany.com). That's it! No more lame blogspot domain.&lt;br /&gt;&lt;br /&gt;This should cover you on the business/IT end. Let's make the app!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Application&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;For years Google skirted around the edges - instead focusing on toolkits, APIs, and company tools like email. With the launch of &lt;a href="http://appengine.google.com/"&gt;Google App Engine&lt;/a&gt; (GAE) last year, they've taken a huge leap toward Amazon. What is App Engine? Think EC2+SimpleDB+S3+deployment tools+data management. It's to Python (and Java) what &lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt; is for Ruby on Rails. In other words - a dream.&lt;br /&gt;&lt;br /&gt;Sign up for a &lt;a href="http://appengine.google.com/"&gt;Google App Engine&lt;/a&gt; account. Again, just use your new gmail account. There are a lot of posts about App Engine, so I won't cover it here. But what I will tell you is, if you choose to write in Python, use Django. There a great &lt;a href="http://code.google.com/p/google-app-engine-django/"&gt;patch project  here&lt;/a&gt;. Follow the instructions. Use it. I've written 3 GAE projects so far - two of them without it. One with. If your application will be non-trivial, just make the investment to learn Django and use it - you'll thank me later.&lt;br /&gt;&lt;br /&gt;Note that your application lives at http://mynewapp.appspot.com/. Again, we want to put the application under our single domain - which is painfully easy. This is where I diverge from the Google party-line. I don't use the Google Site stuff - but by default, your domain will forward to use Google Site. So I instead hijack the "www" subdomain to point to my app-engine app.&lt;br /&gt;&lt;br /&gt;First, we need to tell Google about our application. Go back to the Dashboard, and click on "Add More Services". Under "Other services", you will see "Google App Engine" - "Enter App ID:". Do what it says (this is the unique name you gave your app - the *.appspot.com subdomain). It will then forward you to another settings page - but ignore this for now and return to the main Dashboard page. Click on the "Sites" link, then "Web address mapping". Delete the "www" mapping. Then return to your Dashboard.&lt;br /&gt;&lt;br /&gt;Select the new "app engine" link - the one you just added. Choose "Add new URL", and enter in  the subdomain "www". Viola! Now your application will be the main page. I recommend doing this because Google Sites is pretty limited, both in style and customizability. Your applications index page doesn't have to be anything more than a static template page - which you can 100% customize like any other website. Some people are taking advantage of this, and using App Engine as a pure content delivery network (CDN) to host static pages, images, etc for free. Actually, I host &lt;a href="http://www.ziggypod.com"&gt;ZiggyPod&lt;/a&gt; this way. It' fast, scales, and the price is right :). It's "freemium", so you can pay for more later.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Payments&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;There are many services Google provides, which you can easily hook into via app-engine. Maps, Email, etc. But there's nothing special there. The real cool piece is hooking into &lt;a href="https://checkout.google.com"&gt;Google Checkout&lt;/a&gt;. Sign up for an account. There are many ways you can hook into GC, but my favorite way is to set up your account to automatically update the application when a sale is made - such as making more services available. It's nearly immediate.&lt;br /&gt;&lt;br /&gt;Head to "Settings", "Integration" in the GC page. Under "API callback URL" enter in the URL that will accept the callback. Note that this URL must be https, and that app engine only supports certificates from appspot.com. Meaning, here is one place where you will have to enter in your appspot URL. For example:&lt;br /&gt;&lt;br /&gt;https://mynewapp.appspot.com/googlepaynotify&lt;br /&gt;&lt;br /&gt;This assumes your app has a path "/googlepaynotify" which handles the callback. Google Checkout will send the URL XML or name=value pairs. I prefer XML - just parse it and handle it. There's plenty of &lt;a href="http://code.google.com/apis/checkout/"&gt;documentation on the API&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This handles the callback, but how do you make the sale begin? The easiest way is to create a "Buy Now Button" - you'll see it if you click "Tools" on the top. It will generate the client-side code where you can put the button on your site. Congratulations! You can now accept sales. You can make it as complex as you want, of course, which requires more integration with the API. But that's beyond the scope here.&lt;br /&gt;&lt;br /&gt;So far we have a homepage, full office stack, a blog, application hosting, and the ability to accept payments. What more do we need... oh yeah, customers! Let's integrate with AdWords.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;AdWords&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://adwords.google.com/"&gt;AdWords&lt;/a&gt; is older than dirt. It's Google's first and most profitable business unit - and with good reason. I worked for over a year as a keyword marketer, and I have one piece of advise: Don't try and game the system. All of the obvious ideas have been tried, and the cleverer ones are dominated by a few players who do that, and only that, for a living.&lt;br /&gt;&lt;br /&gt;However - there is something you should know: Turn off keywords. OK, perhaps not completely, but your focus should be on optimizing Content Network. Two reasons for this 1) since the keyword space is basically dominated, you'll pay out the nose for decent keywords. Sure, grab a few - your company name, for example, but for the most part, take a cue from Paul McCartney. Let it be. 2) You'll have far more impression on the content network than keywords. You only pay per click, so if someone sees your website name and don't click, you just got a free impression. I get 100,000 impressions a day. Sure - most don't click, or see it. But some do - and it didn't cost all that much.&lt;br /&gt;&lt;br /&gt;I could go on. Forever. The point being, sign up for adwords - it's a decent driver of traffic and a way to bootstrap your advertising for cheap. Just be wary of the expensive words. I never pay more than 0.50 per click. This is a secret of the pros. If the keywords you've chosen are expensive, they have high traffic, sure, but you can rather visit &lt;a href="https://adwords.google.com/select/KeywordToolExternal"&gt;https://adwords.google.com/select/KeywordToolExternal&lt;/a&gt;, type in the good keyword, and take all of the lesser quality words instead. Let's do the math.&lt;br /&gt;&lt;br /&gt;1 keyword @ $5.00/click * 1,000 click each = $5,000&lt;br /&gt;500 (related) keywords @ $0.10/click * 2 clicks each = $10&lt;br /&gt;&lt;br /&gt;For a savings of $4,990, you still got 1000 clicks. Of course, this takes more management. But there are several of these kinds of tricks available.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Analytics&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="https://www.google.com/analytics"&gt;Google Analytics&lt;/a&gt; is great. Before this gem, most people either rolled their own traffic trackers, or payed huge sums of money for less-awesome analytics tools. I don't have much to say about it other than sign up, put it on your home page (landing page). If you use the same account for AdWords - Analytics will track your traffic which comes from clicking on ads. A handy feature to track conversions.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Integration/Optimization&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;There are so many advanced tools available, it's hard to list them all here. But here are some&lt;br /&gt;&lt;br /&gt;&lt;a href="https://adwords.google.com/support/bin/answer.py?answer=115794&amp;hl=en_US"&gt;Conversion Tracking&lt;/a&gt;: From ad to successful "conversion" (visiting whatever page you consider to be a success), AdWords can self-optimize where/when/who to place your ads.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://adwords.google.com/support/bin/answer.py?answer=60150&amp;hl=en_US"&gt;Conversion Optimizer&lt;/a&gt;: After a month of tracking conversions, you can start paying per action - this means rather than paying Google for keywords/content which may not succeed at sales - you can pay them only when a sale happens.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://checkout.google.com/support/sell/bin/answer.py?answer=42896&amp;topic=8667"&gt;Checkout Badge&lt;/a&gt;: Increase AdWord conversions by letting Google notify and target people with Checkout accounts.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Many people use some aspect of Google services: gmail, applications, analytics. No need to complicate the matter - just wire these services together to create a full-stack for your new web-service company. Most anything you need (short of legal or tax advice) can be done for free or cheap.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-5787629701016715040?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=cWHsBOq95m8:h7V15j1ChZc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=cWHsBOq95m8:h7V15j1ChZc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=cWHsBOq95m8:h7V15j1ChZc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=cWHsBOq95m8:h7V15j1ChZc:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=cWHsBOq95m8:h7V15j1ChZc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=cWHsBOq95m8:h7V15j1ChZc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=5787629701016715040" title="9 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/5787629701016715040?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/5787629701016715040?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/cWHsBOq95m8/build-business-with-google.html" title="Build a Business with Google" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/07/build-business-with-google.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAMSHwzfyp7ImA9WxJSF00.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8317958737761842503</id><published>2009-05-06T19:01:00.019-05:00</published><updated>2009-05-07T09:33:09.287-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-07T09:33:09.287-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="textbarf" /><category scheme="http://www.blogger.com/atom/ns#" term="google app engine" /><category scheme="http://www.blogger.com/atom/ns#" term="gae" /><title>Google App Engine Scales and Develops Fast</title><content type="html">Really. It's amazing. After a quick chat about the concept with &lt;a href="http://jimbojw.com"&gt;Jim&lt;/a&gt;, I bet my wife I could get a web app knocked out which accepted text messages, stored and printed them in an hour (&lt;a href="http://www.textbarf.com"&gt;www.textbarf.com&lt;/a&gt;). It took 35 minutes. In fact, I have a feeling this write-up will take longer. Now THAT's progress.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;(FYI - sauce: &lt;a href="http://github.com/vocmsg/textbarf/tree/master"&gt;http://github.com/vocmsg/textbarf/tree/master&lt;/a&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Getting started&lt;/span&gt;&lt;br /&gt;First, create a &lt;a href="http://appengine.google.com/"&gt;Google App Engine account&lt;/a&gt;. If you already have gmail, just use that. You can have up to 10 apps.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_E3_4LXYT1LI/SgIoDmMuWhI/AAAAAAAAASo/c6HFOyhqd20/s1600-h/Picture+1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 210px;" src="http://1.bp.blogspot.com/_E3_4LXYT1LI/SgIoDmMuWhI/AAAAAAAAASo/c6HFOyhqd20/s320/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5332868951018723858" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you download the SDK and install it for your OS, you'll get a nice little launcher with it. Right-click new, and you're on your way. Launching is just as simple... just click "Deploy", enter your account login, and your app is launched. In my case, &lt;a href="http://textbarf.appspot.com"&gt;http://textbarf.appspot.com&lt;/a&gt; (all GAE apps are subdomains of appspot).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Code&lt;/b&gt;&lt;br /&gt;The source code couldn't be simpler. First, appengine db models are mapped to Google's BigTable - a columnar (not relational) database. The nice thing about this kind of dataset is that OO models map fairly directly to the data on the backend (no ORM). The downside is that you'll have lots of duplicate data - since joins are really expensive. But I digress. All we need to do is store a text message, the phone number it came from, and the datetime the txt was made. Ready? Java people cover your eyes - you may cry from jealousy:&lt;pre name="code" class="java:nocontrols"&gt;class Barf(db.Model):&lt;br /&gt;  text = db.StringProperty(required=True)&lt;br /&gt;  phone = db.PhoneNumberProperty()&lt;br /&gt;  date = db.DateTimeProperty(auto_now_add=True)&lt;/pre&gt;That's it. GAE creates a BigTable type which maps to this design (as well as automatically create any necessary indexes based on given queries, which then populates the &lt;a href="http://github.com/vocmsg/textbarf/tree/master"&gt;index.yaml&lt;/a&gt; file. I could have put this in an external module (normally I'd put it in a file called models.py and import it), but for the sake of speed and simplicity, I just put it in the main.py file generated by the GAE SDK tools.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_E3_4LXYT1LI/SgIvSndk80I/AAAAAAAAASw/_jeji5B1i7U/s1600-h/Picture+2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_E3_4LXYT1LI/SgIvSndk80I/AAAAAAAAASw/_jeji5B1i7U/s320/Picture+2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5332876905637278530" /&gt;&lt;/a&gt;&lt;br /&gt;Next, we need to deal with request. GAE webapp maps URLs to files (in app.yaml - which already points root to main.py by default), and uses 'WSGIApplication' to map internally URLs to RequestHandler classes. In this case, there are two urls: '/' and '/txtback' - one is the website URL, the other is so the SMS service can ping back data to the server. They are mapped in the main module&lt;pre name="code" class="java:nocontrols"&gt;def main():&lt;br /&gt;  app = webapp.WSGIApplication([('/', MainHandler),&lt;br /&gt;                                ('/txtback', TxtBackHandler)])&lt;br /&gt;  wsgiref.handlers.CGIHandler().run(app)&lt;/pre&gt;Let's look at the root URL.&lt;pre name="code" class="java:nocontrols"&gt;class MainHandler(webapp.RequestHandler):&lt;br /&gt;  def get(self):&lt;br /&gt;    barfs = Barf.all().order('-date').fetch(250)&lt;br /&gt;    &lt;br /&gt;    path = os.path.join(os.path.dirname(__file__), 'templates/index.html')&lt;br /&gt;    self.response.out.write(template.render(path, {'barfs': barfs}))&lt;/pre&gt;Again - hopefully straightforward (isn't python so readable?). If a GET request happens, first fetch 250 Barf objects reverse ordered by date. Next, get the path to an external template file and write the rendered template (passing in the barfs objects) to the response output. Most of the magic is in the template.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Template&lt;/b&gt;&lt;br /&gt;The template is mostly a standard HTML file, with a little bit of server-side markup.&lt;pre name="code" class="xml:nocontrols"&gt;{% for barf in barfs %}&lt;br /&gt;  {% ifchanged barf.date.date %}&lt;br /&gt;    &amp;lt;li&gt;&amp;lt;div class="date"&gt;{{ barf.date.date }}&amp;lt;/div&gt;&amp;lt;/li&gt;&lt;br /&gt;  {% endifchanged %}&lt;br /&gt;  &amp;lt;li&gt;&lt;br /&gt;    &amp;lt;div class="time"&gt;{{ barf.date|date:"P" }}&amp;lt;/div&gt;&lt;br /&gt;    &amp;lt;div class="quote"&gt;{{ barf.text|escape }}&amp;lt;/div&gt;&lt;br /&gt;  &amp;lt;/li&gt;&lt;br /&gt;{% endfor %}&lt;/pre&gt;Again - it should be self explanatory. Iterate through each "barf" object. If the date has changed since the previous loop, output it. Then, output the barf.date formatted by the date formatter with type "P" (simple time) and the given text. That's all you need to output stored data. Google manages the rest for you - all with a scalable base.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Accepting Text Messages&lt;/b&gt;&lt;br /&gt;Next, we want to accept text messages to populate the text Barf objects. There's no need to create a template here, a simple text response it sufficient. Our main goal is to store the input.&lt;pre name="code" class="java:nocontrols"&gt;class TxtBackHandler(webapp.RequestHandler):&lt;br /&gt;  def get(self):&lt;br /&gt;    text = self.request.get('message')&lt;br /&gt;    if text: text = text.strip().lower()&lt;br /&gt;    phone = self.request.get('min')&lt;br /&gt;    if phone: phone = phone[-10:]&lt;br /&gt;    &lt;br /&gt;    b = Barf(text=text, phone=phone)&lt;br /&gt;    b.put()&lt;br /&gt;    &lt;br /&gt;    self.response.out.write('http://textbarf.com')&lt;/pre&gt;We expect two request attributes, create a Barf object, save it and write back the URL. Simple as that.&lt;br /&gt;&lt;br /&gt;But that was only the callback. To accept text messages, I signed up for a 41411 shortcode account (named "tbarf") through &lt;a href="http://www.textmarks.com/"&gt;TextMarks.com&lt;/a&gt;. Once you sign up and verify the account, you need to provide the callback URL. Before you can do that, there must be a URL to hit. So, I click the deploy button. 5 seconds later, the app is up an running. All that's left is to give the URL to TextMarks - in my case: http://textbarf.appspot.com/txtback?min=\p&amp;message=\0 (where \p and \0 are interpolated with the calling phone and message data). 41411 is great, free, and they make money by ads.&lt;br /&gt;&lt;br /&gt;Seriously - that's it. That took a grand total of 30 minutes. So, I registered a URL and pointed it at the app (http://www.textbarf.com), then just for good measure, slapped in Google Analytics. This whole app cost a total of $10, for the sweet, sweet domain.&lt;br /&gt;&lt;br /&gt;Voilà! Eat it, EC2! Just for the record - this post took about an hour.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8317958737761842503?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=m4tStmtlWvE:1Exun9A5sR4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=m4tStmtlWvE:1Exun9A5sR4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=m4tStmtlWvE:1Exun9A5sR4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=m4tStmtlWvE:1Exun9A5sR4:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=m4tStmtlWvE:1Exun9A5sR4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=m4tStmtlWvE:1Exun9A5sR4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8317958737761842503" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8317958737761842503?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8317958737761842503?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/m4tStmtlWvE/google-app-engine-scales-and-develops.html" title="Google App Engine Scales and Develops Fast" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_E3_4LXYT1LI/SgIoDmMuWhI/AAAAAAAAASo/c6HFOyhqd20/s72-c/Picture+1.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/05/google-app-engine-scales-and-develops.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMNQH85eCp7ImA9WxJSEEs.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-7157671082245644987</id><published>2009-04-28T23:07:00.000-05:00</published><updated>2009-04-29T22:34:51.120-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-29T22:34:51.120-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 51</title><content type="html">The ancients followed the way of the machine.&lt;br /&gt;Dark, wondrous, complex.&lt;br /&gt;&lt;br /&gt;Difficult beyond knowing.&lt;br /&gt;&lt;br /&gt;Yet for all their knowing, they could not control it.&lt;br /&gt;Commanding:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Machine do this!&lt;br /&gt;Storing:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Machine hold this!&lt;br /&gt;Jumping:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Machine go there!&lt;br /&gt;Linking:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Machine go here!&lt;br /&gt;Finally, Yielding:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Machine, please help!&lt;br /&gt;&lt;br /&gt;And they taught the machine to help.&lt;br /&gt;&lt;br /&gt;Long did they suffer for the sake of their children.&lt;br /&gt;Those who return to such thought do the ancients a disservice.&lt;br /&gt;&lt;br /&gt;So embrace what has been done.&lt;br /&gt;Though the path may wind, it should never go back.&lt;br /&gt;&lt;br /&gt;Honor their memory, and their command of memory.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then forget.&lt;br /&gt;&lt;br /&gt;There is no instruction for honor.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-7157671082245644987?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=x-TNg1QvEZ0:Yg5NlCYp4Uw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=x-TNg1QvEZ0:Yg5NlCYp4Uw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=x-TNg1QvEZ0:Yg5NlCYp4Uw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=x-TNg1QvEZ0:Yg5NlCYp4Uw:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=x-TNg1QvEZ0:Yg5NlCYp4Uw:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=x-TNg1QvEZ0:Yg5NlCYp4Uw:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=7157671082245644987" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/7157671082245644987?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/7157671082245644987?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/x-TNg1QvEZ0/code-te-ching-verse-51.html" title="Code Te Ching - Verse 51" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/04/code-te-ching-verse-51.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkINSHYyfCp7ImA9WxJTFUQ.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-6930744961783328753</id><published>2009-04-24T12:06:00.000-05:00</published><updated>2009-04-24T13:43:19.894-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-24T13:43:19.894-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 50</title><content type="html">Learners!&lt;br /&gt;Great men of learning toil lifetimes&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for a glimpse of what the elite know.&lt;br /&gt;&lt;br /&gt;Great, unconditional love flows from the master.&lt;br /&gt;Great, conditional knowledge flows from the learned.&lt;br /&gt;&lt;br /&gt;When a labor of the mind toils a lifetime to fall short of perfection,&lt;br /&gt;the inaction of love sits silently, content in its experienced and subtle understanding of Code.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-6930744961783328753?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GWpjYianzI4:EbuG1dyiVBQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GWpjYianzI4:EbuG1dyiVBQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GWpjYianzI4:EbuG1dyiVBQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GWpjYianzI4:EbuG1dyiVBQ:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GWpjYianzI4:EbuG1dyiVBQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GWpjYianzI4:EbuG1dyiVBQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=6930744961783328753" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6930744961783328753?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6930744961783328753?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/GWpjYianzI4/code-te-ching-verse-50.html" title="Code Te Ching - Verse 50" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/02/code-te-ching-verse-50.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMCQHw9fSp7ImA9WxVaE08.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-2526511421472894867</id><published>2009-04-09T19:16:00.003-05:00</published><updated>2009-04-09T19:31:01.265-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-09T19:31:01.265-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="google app engine" /><category scheme="http://www.blogger.com/atom/ns#" term="gae" /><title>Google App Engine Java Support</title><content type="html">I know, I know. Envy me. Not only have I gotten to beta test the iPhone 3.0 notification and payment frameworks (incidentally, OS3.0 is piiiimmmppp), I now get to beta test the GAE J6 support. Although I'm still shocked, and slightly dismayed that the first language supported beyond Python is Java, it's clear they did it based on the very vocal rantings of the Javanistas (http://code.google.com/p/googleappengine/issues/detail?id=1) with gems such as "Java/Groovy would make this service much more serious for the individuals and development companies to work with it". Ah, the egocentric hubris of the Java-first crowd.&lt;br /&gt;&lt;br /&gt;Well, enough about the that. The great news isn't Java support - it's JVM support. Since now I can finally run Rails (well, JRails), it may be worth a go. But I still don't know how they're going to handle the lack of opening ports.&lt;br /&gt;&lt;br /&gt;Sadly, I've been so busy knocking the hell out of &lt;a href="http://app.vocmsg.com"&gt;vocmsg&lt;/a&gt;, everything must relate to that. So, I think I'll write my iPhone notifier on GAE using J6, and dump the overview here. &lt;span style="font-style:italic;"&gt;¡Me parece bien!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-2526511421472894867?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=o9iItzTta8Y:jKcnSPU9wmA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=o9iItzTta8Y:jKcnSPU9wmA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=o9iItzTta8Y:jKcnSPU9wmA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=o9iItzTta8Y:jKcnSPU9wmA:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=o9iItzTta8Y:jKcnSPU9wmA:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=o9iItzTta8Y:jKcnSPU9wmA:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=2526511421472894867" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2526511421472894867?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2526511421472894867?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/o9iItzTta8Y/google-app-engine-java-support.html" title="Google App Engine Java Support" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/04/google-app-engine-java-support.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YESHs5fip7ImA9WxVUGEk.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-2843887517871629715</id><published>2009-03-23T15:27:00.005-05:00</published><updated>2009-03-23T15:45:09.526-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-23T15:45:09.526-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Finally! Practical Maven Skinny Wars</title><content type="html">So, Maven 2.1 was finally released. I can't say I'm overly impressed with the pace, but considering it was driven almost exclusively by John Casey, it's a great achievement, nonetheless. Deterministic Build Lifecycle Planning (or DeBLiP, as we [well, just me] in the industry call it) is such a huge leap forward in build repeatability, it's hard to comment on at this stage. It's something best held to the clearer lens of retrospect.&lt;br /&gt;&lt;br /&gt;But enough about that. Something of immediate practical value: real skinny WARs. Until now the maven-war-plugin left you with two choices: no jars, or most jars. Sure, you could manually exclude all but a set, but it was a PITA and unpractical. Behold my surprise to find the newest WAR config addition to 2.1-beta-1: &lt;code&gt;packagingIncludes&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Use case:&lt;/span&gt;&lt;br /&gt;I want a &lt;span style="font-style:italic;"&gt;mostly&lt;/span&gt; skinny WAR that filters out all JARs except for struts.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Solution:&lt;/span&gt;&lt;br /&gt;&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;project&gt;&lt;br /&gt;  ...&lt;br /&gt;  &amp;lt;build&gt;&lt;br /&gt;    &amp;lt;plugins&gt;&lt;br /&gt;      &amp;lt;plugin&gt;&lt;br /&gt;        &amp;lt;artifactId&gt;maven-war-plugin&amp;lt;/artifactId&gt;&lt;br /&gt;        &amp;lt;version&gt;2.1-beta-1&amp;lt;/version&gt;&lt;br /&gt;        &amp;lt;configuration&gt;&lt;br /&gt;          &amp;lt;packagingIncludes&gt;WEB-INF/lib/struts*.jar&amp;lt;/packagingIncludes&gt;&lt;br /&gt;          &amp;lt;archive&gt;&lt;br /&gt;            &amp;lt;manifest&gt;&lt;br /&gt;              &amp;lt;addClasspath&gt;true&amp;lt;/addClasspath&gt;&lt;br /&gt;              &amp;lt;classpathPrefix&gt;lib/&amp;lt;/classpathPrefix&gt;&lt;br /&gt;            &amp;lt;/manifest&gt;&lt;br /&gt;          &amp;lt;/archive&gt;&lt;br /&gt;        &amp;lt;/configuration&gt;&lt;br /&gt;      &amp;lt;/plugin&gt;&lt;br /&gt;    &amp;lt;/plugins&gt;&lt;br /&gt;  &amp;lt;/build&gt;&lt;br /&gt;  ...&lt;br /&gt;&amp;lt;/project&gt;&lt;/pre&gt;Thanks WAR plugin guys! Now just fix the &lt;a href="http://www.coderoshi.com/2009/03/maven-hack-simple-skinny-wars.html"&gt;EAR problem&lt;/a&gt; and I'll be set.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-2843887517871629715?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=XbLGPTTDffA:UvZqFqF-M5w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=XbLGPTTDffA:UvZqFqF-M5w:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=XbLGPTTDffA:UvZqFqF-M5w:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=XbLGPTTDffA:UvZqFqF-M5w:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=XbLGPTTDffA:UvZqFqF-M5w:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=XbLGPTTDffA:UvZqFqF-M5w:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=2843887517871629715" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2843887517871629715?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2843887517871629715?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/XbLGPTTDffA/finally-practical-maven-skinny-wars.html" title="Finally! Practical Maven Skinny Wars" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/03/finally-practical-maven-skinny-wars.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcCRnsyfyp7ImA9WxVVGUU.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-3947906062209358342</id><published>2009-03-13T12:05:00.000-05:00</published><updated>2009-03-13T18:14:27.597-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-13T18:14:27.597-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 49</title><content type="html">The elite rests entirely, his mind is balanced and at ease.&lt;br /&gt;He is not attached to methods or language, but he knows them well anyway.&lt;br /&gt;&lt;br /&gt;A pebble cannot halt a stream, but can block a dam ten thousand times larger.&lt;br /&gt;So too does the elite know, that though a language may have no use to the masses,&lt;br /&gt;There are times where it simply outshines the most elaborate of methods.&lt;br /&gt;&lt;br /&gt;The wise respect the direction of the elite,&lt;br /&gt;The wise knows he is not blind to the world.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-3947906062209358342?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=LfXeNpZAIK0:44nyS1fDDWU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=LfXeNpZAIK0:44nyS1fDDWU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=LfXeNpZAIK0:44nyS1fDDWU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=LfXeNpZAIK0:44nyS1fDDWU:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=LfXeNpZAIK0:44nyS1fDDWU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=LfXeNpZAIK0:44nyS1fDDWU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=3947906062209358342" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/3947906062209358342?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/3947906062209358342?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/LfXeNpZAIK0/code-te-ching-verse-49.html" title="Code Te Ching - Verse 49" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/03/code-te-ching-verse-49.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMMRXo-eCp7ImA9WxVWGUs.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-6330853116318078588</id><published>2009-03-01T22:04:00.005-06:00</published><updated>2009-03-01T22:18:04.450-06:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-01T22:18:04.450-06:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Maven Hack: Simple Skinny WARs</title><content type="html">Reading Maven's skinny-war documentation, it might turn you off to the idea. A skinny war is where your WAR does not contain any jar files, but instead get's packaged within an EAR than instead contains the jars. So rather than a zipped up EAR that looks like this:&lt;pre&gt;myear-1.0.ear&lt;br /&gt;\__mywar-1.0.war&lt;br /&gt;     \__WEB-INF/lib/&lt;br /&gt;          |__ log4j-1.2.8.jar&lt;br /&gt;          \__ commons-logging-1.1.jar&lt;/pre&gt;It will instead look like this:&lt;pre&gt;myear-1.0.ear&lt;br /&gt;|__mywar-1.0.war&lt;br /&gt;\__lib/&lt;br /&gt;     |__ log4j-1.2.8.jar&lt;br /&gt;     \__ commons-logging-1.1.jar&lt;/pre&gt;By default, the maven-ear-plugin assumes that any WAR dependencies already have the required jars packaged into it (in other words, it assumes a "fat" war). Because of this assumption, the EAR plugin will not package the WAR's transitive dependencies (why should it? If it did, you'd have duplicate dependencies, and the EAR would be twice the size).&lt;br /&gt;&lt;br /&gt;The problem is, because of this assumption, when you instead provide it with a "skinny" war (one without JARs), the EAR plugin cannot know that the WAR it is given doesn't have those dependencies already. If you give the EAR as skinny WAR, the package will look like this:&lt;pre&gt;myear-1.0.ear&lt;br /&gt;\__mywar-1.0.war&lt;/pre&gt;Hardly what we wanted! The fix according to the documentation is to duplicate the WAR's dependencies in the EAR, so the EAR can download and install them. Note that the maven-ear-plugin only ignores WAR transitive dependencies, not JARs or EJBs. But duplicating all WAR dependencies isn't only a lot of typing, it breaks encapsulation of the WAR projects. What to do!? (NOTE: "breaks encapsulation" means, if your skinny WAR project depends on log4j:1.2.8 and commons-logging:1.1, then your EAR project will need to also add the dependencies log4j:1.2.8 and commons-logging:1.1. If you upgrade your WAR to use log4j:1.2.12, then you must make the change in your EAR too. Fail.)&lt;br /&gt;&lt;br /&gt;Luckily, we can fix this egregious conundrum.&lt;br /&gt;&lt;br /&gt;The maven-ear-plugin will package up transitive dependencies of all projects except for WARs (and possibly EAR - I've never really tried it). This means, we can add the WAR project's POM as it's own dependency.&lt;br /&gt;&lt;br /&gt;So, your EAR dependency list will now contain two dependencies for each WAR &lt;span style="font-style:italic;"&gt;only&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;mywar:1.0:war and mywar:1.0:pom. Although the EAR won't package up your skinny WAR's dependencies, it _will_ package the POM's dependencies - which just so happen to be exactly the same as the WAR's! Now your EAR POM will look something like this.&lt;pre&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;com.myorg&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;mywar&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;type&amp;gt;war&amp;lt;/type&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;com.myorg&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;mywar&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;/pre&gt;Yatta!&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-6330853116318078588?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GdVIMWF17MU:A51H3qJOvrM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GdVIMWF17MU:A51H3qJOvrM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GdVIMWF17MU:A51H3qJOvrM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GdVIMWF17MU:A51H3qJOvrM:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=GdVIMWF17MU:A51H3qJOvrM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=GdVIMWF17MU:A51H3qJOvrM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=6330853116318078588" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6330853116318078588?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6330853116318078588?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/GdVIMWF17MU/maven-hack-simple-skinny-wars.html" title="Maven Hack: Simple Skinny WARs" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/03/maven-hack-simple-skinny-wars.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UEQXo9cCp7ImA9WxVRE0Q.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-867768119907008167</id><published>2009-01-19T15:05:00.001-06:00</published><updated>2009-01-19T15:06:40.468-06:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-19T15:06:40.468-06:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="organ grinder" /><category scheme="http://www.blogger.com/atom/ns#" term="tips" /><title>The Organ Grinder: Cubicals</title><content type="html">The Cube is a Lie. Cubicles are amongst the most insidious creations ever, erm, &lt;span style="font-style:italic;"&gt;created&lt;/span&gt; - and have gotten worse.&lt;br /&gt;&lt;br /&gt;A short history of the cube:&lt;br /&gt;&amp;nbsp;&amp;nbsp;Working in the open&lt;br /&gt;&amp;nbsp;&amp;nbsp;Working inside&lt;br /&gt;&amp;nbsp;&amp;nbsp;Working in an office&lt;br /&gt;&amp;nbsp;&amp;nbsp;Office shrinks&lt;br /&gt;&amp;nbsp;&amp;nbsp;Invent cubicles, put people in boxes&lt;br /&gt;&amp;nbsp;&amp;nbsp;Shrink them&lt;br /&gt;&amp;nbsp;&amp;nbsp;Start removing walls&lt;br /&gt;&lt;br /&gt;The most uncomfortable layout is the cubical where 1) your boss can see your back and 2) you see a wall. He can stand behind you, watch you work, and you have no idea he's even there. This is fucking retarded. Sure, I check my email 4 times a day - and I swear to Jesus - that every time I do so, my boss shows up... it makes me nervous. And a nervous coder is not a good coder.&lt;br /&gt;&lt;br /&gt;So, this common problem has a common solution: mirrors.&lt;br /&gt;&lt;br /&gt;The best is a small mirror. I'm a fan of &lt;a href="http://www.thinkgeek.com/computing/accessories/2940/"&gt;CHIMP&lt;/a&gt;. It's kind of obvious, but most bosses won't catch on, and if they do, won't say much about it. It's kind of a dick move to tell your employees "Take that mirror down, because I want to keep sneaking up on you." But what if you can't have a mirror?&lt;br /&gt;&lt;br /&gt;Here's what you do (RE: what I did): CDs. Store-bought CDs work best, since they tend to be more mirror-ish - burnable CD's tend to have a colored hugh. BluRay discs are actually glossier, but that's a lot of cash hanging on your wall.&lt;br /&gt;&lt;br /&gt;Anyway, there's not a point to this post other than, it's always good to know when people are standing behind you.&lt;br /&gt;&lt;br /&gt;Ahh shit! Caught blogging... alt-tab. AAAHHHH.... THAT WAS CAPS LOCK! Tab.... taaaabbb!!!!&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-867768119907008167?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=ST6wkGTnZqw:xISsslwqY8s:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=ST6wkGTnZqw:xISsslwqY8s:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=ST6wkGTnZqw:xISsslwqY8s:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=ST6wkGTnZqw:xISsslwqY8s:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=ST6wkGTnZqw:xISsslwqY8s:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=ST6wkGTnZqw:xISsslwqY8s:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=867768119907008167" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/867768119907008167?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/867768119907008167?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/ST6wkGTnZqw/organ-grinder-cubicals.html" title="The Organ Grinder: Cubicals" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/01/organ-grinder-cubicals.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MAQ3w6fCp7ImA9WxVTGUw.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-976838499947352916</id><published>2009-01-02T12:04:00.000-06:00</published><updated>2009-01-02T12:04:02.214-06:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-02T12:04:02.214-06:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 48</title><content type="html">The lamer desires to be the hacker.&lt;br /&gt;The hacker desires to be the electrician.&lt;br /&gt;The electrician desires to be the mathematician.&lt;br /&gt;The mathematician desires to be pure.&lt;br /&gt;&lt;br /&gt;The pure is free from desire.&lt;br /&gt;&lt;br /&gt;Never be clever in two ways where one will do.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-976838499947352916?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=NwYRsO1bUqc:yFWXgfgE9EI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=NwYRsO1bUqc:yFWXgfgE9EI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=NwYRsO1bUqc:yFWXgfgE9EI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=NwYRsO1bUqc:yFWXgfgE9EI:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=NwYRsO1bUqc:yFWXgfgE9EI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=NwYRsO1bUqc:yFWXgfgE9EI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=976838499947352916" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/976838499947352916?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/976838499947352916?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/NwYRsO1bUqc/code-te-ching-verse-48.html" title="Code Te Ching - Verse 48" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2009/01/code-te-ching-verse-48.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04CQXk5fip7ImA9WxRaEUU.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-5896276268969500214</id><published>2008-12-13T11:33:00.003-06:00</published><updated>2008-12-13T11:39:20.726-06:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-13T11:39:20.726-06:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sorry" /><title>Long Time, NP</title><content type="html">The title has nothing to do with this post, let me get that straight. It just sounded funny, as all things do after 19 hour days. No, that isn't a typo.&lt;br /&gt;&lt;br /&gt;Some folks have asked where I've been, and the answer is, blogging on &lt;a href="http://blog.vontoo.com/blog/vontoo-tip-and-ring/"&gt;Vontoo&lt;/a&gt;'s corporate blog. Can't I do both? Well, yes, but I'd rather have one good quality than two half quality posts. Like all good bloggers, my upcoming new years resolution is to post more. We'll see how NP-hard that is to do... lord, I need some sleep.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-5896276268969500214?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=zXi07Fyt83A:Z0-XfeciuWg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=zXi07Fyt83A:Z0-XfeciuWg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=zXi07Fyt83A:Z0-XfeciuWg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=zXi07Fyt83A:Z0-XfeciuWg:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=zXi07Fyt83A:Z0-XfeciuWg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=zXi07Fyt83A:Z0-XfeciuWg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=5896276268969500214" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/5896276268969500214?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/5896276268969500214?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/zXi07Fyt83A/long-time-np.html" title="Long Time, NP" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/12/long-time-np.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MERn8-eCp7ImA9WxRREU8.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-6386923940062049004</id><published>2008-09-08T15:24:00.014-05:00</published><updated>2008-09-22T17:10:07.150-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-22T17:10:07.150-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="meh" /><title>Annotation Hell</title><content type="html">XML Hell, the phenomenon that killed struts, and fear of which launch Seam. Annotations were the savior, so the prophets proclaimed. But have we gone too far? You decide.&lt;pre name="code" class="java:nocontrols"&gt;  /**&lt;br /&gt;   * @return related details for this person&lt;br /&gt;   */&lt;br /&gt;  @ManyToOne( fetch = FetchType.EAGER, cascade = &lt;br /&gt;    { javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.MERGE } )&lt;br /&gt;  @JoinColumn( name = "PERSON_DETAIL", nullable = false )&lt;br /&gt;  @Where( clause = "ACTV_ID = 'T'" )&lt;br /&gt;  @Cascade( { CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE } )&lt;br /&gt;  @Cache( usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE )&lt;br /&gt;  @EqualsMember&lt;br /&gt;  @NotNull&lt;br /&gt;  @Valid&lt;br /&gt;  @Message( "${msg.invalid.person.detail}" )&lt;br /&gt;  @XmlElement( name = "person-detail", required = true )&lt;br /&gt;  public PersonDetail getPersonDetail()&lt;br /&gt;  {&lt;br /&gt;      return personDetail;&lt;br /&gt;  }&lt;/pre&gt;This is a method of a Hibernate ORM object which can be serialized via JAXB. Also utilizes Hibernate validation, an i18n Message manager and EqualsMember, which means this element is a member of the set of fields denoting object equality. Somehow, I'm starting to miss XML...&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-6386923940062049004?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=3VyQ7VIB8_c:An3qGHrNihA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=3VyQ7VIB8_c:An3qGHrNihA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=3VyQ7VIB8_c:An3qGHrNihA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=3VyQ7VIB8_c:An3qGHrNihA:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=3VyQ7VIB8_c:An3qGHrNihA:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=3VyQ7VIB8_c:An3qGHrNihA:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=6386923940062049004" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6386923940062049004?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6386923940062049004?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/3VyQ7VIB8_c/annotation-hell.html" title="Annotation Hell" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/09/annotation-hell.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEIFSX8_fCp7ImA9WxRTFEs.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-827401326618224516</id><published>2008-09-03T12:20:00.001-05:00</published><updated>2008-09-03T12:21:58.144-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-03T12:21:58.144-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="organ grinder" /><category scheme="http://www.blogger.com/atom/ns#" term="tips" /><title>The Organ Grinder: Firewall Tunneling via EC2 Proxy</title><content type="html">&lt;span style="font-style:italic;"&gt;Teaching monkeys new tricks.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I've been around. Let's get that straight. Sure, I no longer work in a corporate environment yet still, on occasion, feel the ache of an old scar. The wounds of the corporate blade just cut too deep. This world is hell to anyone who doesn't thrive in such artificial environments. This is a new segment for a new year (well, fiscal year) - The Organ Grinder: Teaching (Code)Monkeys New Tricks. Perhaps not new, but useful for those who need to survive in this wilderness. Today's trick: getting around corporate firewalls via tunneling.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_E3_4LXYT1LI/R-lzeumQUXI/AAAAAAAAAMM/BXp2iEuNC9w/s1600-h/Organ_grinder_with_monkey.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_E3_4LXYT1LI/R-lzeumQUXI/AAAAAAAAAMM/BXp2iEuNC9w/s400/Organ_grinder_with_monkey.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5181799818008220018" /&gt;&lt;/a&gt;I understand why some corporations wish to limit internet access to the common folk: ensuring that the receptionist does his job instead of playing WoW, blocking an old-lady manager from downloading the new forward "cute-kittens.jpg.exe", stopping sales guys from surfing for porn all day. However, when you are a software developer, blocking your access to the wild, wild web is like keeping a goldfish in one of those colored baggies of water they give out at carnivals; sure the fish live - sort of - only to die a day later when you take it home. Get the analogy? The fish is your &lt;span style="font-style:italic;"&gt;soul&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) Set up EC2&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Firstly, you must have Java 1.5 or greater installed, and the JAVA_HOME env var set.&lt;br /&gt;in unix&lt;pre&gt;$ export JAVA_HOME=&amp;lt;PATH&amp;gt;&lt;/pre&gt;in windows&lt;pre&gt;C:\&amp;gt; set JAVA_HOME=&amp;lt;PATH&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://aws.amazon.com/ec2"&gt;Create an EC2 account&lt;/a&gt; on Amazon (all of these steps can be &lt;a href="http://docs.amazonwebservices.com/AmazonEC2/gsg/2007-01-03/"&gt;found in detail here&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Set up the &lt;a href="http://docs.amazonwebservices.com/AmazonEC2/gsg/2007-01-03/"&gt;account and grab your X.509 certs&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Download and unzip the &lt;a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=351&amp;categoryID=88"&gt;EC2 command-line tools&lt;/a&gt; - works for *nix, osx and windows (afaik).&lt;br /&gt;&lt;br /&gt;Set up your *nix/osx paths:&lt;pre&gt;$ export EC2_HOME=&amp;lt;path-to-tools&amp;gt;&lt;br /&gt;$ export PATH=$PATH:$EC2_HOME/bin&lt;br /&gt;$ export EC2_PRIVATE_KEY=~/.ec2/pk-HKZYKTAIG2ECMXYIBH3HXV4ZBZQ55CLO.pem &lt;br /&gt;$ export EC2_CERT=~/.ec2/cert-HKZYKTAIG2ECMXYIBH3HXV4ZBZQ55CLO.pem&lt;/pre&gt;Or, set up your windows paths:&lt;pre&gt;C:\&amp;gt; set EC2_HOME=&amp;lt;path-to-tools&amp;gt;&lt;br /&gt;C:\&amp;gt; set PATH=%PATH%;%EC2_HOME%\bin&lt;br /&gt;C:\&amp;gt; set EC2_PRIVATE_KEY=c:\ec2\pk-HKZYKTAIG2ECMXYIBH3HXV4ZBZQ55CLO.pem &lt;br /&gt;C:\&amp;gt; set EC2_CERT=c:\ec2\cert-HKZYKTAIG2ECMXYIBH3HXV4ZBZQ55CLO.pem&lt;/pre&gt;&lt;br /&gt;run:&lt;pre&gt;$ ec2-add-keypair my-proxy&lt;/pre&gt;&lt;br /&gt;This will output a key similar in structure to this:&lt;pre&gt;-----BEGIN RSA PRIVATE KEY-----&lt;br /&gt;MIIEoQIBAAKCAQBuLFg5ujHrtm1jnutSuoO8Xe56LlT+HM8v/xkaa39EstM3/aFxTHgElQiJLChp&lt;br /&gt;... blah blah blah, more data ...&lt;br /&gt;-----END RSA PRIVATE KEY----- &lt;/pre&gt;&lt;br /&gt;copy/paste everything between and including "-----BEGIN RSA PRIVATE KEY-----" to "-----END RSA PRIVATE KEY----- " into a file named:&lt;pre&gt;id_rsa-my-proxy&lt;/pre&gt;Then change the permissions of the file&lt;pre&gt;chmod 600 id_rsa-my-proxy&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2) EC2 Instance&lt;/b&gt;&lt;br /&gt;Use the the &lt;a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1065"&gt;Ubuntu 7.1&lt;/a&gt; ami.&lt;br /&gt;&lt;pre&gt;ec2-run-instances ami-b111f4d8 -k my-proxy&lt;/pre&gt;&lt;br /&gt;Which will output a line similar to the following:&lt;br /&gt;&lt;pre&gt;INSTANCE        i-10a64379   ami-b111f4d8     pending   my-proxy  0&lt;/pre&gt;&lt;br /&gt;It will take a few minutes for the instance to launch. To check on its status:&lt;br /&gt;&lt;pre&gt;ec2-describe-instances&lt;/pre&gt;&lt;br /&gt;When the instance is finally running, you will see the instance domain as something like the following:&lt;br /&gt;&lt;pre&gt;domU-12-34-31-00-00-05.usma1.compute.amazonaws.com&lt;/pre&gt;&lt;br /&gt;You can ssh in as you wold expect, using the domain. Root has no password.&lt;br /&gt;&lt;pre&gt;ssh -i id_rsa-my-proxy root@domU-12-34-31-00-00-05.usma1.compute.amazonaws.com &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3) SSH Proxy&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you can connect out through port 443, set up sshd to listen on it. Avoid port 80. Your network may filter encrypted data on that port. If not, you'll have to drop down to port 80 and pray.&lt;pre&gt;ssh -ND 1337 root@domU-12-34-31-00-00-05.usma1.compute.amazonaws.com&lt;/pre&gt;What this does is routes all localhost requests from port 1337 (or whatever port you want) to your EC2 ssh server (over port 22).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4) Important! Shutdown!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Amazon charges you $0.10 for every hour your instance is running. When you are done with your proxy, don't forget to shut it off! Ten cents isn't much money, but it adds up to approx. $72/mo if you leave it running all the time. Pretty expensive for a simple proxy! But in reality, you'll probably only need it a few times a week, costing you maybe $2 per month (20 hours). Use the instance id to terminate, not the domain (ec2-describe-instances if you have forgotten the id). Something like:&lt;br /&gt;&lt;pre&gt;ec2-terminate-instances i-10a64379&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Of course, if you get caught, don't blame me. Or do blame me, I don't really care.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-827401326618224516?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=UmF2KiHB8mY:7Wy1M8f0JdY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=UmF2KiHB8mY:7Wy1M8f0JdY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=UmF2KiHB8mY:7Wy1M8f0JdY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=UmF2KiHB8mY:7Wy1M8f0JdY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=UmF2KiHB8mY:7Wy1M8f0JdY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=UmF2KiHB8mY:7Wy1M8f0JdY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=827401326618224516" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/827401326618224516?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/827401326618224516?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/UmF2KiHB8mY/organ-grinder-firewall-tunneling-via.html" title="The Organ Grinder: Firewall Tunneling via EC2 Proxy" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_E3_4LXYT1LI/R-lzeumQUXI/AAAAAAAAAMM/BXp2iEuNC9w/s72-c/Organ_grinder_with_monkey.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/09/organ-grinder-firewall-tunneling-via.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIER34yeCp7ImA9WxRTE00.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-2458751346068626826</id><published>2008-09-01T12:02:00.000-05:00</published><updated>2008-09-01T15:21:46.090-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-01T15:21:46.090-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 47</title><content type="html">Old technology is old, but not useless.&lt;br /&gt;New technology is new, but does not devalue the old.&lt;br /&gt;&lt;br /&gt;The greatest code was work of the ancients.&lt;br /&gt;Subtle. Efficient. Genius.&lt;br /&gt;&lt;br /&gt;Like the days of old where the weakest would not survive,&lt;br /&gt;Come the days of today where many weak survive on ancient efforts.&lt;br /&gt;&lt;br /&gt;The weak rest blindly on their fathers' graves.&lt;br /&gt;Knowing nothing about what lies beneath the earth.&lt;br /&gt;The wise know what lies beneath, but do not dwell upon it.&lt;br /&gt;Because of this, the ghosts of the past will walk with them in times of need.&lt;br /&gt;&lt;br /&gt;He occupies his time:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Studying the ancient ways.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Understanding the future direction.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Cultivating his skill.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Remaining faithful to the past without resting upon it.&lt;br /&gt;From his gentle actions the world remains powered.&lt;br /&gt;&lt;br /&gt;In this way: the ancients are not lost.&lt;br /&gt;In this way: the elite is immortal.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-2458751346068626826?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CTbQtwHcO04:sRWwGlaOYCM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CTbQtwHcO04:sRWwGlaOYCM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=CTbQtwHcO04:sRWwGlaOYCM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CTbQtwHcO04:sRWwGlaOYCM:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=CTbQtwHcO04:sRWwGlaOYCM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=CTbQtwHcO04:sRWwGlaOYCM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=2458751346068626826" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2458751346068626826?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2458751346068626826?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/CTbQtwHcO04/code-te-ching-verse-47.html" title="Code Te Ching - Verse 47" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/02/code-te-ching-verse-47.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ABRn4zcCp7ImA9WxdbEEo.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-6149637268818031378</id><published>2008-08-05T18:50:00.072-05:00</published><updated>2008-08-06T21:22:37.088-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-06T21:22:37.088-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><title>Ruby on Rails on REST on Java</title><content type="html">(or &lt;span style="font-style:italic;"&gt;Rails can Scales!&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;In this 'very special issue' of &lt;a href="http://www.coderoshi.com"&gt;Coderoshi&lt;/a&gt;, I'll go over how to create a scalable REST service in Java using &lt;a href="http://cxf.apache.org/"&gt;CXF&lt;/a&gt; that can be consumed by Rails via &lt;a href="http://wiki.rubyonrails.org/rails/pages/ActiveResource"&gt;ActiveResource&lt;/a&gt;. I do this based on the following assumptions:&lt;br /&gt;&lt;br /&gt;* Rails sucks at scaling, but rocks at creating quick, slick presentations.&lt;br /&gt;* Java sucks at writing presentation code, but scales like a mofo.&lt;br /&gt;&lt;br /&gt;If you disagree, and really do believe Rails can scale with ActiveRecord's simple abstraction of an RDBMS, or that Java is awesome for presentation, best to stop reading now. But if you know enough about these technologies to muster a knowing nod, then hold onto your face - it's about to be rocked off.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;What's all this, then?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Let us begin with an overview: Ruby on Rails ActiveResource consuming Java REST via CXF defined by JAXB. Whaa??&lt;br /&gt;&lt;br /&gt;ActiveResource (not to be confused with ActiveRecord, Ruby's ORM) is a simple REST consumer. That's it. It wraps structured REST actions and managed data (xml or json) in active objects. Just like ActiveRecord, executing methods on an ARes object triggers backend actions or accessed data. For example:&lt;pre name="code" class="ruby:nocontrols"&gt;@customer = Customer.find( 1 )&lt;/pre&gt;will translate into executing some defined REST service to populate @customer, internally constructing a URL like "http://server/customers/1.xml", getting the xml returned:&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;customer&gt;&lt;br /&gt;  &amp;lt;id&gt;1&amp;lt;/id&gt;&lt;br /&gt;  &amp;lt;name&gt;Joe&amp;lt;/name&gt;&lt;br /&gt;&amp;lt;/customer&gt;&lt;/pre&gt;And making the value accessible by the Ruby program&lt;pre name="code" class="ruby:nocontrols"&gt;#outputs: Joe&lt;br /&gt;puts @customer.name&lt;/pre&gt;It's probably the drop-dead easiest method of consuming a remote service - assuming the remote service conforms to ActiveResource's standards (there's always a catch).&lt;br /&gt;&lt;br /&gt;Happily, this isn't too difficult to do with the magic ingredient of the server-side: CXF. CXF is the evolution of Dan Diephouse's &lt;a href="http://xfire.codehaus.org/"&gt;XFire&lt;/a&gt; (merged with IONA's &lt;a href="http://celtix.objectweb.org/"&gt;Celtix&lt;/a&gt;). It's primarily considered to be a simple annotated WS-* impl, but also has some mean REST hooks. We'll be using the &lt;a href="http://jcp.org/en/jsr/detail?id=311"&gt;JSR 311&lt;/a&gt; implementation rather than CXF's custom annotations.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Getting Started&lt;/b&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Although I suggest you follow along, if you just plain hate writing code, then download both &lt;a href="http://s3.amazonaws.com/coderoshi/roroj.zip"&gt;projects here&lt;/a&gt;. The Ruby on Rails project is called "ror" and the REST on Java project is "roj".&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I hear it time and time again: Ruby on Rails can't scale. This is, of course, an absurd statement. If your software &lt;a href="http://teddziuba.com/2008/04/im-going-to-scale-my-foot-up-y.html"&gt;gets to the point&lt;/a&gt; that this is really a problem, well, then take your millions and rearchitect it. You can dry your eyes on hundred dollar bills while your Java counterparts are still fighting to get GWT to work. Most projects fail anyway - and I don't have enough lives to &lt;a href="http://www.coderoshi.com/2007/10/sustainable-sushmainable.html"&gt;waste on them&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;People complain that Java sucks at presentation, and for good reason. Sure, you can write webservices that scale out to thousands of servers, but try and write JSF and you have two choices: drink the koolaid, or pray for death.&lt;br /&gt;&lt;br /&gt;With those points in mind, I'm going to go over how to create a scalable REST service in Java using CXF that can be consumed by Rails new ActiveResource.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Ingredients&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Like a good cook, I like to gather my ingredients up front. Here is what we'll need to bake our little cake (I'm bolding the versions in case you can't &lt;b&gt;read&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;* Ruby &gt;= &lt;b&gt;1.8.6&lt;/b&gt;&lt;br /&gt;* Ruby on Rails &gt;= &lt;b&gt;2.1&lt;/b&gt; &lt;span style="font-style:italic;"&gt;(2.0 is required for ActiveResource, but 2.1 has tons of bug fixes)&lt;/span&gt;&lt;br /&gt;* Java &gt;= &lt;b&gt;6&lt;/b&gt; &lt;span style="font-style:italic;"&gt;(or v5 - I haven't tried it)&lt;/span&gt;&lt;br /&gt;* Maven &gt;= &lt;b&gt;2.0.9&lt;/b&gt; &lt;span style="font-style:italic;"&gt;(you need the new graph resolution stuff, or dep overload may not work)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I presume you understand these technologies, at least in a cursory way. If not, there are plenty of good guides to get you started. Go ahead. I'll wait...&lt;br /&gt;&lt;br /&gt;Oh, you're back? Then let's begin.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;REST on Java&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Although this is about scalability, the focus here isn't about handling multiple concurrent nodes. If you need a super-scalable data-management system check out &lt;a href="http://hadoop.apache.org/hbase/"&gt;HBase&lt;/a&gt;. If you want to cluster thousands of JVMs, use &lt;a href="http://www.terracotta.org/"&gt;Terracotta&lt;/a&gt;. Wire it all up with &lt;a href="http://www.springframework.org/"&gt;Spring&lt;/a&gt;, and deploy using &lt;a href="http://maven.apache.org/"&gt;Maven&lt;/a&gt;. There's your scalability, buddy.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's begin with the Java service side. In a nutshell, we're going to use Maven to manage our dependencies (naturally), Spring to wire our components (of course), JAXB to define our XML (what else?), CXF as our REST service and Jetty as our web server. The easy way to start (besides &lt;a href="http://s3.amazonaws.com/coderoshi/roroj.zip"&gt;downloading&lt;/a&gt; the finished product) is use a Maven archetype to generate a basic WAR.&lt;pre name="code" class="text:nocontrols"&gt;mvn archetype:generate&lt;/pre&gt;Choose "maven-archetype-webapp" (mine was #18), make up a groupId, artifactId and version, and "packaging: war". Next, replace the generated pom with this one:&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;project&amp;gt;&lt;br /&gt;  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;com.coderoshi&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;roj&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;build&amp;gt;&lt;br /&gt;    &amp;lt;plugins&amp;gt;&lt;br /&gt;      &amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;source&amp;gt;1.6&amp;lt;/source&amp;gt;&lt;br /&gt;          &amp;lt;target&amp;gt;1.6&amp;lt;/target&amp;gt;&lt;br /&gt;        &amp;lt;/configuration&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;      &amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;6.1.11&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;contextPath&amp;gt;/&amp;lt;/contextPath&amp;gt;&lt;br /&gt;        &amp;lt;/configuration&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;    &amp;lt;/plugins&amp;gt;&lt;br /&gt;  &amp;lt;/build&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependencies&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.apache.cxf&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;cxf-rt-frontend-jaxrs&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.apache.cxf&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;cxf-rt-transports-http-jetty&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.apache.cxf&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;cxf-rt-bindings-http&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;spring-core&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.5.5&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;spring-context&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.5.5&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;  &amp;lt;/dependencies&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;/pre&gt;&lt;br /&gt;This pom does two important things: gives us the spring and CXF dependencies that we need, and configures the plugins that we require. Namely, that we assume JDK 1.6, and that we want to use the Jetty plugin to run our war (eventually). &lt;br /&gt;&lt;br /&gt;Next we'll set up our Spring's context file. This describes to spring how much we love CXF and really want to use it. Spring replies by constructing our components correctly for us. Isn't that nice? Create a new file under "src/main/webapp/WEB-INF/" named "beans.xml", and paste the following:&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt;  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;  xmlns:jaxrs="http://cxf.apache.org/jaxrs"&lt;br /&gt;  xmlns:cxf="http://cxf.apache.org/core"&lt;br /&gt;  xsi:schemaLocation="&lt;br /&gt;  http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd&lt;br /&gt;  http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd&lt;br /&gt;  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;import resource="classpath:META-INF/cxf/cxf.xml" /&amp;gt;&lt;br /&gt;  &amp;lt;import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" /&amp;gt;&lt;br /&gt;  &amp;lt;import resource="classpath:META-INF/cxf/cxf-servlet.xml" /&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;jaxrs:server id="campaignREST" address="/"&amp;gt;&lt;br /&gt;    &amp;lt;jaxrs:serviceBeans&amp;gt;&lt;br /&gt;      &amp;lt;bean class="com.coderoshi.service.CampaignService" /&amp;gt;&lt;br /&gt;    &amp;lt;/jaxrs:serviceBeans&amp;gt;&lt;br /&gt;    &amp;lt;jaxrs:entityProviders&amp;gt;&lt;br /&gt;      &amp;lt;bean class="com.coderoshi.service.JAXBCollectionProvider" /&amp;gt;&lt;br /&gt;    &amp;lt;/jaxrs:entityProviders&amp;gt;&lt;br /&gt;    &amp;lt;jaxrs:features&amp;gt;&amp;lt;cxf:logging /&amp;gt;&amp;lt;/jaxrs:features&amp;gt;&lt;br /&gt;  &amp;lt;/jaxrs:server&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;/pre&gt;Cryptic? Well, no, not if you already know Spring. If not, here's the skinny: We are importing some built-in CXF Spring configurations (thanks CXF!) and using them to define our own REST server (we haven't written it yet) named "campaignREST". The "jaxrs:serviceBeans" element contains a list of services we define, in this case, one we are about to write called "com.coderoshi.service.CampaignService". The block between "jaxrs:features" turns on logging, so we can view incomming/outgoing messages and errors. Finally, the "jaxrs:entityProviders" contains a bean we are about to write to convert a JAXB Collection into an XML form consumable by Rails.&lt;br /&gt;&lt;br /&gt;Finally, we need to tell the web server (Jetty) that it needs to load up Spring and also the CXF servlet (which handles requests and passes them off to our REST service).&lt;br /&gt;&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;?xml version="1.0" encoding="ISO-8859-1"?&amp;gt;&lt;br /&gt;&amp;lt;web-app&amp;gt;&lt;br /&gt;  &amp;lt;context-param&amp;gt;&lt;br /&gt;    &amp;lt;param-name&amp;gt;contextConfigLocation&amp;lt;/param-name&amp;gt;&lt;br /&gt;    &amp;lt;param-value&amp;gt;WEB-INF/beans.xml&amp;lt;/param-value&amp;gt;&lt;br /&gt;  &amp;lt;/context-param&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;listener&amp;gt;&lt;br /&gt;    &amp;lt;listener-class&amp;gt;org.springframework.web.context.ContextLoaderListener&amp;lt;/listener-class&amp;gt;&lt;br /&gt;  &amp;lt;/listener&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;servlet&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;CXFServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;servlet-class&amp;gt;org.apache.cxf.transport.servlet.CXFServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;    &amp;lt;load-on-startup&amp;gt;1&amp;lt;/load-on-startup&amp;gt;&lt;br /&gt;  &amp;lt;/servlet&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;    &amp;lt;servlet-name&amp;gt;CXFServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;    &amp;lt;url-pattern&amp;gt;/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;&amp;lt;/web-app&amp;gt;&lt;/pre&gt;Whew! With configuration out of the way, let's get to the fun part... coding!&lt;br /&gt;&lt;br /&gt;First, we'll start with the JAXB bean we eventually want to serialize. Just to keep it simple, we'll make public fields and forgo getters and setters. We're using Maven to build, so you'll need to create the package under src/main/java (where "java" is a sibling of the "webapp" directory.&lt;pre name="code" class="java:nocontrols"&gt;package com.coderoshi.service;&lt;br /&gt;&lt;br /&gt;@javax.xml.bind.annotation.XmlRootElement( name = "campaign" )&lt;br /&gt;public class Campaign&lt;br /&gt;{&lt;br /&gt;  public long id;&lt;br /&gt;  public String name;&lt;br /&gt;  public long budget;&lt;br /&gt;&lt;br /&gt;  public Campaign() {}&lt;br /&gt;  public Campaign( long i, String n, long b ) { id=i; name=n; budget=b; }&lt;br /&gt;}&lt;/pre&gt;Ah, the magic of JAXB! That's all you need to create an XML-marshallable object. Eat it, Rails (and you're about to...). Now we get to make our REST server. Create a new class named "com.coderoshi.service.CampaignService". Next we need to annotate the class with two annotations:&lt;pre name="code" class="java:nocontrols"&gt;@javax.ws.rs.Path( "/campaignService" )&lt;br /&gt;@javax.ws.rs.ProduceMime( "application/xml" )&lt;/pre&gt;These tell the server that this is a service available at the HTTP path "/campaignService", and that by default, calling this service will return XML (it sets the HTTP header's mime-type on response).&lt;br /&gt;&lt;br /&gt;Let's write our standard CRUD operations (which Rails' ActiveResource will presume exist by default).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;It's important to note that we're faking data and operations. If you downloaded the zip provided, then you'll see I made the service database-backed (HSQLDB), but that's just for fun, and really is beyond the code here.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Create&lt;/b&gt; (addCampaign)&lt;pre name="code" class="java:nocontrols"&gt;@Path( "/campaignService" )&lt;br /&gt;@ProduceMime( "application/xml" )&lt;br /&gt;public class CampaignService&lt;br /&gt;{&lt;br /&gt;  // ... define actions ...&lt;br /&gt;  @POST&lt;br /&gt;  @Path("/campaigns.xml")&lt;br /&gt;  @ConsumeMime( "application/xml" )&lt;br /&gt;  public Response addCampaign( Campaign c )&lt;br /&gt;  {&lt;br /&gt;    System.out.println( "Adding Campaign: " + c.name );&lt;br /&gt;    c.id = 99;&lt;br /&gt;    // Adding header Location=/{id} is required so Rails can extract an id&lt;br /&gt;    return Response.status( 200 ).header( "Location", "/" + c.id ).build();&lt;br /&gt;  }&lt;/pre&gt;Rails presumes all adds are "POST" operations. It also assumes the path is the plural name of the resource ("campaigns") and post-fixed by the produced mime-type. In our case XML. On a successful update ActiveResource will assume a 200 (successful) response. We can play with error handling at a later time. Since we are expecting data is in XML form, we explicitly enforce it to be so with @ConsumeMime. Note that all of these method annotations are in the JSR 311 as the "javax.ws.rs.*" package.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Read&lt;/b&gt; (getCampaign, getCampaigns)&lt;pre name="code" class="java:nocontrols"&gt;  @GET&lt;br /&gt;  @Path("/campaigns.xml")&lt;br /&gt;  public List&amp;lt;Campaign&amp;gt; getCampaigns()&lt;br /&gt;  {&lt;br /&gt;    return Arrays.asList( new Campaign(1, "campaign #1", 500), new Campaign(2, "campaign #2", 600) );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  @GET&lt;br /&gt;  @Path("/campaigns/{id}.xml")&lt;br /&gt;  public Campaign getCampaign( @PathParam( "id" ) long id )&lt;br /&gt;  {&lt;br /&gt;    return new Campaign(id, "campaign #"+id, 900);&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;In Rails REST reads are GET operations. It also presumes the pathnames begin with "campaigns", but notice this time the singular "getCampaign" has a param called "id". In CXF/JSR311 you can define that you expect a path to contain a variable - in this case, we expect an ID. However, since Java doesn't keep track of parameter names after compilation, the best thing you can do is name the parameter you want CXF to populate for you. Most basic types are supported, like String or int - in our case long. So when someone "GETs" our url "http://localhost:8080/campaignService/campaigns/35.xml", the "getCampaign" method is called and the value "35" is passed in as parameter "id".&lt;br /&gt;&lt;br /&gt;Also notice that our getCampaigns path is the same as addCampaign. How can this be? Because although the URLs are the same, different operations take place on them (POST versus GET). This is how CXF differentiates between the operations and knows when to expect XML that conforms to our Campaign JAXB, or no arguments at all.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update&lt;/b&gt; (updateCampaign)&lt;pre name="code" class="java:nocontrols"&gt;  @PUT&lt;br /&gt;  @Path("/campaigns/{id}.xml")&lt;br /&gt;  @ConsumeMime( "application/xml" )&lt;br /&gt;  public Response updateCampaign( @PathParam( "id" ) long id, Campaign c )&lt;br /&gt;  {&lt;br /&gt;    System.out.println( "Updating Campaign: " + id );&lt;br /&gt;    return Response.status( 200 ).build();&lt;br /&gt;  }&lt;/pre&gt;Here we PUT Campaign XML to the server, along with the ID. Although technically we shouldn't require the ID (since it should already be in the Campaign data), Rails builds URLs that way, so we need to also. Don't ask me why - I don't know.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Delete&lt;/b&gt; (deleteCampaign)&lt;pre name="code" class="java:nocontrols"&gt;  @DELETE&lt;br /&gt;  @Path("/campaigns/{id}.xml")&lt;br /&gt;  public Response deleteCampaign( @PathParam( "id" ) long id )&lt;br /&gt;  {&lt;br /&gt;    System.out.println( "Deleting Campaign: " + id );&lt;br /&gt;    return Response.status( 200 ).build();&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Finally we handle delete. To delete an object you merely need to pass in the ID as a DELETE operation. Here we don't really do anything, just print to the console so you know it worked.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Yeah yeah, I know REST is not necessarily CRUD, but for our purposes the mapping is close enough, and we're going to run with it.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Yay! We wrote our Java REST server - but we have a problem. Our "getCampaigns" method returns a "List&lt;Campaign&gt;" (list of JAXB Campaigns) - but CXF doesn't know how to handle a list like this. And it certainly wouldn't handle a JAXB list in a way friendly to ActiveResource, which has it's own expectations.&lt;br /&gt;&lt;br /&gt;Luckily for us JSR-311/CXF allows you to create your own custom providers. Providers are responsible for translating to and from one kind of data (an JAXB object, for example) from and to another (an XML stream). The one we're about to write has already been defined in the Spring context above under "jaxrs:entityProvider". Without too much ado, here it is in it's entirety:&lt;pre name="code" class="java:nocontrols"&gt;package com.coderoshi.service;&lt;br /&gt;&lt;br /&gt;import java.io.*;&lt;br /&gt;import java.util.*;&lt;br /&gt;import javax.ws.rs.*;&lt;br /&gt;import javax.ws.rs.core.*;&lt;br /&gt;import javax.ws.rs.ext.*;&lt;br /&gt;import javax.xml.bind.*;&lt;br /&gt;&lt;br /&gt;import org.apache.cxf.binding.http.strategy.EnglishInflector;&lt;br /&gt;&lt;br /&gt;@Provider&lt;br /&gt;@ProduceMime( "application/xml" )&lt;br /&gt;public class JAXBCollectionProvider&lt;br /&gt;  implements MessageBodyWriter&amp;lt;Collection&amp;lt;?&amp;gt;&amp;gt;&lt;br /&gt;{&lt;br /&gt;  public boolean isWriteable( Class&amp;lt;?&amp;gt; type )&lt;br /&gt;  {&lt;br /&gt;    return Collection.class.isAssignableFrom( type );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public long getSize( Collection&amp;lt;?&amp;gt; l )&lt;br /&gt;  {&lt;br /&gt;    return l == null ? 0 : l.size();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void writeTo( Collection&amp;lt;?&amp;gt; li, MediaType mt, MultivaluedMap&amp;lt;String, Object&amp;gt; headers, OutputStream os )&lt;br /&gt;    throws IOException&lt;br /&gt;  {&lt;br /&gt;    try&lt;br /&gt;    {&lt;br /&gt;      if( li == null || li.isEmpty() )&lt;br /&gt;      {&lt;br /&gt;        os.write( "&amp;lt;nil type=\"array\" /&amp;gt;".getBytes() );&lt;br /&gt;        return;&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      Marshaller marshaller = null;&lt;br /&gt;      String envelopeName = null;&lt;br /&gt;      for ( Object object : li )&lt;br /&gt;      {&lt;br /&gt;        if ( object != null )&lt;br /&gt;        {&lt;br /&gt;          if ( marshaller == null )&lt;br /&gt;          {&lt;br /&gt;            JAXBContext context = JAXBContext.newInstance( object.getClass() );&lt;br /&gt;            marshaller = context.createMarshaller();&lt;br /&gt;            marshaller.setProperty( Marshaller.JAXB_FRAGMENT, true );&lt;br /&gt;&lt;br /&gt;            envelopeName = context.createJAXBIntrospector().getElementName( object ).getLocalPart();&lt;br /&gt;&lt;br /&gt;            // pluralize the collection&lt;br /&gt;            envelopeName = new EnglishInflector().pluralize( envelopeName );&lt;br /&gt;&lt;br /&gt;            os.write( "&amp;lt;".getBytes() );&lt;br /&gt;            os.write( envelopeName.getBytes() );&lt;br /&gt;            os.write( " type=\"array\"&amp;gt;".getBytes() );&lt;br /&gt;          }&lt;br /&gt;          marshaller.marshal( object, os );&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;      if ( envelopeName != null )&lt;br /&gt;      {&lt;br /&gt;        os.write( "&amp;lt;/".getBytes() );&lt;br /&gt;        os.write( envelopeName.getBytes() );&lt;br /&gt;        os.write( "&amp;gt;".getBytes() );&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    catch ( JAXBException e )&lt;br /&gt;    {&lt;br /&gt;      throw new IOException( "broke", e );&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Whew! That's some chunk of code, where to begin? Let's start at the top.&lt;br /&gt;&lt;br /&gt;Notice that we annotate our class with &lt;pre name="code" class="java:nocontrols"&gt;@Provider&lt;br /&gt;@ProduceMime( "application/xml" )&lt;/pre&gt;These tell the framework that this is an available provider implementation and that it produces XML. We implement "MessageBodyWriter", which contains hook methods for the REST container. "isWriteable" checks if outbound object classes can utilize this Provider. In our case, we can use anything that is a Collection (which, of course, our List&amp;lt;Campaign&amp;gt; most certainly is).&lt;br /&gt;&lt;br /&gt;The "writeTo" method does the actual work of marshaling any approved Collection object directly into the output stream. Here is where things get weird. First we ensure that the collection contains objects. If not, we just output "&amp;lt;nil type=\"array\" /&amp;gt;". This is a flag to Rails' ActiveResource that we would like to return an array, but we don't have any data to give it. We call the element "nil" since we don't have any objects, we can't exactly know what types they are.&lt;br /&gt;&lt;br /&gt;Assuming our collection contains value, we first wrap the collection within a pluralized version of the elements we envelope. For example, since our List contains two Campaign JAXB objects named "campaign", we wrap them in a root element named "campaigns". How can we know how to correctly pluralize words? We use a built-in CXF class called "EnglishInflector". We then define the type as "array" for the benefit of the ActiveResource consumer.&lt;br /&gt;&lt;br /&gt;That's it! If it seems like a lot, don't worry. When it's all together and chugging along, it's amazing how little code there really is (considering we're dealing with Java here).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Running the REST Service&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Assuming no typos or environment problems, you can now run the server using the Maven Jetty plugin. Don't worry about building it, the plugin will ensure we are built before launching. So go to the project base dir, and type:&lt;pre name="code" class="text:nocontrols"&gt;mvn clean jetty:run-war&lt;/pre&gt;Navigate your browser to "http://localhost:8080/campaignService/campaigns.xml", and you should be treated to a list of your campaigns:&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;campaigns type="array"&gt;&lt;br /&gt;  &amp;lt;campaign&gt;&lt;br /&gt;    &amp;lt;id&gt;1&amp;lt;/id&gt;&lt;br /&gt;    &amp;lt;name&gt;campaign #1&amp;lt;/name&gt;&lt;br /&gt;    &amp;lt;budget&gt;500&amp;lt;/budget&gt;&lt;br /&gt;  &amp;lt;/campaign&gt;&lt;br /&gt;  &amp;lt;campaign&gt;&lt;br /&gt;    &amp;lt;id&gt;2&amp;lt;/id&gt;&lt;br /&gt;    &amp;lt;name&gt;campaign #2&amp;lt;/name&gt;&lt;br /&gt;    &amp;lt;budget&gt;600&amp;lt;/budget&gt;&lt;br /&gt;  &amp;lt;/campaign&gt;&lt;br /&gt;&amp;lt;/campaigns&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Ruby on Rails&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Now on to the Ruby on Rails frontend, which gladly, is simpler. Would you expect less from Rails? Like the Java backend, we can build the project from scratch, or you can download the completed project &lt;a href="http://s3.amazonaws.com/coderoshi/roroj.zip"&gt;here&lt;/a&gt;.&lt;pre name="code" class="text:nocontrols"&gt;rails ror&lt;br /&gt;cd ror&lt;/pre&gt;Next, create the model file "app/models/campaign.rb" with the contents (assuming your server is at port 8080:&lt;pre name="code" class="ruby:nocontrols"&gt;class Campaign &amp;lt; ActiveResource::Base&lt;br /&gt;  self.site = "http://localhost:8080/campaignService"&lt;br /&gt;end&lt;/pre&gt;That's it for the ActiveResource. Rails presumes the rest (REST, get it? ha ha). It points the ActiveResource to the given site as a base URL, and from there constructs URLs based upon a pluralized version of the classname. For each of the following ActiveResource actions:&lt;pre name="code" class="ruby:nocontrols"&gt;Campaign.find(:all)        # GET http://localhost:8080/campaignService/campaigns.xml&lt;br /&gt;Campaign.find(7)           # GET http://localhost:8080/campaignService/campaigns/7.xml&lt;br /&gt;Campaign.create(data).save # POST http://localhost:8080/campaignService/campaigns.xml&lt;br /&gt;Campaign.find(7).save      # PUT http://localhost:8080/campaignService/campaigns/7.xml&lt;br /&gt;Campaign.delete(7)         # DELETE http://localhost:8080/campaignService/campaigns.xml&lt;/pre&gt;This covers it for the ActiveResource, but we need something to show the user. Luckily Rails makes this simple with scaffold generation.&lt;pre name="code" class="text:nocontrols"&gt;./script/generate scaffold Campaign name:string budget:integer&lt;/pre&gt;This generates a controller, resources in the routing table, test cases, and attempt to generate a model in the form of an activerecord object. But since we already created our campain ActiveResource there, it just skips it.&lt;br /&gt;&lt;br /&gt;Next, we need to set up the server. Since we don't actually need Rails to run on a database anymore, we can configure the server to skip it. Open up your "config/environment.rb" file, and within "RailsInitializer" add the line:&lt;pre name="code" class="ruby:nocontrols"&gt;  config.frameworks -= [ :active_record ]&lt;/pre&gt;To stop active_record from loading (and thus any database requirements).&lt;br /&gt;&lt;br /&gt;Now, start up the rails server:&lt;pre name="code" class="text:nocontrols"&gt;./script/server&lt;/pre&gt;Did that work? Probably not. Unless you are running &lt;span style="font-style:italic;"&gt;edge&lt;/span&gt; Rails, there is a current bug (gasp!) that forces you to comment out the lines from "new_rails_defaults.rb" (4th line in the server startup stack trace):&lt;br /&gt;&lt;pre name="code" class="ruby:nocontrols"&gt;# ActiveRecord::Base.include_root_in_json = true&lt;br /&gt;# ActiveRecord::Base.store_full_sti_class = true&lt;br /&gt;&lt;/pre&gt;Try and start the server again. It should work this time. If you visit "http://localhost:3000/campaign", it will probably work. This is just a coincidence.&lt;br /&gt;&lt;br /&gt;Next make a few tweaks to your new rails CampaignController. This is an issue with Rails scaffold&lt;br /&gt;generation. It generated a Controller for an ActiveRecord, not an ActiveResource, so you&lt;br /&gt;need to modify for using ActiveResource objects.&lt;br /&gt;&lt;br /&gt;Under "def update", replace the AR.update_attributes method with the ARes.load and save methods. Like so:&lt;pre name="code" class="ruby:nocontrols"&gt;      if @campaign.update_attributes(params[:campaign])&lt;/pre&gt;with&lt;pre name="code" class="ruby:nocontrols"&gt;      @campaign.load(params[:campaign])&lt;br /&gt;      if @campaign.save&lt;/pre&gt;and replace&lt;pre name="code" class="ruby:nocontrols"&gt;    @campaign = Campaign.find(params[:id])&lt;br /&gt;    @campaign.destroy&lt;/pre&gt;with&lt;pre name="code" class="ruby:nocontrols"&gt;    Campaign.delete(params[:id])&lt;/pre&gt;Better yet, let's just rip out all the stuff we don't need. Here's the complete Controller:&lt;pre name="code" class="ruby:nocontrols"&gt;class CampaignsController &amp;lt; ApplicationController&lt;br /&gt;  # GET /campaigns&lt;br /&gt;  def index&lt;br /&gt;    @campaigns = Campaign.find(:all)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # GET /campaigns/1&lt;br /&gt;  def show&lt;br /&gt;    @campaign = Campaign.find(params[:id])&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # GET /campaigns/new&lt;br /&gt;  def new&lt;br /&gt;    @campaign = Campaign.new()&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # GET /campaigns/1/edit&lt;br /&gt;  def edit&lt;br /&gt;    @campaign = Campaign.find(params[:id])&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # POST /campaigns&lt;br /&gt;  def create&lt;br /&gt;    @campaign = Campaign.new(params[:campaign])&lt;br /&gt;    if @campaign.save&lt;br /&gt;      flash[:notice] = 'Campaign was successfully created.'&lt;br /&gt;      redirect_to(@campaign)&lt;br /&gt;    else&lt;br /&gt;      render :action =&gt; "new"&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # PUT /campaigns/1&lt;br /&gt;  def update&lt;br /&gt;    @campaign = Campaign.find(params[:id])&lt;br /&gt;    @campaign.load(params[:campaign])&lt;br /&gt;    if @campaign.save&lt;br /&gt;      flash[:notice] = 'Campaign was successfully updated.'&lt;br /&gt;      redirect_to(@campaign)&lt;br /&gt;    else&lt;br /&gt;      render :action =&gt; "edit"&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # DELETE /campaigns/1&lt;br /&gt;  def destroy&lt;br /&gt;    Campaign.delete(params[:id])&lt;br /&gt;    redirect_to(campaigns_url)&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt;You'll also need to make a slight change to the generated "new.html.erb" file. Again, this is because the scaffold generator we used presumes it's dealing with an ActiveRecord object, and we provide an ActiveResource. (more info if you care: The "form_for(@campaign)" line generated, given an ARec object, will generate a hidden "_method=put" field in the form. This tells the controller that you are going to update an object - which isn't true - you are creating one. So you really want to POST, not PUT)&lt;br /&gt;&lt;br /&gt;So, as long as I'm giving away free answers, put this in place of the new.html.rb form generated (don't forget your authenticity token):&lt;pre name="code" class="xml:nocontrols"&gt;&amp;lt;form action="/campaigns/" method="post"&amp;gt;&lt;br /&gt;  &amp;lt;%= hidden_field_tag "authenticity_token", form_authenticity_token %&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;    &amp;lt;label for="campaign_name"&amp;gt;Name&amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;    &amp;lt;%= text_field_tag "campaign[name]", "" %&amp;gt;&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;    &amp;lt;label for="campaign_budget"&amp;gt;Budget&amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;    &amp;lt;%= text_field_tag "campaign[budget]", 0 %&amp;gt;&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;&lt;br /&gt;    &amp;lt;%= submit_tag "Create" %&amp;gt;&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/pre&gt;Navigate to "http://localhost:3000/campaigns" and check out your list of two campaigns. click "New campaign" to add one. Fill out the form and "Create". This then forwards you to "show" the newly created campaign, which is retrieved from the Java's REST service. Click "back" to return to the list of campaigns, and you should see your new one sitting there. Click "Edit" to make a change to the campaign, and "Destroy" to delete it. These actions don't actually do anything since we hard-coded our REST service.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;However&lt;/span&gt;, if you downloaded the sample projects and run them, then the REST service is backed by an HSQLDB - so adding/editing/removing values actually works.&lt;br /&gt;&lt;br /&gt;That's it! This convergence of technologies is precisely what ActiveResource was created to deal with. Simple REST serviced is why JSR311 (aka, JAX-RS) exists for. Simplicity of domains - what more could you want?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;RoRoRoJ? Ro3J?&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-6149637268818031378?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Y4n-ibS4vNk:4YOmWWCawbE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Y4n-ibS4vNk:4YOmWWCawbE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Y4n-ibS4vNk:4YOmWWCawbE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Y4n-ibS4vNk:4YOmWWCawbE:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Y4n-ibS4vNk:4YOmWWCawbE:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Y4n-ibS4vNk:4YOmWWCawbE:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=6149637268818031378" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6149637268818031378?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/6149637268818031378?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/Y4n-ibS4vNk/ruby-on-rails-on-rest-on-java.html" title="Ruby on Rails on REST on Java" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/08/ruby-on-rails-on-rest-on-java.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE8MRHkzcSp7ImA9WxdVFkg.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-1080142009319673368</id><published>2008-07-21T09:57:00.003-05:00</published><updated>2008-07-21T10:08:05.789-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-21T10:08:05.789-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="tips" /><title>Cheap Tricks XVI - Terse Equals</title><content type="html">I've been running across a lot of code like this lately. It's something I've brought up with my own developers, but suppose it's just something generally useful. When testing for equality of object, the precursing null check. Here's the code I'm seeing everywhere:&lt;pre name="code" class="java:nocontrols"&gt;  @Override&lt;br /&gt;  public boolean equals( Object obj )&lt;br /&gt;  {&lt;br /&gt;    if ( !( obj instanceof Account ) )&lt;br /&gt;    {&lt;br /&gt;      return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Account that = (Account) obj;&lt;br /&gt;    if ( that.id == null || this.id == null )&lt;br /&gt;    {&lt;br /&gt;      return false;&lt;br /&gt;    }&lt;br /&gt;    if ( !that.id.equals( this.id ) )&lt;br /&gt;    {&lt;br /&gt;      return false;&lt;br /&gt;    }&lt;br /&gt;    return true;&lt;br /&gt;  }&lt;/pre&gt;I'm a fan of short concise code - and even if you're not... come on... it's an equals method! Let's be frank:&lt;pre name="code" class="java:nocontrols"&gt;  @Override&lt;br /&gt;  public boolean equals( Object obj )&lt;br /&gt;  {&lt;br /&gt;    if ( !( obj instanceof Account ) )&lt;br /&gt;    {&lt;br /&gt;      return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Account that = (Account) obj;&lt;br /&gt;    return id == null ? that.id == null : id.equals( that.id );&lt;br /&gt;  }&lt;/pre&gt;If this id is null, then from the tertiary operator, you only need to know if that.id is null. If so, then they are equal. If not, then you just need to execute the equals method on them. Now if only Java would retrofit equals to use generics...&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-1080142009319673368?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=s82zYGK3qRk:EB2vUufSHOk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=s82zYGK3qRk:EB2vUufSHOk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=s82zYGK3qRk:EB2vUufSHOk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=s82zYGK3qRk:EB2vUufSHOk:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=s82zYGK3qRk:EB2vUufSHOk:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=s82zYGK3qRk:EB2vUufSHOk:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=1080142009319673368" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/1080142009319673368?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/1080142009319673368?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/s82zYGK3qRk/cheap-tricks-xvi-terse-equals.html" title="Cheap Tricks XVI - Terse Equals" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/07/cheap-tricks-xvi-terse-equals.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IBRnk8eip7ImA9WxdWFU4.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8939719847094153851</id><published>2008-07-08T10:30:00.008-05:00</published><updated>2008-07-08T12:52:37.772-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-08T12:52:37.772-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rant" /><category scheme="http://www.blogger.com/atom/ns#" term="tips" /><category scheme="http://www.blogger.com/atom/ns#" term="selfindulgence" /><title>Code Naming Rules</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_E3_4LXYT1LI/SHOpVzayTOI/AAAAAAAAAMk/NAXEdGvnjYA/s1600-h/800px-DoNotStop_Stopsign.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_E3_4LXYT1LI/SHOpVzayTOI/AAAAAAAAAMk/NAXEdGvnjYA/s320/800px-DoNotStop_Stopsign.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5220702585097440482" /&gt;&lt;/a&gt;I believe it is a fundamental misunderstanding of nature to assume that rules are not made to be broken. This is the sticking point where breakdowns in communications occur - so I'm letting you know what side of the debate I'm on right away. That said - here are some rules I adhere to when naming things - giving real (poor) examples from the system I currently work on:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Example:&lt;/b&gt; &lt;code&gt;CallRequestRecipientObj&lt;/code&gt;&lt;br /&gt;&lt;b&gt;Reason this is bad:&lt;/b&gt; Classnames should never contain the word "Object" or "Class" or their derivatives - unless you are writing framework code and referring specifically to an object or class type. We know it's an object or class we're talking about. Adding "Class" or "Object" into a classname is just redundantly redundant.&lt;br /&gt;&lt;b&gt;Rename to:&lt;/b&gt; &lt;code&gt;CallRequestRecipient&lt;/code&gt; is sufficient.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Example:&lt;/b&gt; &lt;code&gt;GenericPersistableClass&lt;/code&gt;&lt;br /&gt;&lt;b&gt;Reason this is bad:&lt;/b&gt; For the love of Jesus, don't use the word Generic or Framework in your classnames. Yes yes - your little framework is very clever and flexible and "generic". Stop beating me over the head with it. Especially when naming interfaces... which are, by definition, generic. Also: remove "Class" (see above).&lt;br /&gt;&lt;b&gt;Rename to:&lt;/b&gt; &lt;code&gt;Persistable&lt;/code&gt; ftw.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Example:&lt;/b&gt; &lt;code&gt;interface Account { int getAccountId(); }&lt;/code&gt;&lt;br /&gt;&lt;b&gt;Reason this is bad:&lt;/b&gt; Don't prepend the object name to an ID. Again, it's just redundant. The argument used to be surrounding database joins. But that's precisely what aliases are for:&lt;pre&gt;select a.id, d.id from Account, Details where a.id=d.accountId&lt;/pre&gt;&lt;b&gt;Rename to:&lt;/b&gt; &lt;code&gt;getID()&lt;/code&gt; is terse and descriptive. It's also the same for every object.&lt;br /&gt;&lt;br /&gt;These are a few good naming rules I stand by. Do you have any more? I'd love to hear them. Again, rules are made to be broken - but should be followed, like, 99% of the time - give or take, depending on your rules. Next on the docket: package, project, and namespace naming rules.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8939719847094153851?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Utv4m8brBrk:6_b-3ODVStM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Utv4m8brBrk:6_b-3ODVStM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Utv4m8brBrk:6_b-3ODVStM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Utv4m8brBrk:6_b-3ODVStM:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Utv4m8brBrk:6_b-3ODVStM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Utv4m8brBrk:6_b-3ODVStM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8939719847094153851" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8939719847094153851?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8939719847094153851?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/Utv4m8brBrk/code-naming-rules.html" title="Code Naming Rules" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_E3_4LXYT1LI/SHOpVzayTOI/AAAAAAAAAMk/NAXEdGvnjYA/s72-c/800px-DoNotStop_Stopsign.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/07/code-naming-rules.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEIFRns8fSp7ImA9WxdXF0w.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-868182266916442524</id><published>2008-06-28T12:00:00.001-05:00</published><updated>2008-06-28T23:08:37.575-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-28T23:08:37.575-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="code te ching" /><title>Code Te Ching - Verse 46</title><content type="html">When a tiger begins to run to parts unknown,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;she must first lean forward.&lt;br /&gt;This is called instability.&lt;br /&gt;&lt;br /&gt;When a tiger's paws land on the earth,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;they find their place of comfort.&lt;br /&gt;This is called self-organizing.&lt;br /&gt;&lt;br /&gt;When a tiger's eyes see, ears hear,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;the feet know to move.&lt;br /&gt;This is called multi-learning.&lt;br /&gt;&lt;br /&gt;When a tiger has her senses about her&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;no trap can capture her feet.&lt;br /&gt;This is called subtle control.&lt;br /&gt;&lt;br /&gt;When a cub watches his mother run,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;he will mimic her movements.&lt;br /&gt;This is called knowledge transfer.&lt;br /&gt;&lt;br /&gt;Instability, self-organizing, multi-learning, subtle control, knowledge transfer.&lt;br /&gt;This is the natural way of action.&lt;br /&gt;&lt;br /&gt;Forced unnatural natural force? Madness!&lt;br /&gt;Artificial process? Confusion!&lt;br /&gt;The sage guides but does not manage.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-868182266916442524?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=xkfrvjnW1ZY:sVAgYvPrNeY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=xkfrvjnW1ZY:sVAgYvPrNeY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=xkfrvjnW1ZY:sVAgYvPrNeY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=xkfrvjnW1ZY:sVAgYvPrNeY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=xkfrvjnW1ZY:sVAgYvPrNeY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=xkfrvjnW1ZY:sVAgYvPrNeY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=868182266916442524" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/868182266916442524?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/868182266916442524?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/xkfrvjnW1ZY/code-te-ching-verse-46.html" title="Code Te Ching - Verse 46" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/06/code-te-ching-verse-46.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUYFQns-eyp7ImA9WxdXGEk.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-8388217873123631208</id><published>2008-06-26T16:24:00.016-05:00</published><updated>2008-06-30T11:25:13.553-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-30T11:25:13.553-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tips" /><title>Cheap Tricks XV - Static Collections in Java</title><content type="html">Being back in the Java game from Ruby, makes me miss a lot of the shortcuts. Despite the crushing pain I feel whenever I recognize a 5 line block of Java code that can be reproduced with one short Ruby line, being a professional Ruby developer was overall a great experience. It has made me so lazy, I have amassed quite the little toolkit to save precious lines. Here's another cheap trick around reducing the common need for a static block populated collections (NOTE: this isn't limited to Enums - if you are only dealing with Enums, just use EnumSet.of):&lt;br /&gt;&lt;pre name="code" class="java:nocontrols"&gt;public class StateMachine&lt;br /&gt;{&lt;br /&gt;  enum State { STARTED, COMPLETED, CANCELLED, ABORTED }&lt;br /&gt;&lt;br /&gt;  public static final Set&amp;lt;State&amp;gt; FINAL_STATES;&lt;br /&gt;  static&lt;br /&gt;  {&lt;br /&gt;    HashSet&amp;lt;State&amp;gt; fs = new HashSet&amp;lt;State&amp;gt;();&lt;br /&gt;    fs.add( COMPLETED );&lt;br /&gt;    fs.add( CANCELLED );&lt;br /&gt;    fs.add( ABORTED );&lt;br /&gt;    FINAL_STATES = Collections.unmodifiableSet( fs );&lt;br /&gt;  }&lt;br /&gt;  // ...&lt;br /&gt;}&lt;/pre&gt;into a trim one-liner:&lt;pre name="code" class="java:nocontrols"&gt;import static StaticUtils.sset;&lt;br /&gt;import static StateMachine.State.*;&lt;br /&gt;&lt;br /&gt;public class StateMachine&lt;br /&gt;{&lt;br /&gt;  enum State { STARTED, COMPLETED, CANCELLED, ABORTED }&lt;br /&gt;&lt;br /&gt;  static final List&amp;lt;State&amp;gt; FINAL_STATES = sset( COMPLETED, CANCELLED, ABORTED );&lt;br /&gt;  // ...&lt;br /&gt;}&lt;/pre&gt;Here's a code for a static list creator:&lt;pre name="code" class="java:nocontrols"&gt;public final class StaticUtils&lt;br /&gt;{&lt;br /&gt;  public static final &amp;lt;T&amp;gt; Set&amp;lt;T&amp;gt; sset( T...objects )&lt;br /&gt;  {&lt;br /&gt;    HashSet&amp;lt;T&amp;gt; al = new HashSet&amp;lt;T&amp;gt;( objects.length );&lt;br /&gt;    for ( T t : objects ) al.add( t );&lt;br /&gt;    return Collections.unmodifiableSet( al );&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;As one person pointed out, you can achieve the same effect for Lists via:&lt;pre name="code" class="java:nocontrols"&gt;import static java.util.Arrays.asList;&lt;br /&gt;import static StateMachine.State.*;&lt;br /&gt;&lt;br /&gt;public class StateMachine&lt;br /&gt;{&lt;br /&gt;  enum State { STARTED, COMPLETED, CANCELLED, ABORTED }&lt;br /&gt;&lt;br /&gt;  static final List&amp;lt;State&amp;gt; FINAL_STATES = asList( COMPLETED, CANCELLED, ABORTED );&lt;br /&gt;  // ...&lt;br /&gt;}&lt;/pre&gt;Slight modifications can be made for handling maps:&lt;pre name="code" class="java:nocontrols"&gt;public final class StaticUtils&lt;br /&gt;{&lt;br /&gt;  // ...&lt;br /&gt;  public static final &amp;lt;K, V&amp;gt; Map&amp;lt;K, V&amp;gt; smap( Pair&amp;lt;K, V&amp;gt;...pairs )&lt;br /&gt;  {&lt;br /&gt;    HashMap&amp;lt;K, V&amp;gt; hm = new HashMap&amp;lt;K, V&amp;gt;();&lt;br /&gt;    for ( Pair&amp;lt;K, V&gt; p : pairs ) hm.put( p.k, p.v );&lt;br /&gt;    return Collections.unmodifiableMap( hm );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public static class Pair&amp;lt;K, V&amp;gt;&lt;br /&gt;  {&lt;br /&gt;    private Pair(K k, V v) { this.k = k; this.v = v; }&lt;br /&gt;    private K k;&lt;br /&gt;    private V v;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public static final Pair&amp;lt;K, V&amp;gt; p( K k, V v )&lt;br /&gt;  {&lt;br /&gt;    return new Pair&amp;lt;K, V&amp;gt;(k, v);&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Usage is similar, but bundles keys/values into a pair, the creation of which is shortcutted by the static "p" method.&lt;pre name="code" class="java:nocontrols"&gt;import static StaticUtils.smap;&lt;br /&gt;import static StaticUtils.p;&lt;br /&gt;import static StateMachine.States.*;&lt;br /&gt;&lt;br /&gt;public class StateMachine&lt;br /&gt;{&lt;br /&gt;  enum State { STARTED, COMPLETED, CANCELLED, ABORTED }&lt;br /&gt;&lt;br /&gt;  static final Map&amp;lt;State, String&amp;gt; INCOMPLETE = smap( p(CANCELLED,"cancel"), p(ABORTED,"abort") );&lt;br /&gt;  // ...&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-8388217873123631208?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=PF0U7S5Vljc:bsP8gAPp8CY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=PF0U7S5Vljc:bsP8gAPp8CY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=PF0U7S5Vljc:bsP8gAPp8CY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=PF0U7S5Vljc:bsP8gAPp8CY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=PF0U7S5Vljc:bsP8gAPp8CY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=PF0U7S5Vljc:bsP8gAPp8CY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=8388217873123631208" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8388217873123631208?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/8388217873123631208?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/PF0U7S5Vljc/cheap-tricks-xv-static-collections-in.html" title="Cheap Tricks XV - Static Collections in Java" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/06/cheap-tricks-xv-static-collections-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4GRX4zeyp7ImA9WxdXF0w.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-9171743548454920256</id><published>2008-06-18T09:55:00.028-05:00</published><updated>2008-06-28T23:15:24.083-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-28T23:15:24.083-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="interview" /><category scheme="http://www.blogger.com/atom/ns#" term="review" /><category scheme="http://www.blogger.com/atom/ns#" term="politics" /><title>The Flywheel and the Dead Sea</title><content type="html">First thing's first. Some definitions:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Flywheel Effect&lt;/b&gt;&lt;br /&gt;&lt;blockquote&gt;Now picture a huge, heavy flywheel. It's a massive, metal disk mounted horizontally on an axle. It's about 100 feet in diameter, 10 feet thick, and it weighs about 25 tons. That flywheel is your company. Your job is to get that flywheel to move as fast as possible, because momentum -- mass times velocity -- is what will generate superior economic results over time.&lt;br /&gt;&lt;br /&gt;Right now, the flywheel is at a standstill. To get it moving, you make a tremendous effort. You push with all of your might, and finally, you get the flywheel to inch forward. After two or three days of sustained effort, you get the flywheel to complete one entire turn. You keep pushing, and the flywheel begins to move a bit faster. It takes a lot of work, but at last the flywheel makes a second rotation. You keep pushing steadily. It makes three turns, four turns, five, six. With each turn, it moves faster, and then -- at some point, you can't say exactly when -- you break through. The momentum of the heavy wheel kicks in your favor. It spins faster and faster, with its own weight propelling it. You aren't pushing any harder, but the flywheel is accelerating, its momentum building, its speed increasing.&lt;/blockquote&gt; - &lt;a href="http://www.amazon.com/Good-Great-Companies-Leap-Others/dp/0066620996/"&gt;Jim Collins in &lt;span style="font-style:italic;"&gt;Good to Great&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now consider the above analogy concerning your software development team. Is it descriptive? Do they move via internal momentum, or need cattle-prodded to get work done? Moreover, consider this in terms of hiring software engineers. Good people want to work together - and they want to be valued. This is my positive analogy concerning the makeup of a development organization: if your group is filled with smart people it will attract other smart people, if they are empowered it will attract others looking to make a difference. Keep momentum high, and those incapable of keeping up will be flung off by the sheer force of the flywheel... what an attractive metaphor! Keep this in mind while reading the next one.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Dead Sea Effect&lt;/b&gt;&lt;br /&gt;&lt;blockquote&gt;The Dead Sea, of course, is a large body of water between Israel and Jordan, located well below sea level. The Jordan River empties into it; water leaves only by evaporation, which means that over the eons, the Dead Sea has become very salty (e.g., 8x saltier than the ocean). As such, it is generally unable to support life, except when spring floods temporarily lower the salinity.&lt;br /&gt;&lt;br /&gt;Many large corporate/government IT shops — and not a few small ones — work like the Dead Sea...what happens is that the more talented and effective IT engineers are the ones most likely to leave — to evaporate, if you will. They are the ones least likely to put up with the frequent stupidities and workplace problems that plague large organizations; they are also the ones most likely to have other opportunities that they can readily move to.&lt;br /&gt;&lt;br /&gt;What tends to remain behind is the ‘residue’ — the least talented and effective IT engineers.&lt;/blockquote&gt; - &lt;a href="http://brucefwebster.com/2008/04/11/the-wetware-crisis-the-dead-sea-effect/"&gt;Bruce Webster&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This is another common disease that plagues, not just IT shops, but companies that make their money on software. It tends to perpetuate when upper management has no real development experience, but I've seen it happen when those in charge are former developers themselves - the latter has always puzzled me, but considering middle-management's tendency to mask organizational problems that occur below them, I like to think that upper management is merely blissfully ignorant of the problem. but I digress.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Minding the Gap&lt;/b&gt;&lt;br /&gt;What is at issue here is that, surprisingly, most development teams I encounter gravitate toward either end of the spectrum. The center of gravity is bimodal. Where one might conclude there is a bell curve associated with any particular company's talent pool, real productivity distribution is a little more lopsided. Certainly, overall talent may land somewhere around the middle, but a company entirely consisted of mid-range developers is still a dead sea. A pool of mediocre developers won't give you average results - you'll just get a decent maintenance staff with occasional updates - all the while lagging behind your more agile competitors. You &lt;span style="font-style:italic;"&gt;must&lt;/span&gt; be a flywheel to move forward. It's like trying to break the Earth's gravitational field in a rocket... anything less than the threshold velocity and you'll tumble back to Earth. You &lt;span style="font-style:italic;"&gt;must&lt;/span&gt; have a top developer to advance, and you &lt;span style="font-style:italic;"&gt;must&lt;/span&gt; have a field of them to &lt;span style="font-style:italic;"&gt;attract&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;retain&lt;/span&gt; others, and to keep each other &lt;span style="font-style:italic;"&gt;motivated&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Attraction&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.google.com"&gt;Google&lt;/a&gt; started out with top notch talent. The two founders were computer science PHDs, and naturally that helped bootstrap their organization. They were able to hire the top talent, because they recognized them. &lt;span style="font-style:italic;"&gt;Only talent can recognize talent&lt;/span&gt; (Corollary 2 of the &lt;a href="http://en.wikipedia.org/wiki/Dunning-Kruger_effect"&gt;Dunning-Kruger Effect&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;But this is not all you require. You need to have a reason for talent to want to work with you. Remember: at this point in time, skilled developers are at a premium. &lt;span style="font-style:italic;"&gt;They are not desperate for your job.&lt;/span&gt; Anyone who is, you don't want. This has a couple corollaries:&lt;br /&gt;&lt;br /&gt;You must pay competitive wages for &lt;span style="font-weight:bold;"&gt;today&lt;/span&gt;. This should go unsaid - but it can't be. Don't be cheap. Equity sharing is also good. Give your people a reason to care. Don't fall into the trap of believing because you hired your first crop of developers three years ago at 60k, that a 5% raise is sufficient to match today. Don't wait for your top developers to put in their notice to start offering the raises. You &lt;span style="font-style:italic;"&gt;must&lt;/span&gt; pay competitive wages to keep talent.&lt;br /&gt;&lt;br /&gt;Money is ok, but the most attractive thing you can offer great developers is the opportunity to work with other smart, motivated people. Along with that, from personal experience, the ability to work with new technologies is also a plus (though not always necessary or possible - if you can offer it, make this known).&lt;br /&gt;&lt;br /&gt;Attracting good developers is only part of the issue - then you must retain them. &lt;span style="font-style:italic;"&gt;Attraction without retention of talent creates a dead sea.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Retention&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.atg.com/"&gt;ATG&lt;/a&gt; started out great. Also founded by two PHDs they invented JHTML, later licensed by Sun and converted to JSP. But anymore - well - how many great developers have heard of ATG? This may sound like an unfair comparison, but I don't think so. They both started out attracting great talent, but not anymore. Having worked rather closely with ATG, as well as others who have worked in and with the company, there is nothing attractive about working there. Innovation is stifled, and that knowledge floats around the aether.&lt;br /&gt;&lt;br /&gt;Retaining developers is easy - it's all about empowerment. Beyond that, assuming you are not attracting new developers by lying (about their situation, benefits, etc.), simply allowing talented people to be valued is often enough. &lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Retention of talent without motivation stops a flywheel.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Motivation&lt;/b&gt;&lt;br /&gt;Motivation is, of course, the force that causes the flywheel to turn. Keep the bar high, give talent ownership in the outcome. If you can attract top talent, and keep them motivated, retention handles itself. If you can keep talented people empowered, they will motivate themselves. And this is precisely the point. You don't need to &lt;span style="font-style:italic;"&gt;do&lt;/span&gt; anything - hence the flywheel. Once it's up and running, it moves on it's own - the momentum makes it an unstoppable force.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Starting the Flywheel&lt;/b&gt;&lt;br /&gt;Up to here, I've been pretty vague - purposefully brushing with large strokes. I want to drive home to idea that &lt;span style="font-style:italic;"&gt;attraction&lt;/span&gt;, &lt;span style="font-style:italic;"&gt;retention&lt;/span&gt;, and &lt;span style="font-style:italic;"&gt;motivation&lt;/span&gt; are all required to create a self-sustaining flywheel of talent. So what if you're a company leaking talent, or are already a dead sea, what specifically can you do?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Step 1:&lt;/b&gt; Pinpoint internal talent. Ask around. Don't just ask the bosses, ask the developers' peers. All developers will know who the badasses of your organization are. Leveraging your developers allows them to have a voice in the business process - it's a great &lt;span style="font-style:italic;"&gt;motivator&lt;/span&gt;, which is always great for &lt;span style="font-style:italic;"&gt;retention&lt;/span&gt;. But what if you are new company, or don't have any top talent? This is more difficult - since as stated above, I believe only talent can spot talent. The next best thing to do is ask around, multiple sources, and rely on reputation - though this is the worst of the two options. It's like trying to find a good auto mechanic: you either be an expert yourself, or rely on reputation. Obviously the former is much better - and you'll probably go through several mechanics before you find a good one anyway. Reputations can be lies.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Step 2:&lt;/b&gt; Once you've found some top developers to meet with, leverage that talent to attract new people. Smart people tend to know other smart people. Despite the rumors, great developers are social - there are good ol' boys clubs everywhere. Chances are, your top developers are involved in other projects - either small side projects with others, or top open source projects. You never can tell. Find out. This is how you will &lt;span style="font-style:italic;"&gt;attract&lt;/span&gt; the best.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Step 3:&lt;/b&gt; Generate mindshare. It's well known that Google Engineers are alloted 20% of their time to work on pet projects. That's where gmail, maps, movies, and other projects came from. Why let Google have all the fun? Sure, you may not be able to spare a whole day a week - but I don't care what you do, or how tight your deadlines are, every business can spare 4 hours a week for their developers to write open source tools, corporate blog, manage an external corporate bulletin board.&lt;br /&gt;&lt;br /&gt;Make your place a fun and interesting place to work for developers - and great developers like to brag (who knows why... it must be written in our &lt;span style="font-style:italic;"&gt;awesome&lt;/span&gt; DNA). Create an open source arm and release the code of internal tools your developers have written to solve problems. There are other benefits to doing this as well. Beyond the great "we have an open source arm" press you get a free army of bugfixers (mindshare may drag in new customers too). This gets your name out there, which &lt;span style="font-style:italic;"&gt;attracts&lt;/span&gt; new top talent. This lets your developers run something, which keeps them &lt;span style="font-style:italic;"&gt;motivated&lt;/span&gt; in other areas (for example, the work you pay them to do).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;That's just one possible path to creating a self-retaining flywheel of talented, motivated developers, one of many paths, but the goal is the same. Internal motivation. You can't buy it, and you can't force it, but you certainly can build it. It will take some effort and pain on the front end, but by the time that wheel is moving, you'll wonder how you ever survived before.&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-9171743548454920256?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Lm5JLfbQayA:RGHrJcAtypk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Lm5JLfbQayA:RGHrJcAtypk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Lm5JLfbQayA:RGHrJcAtypk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Lm5JLfbQayA:RGHrJcAtypk:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=Lm5JLfbQayA:RGHrJcAtypk:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=Lm5JLfbQayA:RGHrJcAtypk:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=9171743548454920256" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/9171743548454920256?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/9171743548454920256?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/Lm5JLfbQayA/flywheel-and-dead-sea.html" title="The Flywheel and the Dead Sea" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/06/flywheel-and-dead-sea.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8MRnsyeip7ImA9WxdQEkQ.&quot;"><id>tag:blogger.com,1999:blog-1906868083678732010.post-2488926430392106529</id><published>2008-06-11T12:48:00.023-05:00</published><updated>2008-06-12T13:54:47.592-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-12T13:54:47.592-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="interview" /><title>The Worst of Crimes</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_E3_4LXYT1LI/SFAyHUp2YvI/AAAAAAAAAMc/8rpGBIUiTMU/s1600-h/Curious-George.gif"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_E3_4LXYT1LI/SFAyHUp2YvI/AAAAAAAAAMc/8rpGBIUiTMU/s320/Curious-George.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5210719870252376818" /&gt;&lt;/a&gt;There is no sin so great for a programmer as lack of curiosity. A sufficiently curious programmer can overcome a whole range of deficiencies, including laziness. Traits detrimental to normal jobs such as lack of: intelligence, social skills, or hygiene. Some of the greatest coders I know are lazy, smelly, and not necessarily "smart" in the common meaning. But they are curious about how things work - which is why they're great. These are the people that run into defects, and rather than sit on their hands, find ways to fix them. Curiosity drives research, and from self-driven research comes experience - which can even close the gap to their more intelligent peers.&lt;br /&gt;&lt;br /&gt;Industrious coders will stay at work all night because it is their duty to do so. Curious coders will stay at work all night because they can't sleep without knowing why the database is running so damn slowly. Which would you rather hire?&lt;br /&gt;&lt;br /&gt;I bring this up, because at my new job we're looking to hire, and I want to ensure that we aren't just going to hire someone with lots of education, or intelligence (both important attributes) but someone who has a general curiosity about how the world works.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Question: What activities do you do outside of work?&lt;/b&gt;&lt;br /&gt;I know it's a cliche to say "I want a well-rounded person", but in this case, I do. Not because I particularly care if my co-workers like to play street-hockey, or are into beat poetry, but because curious people tend to have a lot of interests. It's all part and parcel to wanting to know about the world. Even the biggest dullard on Earth has at least one interest. If the best they can say is "I like to watch movie", move on...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Question: What non-school- or non-work- related programming project are you most proud of?&lt;/b&gt;&lt;br /&gt;Of course, lots of people have varied interests. This doesn't mean they are necessarily relevant to the job. My buddy Jim mentioned this good question. It's the python paradox in another form: the best programmers do it outside of work. I think it's a misnomer to claim that good programmers program for fun, because I don't care if you have fun or not. I didn't learn Ruby for fun, I learned it because I needed a good scripting language to write code generators for crappy J2EE/EJB 1.0 projects. I wasn't having fun, but I was driven by a curiosity to know how Ruby worked. And that's enough. Again, no answer equals no employment...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Specify a real problem and try and gauge interest.&lt;/b&gt;&lt;br /&gt;This isn't a question, but a strategy - and one that I prefer. Body language will tell you when someone is really interested in a problem - they'll sit up straighter, perk up a little, and give an obvious effort to solve the problem. Remember, in theory this person &lt;i&gt;wants&lt;/i&gt; to work for you - so trial by fire. OK, at least mediation by lighter. The point is, try explain a specific technical problem you face, just minus the domain-specific jargon. For example: we run Hibernate on top of Oracle and some of the queries are going very slow. It's a good technical question, but don't - as an interviewer - get caught in the trap of just listening for technical correctness. Curious people will ask questions before working out a possible formulation - dullards will either have no answer (take a hike!), or ramble off a list of template answers (indexing, data model, bad query generation). I look for questions, even if it's as simple as "What version of Hibernate are running?". If someone can't get excited about solving a real problem (which involves attempting to understand it) during an interview, how excited will they be after 6 months of 8 [ahem, or 10... or 12] hour days?&lt;br /&gt;&lt;br /&gt;Games aside, good technical interview questions and strategies can be useful - but never foolproof. I assume you are a curious person, and are merely seeking to find others of our kind. You only meet with a person for a short time, so like Bram Cohen (creator of BitTorrent) says: "In an interview you can tell if a person is a pleasant conversationalist, and you can give some technical questions to rule out the truly inept, but beyond that you might as well be rolling dice." That's true, but if you look for specific personality aspect - which I suggest curiosity is a must - you can at least get a good feel for this characteristic.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Good to be back!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;br /&gt;&amp;copy; 2007 Eric Redmond&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1906868083678732010-2488926430392106529?l=www.coderoshi.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=7GcRz9dOWeg:ipZ_pBpgzf4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=7GcRz9dOWeg:ipZ_pBpgzf4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=7GcRz9dOWeg:ipZ_pBpgzf4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=7GcRz9dOWeg:ipZ_pBpgzf4:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/coderoshi?a=7GcRz9dOWeg:ipZ_pBpgzf4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/coderoshi?i=7GcRz9dOWeg:ipZ_pBpgzf4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=1906868083678732010&amp;postID=2488926430392106529" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2488926430392106529?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1906868083678732010/posts/default/2488926430392106529?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/coderoshi/~3/7GcRz9dOWeg/worst-of-crimes.html" title="The Worst of Crimes" /><author><name>Eric Redmond</name><uri>http://www.blogger.com/profile/06061345369534079723</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16795037463538225849" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_E3_4LXYT1LI/SFAyHUp2YvI/AAAAAAAAAMc/8rpGBIUiTMU/s72-c/Curious-George.gif" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.coderoshi.com/2008/06/worst-of-crimes.html</feedburner:origLink></entry></feed>
