<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jason's .plan &#187; Software Development</title>
	<atom:link href="http://blogs.digitar.com/jjww/category/software-development/feed/" rel="self" type="application/rss+xml" />
	<link>http://blogs.digitar.com/jjww</link>
	<description>thoughts &#38; musings</description>
	<lastBuildDate>Thu, 18 Mar 2010 06:29:45 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Cloud-scale DBs in the cloud&#8230;just a quickie</title>
		<link>http://blogs.digitar.com/jjww/2010/03/cloud-scale-dbs-in-the-cloud-just-a-quickie/</link>
		<comments>http://blogs.digitar.com/jjww/2010/03/cloud-scale-dbs-in-the-cloud-just-a-quickie/#comments</comments>
		<pubDate>Thu, 18 Mar 2010 05:59:46 +0000</pubDate>
		<dc:creator>Jason</dc:creator>
				<category><![CDATA[DigiTar]]></category>
		<category><![CDATA[Software Development]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[rackspace]]></category>
		<category><![CDATA[softlayer]]></category>
		<category><![CDATA[voxel]]></category>

		<guid isPermaLink="false">http://blogs.digitar.com/jjww/?p=134</guid>
		<description><![CDATA[Just a quick set of thoughts&#8230;do cloud-scale DBs save money because they&#8217;re based on commodity/cheap servers? Tonight I did some rough back-of-the-pad calculations, and was kind of surprised&#8230;
Let&#8217;s assume we&#8217;ve got an 11TB working set of data, how could we store this redundantly?
(cloud servers in these examples are dedicated servers at a cloud provider)
Option 1: [...]]]></description>
			<content:encoded><![CDATA[<p>Just a quick set of thoughts&#8230;do cloud-scale DBs save money because they&#8217;re based on commodity/cheap servers? Tonight I did some rough back-of-the-pad calculations, and was kind of surprised&#8230;</p>
<p>Let&#8217;s assume we&#8217;ve got an 11TB working set of data, how could we store this redundantly?</p>
<p>(cloud servers in these examples are dedicated servers at a cloud provider)</p>
<p><strong>Option 1: Two beefy storage servers running MySQL in a master/slave config</strong></p>
<ul>
<li>CPU: 4-cores of your favorite CPU vendor</li>
<li>RAM: 16GB</li>
<li>HDDs: 48x 250 GB SATA
<ul>
<li>Lose 2 for mirrored boot, and 2 for RAID-6 parity</li>
</ul>
</li>
<li>Cost:
<ul>
<li>Buy Your Own Hardware (Sun X4500): $50,000 for the pair</li>
<li>Host It in the Cloud (SoftLayer): $4,700/month for the pair</li>
</ul>
</li>
</ul>
<p><strong>Option 2: 28 commodity servers (2 replica copies for each piece of data) running HBase or Cassandra</strong></p>
<ul>
<li>CPU: 4-cores of your favorite CPU vendor</li>
<li>RAM: 4GB</li>
<li>HDDs: 4x 250 GB SATA
<ul>
<li>Lose 1 for RAID-5 parity (we&#8217;ll mingle boot data and data data on the same drive pool)</li>
</ul>
</li>
<li>Cost:
<ul>
<li>Buy Your Own Hardware (Dell R410): $43,300 for set of 28</li>
<li>Host It in the Cloud (SoftLayer): $12,000/month for the set of 28</li>
</ul>
</li>
</ul>
<p><strong>Option 3: 42 commodity servers (3 replica copies for each piece of data) running HBase or Cassandra</strong></p>
<ul>
<li>CPU: 4-cores of your favorite CPU vendor</li>
<li>RAM: 4GB</li>
<li>HDDs: 4x 250 GB SATA
<ul>
<li>Lose 1 for RAID-5 parity (we&#8217;ll mingle boot data and data data on the same drive pool)</li>
</ul>
</li>
<li>Cost:
<ul>
<li>Buy Your Own Hardware (Dell R410): $64,900 for set of 42</li>
<li>Host It in the Cloud (SoftLayer): $18,000/month for the set of 42</li>
</ul>
</li>
</ul>
<p>Now the issue here that surprised me isn&#8217;t the raw cost differential between stuffing your own hardware in your colo or using a cloud provider. And the other thing is, I&#8217;m not picking on SoftLayer&#8230;Rackspace and Voxel all work out to the same cost scaling as SoftLayer (and in the case of the other two vendors worse).</p>
<p>What surprised me:</p>
<ul>
<li>When you buy your own hardware, &#8220;cloud-scale&#8221; databases do cost you less (~$7K)  than buying beefy storage servers and running MySQL for the same data set.</li>
<li><strong>However</strong>, when you are at a cloud provider, using cloud-scale databases on &#8220;cheap&#8221; hardware costs you 3x more than using beefy storage cloud servers running MySQL.</li>
</ul>
<p>As I said, I&#8217;m not comparing the cost of running Option 1 on your own hardware vs. Option 1 at a cloud provider. Yes those costs are more at the cloud provider, but it&#8217;s to be expected (they&#8217;re bundling in bandwidth, colo, power, and most importantly people to manage the hardware and network).</p>
<p>What&#8217;s stunning is that beefy servers at a cloud provider are much more cost efficient. Beefy cloud servers cost you roughly 1/15 of the cost of the hardware every month. Whereas, &#8220;cheap&#8221; commodity cloud servers cost you roughly 1/3 of the cost of the hardware every month. Much higher mark up on the cheaper volume servers.</p>
<p>Please  comment and correct me if I&#8217;m wrong in my analysis&#8230;I would actually like to be.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.digitar.com/jjww/2010/03/cloud-scale-dbs-in-the-cloud-just-a-quickie/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Rabbits and warrens.</title>
		<link>http://blogs.digitar.com/jjww/2009/01/rabbits-and-warrens/</link>
		<comments>http://blogs.digitar.com/jjww/2009/01/rabbits-and-warrens/#comments</comments>
		<pubDate>Wed, 14 Jan 2009 01:21:38 +0000</pubDate>
		<dc:creator>Jason</dc:creator>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[amqp]]></category>
		<category><![CDATA[py-amqplib]]></category>
		<category><![CDATA[rabbitmq]]></category>

		<guid isPermaLink="false">http://blogs.digitar.com/jjww/2009/01/rabbits-and-warrens/</guid>
		<description><![CDATA[<p>However, there are three in-particular, that are designed to be highly flexible message queues for their own sake: Apache ActiveMQ ZeroMQ RabbitMQ Apache ActiveMQ seems to get the most press, but it appears to have some issues not losing messages. ... So, without further ado...here is a reduction of a weeks' worth of reading up on AMQP and how it works in RabbitMQ...and how to play with it in Python: Playing telephone There are four building blocks you really care about in AMQP: virtual hosts, exchanges, queues and bindings.</p>]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.dkimages.com/discover/previews/811/20089825.JPG" border="0" alt="20089825.JPG" width="384" height="293" />The goal was simple enough: decouple a particular type of analysis out-of-band from mainstream e-mail processing. We started down the MySQL road…put the things to be digested into a table…consume them in another daemon…bada bing bada boom. But pretty soon, complex ugliness crept into the design phase… You want to have multiple daemons servicing the queue?…no problem we&#8217;ll just hard code node numbers…what? you want dynamic load re-assignment when daemons join and die?</p>
<p>You get the idea…what was supposed to be simple (decouple something) was spinning its own <a href="http://en.wikipedia.org/wiki/Gordian_knot">Gordian knot</a>. It seemed like a good time to see if every problem was looking like a nail (table), because all we had were hammers (MySQL).</p>
<p>A short search later, and we entered the world of message queueing. No, no…we know obviously what a message queue is. Heck, we do e-mail for a living. We&#8217;ve implemented all sorts of specialized, high-speed, in-memory queues for e-mail processing. What we weren&#8217;t aware of was the family of off-the-shelf, generalized, message queueing (MQ) servers…a language-agnostic, no-assembly required way to wire routing between applications over a network. A message queue we didn&#8217;t have to write ourselves? Hold your tongue.</p>
<h2>Open up your queue…</h2>
<p>Cutting to the chase, over the last 4 years there have been no shortage of open-source message queueing servers written. Most of them are one-offs by folks like LiveJournal to scratch a particular itch. Yeah, they don&#8217;t really care what kind of messages they carry, but their design parameters are usually creator-specific (and message persistence after a crash usually isn&#8217;t one of them). However, there are three in-particular, that are designed to be highly flexible message queues for their own sake:</p>
<ul>
<li><a href="http://activemq.apache.org/">Apache ActiveMQ</a></li>
<li><a href="http://www.zeromq.org/">ZeroMQ</a></li>
<li><a href="http://www.rabbitmq.com/">RabbitMQ</a></li>
</ul>
<p>Apache ActiveMQ gets the most press, but it appears to have some issues not losing messages. <strong>Next.</strong></p>
<p>ZeroMQ and RabbitMQ both support an open messaging protocol called <span class="caps">AMQP.</span> The advantage to <span class="caps">AMQP</span> is that it&#8217;s designed to be a highly-robust and open alternative to the two commercial message queues out there (IBM and Tibco). Muy bueno. However, ZeroMQ doesn&#8217;t support message persistence across crashes reboots. No muy bueno. That leaves us with RabbitMQ. (That being said if you don&#8217;t need persistence ZeroMQ is pretty darn interesting…incredibly low latency and flexible topologies).</p>
<h2>That leaves us with the carrot muncher…</h2>
<p><img src="http://www.rabbitmq.com/img/RabbitMQLogo.png" border="0" alt="" /></p>
<p>RabbitMQ pretty much sold me the minute I read “written in Erlang”. <a href="http://en.wikipedia.org/wiki/Erlang_(programming_language)">Erlang</a> is a highly parallel programming language developed over at Ericsson for running telco switches…yeah the kind with six bazillion 9s of uptime. In Erlang, its supposedly trivial to spin off processes and then communicate between them using message passing. Seems like the ideal underpinning for a message queue no?</p>
<p>Also, RabbitMQ supports persistence. Yes Virginia, if your RabbitMQ dies, your messages don&#8217;t have to die an unwitting death…they can be reborn in your queues on reboot. Oh…and as is always desired @ DigiTar, it <a href="http://barryp.org/software/py-amqplib/">plays nicely with python</a>. All that being said, RabbitMQs documentation is well…horrible. Lemme rephrase, if you already understand <span class="caps">AMQP,</span> the docs are fine. But how many folks know <span class="caps">AMQP</span>? It&#8217;d be like MySQL docs assuming you knew some form of <span class="caps">SQL…</span>er…nevermind.</p>
<p>So, without further ado…here is a reduction of a weeks&#8217; worth of reading up on <span class="caps">AMQP</span> and how it works in RabbitMQ…and how to play with it in Python:</p>
<h2>Playing telephone</h2>
<p>There are four building blocks you really care about in <span class="caps">AMQP</span>: virtual hosts, exchanges, queues and bindings. A virtual host holds a bundle of exchanges, queues and bindings. Why would you want multiple virtual hosts? Easy. A username in RabbitMQ grants you access to a virtual host…in its entirety. So the only way to keep group A from accessing group B&#8217;s exchanges/queues/bindings/etc. is to create a virtual host for A and one for B. Every RabbitMQ server has a default virtual host named “/”. If that&#8217;s all you need, you&#8217;re ready to roll.</p>
<h2>Exchanges, Queues and bindings…oh my!</h2>
<p>Here&#8217;s where my railcar went off the tracks initially. How do all the parts thread together?</p>
<p>Queues are where your “messages” end up. They&#8217;re message buckets…and your messages sit there until a client (a.k.a. consumer) connects to the queue and siphons it off. However, you can configure a queue so that if there isn&#8217;t a consumer ready to accept the message when it hits the queue, the message goes poof. But we digress…</p>
<p>The important thing to remember is that queues are created programmatically by your consumers (not via a configuration file or command line program). That&#8217;s <span class="caps">OK,</span> because if a consumer app tries to “create” a queue that already exists, RabbitMQ pats it on the head, smiles gently and <span class="caps">NOOP</span>s the request. So you can keep your MQ configuration in-line with your app code…what a concept.</p>
<p><span class="caps">OK,</span> so you&#8217;ve created and attached to your queue, and your consumer app is drumming its fingers waiting for a message…and drumming…and drumming…but alas no message. What happened? Well you gotta pump a message in first! But to do that you&#8217;ve got to have an exchange…</p>
<p>Exchanges are routers with routing tables. That&#8217;s it. End stop. Every message has what&#8217;s known as a “routing key”, which is simply a string. The exchange has a list of bindings (routes) that say, for example, messages with routing key “X” go to queue “timbuktu”. But we get slightly ahead of ourselves.</p>
<p>Your consumer application should create your exchanges (plural). Wait? You mean you can have more than one exchange? Yes, you can, but why? Easy. Each exchange operates in its own userland process, so adding exchanges, adds processes allowing you to scale message routing capacity with the number of cores in your server. As an example, on an 8-core server you could create 5 exchanges to maximize your utilization, leaving 3 cores open for handling the queues, etc.. Similarly, in a RabbitMQ cluster, you can use the same principle to spread exchanges across the cluster members to add even more throughput.</p>
<p><span class="caps">OK,</span> so you&#8217;ve created an exchange…but it doesn&#8217;t know what queues the messages go in. You need “routing rules” (bindings). A binding essentially says things like this: put messages that show up in exchange “desert” and have routing key “ali-baba” into the queue “hideout”. In other words, a binding is a routing rule that links an exchange to a queue based on a routing key. It is possible for two binding rules to use the same routing key. For example, maybe messages with the routing key “audit” need to go <strong>both</strong> to the “log-forever” queue and the “alert-the-big-dude” queue. To accomplish this, just create two binding rules (each one linking the exchange to one of the queues) that both trigger on routing key “audit”. In this case, the exchange duplicates the message and sends it to both queues. Exchanges are just routing tables containing bindings.</p>
<p>Now for the curveball: there are multiple types of exchanges. They all do routing, but they accept different styles of binding “rules”. Why not just create one type of exchange for all style of rules? Because each rule style has a different <span class="caps">CPU</span> cost for analyzing if a message matches the rule. For example, a “topic” exchange tries to match a message&#8217;s routing key against a pattern like “<strong>dogs.*</strong>”. Matching that wildcard on the end takes more <span class="caps">CPU</span> than simply seeing if the routing key is “<strong>dogs</strong>” or not (e.g. a “direct” exchange). If you don&#8217;t need the extra flexibility of a “topic” exchange, you can get more messages/sec routed if you choose the “direct” exchange type. So what are the types and how do they route?</p>
<p><strong>Fanout Exchange</strong> &#8211; No routing keys involved. You simply bind a queue to the exchange. Any message that is sent to the exchange is sent to <strong>all</strong> queues bound to that exchange. Think of it like a subnet broadcast. Any host on the subnet gets a copy of the packet. Fanout exchanges route messages the fastest.</p>
<p><strong>Direct Exchange</strong> &#8211; Routing keys are involved. A queue binds to the exchange to request messages that match a particular routing key <em>exactly</em>. This is a straight match. If a queue binds to the exchange requesting messages with routing key “<strong>dog</strong>”, only messages labelled “<strong>dog</strong>” get sent to that queue (not “<strong>dog.puppy</strong>”, not “<strong>dog.guard</strong>“…only “<strong>dog</strong>”).</p>
<p><strong>Topic Exchange</strong> &#8211; Matches routing keys against a pattern. Instead of binding with a particular routing key, the queue binds with a pattern string. The symbol <strong>#</strong> matches one or more words, and the symbol <strong>*</strong> matches any single word (no more, no less). So “<strong>audit.#</strong>” would match “<strong>audit.irs.corporate</strong>”, but “<strong>audit.*</strong>” would only match “<strong>audit.irs</strong>”. Our friends at RedHat have put together a great image to express how topic exchanges work:</p>
<div><a href="http://www.redhat.com/docs/en-US/Red_Hat_Enterprise_MRG/1.0/html/Messaging_Tutorial/sect-Messaging_Tutorial-Initial_Concepts-Topic_Exchange.html"><img src="http://www.redhat.com/docs/en-US/Red_Hat_Enterprise_MRG/1.0/html/Messaging_Tutorial/images/topic-exchange.png" border="0" alt="" width="549" height="295" /></a></div>
<p>Source: <a href="http://www.redhat.com/docs/en-US/Red_Hat_Enterprise_MRG/1.0/html/Messaging_Tutorial/sect-Messaging_Tutorial-Initial_Concepts-Topic_Exchange.html">Red Hat Messaging Tutorial: 1.3 Topic Exchange</a></p>
<p> </p>
<h2>Persistent little bugger…</h2>
<p>You spend all that time creating your queues, exchanges and bindings, and then <span class="caps">BANG</span>!…the server fries faster than the griddle at McDonald&#8217;s. All your queues, exchanges and bindings are there right? Oh geez…what about the messages in the queues you hadn&#8217;t serviced yet?</p>
<p>Relax, providing you created everything with the default arguments, it&#8217;s all gone…poof…whoosh…nada…nil. That&#8217;s right, RabbitMQ rebooted as empty as a baby&#8217;s noggin. You gotta redo everything kemosabe. How do you keep this from happening in the future?</p>
<p>On your queues and your exchanges there&#8217;s a creation-time flag called “durable”. There&#8217;s only one thing durable means in <span class="caps">AMQP</span>-land…the queue or exchange marked durable will be re-created automatically on reboot. It <strong>does not mean</strong> the messages in the queues will survive the reboot. They won&#8217;t. So how do we make not only our config but messages persist through a reboot?</p>
<p>Well the first question is, do you really want your messages to persist? For a message to last through a reboot, it has to be written to disk, and even a simple checkpoint to disk takes time. If you value message routing speed more than the contents of the message, don&#8217;t make your messages persistent. That being said, for our particular needs @ DigiTar, persistence is important.</p>
<p>When you publish your message to an exchange, there&#8217;s a flag called “Delivery Mode”. Depending on the <span class="caps">AMQP</span> library you&#8217;re using there will be different ways of setting it (we&#8217;ll cover the Python library later). But the long and the short of it is you want the “Delivery Mode” set to the value 2, which means “persistent”. “Delivery Mode” usually (depending on your <span class="caps">AMQP</span> library) defaults to a value of 1, which means “non-persistent”. So the steps for persistent messaging are:</p>
<ol>
<li>Mark the exchange “durable”.</li>
<li>Mark the queue “durable”.</li>
<li>Set the message&#8217;s “delivery mode” to a value of 2</li>
</ol>
<p>That&#8217;s it. Not really rocket science, but enough moving parts to make a mistake and send little Sally&#8217;s dental records into cyber-Nirvana.</p>
<p>There may be one thing nagging you though…what about the binding? We didn&#8217;t mark the binding “durable” when we created it. It&#8217;s alright. If you bind a durable queue to a durable exchange, RabbitMQ will automatically preserve the binding. Similarly, if you delete any exchange/queue (durable or not) any bindings that depend on it get deleted automatically.</p>
<p>Two things to be aware of:</p>
<ul>
<li>RabbitMQ will <strong>not</strong> allow you to bind a <strong>non-durable</strong> exchange to a <strong>durable</strong> queue, or vice-versa. Both the exchange and the queue must be durable for the binding operation to succeed.</li>
<li>You cannot change the creation flags on a queue or exchange after you&#8217;ve created it. For example, if you create a queue as “non-durable”, and want to change it to “durable”, the only way to do this is to destroy the queue and re-create it. It&#8217;s a good reason to double check your declarations.</li>
</ul>
<h2>Food for snakes</h2>
<p>A real empty area for <span class="caps">AMQP</span> usage is using it in Python programs. For other languages there are plenty of references:</p>
<ul>
<li>Java &#8211; <a href="http://www.rabbitmq.com/java-client.html">http://www.rabbitmq.com/java-client.html</a></li>
<li>.NET &#8211; <a href="http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf">http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf</a></li>
<li>Ruby &#8211; <a href="http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/">http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/</a></li>
</ul>
<p>But for little old Python, you need to dig it out yourself. So other folks don&#8217;t have to wander in the wilderness like I did, here&#8217;s a little primer on using Python to do the <span class="caps">AMQP</span>-tasks we&#8217;ve talked about:</p>
<p>First, you&#8217;ll need a Python <span class="caps">AMQP</span> library…and there are two:</p>
<ul>
<li><a href="http://barryp.org/software/py-amqplib/">py-amqplib</a> &#8211; General <span class="caps">AMQP</span> library</li>
<li><a href="https://launchpad.net/txamqp">txAMQP</a> &#8211; An <span class="caps">AMQP</span> library that uses the <a href="http://www.twistedmatrix.com">Twisted</a> framework, thereby allowing asynchronous I/O.</li>
</ul>
<p>Depending on your needs, py-amqplib or txAMQP may be more to your liking. Being Twisted-based, txAMQP holds the promise of building super performing <span class="caps">AMQP</span> consumers that use async I/O. But Twisted programming is a topic all its own…so we&#8217;re going to use py-amqplib for clarity&#8217;s sake. <strong>UPDATE: Please check the comments for example code showing use of txAMQP from Esteve Fernandez.</strong></p>
<p><span class="caps">AMQP</span> supports pipelining multiple MQ communication channels over one <span class="caps">TCP</span> connection, where each channel is a communication stream used by your program. Every <span class="caps">AMQP</span> program has at least one connection and one channel:</p>
<div id="wpshdo_1" class="wp-synhighlighter-outer"><div id="wpshdt_1" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_1"></a><a id="wpshat_1" class="wp-synhighlighter-title" href="#codesyntax_1"  onClick="javascript:wpsh_toggleBlock(1)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_1" onClick="javascript:wpsh_code(1)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_1" onClick="javascript:wpsh_print(1)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_1" onClick="javascript:wpsh_about(1)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_1" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;"><span class="kw1">from</span> amqplib <span class="kw1">import</span> client_0_8 <span class="kw1">as</span> amqp
conn = amqp.<span class="me1">Connection</span><span class="br0">&#40;</span>host=<span class="st0">&quot;localhost:5672 &quot;</span>, userid=<span class="st0">&quot;guest&quot;</span>,
    password=<span class="st0">&quot;guest&quot;</span>, virtual_host=<span class="st0">&quot;/&quot;</span>, insist=<span class="kw2">False</span><span class="br0">&#41;</span>
chan = conn.<span class="me1">channel</span><span class="br0">&#40;</span><span class="br0">&#41;</span></pre></div></div>
<p>Each channel is assigned an integer channel number automatically by the <strong>.channel()</strong> method of the <strong>Connection()</strong> class. Alternately, you can specify the channel number yourself by calling <strong>.channel(x)</strong> , where x is the channel number you want. More often than not, its a good idea to just let the <strong>.channel()</strong> method auto-assign the channel number to avoid collisions.</p>
<p>Now we&#8217;ve got a connection and channel to talk over. At this point, our code is going to diverge into two applications that use that same bit we&#8217;ve created so far: a consumer and the publisher. Let&#8217;s create the consumer app by creating a queue named “<strong>po_box</strong>” and an exchange named “<strong>sorting_room</strong>”:</p>
<div id="wpshdo_2" class="wp-synhighlighter-outer"><div id="wpshdt_2" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_2"></a><a id="wpshat_2" class="wp-synhighlighter-title" href="#codesyntax_2"  onClick="javascript:wpsh_toggleBlock(2)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_2" onClick="javascript:wpsh_code(2)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_2" onClick="javascript:wpsh_print(2)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_2" onClick="javascript:wpsh_about(2)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_2" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;">chan.<span class="me1">queue_declare</span><span class="br0">&#40;</span>queue=<span class="st0">&quot;po_box&quot;</span>, durable=<span class="kw2">True</span>,
    exclusive=<span class="kw2">False</span>, auto_delete=<span class="kw2">False</span><span class="br0">&#41;</span>
chan.<span class="me1">exchange_declare</span><span class="br0">&#40;</span>exchange=<span class="st0">&quot;sorting_room&quot;</span>, <span class="kw2">type</span>=<span class="st0">&quot;direct&quot;</span>, durable=<span class="kw2">True</span>,
    auto_delete=<span class="kw2">False</span>,<span class="br0">&#41;</span></pre></div></div>
<p>What did that do? First, it created a queue called “<strong>po_box</strong>” that is durable (will be re-created on reboot) and will not be automatically deleted when the last consumer detaches from it (<strong>auto_delete=False</strong>). It&#8217;s important to set <strong>auto_delete</strong> to false when making a queue (or exchange) durable, otherwise the queue itself will disappear when the last consumer detaches (regardless of the <strong>durable</strong> flag). Setting both <strong>durable</strong> and <strong>auto_delete</strong> to true, would make a queue that would be recreated only if RabbitMQ died unexpectedly with consumers still attached.</p>
<p>(You may have noticed there&#8217;s another flag specified called “<strong>exclusive</strong>”. If set to true, only the consumer that creates the queue will be allowed to attach to it. It&#8217;s a queue that is private to the creating consumer.)</p>
<p>There&#8217;s also the exchange declaration for the “<strong>sorting_room</strong>” exchange. <strong>auto_delete</strong> and <strong>durable</strong> mean the same things as they do in a queue declaration. However, <strong>.exchange_declare()</strong> introduces an argument called <strong>type</strong> that defines what type of exchange you&#8217;re making (as described earlier): <strong>fanout</strong>, <strong>direct</strong> or <strong>topic</strong>.</p>
<p>At this point, you&#8217;ve got a queue to receive messages and an exchange to publish them to initially…but we need a binding to link the two together:</p>
<div id="wpshdo_3" class="wp-synhighlighter-outer"><div id="wpshdt_3" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_3"></a><a id="wpshat_3" class="wp-synhighlighter-title" href="#codesyntax_3"  onClick="javascript:wpsh_toggleBlock(3)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_3" onClick="javascript:wpsh_code(3)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_3" onClick="javascript:wpsh_print(3)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_3" onClick="javascript:wpsh_about(3)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_3" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;">chan.<span class="me1">queue_bind</span><span class="br0">&#40;</span>queue=<span class="st0">&quot;po_box&quot;</span>, exchange=<span class="st0">&quot;sorting_room&quot;</span>,
      routing_key=<span class="st0">&quot;jason&quot;</span><span class="br0">&#41;</span></pre></div></div>
<p>The binding is pretty straight forward. Any messages arriving at the “<strong>sorting_room</strong>” exchange with the routing key “<strong>jason</strong>” gets routed to the “<strong>po_box</strong>” queue.</p>
<p>Now, there&#8217;s two methods of getting messages out of the queue. The first is to call <strong>chan.basic_get()</strong> to pull the next message off the queue (if there are no messages waiting on the queue, <strong>chan.basic_get()</strong> will return a None object&#8230;thereby blowing up the <strong>print msg.body</strong> code below if not trapped) :</p>
<div id="wpshdo_4" class="wp-synhighlighter-outer"><div id="wpshdt_4" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_4"></a><a id="wpshat_4" class="wp-synhighlighter-title" href="#codesyntax_4"  onClick="javascript:wpsh_toggleBlock(4)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_4" onClick="javascript:wpsh_code(4)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_4" onClick="javascript:wpsh_print(4)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_4" onClick="javascript:wpsh_about(4)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_4" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;">msg = chan.<span class="me1">basic_get</span><span class="br0">&#40;</span><span class="st0">&quot;po_box&quot;</span><span class="br0">&#41;</span>
<span class="kw1">print</span> msg.<span class="me1">body</span>
chan.<span class="me1">basic_ack</span><span class="br0">&#40;</span>msg.<span class="me1">delivery_tag</span><span class="br0">&#41;</span></pre></div></div>
<p>But what if you want your application to be notified as soon as a message is available for it? To do that, instead of <strong>chan.basic_get()</strong>, you need to register a callback for new messages using <strong>chan.basic_consume()</strong>:</p>
<div id="wpshdo_5" class="wp-synhighlighter-outer"><div id="wpshdt_5" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_5"></a><a id="wpshat_5" class="wp-synhighlighter-title" href="#codesyntax_5"  onClick="javascript:wpsh_toggleBlock(5)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_5" onClick="javascript:wpsh_code(5)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_5" onClick="javascript:wpsh_print(5)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_5" onClick="javascript:wpsh_about(5)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_5" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;"><span class="kw1">def</span> recv_callback<span class="br0">&#40;</span>msg<span class="br0">&#41;</span>:
     <span class="kw1">print</span> <span class="st0">'Received: '</span> + msg.<span class="me1">body</span>
chan.<span class="me1">basic_consume</span><span class="br0">&#40;</span>queue=<span class="st0">'po_box'</span>, no_ack=<span class="kw2">True</span>,
                callback=recv_callback, consumer_tag=<span class="st0">&quot;testtag&quot;</span><span class="br0">&#41;</span>
<span class="kw1">while</span> <span class="kw2">True</span>:
     chan.<span class="me1">wait</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
chan.<span class="me1">basic_cancel</span><span class="br0">&#40;</span><span class="st0">&quot;testtag&quot;</span><span class="br0">&#41;</span></pre></div></div>
<p><strong>chan.wait()</strong> is looped infinitely, which is what causes the channel to wait for the next message notification from the queue. <strong>chan.basic_cancel()</strong> is how you unregister your message notification callback. The argument specifies the <strong>consumer_tag</strong> you specified in the original <strong>chan.basic_consume()</strong> registration (that&#8217;s how it figures out which callback to unregister). In this case <strong>chan.basic_cancel()</strong> never gets called due to the infinite loop that precedes it…but you need to know about it, so it&#8217;s in the snippet.</p>
<p>The one additional thing you should pay attention to in the consumer is the <strong>no_ack</strong> argument. It&#8217;s accepted on both <strong>chan.basic_get()</strong> and <strong>chan.basic_consume()</strong> and defaults to false. When you grab a message off a queue, RabbitMQ needs you to explicitly acknowledge that you have it. If you don&#8217;t, RabbitMQ will re-assign the message to another consumer on the queue after a timeout interval (or on disconnect by the consumer that initially received it  without ack&#8217;ing it). If you set the <strong>no_ack</strong> argument to true, then <strong>py-amqplib</strong> will add a &#8220;no_ack&#8221; property to your AMQP request for the next message. That will instruct the AMQP server to not expect an acknowledgement for that get/consume. However, in most cases, you probably want to send the acknowledgement yourself (e.g. you need to put the message contents in a database before you acknowledge). Acknowledgements are done by caling the <strong>chan.basic_ack()</strong> method, using the <strong>delivery_tag</strong> property of the message you&#8217;re acknowledging as the argument (see the <strong>chan.basic_get()</strong> code snippet above for an example).</p>
<p>That&#8217;s all she wrote for the consumer. (Download: <a href="/jjww/code-samples/amqp_consumer.py">amqp_consumer.py</a>)</p>
<p>But what good is a consumer, if nobody is sending it messages? So you need a publisher. The code below will publish a simple message to the “<strong>sorting_room</strong>” exchange and mark it with the routing key “<strong>jason</strong>”:</p>
<div id="wpshdo_6" class="wp-synhighlighter-outer"><div id="wpshdt_6" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_6"></a><a id="wpshat_6" class="wp-synhighlighter-title" href="#codesyntax_6"  onClick="javascript:wpsh_toggleBlock(6)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_6" onClick="javascript:wpsh_code(6)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_6" onClick="javascript:wpsh_print(6)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_6" onClick="javascript:wpsh_about(6)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_6" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;">msg = amqp.<span class="me1">Message</span><span class="br0">&#40;</span><span class="st0">&quot;Test message!&quot;</span><span class="br0">&#41;</span>
msg.<span class="me1">properties</span><span class="br0">&#91;</span><span class="st0">&quot;delivery_mode&quot;</span><span class="br0">&#93;</span> = <span class="nu0">2</span>
chan.<span class="me1">basic_publish</span><span class="br0">&#40;</span>msg,exchange=<span class="st0">&quot;sorting_room&quot;</span>,routing_key=<span class="st0">&quot;jason&quot;</span><span class="br0">&#41;</span></pre></div></div>
<p>You may notice that we set the <strong>delivery_mode</strong> element of the message&#8217;s properties to “2”. Since the queue and exchange were marked durable, this will ensure the message is sent as persistent (i.e. will survive a reboot of RabbitMQ while it is in transit to the consumer).</p>
<p>The only other thing we need to do (and this needs to be done on both consumer and publisher apps), is close the channel and connection:</p>
<div id="wpshdo_7" class="wp-synhighlighter-outer"><div id="wpshdt_7" class="wp-synhighlighter-expanded"><table border="0" width="100%"><tr><td align="left" width="80%"><a name="#codesyntax_7"></a><a id="wpshat_7" class="wp-synhighlighter-title" href="#codesyntax_7"  onClick="javascript:wpsh_toggleBlock(7)" title="Click to show/hide code block">Code block</a></td><td align="right"><a href="#codesyntax_7" onClick="javascript:wpsh_code(7)" title="Show code only"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/code.png" /></a>&nbsp;<a href="#codesyntax_7" onClick="javascript:wpsh_print(7)" title="Print code"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png" /></a>&nbsp;<a href="#codesyntax_7" onClick="javascript:wpsh_about(7)" title="Show plugin information"><img border="0" style="border: 0 none" src="http://blogs.digitar.com/jjww/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif" /></a>&nbsp;</td></tr></table></div><div id="wpshdi_7" class="wp-synhighlighter-inner" style="display: block;"><pre class="python" style="font-family:monospace;">chan.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
conn.<span class="me1">close</span><span class="br0">&#40;</span><span class="br0">&#41;</span></pre></div></div>
<p>Pretty simple, no? (Download: <a href="/jjww/code-samples/amqp_publisher.py">amqp_publisher.py</a>)</p>
<h2>Giving it a shot…</h2>
<p>Now we&#8217;ve written our consumer and publisher, so let&#8217;s give it a go. (This assumes you have RabbitMQ <a href="http://www.rabbitmq.com/install.html">installed</a> and running on <strong>localhost</strong>.)</p>
<p>Open up the first terminal, and run <strong>python ./amqp_consumer.py</strong> to get the consumer running and to create your queues, exchanges and bindings.</p>
<p>Then run <strong>python ./amqp_publisher.py &#8220;AMQP rocks.&#8221;</strong> in a second terminal. If everything went well, you should see your message printed by the consumer on the first terminal.</p>
<h2>Taking it all in</h2>
<p>I realize this has been a really fast run through AMQP/RabbitMQ and using it from Python. Hopefully, it will fill in some of the holes of how all the concepts fit together and how they get used in a real Python program. If you find any errors in my write-up, I&#8217;d very much appreciate it if you&#8217;d please let me know (<a href="mailto:williamsjj@digitar.com">williamsjj@digitar.com</a>). Similarly, I&#8217;d be happy to answer any questions that I can. Next up&#8230;.clustering! But I&#8217;ve got to figure it out first. <img src='http://blogs.digitar.com/jjww/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p>NB: Special thanks to Barry Pederson and Gordon Sims for correcting my understanding of no_ack&#8217;s operation and for catching syntactically incorrect Python code I missed.</p>
<p>NB: My knowledge on the subject was distilled from these sources, which are excellent further reading:</p>
<ul>
<li><a href="http://">zeromq: Message-oriented Middleware Analysis</a></li>
<li><a href="http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf">RabbitMQ .NET Client Library User Guide </a></li>
<li><a href="http://jira.amqp.org/confluence/download/attachments/720900/amqp0-8.pdf?version=1">Advanced Message Queuing Protocol: Protocol Specification Version 0-8</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blogs.digitar.com/jjww/2009/01/rabbits-and-warrens/feed/</wfw:commentRss>
		<slash:comments>48</slash:comments>
		</item>
		<item>
		<title>Carpenters vs. general contractors</title>
		<link>http://blogs.digitar.com/jjww/2005/09/carpenters-vs-general-contractors/</link>
		<comments>http://blogs.digitar.com/jjww/2005/09/carpenters-vs-general-contractors/#comments</comments>
		<pubDate>Wed, 07 Sep 2005 12:06:29 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[Software Development]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[The past few years at DigiTar have been a heck of a learning curve for me as a developer&#8230;er&#8230;manager of developers. You graduate college with a CS degree and at the world you go&#8230;happy as a pig in stuff. Think you know everything you need. What you don&#39;t realize is that you don&#39;t know anything. [...]]]></description>
			<content:encoded><![CDATA[<p>The past few years at DigiTar have been a heck of a learning curve for me as a developer&#8230;er&#8230;manager of developers. You graduate college with a CS degree and at the world you go&#8230;happy as a pig in stuff. Think you know everything you need. What you don&#39;t realize is that you don&#39;t know anything. Sure you know data structures, and you can program in 5 different languages. You might even have built some pretty complex programs (or so it seemed at the time). But you don&#39;t know what you&#39;re talking about. Trust me.</p>
<p>You&#39;ve been trained as a carpenter essentially. A really <em>basic</em> carpenter. Sure you know what a hammer looks like and you&#39;ve framed up a door here and a wall there. The problems with what you don&#39;t know come to the surface when you go to build your first house. Or better yet, your first room. Up go the four walls, a door and a window&#8230;but the doors don&#39;t quite hang right and darn&#8230;you forgot to put in conduit for the electrical.</p>
<p>Nobody at &#8220;carpenter school&#8221; ever took you aside and showed you how to <em>think</em> about building a room. Best practices for hanging a door&#8230;laying out a window&#8230;nope&#8230;you haven&#39;t got any of that. The root issue of all the toe stubbing you&#39;re doing at your first job is that nobody ever taught you to build a room on paper first. So what do you do? You start building the room with wood and nails, and you quickly find that mistakes made in that medium are really expensive. It takes a lot of work to recover from the crooked doors. As for the bad framing&#8230;well you just gotta rip it out. The cruelest irony is that you don&#39;t know any better, so you probably spend a lot of years before somebody shows you a better way. </p>
<p>So what am I getting at with all the blathering? Computer Science programs don&#39;t teach their students how to build software (software engineering programs are a different story). They teach them how to be glorified hobbyist programmers, and then throw them into the buzz saw that is experience. For me, it was amazing how much more effective we became in a 6 month period by learning four best practices that school never taught me:</p>
<ul></p>
<li>Use version control.</li>
<p></p>
<li>Track your bugs formally.</li>
<p></p>
<li>Fix bugs before writing new code (you&#39;ll never hit a schedule otherwise).</li>
<p></p>
<li>Don&#39;t write a lick of code until you&#39;ve written a <a href="http://www.joelonsoftware.com/articles/fog0000000036.html">functional spec</a>.</li>
<p>
</ul>
<p>There are a <a href="http://www.joelonsoftware.com/articles/fog0000000043.html">few other important lessons</a> (i.e. unit tests) but those are the four that made the biggest impact. To paraphrase Steve McConnell, CS programs turn out scientists not engineers. Scientists build stuff that works in the lab, and is too darn frail to live in the real world. The software development world expects CS students to be engineers, but they&#39;re not. They don&#39;t receive any of the &#8220;thinking&#8221; training or apprenticing that would make them engineers. I personally owe a debt of gratitude to <a href="http://www.joelonsoftware.com/">Joel Spolsky</a> and <a href="http://www.construx.com/about/people/">Steve McConnell</a> for <a href="http://www.amazon.com/exec/obidos/tg/detail/-/1590593898/qid=1126114364/sr=8-1/ref=pd_bbs_1/002-0721634-2766455?v=glance&amp;s=books&amp;n=507846">writing</a> <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0321193679/qid=1126114409/sr=2-1/ref=pd_bbs_b_2_1/002-0721634-2766455?v=glance&amp;s=books">books</a> that filled in the gaping sink holes in my education.</p>
<p>Another thing to keep in mind with this whole carpenter metaphor is that its actually a lot worse than that! As a software developer, you can&#39;t just be a carpenter. You&#39;re also the general contractor. Everything from the framing to the electrical and the plumbing&#8230;.you&#39;re up slugger!</p>
<p>It seems to me that the CS program I went through (no I won&#39;t tell you where) needs at least two new courses. The first needs to be taught right after they teach you the first programming language, and simultaneous with your data structures course. Let&#39;s call it &#8220;Coding Habits&#8221; for total originality. Coding Habits would teach you how to lay out your program&#39;s functions, classes, etc. in English pseudocode before you do real code, and how to translate your &#8220;English&#8221; into real code. It would basically need to cover a slew of &#8220;best practices&#8221;. A course like that would have saved me a lot of hours in the rest of my CS courses&#8230;and its absolutely essential for doing professional software development. As for a course text, I&#39;d suggest <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0735619670/ref=pd_null_recs_b_i/002-0721634-2766455?v=glance&amp;s=books">Code Complete</a>. Take a look atTom&#39;s <a href="http://blogs.digitar.com/tmr/">Developer&#39;s Nightmare</a> blog for more on this topic. Tom is one of our very own, and has some very definite opinions on the subject.</p>
<p>The second course you need is the one I&#39;m more qualified to pontificate on&#8230;let&#39;s use the moniker &#8220;Software Projects&#8221;. Software Projects would work best as a second-semester Junior-level course. It&#39;s basic premise is how to go from an idea to a full-blown project. Topics would include writing, crystalizing your team&#39;s ideas, dealing with stakeholders (such as users) to winnow features, and the whole functional spec process. People and time management would need to feature prominently. Personally, I&#39;d probably teach off the <a href="http://www.joelonsoftware.com/articles/fog0000000043.html">Joel Test</a>. That&#39;s just me. There&#39;s certain things that only experience can teach, but let&#39;s give CS students a fighting chance. </p>
<p>All universities hope their graduates reach some sort of managment responsibility. They need tools to do that. Heck, if a student&#39;s first interviewer is worth their salt, he&#39;ll ask a few interesting questions regarding how they think about software development. The answers will be drastically improved after a course like Software Projects. At minimum the answers will be more interesting than &#8220;Uh&#8230;object-oriented programming rules!&#8221;</p>
<p>Any CS program that does a better job of training &#8220;engineers&#8221; instead of scientists does their students a great favor. They&#39;ll get better jobs, and the school will get more recognition&#8230;which feeds back into getting better jobs. I&#39;ll make this offer to any school amenable: DigiTar will teach an abridged version of both these courses over the spring break of your choice. You pick the students for each course. We&#39;ll teach them. In fact, we&#39;ll offer the top students jobs. Personally, I&#39;d rather see them learn what they need to know while they&#39;re still in school, and before we get &#39;em!</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.digitar.com/jjww/2005/09/carpenters-vs-general-contractors/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
