Blain Smith Zola 2026-04-16T00:00:00+00:00 https://blainsmith.com/atom.xml Unbounded Concurrency is a Foot Gun 2026-04-16T00:00:00+00:00 2026-04-16T00:00:00+00:00 Unknown https://blainsmith.com/articles/unbounded-concurrency-is-a-foot-gun/ <p>Bluesky went down for half its users for about 8 hours on Monday. Jim Calabro wrote up an <a rel="external" href="https://pckt.blog/b/jcalabro/april-2026-outage-post-mortem-219ebg2">excellent post-mortem</a> that is worth reading in full, but the root cause is something I keep seeing across languages and runtimes, and it's worth talking about on its own.</p> <p>One endpoint in their data plane, <code>GetPostRecord</code>, fanned out concurrent work based on the size of the incoming request. Every other endpoint in the system used bounded concurrency via <a rel="external" href="https://pkg.go.dev/golang.org/x/sync/errgroup#Group.SetLimit"><code>errgroup.SetLimit</code></a>. This one did not. A new internal client started sending batches of 15-20 thousand URIs at a time. That meant 15-20 thousand goroutines per request, each dialing memcached, each blowing through the idle connection pool, each leaving a <code>TIME_WAIT</code> socket behind until the box ran out of ephemeral ports. From there it cascaded into GC pressure, OOMs, and a death spiral that took most of a weekend to unwind.</p> <p>One missing line of code.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>group.</span><span style="color: #FFB454;">SetLimit</span><span>(</span><span style="color: #D2A6FF;">50</span><span>)</span><span style="color: #5A6673;font-style: italic;"> // &lt;-- this</span></span></code></pre><h2 id="the-real-problem-is-everywhere">The Real Problem is Everywhere</h2> <p>The specific bug is Go and <code>errgroup</code>, but the anti-pattern has nothing to do with Go. Any runtime that lets you cheaply spawn concurrent units of work lets you cheaply spawn too many of them. Some examples:</p> <ul> <li><strong>Go</strong>: <code>go func() { ... }()</code> in a loop over untrusted input. Goroutines are cheap but not free. The scheduler, the GC, and the kernel resources each one touches are not free either.</li> <li><strong>Node.js</strong>: <code>Promise.all(items.map(fetchOne))</code> where <code>items</code> came from a user. You just opened <code>len(items)</code> sockets at once. Congratulations.</li> <li><strong>Python</strong>: <code>asyncio.gather(*[work(x) for x in items])</code> or a <code>ThreadPoolExecutor</code> with no <code>max_workers</code>. Same shape, same outcome.</li> <li><strong>Erlang/Elixir</strong>: <code>Task.async_stream</code> without <code>max_concurrency</code> set, or <code>spawn</code> in a loop without a supervisor boundary. The BEAM will happily let you start a million processes.</li> <li><strong>Java</strong>: <code>CompletableFuture.allOf(...)</code> over a user-sized collection. The common ForkJoinPool is shared and it will get thrashed.</li> <li><strong>Rust</strong>: <code>futures::future::join_all</code> or <code>tokio::spawn</code> in a loop. The executor does not protect you from yourself.</li> </ul> <p>In every one of these, the code reads fine in review and the concurrency primitive is idiomatic. The problem only shows up when <code>items</code> gets big, and <code>items</code> is almost always something a client controls directly or indirectly.</p> <p>It gets worse since the entire point of spawning concurrent units of work is to perform I/O against another resource. Now that resource is being saturated with unbounded requests.</p> <h2 id="why-this-keeps-happening">Why This Keeps Happening</h2> <p>Unbounded concurrency is seductive because during development it looks like the fastest and cleanest option. You want to do N things in parallel, so you start N workers. No queue to set up, no worker count to pick, no backpressure to think about. The test suite runs with <code>N=10</code> and everything is fine. The staging environment runs with <code>N=100</code> and everything is fine. Production sees <code>N=20000</code> one Saturday morning and the whole thing falls over.</p> <p>The tradeoff gets papered over because the cost of an individual goroutine, promise, or task is tiny. It is the aggregate cost that kills you, and the aggregate is controlled by your input size, which you do not own. "Small and cheap per unit" is not the same as "free at any scale."</p> <h2 id="the-fix-is-older-than-go">The Fix is Older than Go</h2> <p>Fixed pool of workers, buffered channel of work, drop or block when the channel fills up. It is not a novel idea. It is the standard answer and has been for a long time. There are many ways to address this, but one way to do it in Go is:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> GetPostRecords</span><span>(</span><span style="color: #D2A6FF;">uris</span><span> []</span><span style="color: #39BAE6;">string</span><span>) ([]</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Post</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Pre-allocate the results slice. Each worker writes to its own index</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // so there&#39;s no contention and no need for a mutex.</span></span> <span class="giallo-l"><span> posts</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>([]</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Post</span><span>,</span><span style="color: #FFB454;"> len</span><span>(uris))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // The concurrency bound. This is the whole point of the pattern. No</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // matter how big `uris` is, we will never have more than `workers`</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // lookups in flight at once.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> const</span><span> workers</span><span style="color: #F29668;"> =</span><span style="color: #D2A6FF;"> 50</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Buffered channel of work. The buffer size is a small multiple of</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // the worker count to keep workers fed without letting the queue</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // grow unbounded. Values are indices into `uris` so the workers can</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // write results back to the right slot in `posts`.</span></span> <span class="giallo-l"><span> jobs</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">chan</span><span style="color: #39BAE6;"> int</span><span>, workers</span><span style="color: #F29668;">*</span><span style="color: #D2A6FF;">2</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Buffered error channel with capacity 1. We only care about the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // first error, so additional errors get dropped via the non-blocking</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // send below.</span></span> <span class="giallo-l"><span> errs</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">chan</span><span style="color: #39BAE6;"> error</span><span>,</span><span style="color: #D2A6FF;"> 1</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // WaitGroup so the main goroutine can tell when every worker has</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // finished draining the jobs channel.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> wg</span><span style="color: #59C2FF;"> sync</span><span>.</span><span style="color: #59C2FF;">WaitGroup</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Always add the number of workers you need right after the WaitGroup</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // is initialized and avoid doing wg.Add(1) within loops.</span></span> <span class="giallo-l"><span> wg.</span><span style="color: #FFB454;">Add</span><span>(workers)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Spin up a fixed number of workers. This is the bounded part.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Compare to the Bluesky bug, where the loop that spawned goroutines</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ran once per input URI.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> w</span><span style="color: #F29668;"> :=</span><span style="color: #D2A6FF;"> 0</span><span style="color: #BFBDB6B3;">;</span><span> w</span><span style="color: #F29668;"> &lt;</span><span> workers</span><span style="color: #BFBDB6B3;">;</span><span> w</span><span style="color: #F29668;">++</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> go func</span><span>() {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Always, always, always put this immediately at the top of the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // spawned unit of work that way you know you have this covered</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // and during code reviews others will also begin to notice when</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // it is not present.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> defer</span><span> wg.</span><span style="color: #FFB454;">Done</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Each worker pulls indices off the jobs channel until it&#39;s</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // closed and drained. `range` over a channel handles both</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // the receive and the close-detection for us.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> i</span><span style="color: #F29668;"> :=</span><span style="color: #FF8F40;"> range</span><span> jobs {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // This is the expensive I/O work and all this setup ensures that</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // this work does not also get hammered by user input.</span></span> <span class="giallo-l"><span> post, err</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> lookup</span><span>(uris[i])</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Non-blocking send: if the error channel already</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // holds an error, skip. Prevents workers from</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // deadlocking on an unbuffered send when multiple</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // errors happen.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> select</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span> errs</span><span style="color: #F29668;"> &lt;-</span><span> err:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> default</span><span>:</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Safe to write without a lock because each worker</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // owns a unique index `i` for the duration of this</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // iteration.</span></span> <span class="giallo-l"><span> posts[i]</span><span style="color: #F29668;"> =</span><span> post</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }()</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Feed the workers. Sends block once the buffer fills, which is</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // exactly the backpressure we want. The producer can&#39;t outrun the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // pool.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> i</span><span style="color: #F29668;"> :=</span><span style="color: #FF8F40;"> range</span><span> uris {</span></span> <span class="giallo-l"><span> jobs</span><span style="color: #F29668;"> &lt;-</span><span> i</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Close the channel to signal &quot;no more work.&quot; Workers see the close,</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // exit their `range` loop, and call wg.Done().</span></span> <span class="giallo-l"><span style="color: #FFB454;"> close</span><span>(jobs)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Block until every worker has returned.</span></span> <span class="giallo-l"><span> wg.</span><span style="color: #FFB454;">Wait</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Surface the first error if there was one, otherwise return the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // results. Non-blocking receive so we don&#39;t hang if no error was</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ever sent.</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> select</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span> err</span><span style="color: #F29668;"> := &lt;-</span><span>errs:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> default</span><span>:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> posts,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p><code>errgroup.SetLimit(50)</code> collapses most of that boilerplate, which is why the Bluesky fix was one line. The particular API does not matter. What matters is that the number of in-flight operations is bounded by the server and not by the caller.</p> <p>If the channel fills up you have to either block and apply backpressure, or drop the work and return an error. Both are reasonable. Letting the caller dictate how much work you do is not.</p> <h2 id="what-to-look-for">What to Look For</h2> <p>When reviewing code, the pattern is easy to spot once you are looking for it. Any loop that spawns concurrent work and whose iteration count comes from input. Any <code>gather</code>, <code>join_all</code>, <code>Promise.all</code>, <code>Task.async_stream</code> that takes a user-sized list. Any comment that says "we just parallelize the lookups" with no worker count in sight.</p> <p>Ask the author one question: "What is the maximum number of concurrent operations this can produce, and is that number bounded by the server or by the request?" If they cannot answer, or the answer is "the request," that is the bug. It might not fire this quarter. It will fire the first time a client sends you something bigger than you planned for.</p> <h2 id="one-more-thing">One More Thing</h2> <p>The Bluesky post-mortem mentions that logging inside the hot path made the death spiral worse. Millions of blocking <code>write(2)</code> calls per second caused the Go runtime to spawn a pile of OS threads, which tanked the GC, which caused OOMs, which restarted the process into the same <code>TIME_WAIT</code>-saturated state it had just died from. Every layer of the stack had a foot gun of its own and they all went off at the same time.</p> <p>The meta-lesson is the same one. Any resource whose consumption scales with input size and is not explicitly bounded is a ticking bomb. Goroutines, sockets, log writes, connections, database queries. Pick a bound, put it in code, and flag it in code review when you see it missing.</p> <p>Unbounded concurrency is not a clever shortcut. It is a loaded gun you have pointed at your production environment and handed to whoever happens to send you the next request.</p> Wired for Fairness 2026-04-13T00:00:00+00:00 2026-04-13T00:00:00+00:00 Unknown https://blainsmith.com/essays/wired-for-fairness/ <p>I asked my son if he could learn to build anything, what would he want to build. He is twelve years old and autistic and he does not elaborate unless you ask him to. He said, "I want to measure the temperature outside." That was the whole answer. No explanation, no context, no story about why. Just the thing itself.</p> <p>So I asked the follow-up questions, because that is how we talk. "Do you want to see that on your phone, or something else? Like, a sensor outside and then you read the number on something inside?" He said yes. That was enough. We are going to build a thermostat together.</p> <p>This is how every conversation with my son has gone for twelve years. He is very smart, but his verbal skills are something he has to work at constantly in order to get what is in his head out into words someone else can follow. The ideas are there and they have always been there. Reaching them means asking the right questions and waiting.</p> <p>He does not want an app or a platform. He wants to understand how a temperature gets measured, how that number travels somewhere, and how it shows up on a screen. I recognize that impulse from thirty years ago when I sat in front of my first computer and wanted the same thing: to understand how something works and to build something that made sense and did what it was supposed to do.</p> <p>I also think about what kind of world he is going to do that in, and I am afraid.</p> <p>+++</p> <p>In 2020, during the pandemic shutdown, I was in the worst depression of my life. The world had closed. Routine, which for an autistic person is load-bearing architecture, had collapsed. I was raising my son alone, trying to keep him stable while I could barely keep myself upright. I was not sleeping well. I was not eating well. I had stopped training, which for most of my adult life had been the one reliable place where my body and my mind worked together instead of against each other.</p> <p>In the middle of this, a client asked me to fabricate user traffic. They wanted the numbers to show ten times more users than actually existed. The purpose was to inflate metrics for investors, to make growth look real when it was not. They presented this casually, as a business decision, as something that simply needed to get done.</p> <p>I could not do it. My body would not let me. There was no weighing of pros and cons. The request hit something inside me that I did not have a name for at the time, something that responded to dishonesty and exploitation the way a nerve responds to a burn. The objection was visceral, immediate, and total. I shut down. I could not think about anything else. I could not work on anything else. The collision of being already depressed, already fragile, already running on fumes, with a request that violated something fundamental in how I understood my work and my responsibility to the people who would be deceived by those numbers, broke something in me that took years to rebuild.</p> <p>That moment did not just feel like it changed me. It did change me. Research in neuroplasticity has established that chronic stress and depression cause structural changes in the brain. Sustained exposure to distress reduces dendrites and synaptic connections in the hippocampus and prefrontal cortex, the regions responsible for memory, learning, and decision-making. The amygdala, which processes fear and threat, becomes hyperactive. Neural pathways are built by experience, the same way learning builds them. Cells that fire together wire together, and the reverse is equally true: sustained distress degrades and reshapes the pathways you spent your career building. The brain does not merely experience trauma. It encodes it into its physical structure. New pathways form around the damage, and those pathways carry the weight of what happened into everything that comes after.</p> <p>I did not know any of this at the time. I only knew that I was broken and that I had a son who needed me not to be. So I forced myself to sleep. I forced myself to eat. Eventually I forced myself back under a barbell because that was the only place left where the rules were simple: pick the weight up, put it down, do it again. The body remembers discipline even when the mind has forgotten it.</p> <p>+++</p> <p>The wiring that made that moment so destructive was already there long before the pandemic. It was there before I ever wrote a line of professional code. I just did not have language for it until much later.</p> <p>Research over the past decade has identified a trait pattern common among many autistic people that psychologists now call justice sensitivity. It describes a heightened awareness of fairness, an intense emotional response to perceived injustice, and a strong drive to correct wrongs even when doing so is personally costly. This is not universal to every autistic person, and the ways it presents vary widely across the spectrum. But the pattern is well-documented and consistent enough that researchers have studied it across multiple populations and methodologies.</p> <p>A study published in Molecular Autism by Simon Baron-Cohen and colleagues examined moral foundations in autistic people and people with systemizing cognitive types. They found that people with systemizing minds, a cognitive profile that overlaps significantly with autism, scored higher on Fairness as a moral foundation than people with empathizing cognitive types. Fairness in this context is defined by rule-based reasoning: the application of consistent principles regardless of social context, personal benefit, or group loyalty. The study found that this commitment to fairness in systemizing minds was linked to reasoning rather than social conformity, that the moral judgment was driven by internal principle rather than external pressure.</p> <p>Al-Attar and Worthington, writing in Advances in Autism, went further. They examined how moral distress and moral injury operate in the context of autism. Moral injury is a clinical term for psychological damage caused by witnessing or participating in acts that violate a person's deeply held moral beliefs. They hypothesized that autistic individuals may have a lower threshold for moral distress, and that the heightened need for rules and the acute distress caused by rule violations can make autistic people more prone to moral injury. For autistic people with high justice sensitivity, exposure to dishonesty, exploitation, or systemic unfairness does not produce a passing discomfort. It produces genuine harm.</p> <p>This matters because the trait researchers are describing operates closer to a reflexive response than a preference or opinion. Many autistic people report that they cannot compartmentalize injustice the way neurotypical peers seem to. Where someone else might notice something unfair, feel briefly bothered, and move on, an autistic person with high justice sensitivity may find the violation lodged in their thinking, returning unbidden, producing physiological stress responses that interfere with sleep, focus, and daily functioning. The brain does not treat the violation as resolved just because the workday ended. It keeps processing and signaling that something is wrong.</p> <p>The wiring works and when applied to the right environment, it produces people who build honest systems, who fight for users over metrics, who refuse to ship something they know is harmful, who care about craft and correctness because those things are expressions of respect for the people on the other side of the screen. Applied to the wrong environment, it produces burnout, depression, withdrawal, and rage.</p> <p>The environment has been getting worse for a long time.</p> <p>+++</p> <p>I have been writing software professionally for nearly three decades. For most of that time, the work felt aligned with something I cared about. I built systems that connected people to games they loved, that delivered video to devices in living rooms, that helped workers stay safe, that moved packets across networks so other engineers could build things on top of them. The work was hard and the organizations were imperfect, but the underlying purpose was legible. You could see the person on the other end. You could understand how what you built served them.</p> <p>Somewhere in the last ten to fifteen years, that alignment broke. I watched the industry shift from building things people needed to building things that extracted from people as efficiently as possible. Engagement metrics replaced user satisfaction. Growth at all costs replaced sustainable design. The people making decisions about what got built were increasingly people who had never written code or debugged a production system, who had never sat across from a user and watched them struggle with something they made. They understood spreadsheets and fundraising decks and quarterly targets. They did not understand, or did not care, what their products did to the people using them.</p> <p>For someone wired the way I am, this shift was a slow-moving injury. Meetings where someone prioritized vanity metrics over real user needs left a mark. Architecture decisions driven by "how fast can we ship this" rather than "how will this serve the person using it" accumulated. Pushing back and getting overruled, or worse, getting the dead-eyed look that told me the person across the table did not even understand what I was objecting to, made the cost go up every time.</p> <p>Autistic burnout is a condition that researchers are only now beginning to study rigorously. A systematic review published in Clinical Psychology Review by Ali, Bougoure, Cooper, and colleagues pulled together findings across multiple studies and confirmed that autistic burnout is distinct from occupational burnout. It arises from sensory and social overwhelm, from the cognitive demands of masking and camouflaging autistic traits in professional environments, and from sustained conflict between a person's internal experience and the external world's demands. People in burnout describe being entirely drained of energy, losing previously acquired abilities, experiencing heightened sensory sensitivity, and withdrawing from social contact. One participant in an earlier study described it as a complete regression of skills.</p> <p>I recognize every word of that description. I have lived it more than once. The version that hit during the pandemic was the worst because it combined the isolation of a global shutdown with the moral injury of being asked to participate in fraud while my capacity to absorb any additional harm was already at zero.</p> <p>What people who have not experienced autistic burnout may not understand is what the withdrawal looks like from the inside. The mind reaches a threshold where it can no longer sustain the gap between what it knows to be right and what it is being asked to do, between who you are and who the environment demands you pretend to be. The masking research confirms this: the sustained effort of passing as neurotypical in professional settings, of suppressing your natural responses, of performing social scripts that do not come naturally, is cognitively exhausting. It drains the same resources you need to actually do your job. Add moral injury on top of that cognitive load and the system crashes.</p> <p>I stopped training, building side projects, and reading technical papers. For a period of time I stopped doing anything beyond the minimum required to keep my son fed, safe, and stable. Without him I do not know where the bottom would have been.</p> <p>Recovery was mechanical. Sleep. Food. Eventually the barbell again. Enough days in a row of doing the basic things that the pathways started to rebuild. Not the same pathways since the brain does not restore to a backup. It builds new ones, and the new ones carry the scar tissue of what happened. I am not the same engineer I was before that period. I am more guarded, more selective about who I work with and what I work on. I am faster to walk away from situations that trip the wiring. Some people might call that growth. It feels more like adaptation.</p> <p>+++</p> <p>If you have read this far and recognized yourself in any of it, I want you to know you are not imagining things and you are not too sensitive. The thing you feel when you watch a company optimize for addiction over wellbeing, when you see a colleague ship something they know is broken because the deadline matters more than the user, when you sit in a meeting and realize that nobody else in the room is bothered by what is being proposed, that thing is real. It has a name and a neurological basis, and it carries a cost that accumulates whether you acknowledge it or not.</p> <p>There is a pattern among neurodivergent people in technology that does not get talked about enough. Many of us entered this field because it seemed like a place where the things we valued, precision, honesty, systems that work the way they are supposed to, fairness encoded into logic, were also the things the field valued. Programming rewarded the way our minds worked. Building systems that were correct, reliable, and honest felt like an extension of who we were. The alignment between our wiring and our work was not just professional satisfaction. It was a rare experience of belonging for people who had spent most of their lives not belonging anywhere.</p> <p>The erosion of that alignment is what makes the current state of technology so specifically damaging to neurodivergent engineers. The work did not get harder. Hard work was never the problem. The purpose of the work changed. The field we entered because it valued what we valued started valuing something else, and the thing it started valuing is in direct conflict with the wiring that brought us here.</p> <p>Research on justice sensitivity in neurodivergent populations consistently finds that autistic people's moral reasoning is resistant to peer pressure. Where neurotypical individuals often shift their moral positions when confronted by social consensus, autistic individuals tend to hold steady. Their sense of right and wrong is built on internal principle, not social negotiation. In a healthy environment this produces people of extraordinary integrity. In a toxic one, it produces people who cannot adapt to the toxicity and burn out instead.</p> <p>If you are neurodivergent and working in technology and you have burned out, or you are burning out right now, consider that the problem might not be your resilience. The problem might be that you are responding correctly to an environment that has become genuinely hostile to the values that define how your mind works. Your response is information, and you should treat it that way.</p> <p>+++</p> <p>My son is twelve. My stepdaughter is eleven. In ten years they will be young adults navigating a world that I helped build and that I am increasingly afraid of.</p> <p>My son's school issued him a Chromebook. It is a Google device running Google software connected to Google services that track what he does, when he does it, and how long it takes. He did not choose this. His parents did not choose this. The school district chose it because Google made the devices cheap and the software free, and the actual cost, his behavioral data, his attention patterns, his developing digital identity, is paid by him in a currency he does not yet understand. He is twelve. He cannot opt out. The Chromebook is required for class.</p> <p>This is one device in one school in one district. Scale it out.</p> <p>Algorithmic feeds on platforms that children are socially required to participate in, TikTok, YouTube, Instagram, are engineered to maximize time on screen. The optimization target is not the child's wellbeing, education, or development. The optimization target is engagement, which in practice means emotional provocation, because provoked people scroll longer. A twelve-year-old autistic boy with high justice sensitivity being fed a stream of content algorithmically selected to provoke emotional reactions is a resource being extracted from, not a user being served.</p> <p>AI systems are being marketed into education as tutors, assistants, and companions. Companies are positioning AI chatbots as tools that children should interact with for learning, for emotional support, for socialization. A generation of children is being trained to treat a statistical model as a conversational partner, to trust the output of a system that has no understanding of truth, no commitment to their wellbeing, and no accountability for the consequences of what it tells them. The children are not the customers. They are the training data.</p> <p>AI-generated content is flooding the information environment with synthetic text, images, and video. A twelve-year-old trying to research a school project now has to navigate an internet where a significant and growing percentage of the content was not created by a human being, was not reviewed by a human being, and may contain fabricated information presented with the visual authority of legitimate sources. Adults struggle to distinguish real from synthetic. We are asking children to do it as a baseline life skill.</p> <p>The standard response to all of this is "digital literacy." Teach kids to think critically about what they see online. Teach them to evaluate sources. Teach them to recognize manipulation. All of that is fine, and insufficient. Teaching someone defensive driving does not help when the highway itself is designed to crash them. The systems are adversarial, designed by teams of engineers and psychologists to defeat exactly the kind of critical thinking that digital literacy programs try to instill. You cannot educate your way out of an arms race when one side has billions of dollars and the other side is a sixth grader.</p> <p>The thread that runs through all of this is isolation. Chromebooks replace shared physical workspaces with individual screens. Algorithmic feeds turn shared social experience into personalized content silos. AI companions stand in for human relationships with synthetic ones. Every layer of technology being introduced into children's lives is a layer that moves them further from direct human contact, from the messy, unpredictable, irreplaceable experience of being with other people.</p> <p>My son said he wants to measure the temperature outside. Six words. But those six words carry the same impulse that built the technology industry in the first place: the need to understand how something works, to see the whole system from sensor to wire to screen. That impulse produced engineers who cared about craft, about correctness, about the people who would use what they built. It is the impulse that the current trajectory of technology is systematically grinding out of the next generation by replacing understanding with consumption, building with scrolling, and human connection with algorithmic engagement.</p> <p>I am afraid that my son will grow up in a world that takes his beautiful autistic mind, his need for fairness, his drive to understand systems honestly, and treats those qualities as friction to be optimized away. He might learn to trust platforms because they are ubiquitous and assume they are benevolent because they are free. The false sense of security that comes from living inside technology you did not build and do not understand could become the default state of his generation. By the time he realizes what he has given up, the alternatives may be harder to find.</p> <p>+++</p> <p>I am more on the outside of the industry now than I have been at any point in my career, and from the outside I can see things I could not see when I was in the middle of it.</p> <p>I can see that the path we are on was built by human decisions and can be changed by human decisions. There is nothing inevitable about the current trajectory. Every platform, every algorithm, every exploitative design pattern was chosen by people who could have chosen differently. The choices are the problem. And choices can be refused.</p> <p>I choose differently now. At home, I built a central digital dashboard for my family on a Raspberry Pi running open source software. It shows our shopping list, the weather, the information we actually need. There is no platform. There is no account. There is no company collecting data on what my family buys or when we check the forecast. It is a computer doing what a computer is supposed to do: serving the people who use it, not the people who built it.</p> <p>My son asked to build a thermostat because he saw the dashboard and understood something without needing to say it in a lot of words: technology does not have to be opaque. It does not have to phone home. It does not have to belong to someone else. He is twelve years old and he already knows something that billion-dollar companies are hoping his generation never figures out: that you can build your own tools, that the tools can be simple, that the tools can belong to you.</p> <p>I choose the clients I work with based on whether their work serves people honestly, I choose tools without platforms when the option exists, and I teach my family that technology is something you build and understand rather than something you consume and trust. These are small choices. They do not scale. They do not disrupt anything. They are acts of refusal so small that the systems I am refusing would not notice if I described them in a board meeting.</p> <p>But refusal is where it starts.</p> <p>Every person who chooses to build instead of consume, to understand instead of trust blindly, to walk away from work that violates their sense of what technology should be for, is a crack in the consensus that this is just how things are now. The consensus is a lie. Humans built this path, humans maintain it, and humans can build a different one.</p> <p>Unfortunately, I am not optimistic about the industry. I have seen too much to believe that the companies driving the current trajectory will change direction because someone wrote a persuasive essay or because regulators caught up. They will not bwcause the incentives are too strong and the money is too good and the people making the decisions are too insulated from the consequences.</p> <p>But I am not writing this for the industry. I am writing this for the person who recognizes themselves in what I have described. The autistic engineer who burned out and thought it was their fault. The neurodivergent parent who watches their kid on a school-issued device and feels something wrong they cannot articulate. The person who cares about fairness in a world that has decided fairness is not profitable.</p> <p>You are not broken. The thing you feel when something is wrong is a signal, and it is accurate. The cost of carrying that signal in an environment that punishes you for receiving it is real, and you are not imagining it.</p> <p>Do not stop fighting for yourself and for yours. Build the thermostat. Build the dashboard. Teach your kids that the tools can belong to them. Choose the work that lets you sleep at night. Walk away from the work that does not. Find the people who are wired like you and hold onto them, because the isolation is the thing that breaks you and the connection is the thing that keeps you whole.</p> <p>At the end of the day, you are the only one you can truly count on. That is the foundation everything else gets built on. Remember the plaque hanging above the Oracle's kitchen doorway.</p> <blockquote> <p>Temet Nosce: "It's Latin. It means 'know thyself'." - Oracle</p> </blockquote> <p>Trust what your mind is telling you. And refuse, as many times as it takes, to participate in systems that violate what you know to be right.</p> <p>The systems we build should support and serve the people who use them. A twelve-year-old who said "I want to measure the temperature outside" understands that instinctively. The fact that an industry of adults has forgotten it makes insisting matter more, not less.</p> <p>+++</p> <h2 id="references">References</h2> <p>[1]: Greenberg, Y.D.M., Holt, R., Allison, C., Smith, P., Newman, R., Boardman-Pretty, T., Haidt, J., &amp; Baron-Cohen, S. (2024). Moral foundations in autistic people and people with systemizing minds. <em>Molecular Autism</em>, 15, 20. https://doi.org/10.1186/s13229-024-00591-8</p> <p>[2]: Al-Attar, Z. &amp; Worthington, R. (2024). Moral distress and moral injury in the context of autism. <em>Advances in Autism</em>, 10(3), 200-219. https://doi.org/10.1108/AIA-05-2023-0025</p> <p>[3]: Ali, D., Bougoure, M., Cooper, B., Quinton, A.M.G., Tan, D., Brett, J., Mandy, W., Maybery, M., Magiati, I., &amp; Happe, F. (2025). Burnout as experienced by autistic people: A systematic review. <em>Clinical Psychology Review</em>, 122, 102669. https://doi.org/10.1016/j.cpr.2025.102669</p> <p>[4]: Raymaker, D.M., Teo, A.R., Steckler, N.A., Lentz, B., Scharer, M., Delos Santos, A., Kapp, S.K., Hunter, M., Joyce, A., &amp; Nicolaidis, C. (2020). "Having all of your internal resources exhausted beyond measure and being left with no clean-up crew": Defining autistic burnout. <em>Autism in Adulthood</em>, 2(2), 132-143. https://doi.org/10.1089/aut.2019.0079</p> <p>[5]: Mantzalas, J., Richdale, A.L., Li, X., &amp; Dissanayake, C. (2024). Measuring and validating autistic burnout. <em>Autism Research</em>, 17(5), 812-823. https://doi.org/10.1002/aur.3129</p> <p>[6]: Rădulescu, I., Drăgoi, A.M., Trifu, S.C., &amp; Cristea, M.B. (2021). Neuroplasticity and depression: Rewiring the brain's networks through pharmacological therapy (Review). <em>Experimental and Therapeutic Medicine</em>, 22(4), 1131. https://doi.org/10.3892/etm.2021.10565</p> Building Go APIs with Huma, sqlc, and Goose 2026-04-07T00:00:00+00:00 2026-04-07T00:00:00+00:00 Unknown https://blainsmith.com/articles/building-go-apis-with-huma-slqc-and-goose/ <p>I've been building an API recently with a stack that I think hits a sweet spot of just enough structure to keep a team productive, not so much that you're fighting the tools. The stack is <a rel="external" href="https://huma.rocks">Huma</a> for the HTTP layer, <a rel="external" href="https://sqlc.dev">sqlc</a> for database queries, and <a rel="external" href="https://github.com/pressly/goose">Goose</a> for migrations. Each tool does one thing well and stays out of your way for everything else.</p> <p>The ideas is simple. Write SQL for your schema and queries to generate the Go code. Then, wire it into typed HTTP operations that produce an OpenAPI spec automatically. This avoids an ORM and its magic, especially no runtime reflection surprises. Also, a new engineer can clone the repo and understand the full request path in an afternoon.</p> <h2 id="project-layout">Project Layout</h2> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-api/</span></span> <span class="giallo-l"><span>├── cmd/</span></span> <span class="giallo-l"><span>│ └── server/</span></span> <span class="giallo-l"><span>│ └── main.go</span></span> <span class="giallo-l"><span>├── sql/</span></span> <span class="giallo-l"><span>│ ├── migrations/</span></span> <span class="giallo-l"><span>│ │ ├── 001_create_widgets.sql</span></span> <span class="giallo-l"><span>│ │ └── 002_add_widget_status.sql</span></span> <span class="giallo-l"><span>│ └── queries/</span></span> <span class="giallo-l"><span>│ └── widgets.sql</span></span> <span class="giallo-l"><span>├── db/</span></span> <span class="giallo-l"><span>│ ├── db.go</span></span> <span class="giallo-l"><span>│ ├── models.go</span></span> <span class="giallo-l"><span>│ └── widgets.sql.go</span></span> <span class="giallo-l"><span>├── sqlc.yaml</span></span> <span class="giallo-l"><span>├── go.mod</span></span> <span class="giallo-l"><span>└── Makefile</span></span></code></pre> <p>There is nothing novel here. All SQL lives under <code>sql/</code> with migrations and queries separated into their own subdirectories. sqlc dumps its generated Go code into <code>db/</code> which becomes your importable package. The <code>db/</code> directory is checked into version control so the project builds without needing sqlc installed, but it's also <code>.gitignore</code>-able if your team prefers to generate on CI. Pick one and be consistent.</p> <h2 id="migrations-with-goose">Migrations with Goose</h2> <p>Goose handles schema changes with plain SQL files. Each migration has an <code>-- +goose Up</code> and <code>-- +goose Down</code> block. There is no additional cognitive overhead to understand some migration DSL. It is just SQL.</p> <p><strong>sql/migrations/001_create_widgets.sql</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="sql"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- +goose Up</span></span> <span class="giallo-l"><span style="color: #FF8F40;">CREATE TABLE</span><span style="color: #FFB454;"> widgets</span><span> (</span></span> <span class="giallo-l"><span> id </span><span style="color: #FF8F40;">BIGSERIAL PRIMARY KEY</span><span>,</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> name TEXT NOT NULL</span><span>,</span></span> <span class="giallo-l"><span> created_at </span><span style="color: #FF8F40;">TIMESTAMPTZ NOT NULL DEFAULT now</span><span>()</span></span> <span class="giallo-l"><span>);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- +goose Down</span></span> <span class="giallo-l"><span style="color: #FF8F40;">DROP TABLE</span><span> widgets;</span></span></code></pre> <p><strong>sql/migrations/002_add_widget_status.sql</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="sql"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- +goose Up</span></span> <span class="giallo-l"><span style="color: #FF8F40;">ALTER TABLE</span><span> widgets </span><span style="color: #FF8F40;">ADD</span><span> COLUMN </span><span style="color: #FF8F40;">status TEXT NOT NULL DEFAULT</span><span style="color: #AAD94C;"> &#39;active&#39;</span><span>;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- +goose Down</span></span> <span class="giallo-l"><span style="color: #FF8F40;">ALTER TABLE</span><span> widgets </span><span style="color: #FF8F40;">DROP</span><span> COLUMN </span><span style="color: #FF8F40;">status</span><span>;</span></span></code></pre> <p>Run them forward with <code>goose -dir sql/migrations postgres "$DATABASE_URL" up</code> and roll them back with <code>down</code>. That's it. The migration files are also your schema source of truth, which matters for the next step.</p> <h2 id="queries-with-sqlc">Queries with sqlc</h2> <p>sqlc reads your schema, reads your annotated SQL queries, and generates type-safe Go code. The key decision here is pointing sqlc at the <code>sql/migrations</code> directory for its schema instead of maintaining a separate <code>schema.sql</code> file.</p> <p><strong>sqlc.yaml</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #39BAE6;">version</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;2&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;">sql</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> engine</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;postgresql&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> queries</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;sql/queries&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> schema</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;sql/migrations&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> gen</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> go</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> package</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;db&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;db&quot;</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> sql_package</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;pgx/v5&quot;</span></span></code></pre> <p>Setting <code>schema</code> to <code>sql/migrations</code> tells sqlc to read every <code>.sql</code> file in that directory, parse the goose annotations, and build up the full schema from the migration history. This means adding a column in a new migration automatically makes it available to your queries without touching any config. sqlc understands the goose format natively so the <code>-- +goose Up</code> and <code>-- +goose Down</code> blocks are handled correctly.</p> <p>Now write some queries.</p> <p><strong>sql/queries/widgets.sql</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="sql"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- name: GetWidget :one</span></span> <span class="giallo-l"><span style="color: #FF8F40;">SELECT</span><span style="color: #F29668;"> *</span><span style="color: #FF8F40;"> FROM</span><span> widgets</span></span> <span class="giallo-l"><span style="color: #FF8F40;">WHERE</span><span> id </span><span style="color: #F29668;">=</span><span> $</span><span style="color: #D2A6FF;">1</span><span style="color: #FF8F40;"> LIMIT</span><span style="color: #D2A6FF;"> 1</span><span>;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- name: ListWidgets :many</span></span> <span class="giallo-l"><span style="color: #FF8F40;">SELECT</span><span style="color: #F29668;"> *</span><span style="color: #FF8F40;"> FROM</span><span> widgets</span></span> <span class="giallo-l"><span style="color: #FF8F40;">ORDER BY</span><span> created_at </span><span style="color: #FF8F40;">DESC</span><span>;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- name: CreateWidget :one</span></span> <span class="giallo-l"><span style="color: #FF8F40;">INSERT INTO</span><span> widgets (</span><span style="color: #FF8F40;">name</span><span>, </span><span style="color: #FF8F40;">status</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">VALUES</span><span> ($</span><span style="color: #D2A6FF;">1</span><span>, $</span><span style="color: #D2A6FF;">2</span><span>)</span></span> <span class="giallo-l"><span>RETURNING </span><span style="color: #F29668;">*</span><span>;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">-- name: DeleteWidget :exec</span></span> <span class="giallo-l"><span style="color: #FF8F40;">DELETE FROM</span><span> widgets</span></span> <span class="giallo-l"><span style="color: #FF8F40;">WHERE</span><span> id </span><span style="color: #F29668;">=</span><span> $</span><span style="color: #D2A6FF;">1</span><span>;</span></span></code></pre> <p>Run <code>sqlc generate</code> and you get <code>db/</code> with models that match your schema and methods that match your queries. The generated <code>Queries</code> struct accepts a <code>pgx</code> connection and gives you methods like <code>GetWidget(ctx, id)</code> that return a typed <code>Widget</code> struct. No <code>interface{}</code>, no <code>map[string]any</code>, no row scanning boilerplate.</p> <p>The generated code looks roughly like this (abbreviated):</p> <p><strong>db/models.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> db</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> &quot;time&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Widget</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> ID</span><span style="color: #39BAE6;"> int64</span></span> <span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> CreatedAt</span><span style="color: #59C2FF;"> time</span><span>.</span><span style="color: #59C2FF;">Time</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p><strong>db/widgets.sql.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> db</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> &quot;context&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">const</span><span> getWidget</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> `-- name: GetWidget :one</span></span> <span class="giallo-l"><span style="color: #AAD94C;">SELECT id, name, status, created_at FROM widgets</span></span> <span class="giallo-l"><span style="color: #AAD94C;">WHERE id = $1 LIMIT 1</span></span> <span class="giallo-l"><span style="color: #AAD94C;">`</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">q </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Queries</span><span>)</span><span style="color: #FFB454;"> GetWidget</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> id</span><span style="color: #39BAE6;"> int64</span><span>) (</span><span style="color: #59C2FF;">Widget</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> row</span><span style="color: #F29668;"> :=</span><span> q.db.</span><span style="color: #FFB454;">QueryRow</span><span>(ctx, getWidget, id)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> i</span><span style="color: #59C2FF;"> Widget</span></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> :=</span><span> row.</span><span style="color: #FFB454;">Scan</span><span>(</span><span style="color: #F29668;">&amp;</span><span>i.ID,</span><span style="color: #F29668;"> &amp;</span><span>i.Name,</span><span style="color: #F29668;"> &amp;</span><span>i.Status,</span><span style="color: #F29668;"> &amp;</span><span>i.CreatedAt)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> i, err</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// CreateWidget, ListWidgets, DeleteWidget follow the same pattern</span></span></code></pre> <p>You write SQL and you get Go. The generated code is readable, boring, and correct. That's what you want.</p> <h2 id="http-operations-with-huma">HTTP Operations with Huma</h2> <p>Huma is a framework that generates OpenAPI specs from your Go types. You define input and output structs with struct tags and Huma does the rest: validation, documentation, request parsing, response marshaling, and serving the spec at <code>/openapi.json</code> with an interactive doc site built in.</p> <p>Start with the input and output types for a "get widget" operation.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> GetWidgetInput</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> ID</span><span style="color: #39BAE6;"> int64</span><span style="color: #AAD94C;"> `path:&quot;id&quot; doc:&quot;Widget ID&quot; example:&quot;42&quot;`</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> WidgetOutput</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Body</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> ID</span><span style="color: #39BAE6;"> int64</span><span style="color: #AAD94C;"> `json:&quot;id&quot; example:&quot;42&quot; doc:&quot;Widget ID&quot;`</span></span> <span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;name&quot; example:&quot;Sprocket&quot; doc:&quot;Widget name&quot;`</span></span> <span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;status&quot; example:&quot;active&quot; doc:&quot;Widget status&quot;`</span></span> <span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;created_at&quot; example:&quot;2025-04-07T12:00:00Z&quot; doc:&quot;Creation timestamp&quot;`</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> CreateWidgetInput</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Body</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;name&quot; required:&quot;true&quot; maxLength:&quot;255&quot; doc:&quot;Widget name&quot;`</span></span> <span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;status&quot; required:&quot;true&quot; enum:&quot;active,inactive&quot; doc:&quot;Widget status&quot;`</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ListWidgetsOutput</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Body []</span><span style="color: #FF8F40;">struct</span><span> {</span></span> <span class="giallo-l"><span> ID</span><span style="color: #39BAE6;"> int64</span><span style="color: #AAD94C;"> `json:&quot;id&quot;`</span></span> <span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;name&quot;`</span></span> <span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;status&quot;`</span></span> <span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;created_at&quot;`</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Every struct tag feeds directly into the OpenAPI spec. The <code>required</code>, <code>maxLength</code>, and <code>enum</code> tags become validation rules that Huma enforces before your handler ever runs. If someone sends a widget name longer than 255 characters they get a 422 back with a clear error message and your handler never sees the request. That's free input validation from type definitions you were going to write anyway.</p> <h2 id="wiring-it-together">Wiring It Together</h2> <p>Here's what <code>main.go</code> looks like when you connect all three pieces.</p> <p><strong>cmd/server/main.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> main</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span> (</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;context&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;fmt&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;log&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;net/http&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;github.com/danielgtaylor/huma/v2&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;github.com/danielgtaylor/huma/v2/adapters/humago&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;github.com/go-chi/chi/v5&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;github.com/jackc/pgx/v5/pgxpool&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> gendb</span><span style="color: #AAD94C;"> &quot;my-api/db&quot;</span></span> <span class="giallo-l"><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span> ctx</span><span style="color: #F29668;"> :=</span><span> context.</span><span style="color: #FFB454;">Background</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Connect to PostgreSQL</span></span> <span class="giallo-l"><span> pool, err</span><span style="color: #F29668;"> :=</span><span> pgxpool.</span><span style="color: #FFB454;">New</span><span>(ctx,</span><span style="color: #AAD94C;"> &quot;postgres://localhost:5432/myapi&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatal</span><span>(err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> defer</span><span> pool.</span><span style="color: #FFB454;">Close</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Create the sqlc queries instance</span></span> <span class="giallo-l"><span> queries</span><span style="color: #F29668;"> :=</span><span> gendb.</span><span style="color: #FFB454;">New</span><span>(pool)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Set up the router and Huma API</span></span> <span class="giallo-l"><span> router</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewServeMux</span><span>()</span></span> <span class="giallo-l"><span> api</span><span style="color: #F29668;"> :=</span><span> humago.</span><span style="color: #FFB454;">New</span><span>(router, huma.</span><span style="color: #FFB454;">DefaultConfig</span><span>(</span><span style="color: #AAD94C;">&quot;My API&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;1.0.0&quot;</span><span>))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Register operations</span></span> <span class="giallo-l"><span> huma.</span><span style="color: #FFB454;">Get</span><span>(api,</span><span style="color: #AAD94C;"> &quot;/widgets/{id}&quot;</span><span>,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> input</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">GetWidgetInput</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">WidgetOutput</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> widget, err</span><span style="color: #F29668;"> :=</span><span> queries.</span><span style="color: #FFB454;">GetWidget</span><span>(ctx, input.ID)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, huma.</span><span style="color: #FFB454;">Error404NotFound</span><span>(</span><span style="color: #AAD94C;">&quot;widget not found&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> resp</span><span style="color: #F29668;"> := &amp;</span><span style="color: #59C2FF;">WidgetOutput</span><span>{}</span></span> <span class="giallo-l"><span> resp.Body.ID</span><span style="color: #F29668;"> =</span><span> widget.ID</span></span> <span class="giallo-l"><span> resp.Body.Name</span><span style="color: #F29668;"> =</span><span> widget.Name</span></span> <span class="giallo-l"><span> resp.Body.Status</span><span style="color: #F29668;"> =</span><span> widget.Status</span></span> <span class="giallo-l"><span> resp.Body.CreatedAt</span><span style="color: #F29668;"> =</span><span> widget.CreatedAt.</span><span style="color: #FFB454;">Format</span><span>(</span><span style="color: #AAD94C;">&quot;2006-01-02T15:04:05Z&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> resp,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> huma.</span><span style="color: #FFB454;">Post</span><span>(api,</span><span style="color: #AAD94C;"> &quot;/widgets&quot;</span><span>,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> input</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">CreateWidgetInput</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">WidgetOutput</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> widget, err</span><span style="color: #F29668;"> :=</span><span> queries.</span><span style="color: #FFB454;">CreateWidget</span><span>(ctx,</span><span style="color: #59C2FF;"> gendb</span><span>.</span><span style="color: #59C2FF;">CreateWidgetParams</span><span>{</span></span> <span class="giallo-l"><span> Name: input.Body.Name,</span></span> <span class="giallo-l"><span> Status: input.Body.Status,</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, fmt.</span><span style="color: #FFB454;">Errorf</span><span>(</span><span style="color: #AAD94C;">&quot;creating widget: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> resp</span><span style="color: #F29668;"> := &amp;</span><span style="color: #59C2FF;">WidgetOutput</span><span>{}</span></span> <span class="giallo-l"><span> resp.Body.ID</span><span style="color: #F29668;"> =</span><span> widget.ID</span></span> <span class="giallo-l"><span> resp.Body.Name</span><span style="color: #F29668;"> =</span><span> widget.Name</span></span> <span class="giallo-l"><span> resp.Body.Status</span><span style="color: #F29668;"> =</span><span> widget.Status</span></span> <span class="giallo-l"><span> resp.Body.CreatedAt</span><span style="color: #F29668;"> =</span><span> widget.CreatedAt.</span><span style="color: #FFB454;">Format</span><span>(</span><span style="color: #AAD94C;">&quot;2006-01-02T15:04:05Z&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> resp,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> huma.</span><span style="color: #FFB454;">Get</span><span>(api,</span><span style="color: #AAD94C;"> &quot;/widgets&quot;</span><span>,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> input</span><span style="color: #F29668;"> *</span><span style="color: #FF8F40;">struct</span><span>{}) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">ListWidgetsOutput</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Fill in logic here</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> huma.</span><span style="color: #FFB454;">Delete</span><span>(api,</span><span style="color: #AAD94C;"> &quot;/widgets/{id}&quot;</span><span>,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> input</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">GetWidgetInput</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #FF8F40;">struct</span><span>{},</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Fill in logic here</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Println</span><span>(</span><span style="color: #AAD94C;">&quot;listening on :8080&quot;</span><span>)</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">ListenAndServe</span><span>(</span><span style="color: #AAD94C;">&quot;:8080&quot;</span><span>, router)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>That's the full loop. The handler receives validated, typed input from Huma. It calls a generated, typed query from sqlc. It returns a typed response that Huma serializes to JSON and documents in the OpenAPI spec. No reflection at runtime. No hand-written row scanning. No spec that drifts from the implementation.</p> <h2 id="the-openapi-spec">The OpenAPI Spec</h2> <p>Start the server and hit <code>http://localhost:8080/openapi.json</code>. You get a full OpenAPI 3.1 spec generated from the types you already wrote. Huma also serves interactive docs at <code>/docs</code> out of the box. Every <code>doc</code>, <code>example</code>, <code>enum</code>, <code>required</code>, and <code>maxLength</code> tag you put on your structs shows up in the spec and the doc site.</p> <p>This matters when you're working with a team. Frontend engineers, mobile developers, QA, whoever needs to know what the API does can pull up the docs and see the current state of things. The spec is always correct because it's generated from the code that's running. There is no separate YAML file to maintain, forget about, or let rot.</p> <h2 id="the-makefile">The Makefile</h2> <p>A simple <code>Makefile</code> ties the workflow together so any engineer on the team can run the same commands.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="make"><span class="giallo-l"><span>DATABASE_URL</span><span style="color: #BFBDB6B3;"> ?=</span><span> postgres://localhost:5432/myapi</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F07178;">.PHONY</span><span style="color: #BFBDB6B3;">:</span><span> generate migrate run</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">generate</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> sqlc generate</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">migrate-up</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> goose -dir sql/migrations postgres &quot;</span><span style="color: #AAD94C;">$(</span><span>DATABASE_URL</span><span style="color: #AAD94C;">)</span><span>&quot; up</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">migrate-down</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> goose -dir sql/migrations postgres &quot;</span><span style="color: #AAD94C;">$(</span><span>DATABASE_URL</span><span style="color: #AAD94C;">)</span><span>&quot; down</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">run</span><span style="color: #BFBDB6B3;">:</span><span> generate</span></span> <span class="giallo-l"><span> go run ./cmd/server</span></span></code></pre> <p><code>make migrate-up</code> runs them forward, <code>make migrate-down</code> rolls one back. If you add a new column just write a migration, write a query, run <code>make generate</code>, and wire it into a handler. The cycle is short and the feedback is immediate because every step produces something you can read and verify.</p> <h2 id="why-this-stack">Why This Stack</h2> <p>There are a lot of ways to build an API in Go. ORMs like GORM and Ent generate code too, but they also hide your SQL behind method chains and builders that make it harder to reason about what's actually hitting the database. sqlc flips this: you write the SQL and it generates the Go. The SQL is right there in your repo, reviewable, explainable, and directly runnable against your database for debugging.</p> <p>Goose stays out of the way. It's a binary that reads SQL files and applies them in order. No migration DSL to learn, no Go structs that model your schema changes. The migration files are the same SQL you'd run by hand and that makes them easy to review in a pull request.</p> <p>Huma earns its place by giving you an OpenAPI spec without a separate design step. The types you define for your handlers are the spec. The validation you need is expressed in struct tags, not in middleware or manual checks scattered across your handlers. And because Huma is bring-your-own-router, you're not locked into anything you can't swap out later.</p> <p>The three tools don't know about each other. They compose through the filesystem: goose writes migrations, sqlc reads them, Huma reads Go types. There's no plugin system, no framework coupling, and no vendor lock-in between them. If you outgrow one you replace it without touching the others. I've had new folks join a team with this stack and every single person was up and running adding new features in less than a few days, even folks who are not usually backend software developers.</p> <p>That's the whole point. Enough boilerplate to keep things consistent across a team. Not enough to make you hate the tooling and espcially obfuscate the important logic.</p> Vendoring Modules in Hare 2026-03-28T00:00:00+00:00 2026-03-28T00:00:00+00:00 Unknown https://blainsmith.com/articles/vendoring-modules-in-hare/ <p>Hare doesn't have a package manager. This is by design and I'm glad for it. No dependency hell, no lock files, no <code>node_modules</code> black hole. However, you still need a way to pull in third-party code. There are two approaches:</p> <ol> <li>system-wide installation via <code>make install</code>, and</li> <li>vendoring directly into your project with <code>git subtree</code>.</li> </ol> <p>I'll use <a rel="external" href="https://git.sr.ht/~sircmpwn/hare-json">hare-json</a> as the running example throughout. Our goal is to be able to use this module like so:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>use encoding::json;</span></span> <span class="giallo-l"><span>use fmt</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn main() void = {</span></span> <span class="giallo-l"><span> let obj = json::newobject();</span></span> <span class="giallo-l"><span> defer json::finish(obj);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> json::set(&amp;obj, &quot;hello&quot;, &quot;world&quot;)!;</span></span> <span class="giallo-l"><span> json::set(&amp;obj, &quot;foo&quot;, &quot;bar&quot;)!;</span></span> <span class="giallo-l"><span> json::set(&amp;obj, &quot;the answer&quot;, 42.0)!;</span></span> <span class="giallo-l"><span> ...</span></span> <span class="giallo-l"><span>};</span></span></code></pre> <p>The <code>encoding/</code> folder within the <code>hare-json</code> module needs to be found by the Hare toolchain so that when we <code>use</code> it it will then follow the folder path <code>encoding/json/</code> via <code>encoding::json</code> and find all the <code>*.ha</code> files.</p> <h2 id="how-hare-finds-modules">How Hare finds modules</h2> <p>When you write <code>use encoding::json;</code> in your code, the build driver looks for a directory called <code>encoding/json/</code> containing <code>.ha</code> files. It searches a list of source roots in this order:</p> <ol> <li>The current working directory (<code>.</code>) which is always first, always implicit</li> <li>Each directory listed in the <code>HAREPATH</code> environment variable, colon-delimited</li> <li>If <code>HAREPATH</code> is unset, a compiled-in default (typically the stdlib and third-party install locations)</li> </ol> <p>Check yours with:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> hare version</span><span style="color: #95E6CB;"> -v</span></span> <span class="giallo-l"><span style="color: #59C2FF;">hare</span><span style="color: #D2A6FF;"> 0.26.0</span></span> <span class="giallo-l"><span style="color: #59C2FF;">build</span><span style="color: #AAD94C;"> tags:</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> +x86_64</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> +linux</span></span> <span class="giallo-l"><span style="color: #59C2FF;">tool</span><span style="color: #AAD94C;"> paths:</span></span> <span class="giallo-l"><span style="color: #59C2FF;">HAREPATH</span><span> (from</span><span style="color: #AAD94C;"> environment</span><span>):</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> vendor/*/</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> /usr/src/hare/third-party</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> /usr/src/hare/stdlib</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> /usr/local/src/hare/third-party</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> /usr/local/src/hare/stdlib</span></span> <span class="giallo-l"><span style="color: #59C2FF;">toolchains:</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> aarch64:</span></span> <span class="giallo-l"><span> AS</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">as</span></span> <span class="giallo-l"><span> CC</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">cc</span></span> <span class="giallo-l"><span> LD</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">ld</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> riscv64:</span></span> <span class="giallo-l"><span> AS</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">as</span></span> <span class="giallo-l"><span> CC</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">cc</span></span> <span class="giallo-l"><span> LD</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">ld</span></span> <span class="giallo-l"><span style="color: #59C2FF;"> x86_64:</span></span> <span class="giallo-l"><span> AS</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">as</span></span> <span class="giallo-l"><span> CC</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">cc</span></span> <span class="giallo-l"><span> LD</span><span style="color: #F29668;">=</span><span style="color: #AAD94C;">ld</span></span></code></pre> <p>This is the result of mine since I set my <code>HAREPATH</code> in my <code>.zshrc</code>, but on a typical Linux system with Hare installed to <code>/usr/local</code>, the default <code>HAREPATH</code> looks something like:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>/usr/local/src/hare/stdlib:/usr/local/src/hare/third-party</span></span></code></pre> <p>First directory with <code>.ha</code> files matching the module path wins. That's the entire resolution algorithm. Simple.</p> <h2 id="the-repo-layout-convention">The repo layout convention</h2> <p>Most Hare libraries follow a naming convention: the repo is called <code>hare-MODULE</code>, and the actual module code lives in a top-level directory named after the module. For hare-json:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>hare-json/</span></span> <span class="giallo-l"><span>├── encoding/</span></span> <span class="giallo-l"><span>│ └── json/</span></span> <span class="giallo-l"><span>│ ├── types.ha</span></span> <span class="giallo-l"><span>│ ├── lex.ha</span></span> <span class="giallo-l"><span>│ ├── value.ha</span></span> <span class="giallo-l"><span>│ └── ...</span></span> <span class="giallo-l"><span>├── Makefile</span></span> <span class="giallo-l"><span>├── README</span></span> <span class="giallo-l"><span>└── COPYING</span></span></code></pre> <p>The code you import as <code>encoding::json</code> lives under <code>encoding/json/</code>. This means the <em>parent directory of <code>encoding/</code></em> is what needs to be on <code>HAREPATH</code>. Not the <code>json/</code> directory itself. Not the repo root unless the repo root <em>is</em> that parent directory.</p> <p>This is where most people trip up.</p> <h2 id="approach-1-system-wide-install">Approach 1: System-wide install</h2> <p>Clone, make, done.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> git clone https://git.sr.ht/~sircmpwn/hare-json</span></span> <span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> cd hare-json</span></span> <span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> make install</span></span></code></pre> <p>The Makefile handles putting <code>.ha</code> files into the right location. A typical Hare library Makefile looks like:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="make"><span class="giallo-l"><span style="color: #F07178;">.POSIX</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #F07178;">.SUFFIXES</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>HARE</span><span style="color: #BFBDB6B3;">=</span><span>hare</span></span> <span class="giallo-l"><span>HAREFLAGS</span><span style="color: #BFBDB6B3;">=</span></span> <span class="giallo-l"><span>DESTDIR</span><span style="color: #BFBDB6B3;">=</span></span> <span class="giallo-l"><span>PREFIX</span><span style="color: #BFBDB6B3;">=</span><span>/usr/local</span></span> <span class="giallo-l"><span>SRCDIR</span><span style="color: #BFBDB6B3;">=</span><span style="color: #AAD94C;">$(</span><span>PREFIX</span><span style="color: #AAD94C;">)</span><span>/src</span></span> <span class="giallo-l"><span>HARESRCDIR</span><span style="color: #BFBDB6B3;">=</span><span style="color: #AAD94C;">$(</span><span>SRCDIR</span><span style="color: #AAD94C;">)</span><span>/hare</span></span> <span class="giallo-l"><span>THIRDPARTYDIR</span><span style="color: #BFBDB6B3;">=</span><span style="color: #AAD94C;">$(</span><span>HARESRCDIR</span><span style="color: #AAD94C;">)</span><span>/third-party</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">install</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> install -Dm644 encoding/json/* </span><span style="color: #AAD94C;">$(</span><span>DESTDIR</span><span style="color: #AAD94C;">)$(</span><span>THIRDPARTYDIR</span><span style="color: #AAD94C;">)</span><span>/encoding/json/</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">uninstall</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> rm -rf </span><span style="color: #AAD94C;">$(</span><span>DESTDIR</span><span style="color: #AAD94C;">)$(</span><span>THIRDPARTYDIR</span><span style="color: #AAD94C;">)</span><span>/encoding/json</span></span></code></pre> <p>The default <code>THIRDPARTYDIR</code> resolves to <code>/usr/local/src/hare/third-party</code>, which is already in the default <code>HAREPATH</code>. After <code>make install</code>, <code>use encoding::json;</code> just works.</p> <p>If your Hare installation lives under <code>/usr</code> instead of <code>/usr/local</code>:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> make PREFIX=/usr install</span></span></code></pre> <p>Or a custom path entirely:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> make THIRDPARTYDIR=/opt/hare/libs install</span></span></code></pre> <p>Just make sure whatever path you install to is in your <code>HAREPATH</code>.</p> <p>This approach is clean and simple. Every project on the system can use the module with zero configuration. The downside is versioning: all projects share the same installed version. If project A needs an older hare-json and project B needs a newer one, you're stuck. There's no record in your project's repo of which dependencies you need or what versions they are either. A new contributor has to read your README and manually install each one.</p> <h2 id="approach-2-vendoring-with-git-subtree">Approach 2: Vendoring with git subtree</h2> <p>For reproducible builds and per-project dependency management, vendor the module source directly into your project.</p> <p>From your project root:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> git subtree add</span><span style="color: #95E6CB;"> --prefix</span><span style="color: #AAD94C;"> vendor/hare-json</span><span style="color: #95E6CB;"> \</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> https://git.sr.ht/~sircmpwn/hare-json master</span><span style="color: #95E6CB;"> --squash</span></span></code></pre> <p>This creates <code>vendor/hare-json/</code> with the full contents of the repo:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-project/</span></span> <span class="giallo-l"><span>├── main.ha</span></span> <span class="giallo-l"><span>├── vendor/</span></span> <span class="giallo-l"><span>│ └── hare-json/</span></span> <span class="giallo-l"><span>│ ├── encoding/</span></span> <span class="giallo-l"><span>│ │ └── json/</span></span> <span class="giallo-l"><span>│ │ ├── types.ha</span></span> <span class="giallo-l"><span>│ │ ├── lex.ha</span></span> <span class="giallo-l"><span>│ │ └── ...</span></span> <span class="giallo-l"><span>│ ├── Makefile</span></span> <span class="giallo-l"><span>│ └── README</span></span> <span class="giallo-l"><span>└── Makefile</span></span></code></pre> <p>Remember the layout convention: <code>encoding/json/</code> is relative to the repo root <code>vendor/hare-json/</code>. So the path that goes on <code>HAREPATH</code> is <code>vendor/hare-json</code>. Not <code>vendor/</code>. Not <code>vendor/hare-json/encoding/json/</code>.</p> <h3 id="updating-a-vendored-dependency">Updating a vendored dependency</h3> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #59C2FF;">$</span><span style="color: #AAD94C;"> git subtree pull</span><span style="color: #95E6CB;"> --prefix</span><span style="color: #AAD94C;"> vendor/hare-json</span><span style="color: #95E6CB;"> \</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> https://git.sr.ht/~sircmpwn/hare-json master</span><span style="color: #95E6CB;"> --squash</span></span></code></pre> <p>Upstream changes get merged in. <code>--squash</code> collapses the upstream history into a single commit.</p> <h3 id="setting-harepath">Setting HAREPATH</h3> <p>Important: when you set <code>HAREPATH</code> as an environment variable, it <strong>completely replaces</strong> the compiled-in default. You must re-include the stdlib and third-party paths yourself or the build driver won't find the standard library. Run <code>hare version -v</code> to see what those paths are on your system.</p> <p>The Hare docs mention establishing <code>vendor/*/</code> as a default convention for <code>HAREPATH</code>, but as of this writing it does not work. The glob does not expand the way you'd expect and the build driver won't find your modules. You need to specify each vendored module path explicitly.</p> <p>The way to do this is in your project Makefile:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="make"><span class="giallo-l"><span style="color: #F07178;">.POSIX</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #F07178;">.SUFFIXES</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>HARE</span><span style="color: #BFBDB6B3;">=</span><span>hare</span></span> <span class="giallo-l"><span>HAREFLAGS</span><span style="color: #BFBDB6B3;">=</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>HAREPATH</span><span style="color: #BFBDB6B3;">=</span><span>vendor/hare-json:/usr/local/src/hare/third-party:/usr/local/src/hare/stdlib</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">build</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> HAREPATH=</span><span style="color: #AAD94C;">$(</span><span>HAREPATH</span><span style="color: #AAD94C;">) $(</span><span>HARE</span><span style="color: #AAD94C;">)</span><span> build </span><span style="color: #AAD94C;">$(</span><span>HAREFLAGS</span><span style="color: #AAD94C;">)</span><span> -o my-project cmd/my-project/</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;">check</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> HAREPATH=</span><span style="color: #AAD94C;">$(</span><span>HAREPATH</span><span style="color: #AAD94C;">) $(</span><span>HARE</span><span style="color: #AAD94C;">)</span><span> test </span><span style="color: #AAD94C;">$(</span><span>HAREFLAGS</span><span style="color: #AAD94C;">)</span></span></code></pre> <p>Multiple vendored deps, list each one:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="make"><span class="giallo-l"><span>HAREPATH</span><span style="color: #BFBDB6B3;">=</span><span>vendor/hare-json:vendor/hare-xml:vendor/hare-http:/usr/local/src/hare/third-party:/usr/local/src/hare/stdlib</span></span></code></pre> <p>Colon-separated, just like <code>PATH</code>. Adjust the stdlib and third-party paths to match your system. The tradeoff is you need to update the Makefile every time you add or remove a vendored dependency, and the stdlib paths are hardcoded to one install prefix.</p> <h3 id="vendoring-pros-and-cons">Vendoring pros and cons</h3> <p>You get reproducible builds, per-project versioning, and a self-contained repo that any contributor can clone and build immediately. Dependencies are explicit in the source tree.</p> <p>The cost is a larger repo since you're committing the dependency source and a slightly more complex build setup. You also need to manually pull upstream changes when you want them.</p> <h2 id="which-to-use">Which to use</h2> <p>Personal projects, scripts, small tools: <code>make install</code>. Fast, simple, done.</p> <p>Anything collaborative, anything you want reproducible builds for, anything that might need pinned dependency versions: vendor with <code>git subtree</code>. The overhead is minimal and the payoff is real.</p> <p>You can combine both. Install common modules system-wide for convenience during development and vendor them for CI and release builds. Since <code>.</code> has the highest priority in <code>HAREPATH</code> and vendored paths come before the defaults, the resolution order does what you'd expect.</p> <h2 id="hare-s-growing-ecosystem">Hare's growing ecosystem</h2> <p>You can find a lot of Hare projects on <a rel="external" href="https://sourcehut.org">https://sourcehut.org</a> and <a rel="external" href="https://codeberg.org">https://codeberg.org</a>, but there is also a community currated list that is growing all the time on the <a rel="external" href="https://harelang.org/project-library/">Project Library</a> page of the official Hare website.</p> Crocker's Rules 2026-03-23T00:00:00+00:00 2026-03-23T00:00:00+00:00 Unknown https://blainsmith.com/articles/crockers-rules/ <p>I came across an <a rel="external" href="https://lr0.org/blog/p/crocker/">article</a> recently that references something called <a rel="external" href="http://sl4.org/crocker.html">Crocker's Rules</a>. I had never heard of them by name, but when I read the definition I realized I've been operating this way for most of my career without knowing there was a term for it.</p> <p>The short version is this: if you declare Crocker's Rules you are telling people around you that they can optimize their communication for information, not for your feelings. You take full responsibility for your own emotional reaction to what someone tells you. It doesn't mean people get to be cruel. It means neither party wastes time on social padding that adds zero signal to the conversation.</p> <p>I've written before about <a href="/articles/software-engineering-discipline-and-posture/">discipline and posture</a> in engineering and about being <a href="/articles/allergic-to-unnecessary-complexity/">allergic to unnecessary complexity</a>. If you've read any of those you already know I don't soften things much. I say what I mean. I expect others to do the same.</p> <h2 id="why-this-matters-in-engineering">Why This Matters in Engineering</h2> <p>Think about the last code review you did. How much of the feedback was buried under "hey, great work overall, just a small thought, feel free to ignore this, but..." before the reviewer finally got to the point? That padding isn't politeness. It's noise. It trains people to skim your reviews, which means when you flag something that actually matters they'll probably miss it.</p> <p>Compare these two PR comments:</p> <blockquote> <p>"I hope this doesn't come across the wrong way but I was wondering if maybe the error handling here could potentially be improved? Just a thought!"</p> </blockquote> <p>vs.</p> <blockquote> <p>"This swallows the error silently. Propagate it to the caller so we can debug failures."</p> </blockquote> <p>The second one is more useful, faster to read, and honestly more respectful of everyone's time. Nobody reading the first one thinks better of the person who wrote it. They just have to dig through the fluff to find out what they actually need to change.</p> <p>The same applies to incident reports. If the payment service went down because a config value was wrong then write that. The fact that you were stressed, inherited the config from someone else, or that the docs were unclear is not relevant unless it leads to a structural fix you can attach a concrete action item to. Everything else is noise that buries what the on-call engineer needs to act on right now.</p> <h2 id="it-s-a-discipline-not-a-personality-trait">It's a Discipline, Not a Personality Trait</h2> <p>The original definition makes an important distinction. Crocker's Rules are a discipline, not a privilege. You don't get to be an asshole and call it "being direct." The point is that the <em>receiver</em> takes responsibility for managing their emotional reaction so the <em>sender</em> doesn't have to spend time softening the message. Both parties benefit because the information moves faster and cleaner.</p> <p>This also means you have to actually have the mental discipline to not take things personally when someone tells you your approach is wrong. That's the hard part. Most people skip right to the "I get to be blunt" side without doing the work on the "I can receive blunt feedback without spiraling" side. If you can't do the second part then don't bother with the first.</p> <h2 id="where-i-ve-seen-it-work">Where I've Seen It Work</h2> <p>The best teams I've been on didn't know about Crocker's Rules either, but they operated that way naturally. Code reviews had real questions and real feedback. Pull requests came with context, not apologies. When something broke we talked about what broke and why, not who was at fault or what extenuating circumstances led to the mistake.</p> <p>The worst teams I've been on were the opposite. Everything was wrapped in so much politeness that nobody said what they actually meant. Problems festered because pointing them out felt like an attack. PRs got rubber-stamped with "LGTM" because giving real feedback might hurt someone's feelings. That's not kindness. That's how you end up with systems nobody understands and nobody wants to touch.</p> <h2 id="adopt-it-or-don-t">Adopt It or Don't</h2> <p>I'm not going to preach that everyone needs to follow Crocker's Rules. Some people aren't wired for it and that's fine. But if you work with me you should know that when I say something is wrong I'm not attacking you, I'm telling you what I see so we can fix it together. And I expect the same in return. Tell me my code is bad. Tell me my design has a flaw. Skip the preamble and just say the thing. I'd rather hear it now than debug it at 2am.</p> <p>If you want to give me feedback on this you can hit me up via any of the links in the footer of this site.</p> Humanities in the Machine 2026-03-13T00:00:00+00:00 2026-03-13T00:00:00+00:00 Unknown https://blainsmith.com/essays/humanities-in-the-machine/ <p>There is something quietly remarkable about the people who built the foundations of modern computing. Not their technical achievements, though those are extraordinary. What is remarkable is where they came from before they ever touched a machine.</p> <p>Tony Hoare, who inspired the writing of this essay, studied Classics and Philosophy at Oxford: Latin, Greek, and modern philosophy under the "Greats" program at Merton College. As a schoolboy, he read George Bernard Shaw and Bertrand Russell and harbored a youthful aspiration to become a writer. He did not encounter computing until after he graduated in 1956 and only stumbled into programming after completing his National Service in the Royal Navy, where he had learned Russian. His interest in computers was awakened not by engineering curiosity, but by a fascination with the power of mathematical logic as an explanation of the apparent certainty of mathematical truth. That is a philosopher's question, not a programmer's.</p> <p>Hoare went on to create Quicksort, one of the most widely used sorting algorithms in the history of computing. He developed Hoare logic, which gave programmers a formal way to prove that their programs are correct. He introduced Communicating Sequential Processes, a language for reasoning about how concurrent programs interact. Each of these contributions changed the shape of computer science. And each of them carries the fingerprints of a mind trained not in engineering but in philosophy, in the careful construction of arguments, in the pursuit of clarity and correctness not as engineering goals but as intellectual virtues.</p> <p>Hoare himself said his interests went back to the ancient philosophers Aristotle and Euclid, and that their teachings provided an excellent basis for a general understanding, even today. A book of essays honoring his 60th birthday was titled A Classical Mind, a reference to his educational background in Literae Humaniores. His colleagues described his writing as having a "cultured style." When he spoke about software design, he said there are two ways to construct it: make it so simple that there are obviously no deficiencies, or make it so complicated that there are no obvious deficiencies. That is not an engineering observation. That is a philosophical one, rooted in the same tradition of clarity and economy of expression that the classical education instills.</p> <p>Tony Hoare passed away in March 2026. The field lost not just a computer scientist, but a classicist who happened to turn his attention to machines.</p> <p>+++</p> <p>Before Hoare, before any of them, there was Ada Lovelace.</p> <p>Born in 1815 as the only legitimate child of the poet Lord Byron, Lovelace was steered deliberately away from poetry by her mother, who feared the "insanity" she associated with Byron's literary temperament. Mathematics and science were prescribed as a kind of intellectual medicine. Lovelace's mother ensured she received rigorous tutoring in logic and mathematics from some of the finest minds available, including the mathematician Augustus De Morgan and the scientific author Mary Somerville.</p> <p>But the poetical inheritance could not be suppressed. Lovelace described her own approach as "poetical science" and herself as an "Analyst and Metaphysician." She did not see mathematics and imagination as opposing forces. She saw them as collaborators. When she encountered Charles Babbage's Analytical Engine, a mechanical computing device that existed only in design, she saw something that Babbage himself and every other mathematician of her era did not see: that the machine could go beyond pure calculation. She wrote that if the fundamental relations of pitched sounds in the science of harmony were susceptible to such expression, the engine might compose elaborate pieces of music of any degree of complexity or extent.</p> <p>That insight, that a computing machine could manipulate any symbolic system and not merely numbers, is the founding conceptual leap of modern computing. It did not come from mathematics. It came from a mind that refused to separate the analytical from the poetical, a mind that understood machines not merely as calculators but as extensions of human creative capacity. A pure mathematician would not have made that leap. It required someone who lived between disciplines, who saw the world through both lenses at once.</p> <p>Lovelace died at 36. She never saw a working computer. But her "poetical science" anticipated the entire field by more than a century.</p> <p>+++</p> <p>Edsger Dijkstra was born in Rotterdam in 1930. His mother was a mathematician and his father a chemist. Before he ever encountered a computer, he attended the Erasmiaans Gymnasium, one of the most prestigious secondary schools in the Netherlands, where he studied classical Greek and Latin alongside French, German, English, mathematics, physics, chemistry, and biology. He scored the highest possible marks in six out of thirteen subjects on his final exams. His early ambition was not to become a scientist. He wanted to study law and represent the Netherlands at the United Nations.</p> <p>He chose theoretical physics instead, and then stumbled into programming at the Mathematical Centre in Amsterdam, where he was offered a part-time job in 1952. But the classical education never left him. Dijkstra became one of the most important figures in the history of computing. He formulated the shortest path algorithm, developed structured programming, introduced the concept of semaphores for coordinating concurrent processes, and wrote the famous letter, "Go To Statement Considered Harmful", that reshaped how programmers think about control flow. His Turing Award lecture was titled "The Humble Programmer."</p> <p>What set Dijkstra apart from his peers was not only the depth of his technical insight, but the way he expressed it. Over the course of his career, he wrote more than 1,300 numbered manuscripts, known as the EWDs (Edsger Wybe Dijkstra), almost entirely by hand. They were not technical reports. They were essays, written in a prose style that his colleagues described as extraordinary. He could write about formal issues in the form of an essay, with hardly any formulas. He discussed algorithms in plain prose, deriving intricate solutions in distributed computing in a seemingly informal way. His handwriting was so distinctive that a computer font was later created from it. Colleagues who received one of his handwritten letters sometimes thought they were personal notes before realizing they were formal publications.</p> <p>Dijkstra increasingly viewed programming as a mental activity, an intellectual discipline that demanded clarity of thought above all else. His courses at the University of Texas at Austin had little to do with computer science in the conventional sense. They dealt with the presentation of mathematical proofs. On the department's homepage, his research summary read simply: "My area of interest focuses on the streamlining of the mathematical argument." He was not interested in machines. He was interested in how humans think, and in building systems of thought that were honest, clear, and beautiful.</p> <p>That concern for beauty, for the elegance of an argument, for the moral weight of clarity, did not come from computer science. It came from a gymnasium education steeped in classical languages and literature, from a young man who once dreamed of diplomacy, from a mind that never stopped treating writing as an act of care.</p> <p>+++</p> <p>Alan Turing studied mathematics at King's College, Cambridge, beginning in 1931. His formal education was entirely in mathematics and mathematical logic. At school, his teachers noted the contrast between his absorbed interest in science and mathematics and his indifference to Latin and English subjects. He was not, by any standard account, a humanities student.</p> <p>But Turing's intellectual life was far wider than his formal education suggests. King's College in the 1930s was not merely a mathematics department. It was a place defined by its progressive intellectual culture, centered on figures like the economist John Maynard Keynes. Turing drew deeply from the work of Bertrand Russell, whose mathematical philosophy sits at the intersection of logic, language, and epistemology. Russell was not simply a mathematician. He was a philosopher who used mathematics to ask questions about the nature of knowledge, truth, and meaning. Turing absorbed that orientation.</p> <p>When Turing published his most famous non-technical work, "Computing Machinery and Intelligence," he did not publish it in a mathematics journal or an engineering journal. He published it in Mind, a quarterly review of psychology and philosophy. The paper, which introduced what we now call the Turing test, is fundamentally a philosophical argument about the nature of thought, consciousness, and what it means to say a machine can "think." It engages with objections from theology, from mathematics, from Lady Lovelace's own writings, and from the philosophy of mind. Turing was not building a machine in that paper. He was asking what it means to be human, and whether the boundary between human thought and machine process is as firm as one might assume.</p> <p>Turing's lasting fascination with problems of mind and matter was present throughout his life. He thought about consciousness, about biology, about morphogenesis; the process by which organisms develop their shapes. In the final years of his life, he was working on "The Chemical Basis of Morphogenesis," a paper that founded modern nonlinear dynamical theory. He moved fluidly between mathematics, philosophy, biology, and what we would now call cognitive science. He did not respect the boundaries between disciplines because the questions he cared about did not inherently respect them either.</p> <p>His schoolmasters wanted him to have a "well-balanced education." He resisted. But the philosophical questions found him anyway, and they produced some of the most consequential thinking in the history of human knowledge.</p> <p>+++</p> <p>Grace Hopper arrived at Vassar College in 1924 to study mathematics and physics. Vassar was, and remains, a liberal arts college, and Hopper's education there was not confined to her concentrations. She took courses in economics, public finance, botany, physiology, geology, and electronics. She graduated Phi Beta Kappa in 1928 and went on to earn a master's degree and a PhD in mathematics from Yale. She returned to Vassar to teach, and her colleagues described her as both a great teacher and a sharp wit at the luncheon table.</p> <p>During the war, she joined the Navy and was assigned to work on the Harvard Mark I, one of the earliest electromechanical computers. After the war, she turned down a full professorship at Vassar to keep working with computers. It was in this second career that her humanities sensibility became most visible.</p> <p>Hopper's defining insight was not mathematical. It was humanistic. She realized that programming in mathematical symbols and machine code was a barrier that excluded the vast majority of people from using computers. She believed that computers should speak to people in English, not in the language of mathematics. When she proposed building a compiler that could translate English-language instructions into machine code, she was told flatly that she could not do it because computers did not understand English. She did it anyway.</p> <p>The result was FLOW-MATIC, the first programming language to use English words, and later COBOL, which became the dominant language of business computing for decades. Hopper said it plainly: "Manipulating symbols was fine for mathematicians but it was no good for data processors who were not symbol manipulators. Very few people are really symbol manipulators. If they are, they become professional mathematicians, not data processors. It is much easier for most people to write an English statement than it is to use symbols."</p> <p>That reasoning is not technical. It is an observation about human beings, about how people actually think and communicate, about the gap between expert knowledge and ordinary capability. It is the kind of observation that comes from someone who studied broadly, who taught across disciplines, who invented a mythical country to bring a dry mechanical drawing class to life for her students. Hopper understood that the value of a computer depended not on what it could compute but on who could use it. That is a humanities insight dressed in technical clothing.</p> <p>When she accepted the National Medal of Technology, Hopper said that the accomplishment she was most proud of was all the young people she had trained over the years; that it was more important than writing the first compiler. She understood that technology lives and dies by its relationship to people.</p> <p>+++</p> <p>Dennis Ritchie grew up in Summit, New Jersey, the son of a Bell Labs scientist. He attended Harvard, where he studied physics as an undergraduate and applied mathematics as a graduate student. His education was entirely technical. He completed a draft of his PhD thesis on subrecursive hierarchies of functions but never formally received the degree. He described his own trajectory with characteristic dry humor: "My undergraduate experience convinced me that I was not smart enough to be a physicist, and that computers were quite neat. My graduate school experience convinced me that I was not smart enough to be an expert in the theory of algorithms and also that I liked procedural languages better than functional ones."</p> <p>Ritchie went to Bell Labs in 1967 and stayed for forty years. There he created the C programming language and, with Ken Thompson, the Unix operating system. These two creations are arguably the most influential artifacts in the history of software. C is the ancestor of nearly every widely used programming language today. Unix and its descendants power the majority of servers, phones, and embedded systems on Earth. The conceptual DNA of modern computing flows directly through Ritchie's work.</p> <p>Ritchie had no formal humanities training, but his colleagues paint a picture of someone whose intellectual life extended far beyond programming. He was widely read and was interested in classical music. He was knowledgeable on a broad range of topics. One colleague recalled being at the San Diego Zoo with Ritchie and discovering that he knew the details of seemingly every animal they encountered. He was described universally as modest, kind, and generous, always giving credit to others while downplaying his own contributions.</p> <p>What is most telling about Ritchie's humanities sensibility is his writing. The book he co-authored with Brian Kernighan, The C Programming Language, is still considered one of the finest technical books ever written, more than four decades after its publication. It is precise, economical, and elegant. Every sentence does work. There is no bloat, no unnecessary complexity, no showing off. The book teaches not just C, but a way of thinking about programming that values clarity and simplicity above all else. That is not a technical achievement. That is a literary one.</p> <p>Ritchie was also shaped by Bell Labs itself, which in its golden era was one of the most extraordinary intellectual environments ever assembled. Physicists, mathematicians, linguists, and engineers worked side by side. The culture valued breadth, curiosity, and the freedom to pursue ideas across disciplinary boundaries. Ritchie did not study humanities in school, but he spent his career in a place that embodied the humanities ideal of cross-pollination between fields.</p> <p>Dennis Ritchie died in October 2011, the same week as Steve Jobs. The world mourned Jobs loudly. Ritchie's death was quieter. One commentator noted that Ritchie's work played a key role in spawning the technological revolution of the last forty years, including technology on which Apple built its fortune. The tools that made the modern world possible were built by a quietly brilliant man who read widely, wrote beautifully, and never sought the spotlight.</p> <p>+++</p> <p>Brian Kernighan was born in Toronto and studied engineering physics at the University of Toronto, then earned his PhD in electrical engineering from Princeton. Like Ritchie, his education was purely technical. He spent thirty years at Bell Labs, where he contributed to the development of Unix, co-authored The C Programming Language, and created the AWK programming language.</p> <p>But something interesting happened when Kernighan moved from Bell Labs to Princeton's Computer Science department in 2000. He began to gravitate, with increasing purpose, toward the humanities.</p> <p>He started teaching a course called "Computers in Our World," designed not for computer science majors but for students studying literature, politics, history, and other humanities disciplines. He co-taught a course with a professor from the French and Italian department and he co-taught another with a professor from the English department on "data in the humanities." He became involved with Princeton's Center for Digital Humanities. He ran independent work seminars where computer science juniors explored data from the humanities, looking at properties of books, history, social interactions, and patterns in human behavior. A New York Times profile of him was titled, "To the Liberal Arts, He Adds Computer Science."</p> <p>This is a man who spent three decades building some of the most important systems-level software ever written and then spent the next two decades trying to connect that work back to the humanities. He has said in interviews that Princeton stresses breadth over depth, and that his job as an advisor is to get students to sample the amazing collection of different things that humankind does. He encourages computer science students to explore broadly because he believes exposure to those other disciplines is part of what makes a complete thinker.</p> <p>Kernighan did not arrive at computing through the humanities. But his career arc suggests that he recognized something was missing, that technical excellence alone was not enough, and he spent the second half of his professional life trying to build the bridge he never had as a student. That search is itself a kind of evidence. When one of the most accomplished systems programmers in history spends his later years teaching literature majors about computers and computer scientists about poetry, it tells you something about what the field needs and does not have.</p> <p>+++</p> <p>Richard Stallman graduated from Harvard in 1974 with a bachelor's degree in physics, magna cum laude. He had been programming since high school and had been working at MIT's Artificial Intelligence Laboratory since his freshman year. His formal education was entirely in the sciences. He chose physics over mathematics for a pragmatic reason: the physics degree did not require a thesis.</p> <p>Stallman's contribution to computing is not primarily technical, though he is a gifted programmer who created GNU Emacs, the GNU Compiler Collection, and the GNU Debugger. His lasting contribution is philosophical. He founded the Free Software Foundation and articulated a moral framework for software that treats user freedom as a fundamental ethical principle, not a business strategy. He argues that the ability to study, modify, and share software is a human right, and that proprietary software is antisocial and unethical. He has framed control over software as a civil liberties issue, speaking and writing about it for decades with the intensity of a moral philosopher.</p> <p>Stallman is an interesting case for this essay because his thinking is deeply humanistic while his training is not. He arrived at ethical philosophy through personal conviction and lived experience, not through formal study. The free software movement is, at its core, a philosophical argument about freedom, autonomy, and the relationship between creators and users. It draws on traditions of thought that belong to ethics and political philosophy, whether Stallman studied them formally or not.</p> <p>But there is something instructive in the gap between the depth of Stallman's ethical insight and the way he has communicated it over the years. His positions are often correct and almost always principled. His delivery has frequently been abrasive, uncompromising, and alienating to potential allies. Eric Raymond, one of the founders of the adjacent open-source movement, argued that Stallman's moral arguments, rather than pragmatic ones, alienate potential allies and hurt the end goal. Whether one agrees with Raymond or not, the observation points to something worth considering: having the right ethical instincts is not the same as having the skill to communicate them in ways that move people. The humanities do not just teach you what to care about. They teach you how to bring others along.</p> <p>+++</p> <p>There is a pattern in these stories, and it is not subtle.</p> <p>The earliest figures in computing, Lovelace, Turing, Hoare, and Dijkstra, were either formally educated in the humanities or deeply immersed in philosophical thinking as a central part of their intellectual lives. Lovelace called herself a poetical scientist. Hoare studied Latin, Greek, and philosophy before he ever wrote a line of code. Dijkstra studied classical languages and wanted to be a diplomat before he became a programmer. Turing published in philosophy journals and spent his life asking questions about consciousness and the nature of thought.</p> <p>The next generation, Hopper and Ritchie, came through educational environments that still valued breadth. Hopper's Vassar education exposed her to economics, botany, physiology, and geology alongside mathematics. Ritchie's Harvard and Bell Labs years immersed him in a culture where intellectual curiosity across disciplines was the norm, not the exception.</p> <p>By the time we reach Kernighan and Stallman, the humanities influence has thinned to almost nothing in their formal education. Kernighan studied engineering physics. Stallman studied physics. Both are brilliant. Both are consequential. But Kernighan has spent the second half of his career actively seeking out the humanities connection he never had, and Stallman's philosophical contributions, while profound, have sometimes suffered from a lack of the communicative skill that humanities training cultivates.</p> <p>And after them? The pattern does not continue. It ends.</p> <p>Look at the technology leaders and influential engineers of the last twenty years. Look at the people building the platforms, the social networks, the AI systems, the attention economies. How many of them studied philosophy? How many of them read classical literature or engaged seriously with ethics or political theory or the history of human thought? How many of them could write an essay in the style of Dijkstra, or articulate a vision of human-centered computing like Hopper, or reason about consciousness and moral responsibility like Turing?</p> <p>The answer is vanishingly few. And we can see the results.</p> <p>+++</p> <p>This is not an abstract concern. The absence of humanities thinking in modern technology has consequences that are felt by real people in their daily lives.</p> <p>Social media platforms were designed by engineers who optimized for engagement metrics without understanding, or perhaps without caring, what the deliberate manufacture of outrage and addiction does to a human mind. Attention economies were built by people who studied computer science and business but never studied propaganda, rhetoric, or the psychology of manipulation. Algorithms that determine what billions of people see, read, and believe every day were written by teams that included no one trained in ethics, epistemology, or the philosophy of information.</p> <p>The result is a generation of technology that is technically sophisticated and humanistically bankrupt. We have systems that can predict what you want to buy but cannot be bothered to consider whether showing a teenager an endless feed of curated suffering is a good idea. We have AI models trained on the full breadth of human knowledge being deployed by people who have never seriously asked what responsibility comes with that power. We have apps that children are required to use for school that crash, load slowly, and harvest data, built by engineers who never stopped to think about the family on a slow internet connection whose only concern is whether their kid got to school safely.</p> <p>Alan Turing was asking whether machines could think in 1950. He published that question in a philosophy journal because he understood it was a philosophical question, not a technical one. Seventy-five years later, we are building machines that increasingly influence how humans think, and the people building them have largely abandoned the disciplines that would help them understand the gravity of what they are doing.</p> <p>Lovelace saw that computing could touch music, language, and art. She saw that because she lived between poetry and mathematics and refused to choose. Hopper saw that programming had to speak human language because she understood that most people are not symbol manipulators. Dijkstra wrote essays instead of technical reports because he believed clarity of expression was a moral obligation. These were not decorative sensibilities. They were the insights that shaped the field itself.</p> <p>When you strip the humanities out of the people who build technology, you do not get more efficient engineering. You get engineering that has lost its sense of purpose, its awareness of consequence, and its ability to see the people on the other side of the screen.</p> <p>+++</p> <p>None of this is meant to suggest that every programmer needs a philosophy degree or that studying Latin will make you better at writing Go. The point is not about credentials. It is about disposition.</p> <p>The people who built the foundations of computing shared something that went beyond technical brilliance. They had a concern for clarity that extended past code into how they communicated with other human beings. They had a sense that their work existed in a larger context, that the systems they built would be used by people whose lives would be shaped by those systems. They asked not just "does it work" but "is it good," and they understood that "good" meant something more than functional correctness.</p> <p>That disposition does not require a degree. But it does require more than casual interest. Reading a philosophy book on a weekend is a hobby. Spending serious, sustained time with literature, with history, with ethics, with the long conversation that humanity has been having with itself for thousands of years about what it means to live well and treat each other justly, that is something different. It changes how you see problems. It changes what questions you think to ask. It changes what you are willing to ship and what you refuse to.</p> <p>Higher education bears some responsibility for the current state of things. Computer science programs at most universities require little to no engagement with the humanities. Students can graduate with the technical skills to build systems that affect millions of lives without ever having been asked to seriously consider what that responsibility means. But blaming institutions only goes so far. The choice to engage with the humanities is ultimately a personal one, and it is available to anyone with a library card, an internet connection, and the willingness to sit with difficult, unfamiliar ideas long enough to let them change how you think.</p> <p>+++</p> <p>This essay has asked you to spend some time with the stories of people who built the world you work in every day. It has asked you to slow down, to read about their lives, to notice that the things you admire most about their work, the elegance, the clarity, the concern for people, the refusal to accept unnecessary complexity, came not from their technical training but from something deeper and older.</p> <p>If you are a technically oriented person and you have made it this far, you have already demonstrated something. You have invested time in a piece of writing that does not teach you a new framework or show you a clever optimization. You have sat with ideas that do not have immediate practical application. You have, for a few minutes, done the thing that this essay is asking you to consider doing more of.</p> <p>The founders of your field read philosophy and wrote essays and studied classical languages and asked questions about consciousness and beauty and the nature of human thought. They did not do these things because they had spare time or because someone made them. They did them because they understood, implicitly or explicitly, that building machines worthy of human trust requires understanding what it means to be human.</p> <p>That understanding is not going to come from another tutorial, another side project, or another Hacker News thread. It is going to come from the long, slow, sometimes difficult work of engaging with the humanities. Read philosophy. Read literature. Read history. Study how humans have thought about ethics, about power, about communication, about what it means to build something that lasts. Sit with those ideas long enough to let them reshape how you approach your own work.</p> <p>The tradition that produced Lovelace's poetical science and Hoare's classical mind and Dijkstra's handwritten essays and Hopper's insistence that computers must speak to people is not dead. But it is diminishing, generation by generation, as the field grows further from its roots. You have the opportunity to reverse that. Not by going back to school, necessarily, but by taking the humanities seriously enough to let them into your professional life, your design decisions, your code reviews, your conversations with the people who will use what you build.</p> <p>The machines we build reflect the minds that build them. If those minds are nourished only by technical knowledge, the machines will be technically competent and humanistically hollow. If those minds are broad, curious, and grounded in the long tradition of human thought, the machines will be something better. They will be worthy of the people who depend on them.</p> <p>The choice, as it has always been, is yours.</p> 10th Person 2026-02-13T00:00:00+00:00 2026-02-13T00:00:00+00:00 Unknown https://blainsmith.com/articles/10th-person/ <p>In World War Z, Israel survives the zombie apocalypse because of a simple rule: if nine intelligence officials agree on something, the tenth must assume they're all wrong and explore alternatives. Not to be difficult. Not to play devil's advocate. But because consensus is dangerous and groupthink kills.</p> <p>I've spent my career being that tenth person. The one questioning why we need Kubernetes for a service with three endpoints. The one asking if we really need to rewrite the whole thing in Rust. The one mapping out what happens when your "temporary workaround" becomes permanent infrastructure that nobody understands.</p> <p>People think I'm being difficult. I'm not. I'm being the 10th person.</p> <h2 id="consensus-is-a-red-flag">Consensus is a Red Flag</h2> <p>When everyone in the room agrees, someone isn't thinking. They're following. They're deferring. They're nodding along because it's easier than asking the question that makes them look stupid.</p> <blockquote> <p>Let's use microservices.</p> </blockquote> <blockquote> <p>We should add GraphQL.</p> </blockquote> <blockquote> <p>This needs to be event-driven.</p> </blockquote> <p>Nobody asks why. Nobody maps the decision three moves ahead. Nobody considers what happens when the junior engineer who built this quits and we're left maintaining a distributed system that could've been a monolith with a cron job.</p> <p>The 10th person asks those questions. Not because they're smarter. Because they're wired differently.</p> <h2 id="the-autism-advantage">The Autism Advantage</h2> <p>I'm autistic. That means I can't <strong>not</strong> see the decision tree. When you propose a solution, my brain immediately forks into every possible timeline:</p> <blockquote> <p>What if this scales?</p> </blockquote> <blockquote> <p>What if it doesn't?</p> </blockquote> <blockquote> <p>What happens when the database fills up?</p> </blockquote> <blockquote> <p>What happens when the cache gets stale?</p> </blockquote> <blockquote> <p>What happens when the person who wrote this leaves?</p> </blockquote> <blockquote> <p>What happens when we need to debug this at 3 AM?</p> </blockquote> <p>Most people see one path forward. I see thirteen. Most people are surprised by outcomes. I'm not. I've already lived them in my head before we wrote a single line of code.</p> <p>This isn't a superpower. It's fucking exhausting and I can't just turn it off. But it's also why I'm good at being the 10th person. I can't ignore the futures everyone else hasn't considered yet.</p> <h2 id="question-everything-especially-the-easy-stuff">Question Everything, Especially the Easy Stuff</h2> <p>The hardest part of being the 10th person isn't questioning big decisions. It's questioning the small ones. The "obvious" ones. The ones where everyone assumes there's only one right answer.</p> <blockquote> <p>We'll just add a new service for that.</p> </blockquote> <blockquote> <p>We'll just add another dependency.</p> </blockquote> <blockquote> <p>We'll just patch it later.</p> </blockquote> <p>No. Stop. Why?</p> <blockquote> <p>Why do we need another service?</p> </blockquote> <blockquote> <p>Why can't the existing one handle this?</p> </blockquote> <blockquote> <p>Why are we optimizing for convenience over maintainability?</p> </blockquote> <blockquote> <p>Why are we assuming future-us will have more time than present-us?</p> </blockquote> <p>These aren't rhetorical questions. They're the 10th person protocol. Assume the consensus is wrong. Map the alternatives. Force the room to think instead of react.</p> <h2 id="it-s-not-contrarianism-it-s-discipline">It's Not Contrarianism, It's Discipline</h2> <p>Being the 10th person isn't about being contrarian for sport. It's not about saying "no" to everything. It's about disciplined skepticism. It's about recognizing that the path of least resistance is usually the path to technical debt, production outages, and systems nobody understands.</p> <p>When I question your design, I'm not attacking you. I'm stress-testing the decision. I'm forcing us to articulate why this is the right call instead of just the easiest one.</p> <p>If you can defend it, great. We move forward with confidence.<br /> If you can't, we just saved ourselves months of regret.</p> <h2 id="don-t-waste-my-time-if-you-just-want-agreement">Don't Waste My Time If You Just Want Agreement</h2> <p>Here's what pisses me off: teams that bring me in, ask for my input, and then get upset when I don't just nod along. They don't want the 10th person. They want a rubber stamp.</p> <p>If you already decided on the solution and you're just looking for validation, don't waste my time. Use some fucking LLM. It'll tell you your idea is great. It'll suggest some optimizations. It'll never ask "why are we doing this at all?"</p> <p>That's not what I'm here for.</p> <p>When you pull me into a meeting, a design review, or a code review you're asking me to map the decision tree. To consider the futures you haven't thought about. To question the assumptions you're making. That's the job. That's what my brain does whether I want it to or not.</p> <p>But if you've already made up your mind? If you're just checking a box so you can say you "got feedback"? Then you're wasting both our time. Mine, because I'm going to give you real feedback anyway. Yours, because you're going to ignore it and then act surprised six months later when everything I predicted comes true.</p> <p>So here's the deal: if you want agreement, go find it somewhere else. There are plenty of people who will tell you what you want to hear. There are infinite blog posts that will validate your choices. There are LLMs that will generate enthusiastic affirmations on demand.</p> <p>But if you want the 10th person and you actually want someone to stress-test your thinking then bring me in. But don't get mad when I do the job.</p> <p>And here's the brutal trueth... If this happens again and again I will do what the 10th person should be doing, but I won't tell anyone. I will prepare for the invetiable future to protect my own mental health when there comes a time when the brilliant, clever, or lazy decision crashes at midnight.</p> <h2 id="the-room-needs-a-10th-person">The Room Needs a 10th Person</h2> <p>Most teams don't have one. They have nine people who all think the same way, went to the same bootcamps, read the same blog posts, and worship the same thought leaders. They add complexity because everyone else is doing it. They chase trends because nobody wants to be the one who didn't adopt the hot new framework.</p> <p>That's how you end up with a "simple CRUD app" running on Kubernetes with a service mesh, an event bus, three databases, and a deployment pipeline that takes 40 minutes.</p> <p>Someone needed to be the 10th person. Someone needed to ask "why" before it was too late.</p> <h2 id="how-to-be-the-10th-person">How to Be the 10th Person</h2> <p>You don't need to be autistic. You don't need to see thirteen futures. You just need discipline:</p> <ol> <li><strong>Assume consensus is wrong.</strong> If nine people agree, somebody isn't thinking.</li> <li><strong>Map the decision tree.</strong> Don't just ask "will this work?" Ask "what happens when it doesn't?"</li> <li><strong>Question the small stuff.</strong> Big decisions get scrutiny. Small ones compound into disasters.</li> <li><strong>Force articulation.</strong> Make people explain their reasoning out loud. If they can't, the decision isn't ready.</li> <li><strong>Accept the discomfort.</strong> Being the 10th person is lonely. Do it anyway.</li> </ol> <h2 id="in-closing">In Closing</h2> <p>Tech doesn't need more consensus. It needs more 10th people. People who question the defaults. People who map the futures everyone else ignores. People who have the discipline to say "wait, why are we doing this?" even when it's easier to just nod along.</p> <p>I've been that person my whole career. It's made me difficult to work with sometimes. It's also made me right more often than I'm wrong.</p> <p>So be difficult. Be the 10th person. Because if everyone agrees, someone isn't thinking.</p> <p>Also, Happy Friday the 13th!</p> 30 Years of Programming at 44 Years Old 2026-01-13T00:00:00+00:00 2026-01-13T00:00:00+00:00 Unknown https://blainsmith.com/articles/30-years-of-programming-at-44-years-old/ <p>In 1995, I built my first website. It was a Metallica fan page. Black background, red text, animated GIFs of pixelated flames, a MIDI file that autoplayed "Enter Sandman" whether you wanted it to or not. I thought it was the coolest thing I had ever made and I was only 14.</p> <p>I had no idea what I was doing. I just knew that if I typed the right words into Notepad and saved the file with <code>.html</code> at the end, something would appear in Netscape Navigator that didn't exist before I made it. That feeling of conjuring something from nothing hooked me immediately.</p> <p>By 1998, I was one of the kids in computer shop tasked with redesigning our <a rel="external" href="https://spectra.video/w/sZM9WBZy5S8Mzd2FNPiPMc">high school's website</a>. It was a technical high school where kids learned trades like HVAC, automotive, masonry, IT, etc. Our IT shop teachers supervised us as we figured it out. Same tools I'd been using at home: Notepad, HTML. No frameworks, no build steps, no dependencies. Just text files that turned into something the whole school could see and use.</p> <p>There was something pure about that. We were students building something real, supervised by teachers who understood the work. No pitch decks or growth metrics. They just wanted us make it work, make it useful, and learn something in the process.</p> <h2 id="growing-and-learning">Growing and Learning</h2> <p>The next 15 years were a blur of growth. I learned PHP, then Java, then Ruby, then ColdFusion, then Node.js. I worked at Harvard building school-wide course evaluation and inventory management systems for research labs. I built tournament systems for Riot Games, streaming platforms for media companies, backend services for mobile games you've probably played. I got good at the craft. I learned how to build systems that didn't fall over, how to write code that other people could read, how to lead teams without being an asshole about it.</p> <p>Then in 2016, I found Go. Something clicked that hadn't clicked in years. Here was a language that felt like the tools I started with since it was simple, direct, and with no magic, but capable of building the distributed systems the modern internet demanded. No runtime to babysit and no framework churn. It was just a compiler, a standard library that actually had what you needed, and code that did exactly what it looked like it did.</p> <p>I went all in. Compiled languages. Concurrent systems. I started caring deeply about performance, about memory, about what was actually happening when my code ran. I read RFCs for fun. I traced packets through the kernel. I wanted to understand the machine, understand the network, not just ship features on top of abstractions I couldn't explain.</p> <p>For a while, it felt like the joy was back that I didn't know I lost. Programming with purpose. Building things that worked because I understood <em>why</em> they worked. I climbed the ladder. Senior engineer then to Staff engineer. I worked on CDNs pushing packets at the edge. I wrote eBPF programs to measure network latency at the kernel level. I helped build the infrastructure that routes traffic for millions of users. The problems got harder and more interesting. The compensation got better. By any reasonable measure, I had made it.</p> <h2 id="tapped-out">Tapped Out</h2> <p>And then I looked around.</p> <p>The industry I had grown up in that promised to connect people, to democratize information, to make the world smaller and more accessible had become something else entirely. The fun was and the discovery was gone. What remained was a ruthless machine optimized for extraction.</p> <p>I watched companies I respected pivot from building useful things to chasing metrics that looked good in pitch decks. I sat in meetings where the question was never "does this help anyone?" but "how do we increase engagement?" I saw smart and talented engineers burn themselves out building features designed to manipulate rather than serve. The word <a rel="external" href="https://blainsmith.com/articles/fuck-your-hustle/">"hustle"</a> got thrown around like a badge of honor instead of the warning sign it actually is.</p> <p>The VCs wanted growth at any cost and the founders wanted exits. The middle managers wanted to hit their OKRs. And the users? They became the product. Their attention, their data, and their habits were all commodified and sold to the highest bidder.</p> <p>I burned out. Multiple times. Not the cute kind of burnout where you take a long weekend and feel better. The kind where you stop eating. The kind where you lose weight you can't afford to lose. The kind where the barbell sits untouched in the garage for months because you can't find the will to pick it up, even though lifting was one of the few things that kept you sane. The kind where you're sitting alone wondering how you got here and whether any of it was worth it.</p> <p>And through all of it, I was raising my son. He's autistic, and he's brilliant, and he's growing up in a world that this industry helped create. A world designed to hijack attention, to exploit dopamine loops, to turn human connection into engagement metrics. Every dark pattern I helped normalize, every addictive feature I stayed silent about, every compromise I made to hit a deadline. I think about what that world means for him. For all the kids who deserve better than to be optimized against.</p> <p>I worked at companies during this era that were doing genuinely interesting technical work. Layer 1 networking. Real-time packet routing. Security research. The engineering was excellent. But even in those places, I could feel the pressure of the constant pull toward whatever would juice the numbers, whatever would impress the board, whatever would get us closer to the next funding round or acquisition.</p> <p>This wasn't what I signed up for.</p> <p>The kid in 1995 who made a Metallica fan page wasn't dreaming of surveillance capitalism. The teenager in 1998 who built his high school's website wasn't imagining a future where every click would be tracked, analyzed, and monetized. I got into this because I loved making things. Somewhere along the way, the industry decided that making things wasn't enough anymore and instead you had to scale them, monetize them, disrupt them, exit them.</p> <p>Fuck that.</p> <h2 id="end-game">End Game</h2> <p>I'm 44 <a rel="external" href="https://www.onthisday.com/date/1982/january/13">today</a>. If I'm lucky, I have maybe 20 more years of writing code in me. Probably less at the pace this industry moves. That's not a lot of time, but it's enough to be deliberate about how I spend it.</p> <p>Here's what I've realized... The skills I've accumulated over 30 years— the system design, the networking knowledge, the understanding of how software actually works at a low level—those skills have value beyond making rich people richer. They can be used to build things that matter. Things that respect the people who use them. Things that don't treat privacy as an obstacle or users as resources to be harvested.</p> <p>I'm thinking about co-ops and nonprofits and community organizations that need software but can't afford to have their data sold to advertisers. I'm thinking about tools that are secure by default, that don't phone home, that do one thing well and then get out of the way. I'm thinking about the kind of software I would have wanted to use when I was 14. The software that felt like it was made by someone who gave a damn.</p> <p>The irony isn't lost on me. I spent years building expertise in distributed systems and cloud infrastructure, and now I want to use that expertise to help people who are actively trying to avoid the cloud. I learned how to scale services to millions of users, and now I'm more interested in software that works for thousands. I got really good at the game, and now I want to play a different one.</p> <p>Maybe that's what 30 years buys you: the clarity to know what you actually want.</p> <p>I don't have all the answers. I'm still figuring out what this next chapter looks like. But I know it involves simpler tools, smaller communities, and software that exists to serve rather than extract. I know it involves working with people who share those values.</p> <p>The Metallica fan page is long gone and so is the high school website. But the feeling I had when I made them, that sense of wonder at creating something from nothing, that belief that I could build things that mattered, that's still there. It just got buried under two decades of sprints and standups and growth metrics.</p> <p>The next 10 to 20 years will be about protectng my own peace and working on things that make me proud of myself.</p> Read the Fucking Manual 2025-11-19T00:00:00+00:00 2025-11-19T00:00:00+00:00 Unknown https://blainsmith.com/articles/read-the-fucking-manual/ <p>There's a particular kind of engineer I've been noticing more and more lately. They show up in Slack channels with half-formed questions. They post screenshots of error messages on Twitter asking "anyone know what this means?" They copy-paste compiler errors into ChatGPT and treat whatever hallucination comes back as gospel. They interrupt their teammates with questions that could be answered in thirty seconds of reading documentation.</p> <p>This isn't engineering. This is learned helplessness dressed up in collaborative clothing.</p> <h2 id="the-death-of-self-sufficiency">The Death of Self-Sufficiency</h2> <p>Engineering used to mean something. It meant you knew how to research. You could read a language specification and understand what the compiler was doing. You could trace through library source code to figure out why something wasn't working. You read academic papers, man pages, and RFCs. You debugged your own problems before asking for help.</p> <p>Somewhere along the way, we convinced ourselves that this was inefficient. Why spend thirty minutes reading documentation when you could ask someone and get an answer in five? Why dig through source code when you could prompt an AI and get code back immediately? Why think for yourself when the collective intelligence of social media is just a post away?</p> <p>Here's what we've traded for that convenience: actual engineering skill.</p> <h2 id="the-problem-with-modern-research">The Problem with Modern "Research"</h2> <p><strong>Stack Overflow is dead.</strong> I'm not being dramatic. The quality has been declining for years, and lately it's gotten worse. The good answers are buried under years of outdated advice, version-specific solutions that no longer apply, and increasingly, AI-generated nonsense that looks helpful but doesn't actually work. The experts who used to answer questions there have moved on because why would they waste time competing with bots?</p> <p><strong>AI will confidently lie to you.</strong> ChatGPT and its cousins will generate API calls that don't exist. They'll suggest libraries that were deprecated years ago. They'll write code that compiles but does completely the wrong thing. And they'll do it all with the confidence of a junior developer on their first day, which is to say, absolute certainty masking complete ignorance.</p> <p><strong>Social media is full of grifters.</strong> Post a technical question on Twitter and watch the replies roll in. Half will be people trying to sell you their course, their book, their SaaS product. The other half will be people who didn't read your question carefully and are answering something completely different. The actual helpful responses? Buried somewhere in the noise.</p> <p><strong>Slack is a nerd snipe waiting to happen.</strong> Every time you drop a question into a channel, you're potentially derailing someone else's deep work. They see the notification. They context switch. They try to help because they're good people. And now you've cost the team thirty minutes of productive time across three people instead of spending ten minutes reading the docs yourself.</p> <h2 id="what-engineering-actually-is">What Engineering Actually Is</h2> <p>Engineering is problem-solving through research and experimentation. It's:</p> <ul> <li>Reading the fucking language specification when you don't understand how something works</li> <li>Tracing through library source code to see what it's actually doing</li> <li>Writing small test programs to verify your understanding</li> <li>Reading error messages carefully and understanding what they're telling you</li> <li>Consulting API documentation as your first source of truth</li> <li>Looking at academic papers when you need to understand the theory</li> <li>Checking the release notes when something breaks after an upgrade</li> </ul> <p>It's doing the work. The real work. The thinking work.</p> <p>When you skip this step and go straight to asking someone else, you're not learning. You're getting a fish instead of learning to fish. And unlike the proverb, in engineering, you need to catch new fish every single day.</p> <h2 id="the-collaboration-fallacy">The Collaboration Fallacy</h2> <p>Someone is going to read this and say "but collaboration is important!" Sure. But collaboration isn't what's happening when you post "how do I parse JSON in Go?" in a Slack channel.</p> <p>Real collaboration happens in:</p> <ul> <li>Code reviews where everyone has done their homework</li> <li>Design discussions where people have researched the problem space</li> <li>Standups where blockers are identified and documented</li> <li>Office hours where dedicated time is set aside for helping others</li> <li>Pair programming sessions where knowledge transfer is the explicit goal</li> </ul> <p>What's not collaboration:</p> <ul> <li>Interrupting someone's flow state with a question you could answer yourself</li> <li>Expecting teammates to debug your code for you</li> <li>Using your colleagues as a replacement for documentation</li> <li>Treating your team as a just-in-time search engine</li> </ul> <p>Respect for other people's time isn't optional. It's fundamental to being a good teammate. And the first way to show that respect is to try figuring things out yourself before you ask.</p> <h2 id="the-skills-you-re-not-building">The Skills You're Not Building</h2> <p>Every time you take the shortcut, you're weakening your engineering muscles. You're not learning:</p> <ul> <li>How to read technical documentation effectively</li> <li>How to trace through unfamiliar codebases</li> <li>How to form and test hypotheses about what's wrong</li> <li>How to narrow down the root cause of a problem</li> <li>How to verify your understanding is correct</li> </ul> <p>These skills compound. The engineer who has spent years reading source code can scan a new codebase and understand it in hours. The one who's always asked others for answers gets lost immediately.</p> <p>The engineer who reads specifications knows not just what works, but why it works and when it breaks. The one who copies from Stack Overflow has a collection of magic incantations they don't understand.</p> <p>The engineer who's debugged hundreds of issues can look at a stack trace and immediately narrow down the problem. The one who's always been handed solutions is helpless when something goes wrong.</p> <h2 id="when-asking-is-appropriate">When Asking Is Appropriate</h2> <p>I'm not saying never ask for help. I'm saying exhaust your options first.</p> <p>Ask when:</p> <ul> <li>You've read the docs and still don't understand</li> <li>You've traced through the code and the behavior doesn't match what you expected</li> <li>You've spent a reasonable amount of time (30-60 minutes) trying to figure it out</li> <li>You have a specific question about a specific thing, not a vague "how do I...?"</li> <li>You can show what you've tried and where you're stuck</li> </ul> <p>Don't ask when:</p> <ul> <li>You haven't looked at the documentation yet</li> <li>You got an error message and didn't bother reading it</li> <li>You want someone to write code for you</li> <li>You're being lazy and hoping someone else will do the work</li> <li>You're interrupting people's flow state for something non-urgent</li> </ul> <h2 id="sources-of-truth">Sources of Truth</h2> <p>When you do research, use actual sources of truth:</p> <p><strong>Language specifications</strong> - Want to know how something works? Read the spec. Not a blog post about the spec. Not someone's interpretation on social media. The actual specification.</p> <p><strong>API documentation</strong> - Official docs from the library maintainers. Not random tutorials. Not AI summaries. The actual documentation.</p> <p><strong>Source code</strong> - When the docs aren't clear, read the code. It's the ultimate source of truth about what something actually does.</p> <p><strong>Man pages</strong> - For Unix tools, the man page is the authority. Not a blog post from 2015.</p> <p><strong>RFCs and academic papers</strong> - For protocols and algorithms, go to the source. The people who invented the thing wrote down how it works.</p> <p><strong>Release notes and changelogs</strong> - When something breaks after an upgrade, start here. Don't ask why something changed. Read what changed.</p> <h2 id="building-the-skill">Building the Skill</h2> <p>If you've been taking shortcuts, it's going to feel slow and frustrating to start doing the work. Good. That friction is where learning happens.</p> <p>Start small:</p> <ul> <li>Next time you get a compiler error, read it carefully before asking</li> <li>When you need to use a library function, read its documentation first</li> <li>Set a timer for 30 minutes and try to solve the problem yourself</li> <li>Keep notes on what you learn so you don't have to learn it again</li> <li>Build a habit of checking the source code when docs are unclear</li> </ul> <p>Over time, you'll get faster. You'll learn where to look. You'll develop intuition for what kinds of problems have certain solutions. You'll become self-sufficient in a way that makes you more valuable and less dependent.</p> <h2 id="respect-and-discipline">Respect and Discipline</h2> <p>This comes back to the themes I keep hitting in my writing: discipline and respect for others.</p> <p>Discipline means doing the work even when it's easier not to. It means reading the manual instead of asking. It means thinking through the problem instead of outsourcing your thinking to someone else.</p> <p>Respect means not wasting other people's time. It means not interrupting their flow state with questions you could answer yourself. It means coming to code reviews prepared. It means treating collaboration as something precious, not something to be squandered on laziness.</p> <p>If you want to be a senior engineer, this is part of it. Not just writing good code, but being someone who can solve their own problems. Being someone who does the research. Being someone who reads the fucking manual.</p> <h2 id="do-better">Do Better</h2> <p>The next time you're about to fire off a question in Slack, or paste an error into ChatGPT, or post "anyone know how to...?" on social media, stop. Open the documentation. Read the error message. Look at the source code. Think about the problem. Try something. Fail. Learn. Try again. This is engineering. This is the job. And if you're not willing to do it, you're not engineering. You're just asking other people to engineer for you.</p> <p>Read the fucking manual. Your future self and your teammates will thank you for it. If you want to challenge yourself, cut the internet connection from your machine for an hour and see if you can without direct and immediate access to Slack and social media and use the local instance of your library documentation and in-memory unit tests to figure shit out.</p> How I Write HTTP Servers 2025-10-31T00:00:00+00:00 2025-10-31T00:00:00+00:00 Unknown https://blainsmith.com/articles/how-i-write-http-servers/ <p>I am surprised I haven't written this sooner, especially since I have written about <a href="/articles/how-i-write-http-clients">How I Write HTTP Clients</a>. It was inevitable this post was going to be written. With that said, I want this post to follow the same format so a lot of this will read very similar since I copied a lot of the language from it and just adapted it for HTTP servers.</p> <p>I have written a lot of HTTP server that serve APIs with different types of content over the years and there have been some patterns that have emerged and so I thought I would share them in hopes they help others. The major concepts I want to highlight are:</p> <ul> <li>Composing Server Dependencies and Options</li> <li>Composing Middleware</li> <li>Data Encoding/Decoding/Validation</li> <li>Testing Handlers</li> </ul> <p>To illustrate these concepts I am going to write a REST API with the following key features:</p> <ul> <li>Support JSON / XML</li> <li>Authorization Tokens</li> <li>Stores data in a database</li> </ul> <h2 id="write-handlers-first">Write Handlers First</h2> <p>Most of you might think to start writing the main HTTP server first, which makes a lot of sense, but I like to focus on the micro and write my individual handlers first. This allows me to discover dependencies as they come and keeps me focused on small bits of single-purpose and testable logic first.</p> <p>I also like to start with the routes that create data since we need data to exist to read it and delete it.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> CreateArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>The <code>CreateArticle()</code> is a simple function that takes in its dependencies as arguments and returns a <code>http.Handler</code> which we can mount to a route. This particular handler requires a <code>Logger</code> and a <code>DataStore</code> to do its work. By keeping these as interfaces we can pass in any implementation of them we want.</p> <p>For now lets complete the rest of the handlers for working with articles.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> CreateArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> DeleteArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> ListArticles</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> GetArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> GetArticleImage</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> bs</span><span style="color: #59C2FF;"> BlobStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Looking over the dependencies for all of these handlers we can can see we need a <code>Logger</code>, <code>DataStore</code>, and a <code>BlobStore</code>.</p> <h2 id="interface-implementations">Interface Implementations</h2> <p>Since we discovered the dependenices we need for our handlers we can rough out what those would be. The important concept here is that it doesn't matter how each of them are implemented since we are only concerned with the handlers themselves. For illustrative purposes we could have the following interfaces defined as:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Logger</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Log</span><span>(</span><span style="color: #D2A6FF;">lvl</span><span style="color: #59C2FF;"> Level</span><span>,</span><span style="color: #D2A6FF;"> msg</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> kvpairs</span><span style="color: #F29668;"> ...</span><span style="color: #39BAE6;">string</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> DataStore</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> SaveArticle</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> article</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Article</span><span>)</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"><span style="color: #FFB454;"> QueryArticles</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> filterby</span><span style="color: #39BAE6;"> string</span><span>) ([]</span><span style="color: #59C2FF;">Articles</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> FindArticle</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> id</span><span style="color: #39BAE6;"> int</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Article</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> DeleteArticle</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> id</span><span style="color: #39BAE6;"> int</span><span>)</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> BlobStore</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> ReadBlob</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> path</span><span style="color: #39BAE6;"> string</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Blob</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> WriteBlob</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> path</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #59C2FF;"> io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>)</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Each of these could have a few implementations and in most scenarios they would have only one, especially when the server is running in production. However, there are times where they might need to change and/or its impossible to test these handlers with a concrete implementation of something that requires network access to a 3rd party system.</p> <ul> <li><code>Logger</code> could be implemented with <code>log/slog</code>, <code>zap.Logger</code>, etc.</li> <li><code>DataStore</code> could be PostgreSQL with <code>pgx</code> or as simple as <code>database/sql</code>.</li> <li><code>BlobStore</code> could be the local filesystem, AWS S3, or even <code>scp</code> if you're feeling spicy</li> </ul> <h2 id="json-xml-support">JSON / XML Support</h2> <p>APIs should leverage the <code>Content-Type</code> and <code>Accept</code> HTTP headers to negotiate what encoding format should be used during the lifecycle of a request and response. Let's just focus on the <code>CreateArticle()</code> handler since the rest will follow the same idea.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> CreateArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> req</span><span style="color: #59C2FF;"> ReqPayload</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> res</span><span style="color: #59C2FF;"> ResPayload</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Content-Type&quot;</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/xml&quot;</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> article</span><span style="color: #F29668;"> :=</span><span style="color: #59C2FF;"> Article</span><span>{</span></span> <span class="giallo-l"><span> Title: req.Title,</span></span> <span class="giallo-l"><span> Contents: req.Contents,</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> :=</span><span> ds.</span><span style="color: #FFB454;">CreateArticle</span><span>(r.</span><span style="color: #FFB454;">Context</span><span>(),</span><span style="color: #F29668;"> &amp;</span><span>article)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> res</span><span style="color: #F29668;"> =</span><span style="color: #59C2FF;"> ResPayload</span><span>{</span></span> <span class="giallo-l"><span> ID: article.ID,</span></span> <span class="giallo-l"><span> CreatedAt: time.</span><span style="color: #FFB454;">Now</span><span>(),</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Accept&quot;</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/xml&quot;</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // ...</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>This would be the ideal setup for each.</p> <ol> <li>Set up the request and response structs</li> <li>Decode the request depending on the <code>Content-Type</code> header</li> <li>Do the work of the handler and build the response</li> <li>Encode the response depending on the <code>Accept</code> header</li> </ol> <p>This can get very repetative for every route so, fortuately, we can leverage some generics to make this a bit easier on ourselves.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> decode</span><span>[</span><span style="color: #D2A6FF;">T</span><span style="color: #59C2FF;"> any</span><span>](</span><span style="color: #D2A6FF;">r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) (</span><span style="color: #59C2FF;">T</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> req</span><span style="color: #59C2FF;"> T</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Content-Type&quot;</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> json.</span><span style="color: #FFB454;">NewDecoder</span><span>(r.Body).</span><span style="color: #FFB454;">Decode</span><span>(</span><span style="color: #F29668;">&amp;</span><span>req)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/xml&quot;</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> xml.</span><span style="color: #FFB454;">NewDecoder</span><span>(r.Body).</span><span style="color: #FFB454;">Decode</span><span>(</span><span style="color: #F29668;">&amp;</span><span>req)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> req,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> encode</span><span>[</span><span style="color: #D2A6FF;">T</span><span style="color: #59C2FF;"> any</span><span>](</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>,</span><span style="color: #D2A6FF;"> status</span><span style="color: #39BAE6;"> int</span><span>,</span><span style="color: #D2A6FF;"> res</span><span style="color: #59C2FF;"> T</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span></span> <span class="giallo-l"><span> mimeType</span><span style="color: #F29668;"> :=</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Accept&quot;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> w.</span><span style="color: #FFB454;">Header</span><span>().</span><span style="color: #FFB454;">Set</span><span>(</span><span style="color: #AAD94C;">&quot;Content-Type&quot;</span><span>, mimeType)</span></span> <span class="giallo-l"><span> w.</span><span style="color: #FFB454;">WriteHeader</span><span>(status)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> mimeType {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> json.</span><span style="color: #FFB454;">NewEncoder</span><span>(w).</span><span style="color: #FFB454;">Encode</span><span>(v)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/xml&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> xml.</span><span style="color: #FFB454;">NewEncoder</span><span>(w).</span><span style="color: #FFB454;">Encode</span><span>(v)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Now that we have some nice encoding/decoding functions we can reuse these in our handlers and the proper encoding format will be used based on the headers in the <code>http.Request</code>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> CreateArticle</span><span>(</span><span style="color: #D2A6FF;">logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> res</span><span style="color: #59C2FF;"> ResPayload</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> req, err</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> decode</span><span>[</span><span style="color: #59C2FF;">ReqPayload</span><span>](r)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> article</span><span style="color: #F29668;"> :=</span><span style="color: #59C2FF;"> Article</span><span>{</span></span> <span class="giallo-l"><span> Title: req.Title,</span></span> <span class="giallo-l"><span> Contents: req.Contents,</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> :=</span><span> ds.</span><span style="color: #FFB454;">CreateArticle</span><span>(r.</span><span style="color: #FFB454;">Context</span><span>(),</span><span style="color: #F29668;"> &amp;</span><span>article)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> res</span><span style="color: #F29668;"> =</span><span style="color: #59C2FF;"> ResPayload</span><span>{</span></span> <span class="giallo-l"><span> ID: article.ID,</span></span> <span class="giallo-l"><span> CreatedAt: time.</span><span style="color: #FFB454;">Now</span><span>(),</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> encode</span><span>[</span><span style="color: #59C2FF;">ResPayload</span><span>](w, r, http.StatusOK, res)</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>We can even take this a step further and introduce some nice request validation too. We can accomplish this by expanding the <code>decode()</code> to not just use any type <code>T</code>, but a certain <code>Validator</code> and if the request is invalid decoding fails.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Validator</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Valid</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>)</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> decode</span><span>[</span><span style="color: #D2A6FF;">T</span><span style="color: #59C2FF;"> Validator</span><span>](</span><span style="color: #D2A6FF;">r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) (</span><span style="color: #59C2FF;">T</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> req</span><span style="color: #59C2FF;"> T</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Content-Type&quot;</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>:</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> json.</span><span style="color: #FFB454;">NewDecoder</span><span>(r.Body).</span><span style="color: #FFB454;">Decode</span><span>(</span><span style="color: #F29668;">&amp;</span><span>req)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;application/xml&quot;</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> xml.</span><span style="color: #FFB454;">NewDecoder</span><span>(r.Body).</span><span style="color: #FFB454;">Decode</span><span>(</span><span style="color: #F29668;">&amp;</span><span>req)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> req.</span><span style="color: #FFB454;">Valid</span><span>(r.</span><span style="color: #FFB454;">Context</span><span>())</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> req,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>We define a new <code>Validator</code> interface and change our <code>decode[T Validator]()</code> to work with that interface. Once decoding is completed we can call <code>Valid()</code> on the struct and return any validation errors that occur back to our handler. We didn't even need to change out handler at all!</p> <h2 id="testing-handlers">Testing Handlers</h2> <p>Now that we have a fully built handler we can begin to test it. I do want to stress that what we are testing should be the HTTP logic only. We should not be testing the behavior of <code>DataStore.CreateArticle()</code> since that should be handled by its own tests of its own implementations. We are merely testing the request / response lifecycle to ensure out payloads uphold the contracts with HTTP clients.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> mockLogger</span><span style="color: #FF8F40;"> struct</span><span> {}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> mockDataStore</span><span style="color: #FF8F40;"> struct</span><span> {}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> TestCreateArticle</span><span>(</span><span style="color: #D2A6FF;">t</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">testing</span><span>.</span><span style="color: #59C2FF;">T</span><span>) {</span></span> <span class="giallo-l"><span> handler</span><span style="color: #F29668;"> :=</span><span> handlers.</span><span style="color: #FFB454;">CreateArticle</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">mockLogger</span><span>{},</span><span style="color: #F29668;"> &amp;</span><span style="color: #59C2FF;">mockDataStore</span><span>{})</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Create the </span></span> <span class="giallo-l"><span> reqPayload</span><span style="color: #F29668;"> :=</span><span> bytes.</span><span style="color: #FFB454;">NewStringBuffer</span><span>(</span><span style="color: #AAD94C;">`{&quot;title&quot;:&quot;Article Title&quot;,&quot;contents&quot;:&quot;This is the contents of the article&quot;}`</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // The &quot;/&quot; path does not matter since we&#39;re calling the handler directly</span></span> <span class="giallo-l"><span> r</span><span style="color: #F29668;"> :=</span><span> httptest.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodPost,</span><span style="color: #AAD94C;"> &quot;/&quot;</span><span>, reqPayload)</span></span> <span class="giallo-l"><span> r.Header.</span><span style="color: #FFB454;">Set</span><span>(</span><span style="color: #AAD94C;">&quot;Content-Type&quot;</span><span>:</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>)</span></span> <span class="giallo-l"><span> r.Header.</span><span style="color: #FFB454;">Set</span><span>(</span><span style="color: #AAD94C;">&quot;Accept&quot;</span><span>:</span><span style="color: #AAD94C;"> &quot;application/json&quot;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> w</span><span style="color: #F29668;"> :=</span><span> httptest.</span><span style="color: #FFB454;">NewRecorder</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> handler.</span><span style="color: #FFB454;">ServeHTTP</span><span>(w, r)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> w.StatusCode</span><span style="color: #F29668;"> !=</span><span> http.StatusOK {</span></span> <span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Fail</span><span>(</span><span style="color: #AAD94C;">&quot;expected 200 response code&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>strings.</span><span style="color: #FFB454;">Contains</span><span>(w.Body.</span><span style="color: #FFB454;">String</span><span>(),</span><span style="color: #AAD94C;"> &quot;id&quot;</span><span>) {</span></span> <span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Fail</span><span>(</span><span style="color: #AAD94C;">&quot;expected response to contain generated article id&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Notice we didn't even need to start a real HTTP server and bind to a port to test this. We only needed to create an instance of the handler with its dependencies. We were able to provide our own in-memory implementation of them since they are just interfaces. Next, we created a request with <code>httptest.NewRequest()</code> with the body of the request as some JSON. <code>bytes.NewStringBuffer()</code> lets us make some JSON that implements the required <code>io.ReadCloser</code>. Then we make a <code>httptest.NewRecorder()</code> so our handler can write results to an in-memory buffer that simulates the client end of the connection. We can use parts of the record to test the status code and the contents of the response body to ensure our handler is doing what it is meant to do.</p> <h2 id="layers-of-middleware">Layers of Middleware</h2> <p>At this point we have all of our handlers written and tested and everything is working as expected. All tests run in-memory and can also run in parallel.</p> <p>Next we need to layer some middleware on top of them to protect them with authorization tokens and get insight into them with some basic logging. We just keep following the rule of writing functions that return <code>http.Handler</code>, but now they also accept a <code>http.Handler</code> as their first argument.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> LoggerMiddleware</span><span>(</span><span style="color: #D2A6FF;">next</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span>,</span><span style="color: #D2A6FF;"> logger</span><span style="color: #59C2FF;"> Logger</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span> logger.</span><span style="color: #FFB454;">Log</span><span>(logger.INFO,</span><span style="color: #AAD94C;"> &quot;request&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;method&quot;</span><span>, r.Method,</span><span style="color: #AAD94C;"> &quot;path&quot;</span><span>, r.RequestURI)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;"> next</span><span>(w, r)</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> AuthorizationMiddleware</span><span>(</span><span style="color: #D2A6FF;">next</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span>,</span><span style="color: #D2A6FF;"> as</span><span style="color: #59C2FF;"> AuthStore</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span> authheader</span><span style="color: #F29668;"> :=</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Authorization&quot;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> method, token, found</span><span style="color: #F29668;"> :=</span><span> strings.</span><span style="color: #FFB454;">Cut</span><span>(authheader,</span><span style="color: #AAD94C;"> &quot; &quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>found {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w, http.StatusUnauthorized,</span><span style="color: #AAD94C;"> &quot;not authorized&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> method</span><span style="color: #F29668;"> !=</span><span style="color: #AAD94C;"> &quot;Bearer&quot;</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w, http.StatusBadRequest,</span><span style="color: #AAD94C;"> &quot;invalid authorization method&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> :=</span><span> as.</span><span style="color: #FFB454;">ValidToken</span><span>(r.</span><span style="color: #FFB454;">Context</span><span>(), token)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w, http.StatusUnauthorized,</span><span style="color: #AAD94C;"> &quot;not authorized&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FFB454;"> next</span><span>(w, r)</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Both middlewares accept and return <code>http.Handler</code> which will allows us to chain them together. It also allows us to test them in a similar fashion as our other handlers.</p> <h2 id="pulling-it-all-together">Pulling it All Together</h2> <p>We have handlers and middleware all ready to go now. Each are small discrete functions that can be reasoned about and tested by themselves. Now it is time to pull them all togther to make our HTTP server. I like to make a <code>NewServer()</code> function that does all of this for me and, you guessed it, it will return a <code>http.Handler</code>. Shocking, I know.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> NewServer</span><span>(</span></span> <span class="giallo-l"><span style="color: #D2A6FF;"> logger</span><span style="color: #59C2FF;"> Logger</span><span>,</span></span> <span class="giallo-l"><span style="color: #D2A6FF;"> ds</span><span style="color: #59C2FF;"> DataStore</span><span>,</span></span> <span class="giallo-l"><span style="color: #D2A6FF;"> bs</span><span style="color: #59C2FF;"> BlobStore</span><span>,</span></span> <span class="giallo-l"><span style="color: #D2A6FF;"> as</span><span style="color: #59C2FF;"> AuthStore</span><span>,</span></span> <span class="giallo-l"><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span> mux</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewServeMux</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;GET /articles&quot;</span><span>, handlers.</span><span style="color: #FFB454;">ListArticles</span><span>(logger, ds))</span></span> <span class="giallo-l"><span> mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;GET /articles/{id}&quot;</span><span>, handlers.</span><span style="color: #FFB454;">GetArticles</span><span>(logger, ds))</span></span> <span class="giallo-l"><span> mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;POST /articles&quot;</span><span>, handlers.</span><span style="color: #FFB454;">CreateArticle</span><span>(logger, ds))</span></span> <span class="giallo-l"><span> mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;DELETE /articles/{id}&quot;</span><span>, handlers.</span><span style="color: #FFB454;">DeleteArticle</span><span>(logger, ds))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> handler</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span style="color: #F29668;"> =</span><span> mux</span></span> <span class="giallo-l"><span> handler</span><span style="color: #F29668;"> =</span><span style="color: #FFB454;"> LoggerMiddleware</span><span>(handler, logger)</span></span> <span class="giallo-l"><span> handler</span><span style="color: #F29668;"> =</span><span style="color: #FFB454;"> AuthorizationMiddleware</span><span>(handler, as)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> handler</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Now we have one <code>http.Handler</code> to rule them all. We composed a <code>http.ServeMux</code> which is also a <code>http.Handler</code> so we can attach our handlers to routes. Then we layer on the middleware passing one into the next to build it up and finally returning the completed <code>http.Handler</code>.</p> <h2 id="the-real-http-server">The Real HTTP Server</h2> <p>The last piece is attaching all of our work to a real HTTP server and binding it to a port with some additional options. This is where <code>http.Server</code> gets used in <code>main.go</code>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> logger</span><span style="color: #59C2FF;"> logger</span><span>.</span><span style="color: #59C2FF;">Logger</span><span style="color: #F29668;"> =</span><span> logger.</span><span style="color: #FFB454;">NewNoOp</span><span>()</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> config.logger {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;slog&quot;</span><span>:</span></span> <span class="giallo-l"><span> logger</span><span style="color: #F29668;"> =</span><span> logger.</span><span style="color: #FFB454;">NewSlog</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;zap&quot;</span><span>:</span></span> <span class="giallo-l"><span> logger</span><span style="color: #F29668;"> =</span><span> logger.</span><span style="color: #FFB454;">NewZap</span><span>()</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> ds</span><span style="color: #59C2FF;"> datastore</span><span>.</span><span style="color: #59C2FF;">DataStore</span><span style="color: #F29668;"> =</span><span> datastore.</span><span style="color: #FFB454;">NewSQLite</span><span>()</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> config.datastore {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;postgresql&quot;</span><span>:</span></span> <span class="giallo-l"><span> ds</span><span style="color: #F29668;"> =</span><span> datastore.</span><span style="color: #FFB454;">NewPostgreSQL</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;in-memory&quot;</span><span>:</span></span> <span class="giallo-l"><span> ds</span><span style="color: #F29668;"> =</span><span> datastore.</span><span style="color: #FFB454;">NewInMemory</span><span>()</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> bs</span><span style="color: #59C2FF;"> bs</span><span>.</span><span style="color: #59C2FF;">BlobStore</span><span style="color: #F29668;"> =</span><span> bs.</span><span style="color: #FFB454;">NewLocalFileSystem</span><span>()</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> config.bs {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;s3&quot;</span><span>:</span></span> <span class="giallo-l"><span> bs</span><span style="color: #F29668;"> =</span><span> bs.</span><span style="color: #FFB454;">NewS3</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;scp&quot;</span><span>:</span></span> <span class="giallo-l"><span> bs</span><span style="color: #F29668;"> =</span><span> bs.</span><span style="color: #FFB454;">NewSCP</span><span>()</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> as</span><span style="color: #59C2FF;"> as</span><span>.</span><span style="color: #59C2FF;">AuthStore</span><span style="color: #F29668;"> =</span><span> as.</span><span style="color: #FFB454;">NewSQLite</span><span>()</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> config.as {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;redis&quot;</span><span>:</span></span> <span class="giallo-l"><span> as</span><span style="color: #F29668;"> =</span><span> as.</span><span style="color: #FFB454;">NewRedis</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;in-memory&quot;</span><span>:</span></span> <span class="giallo-l"><span> as</span><span style="color: #F29668;"> =</span><span> as.</span><span style="color: #FFB454;">NewInMemory</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> &quot;bypass&quot;</span><span>:</span></span> <span class="giallo-l"><span> as</span><span style="color: #F29668;"> =</span><span> as.</span><span style="color: #FFB454;">NewByPass</span><span>()</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> handler</span><span style="color: #F29668;"> :=</span><span> handlers.</span><span style="color: #FFB454;">NewServer</span><span>(logger, ds, bs, as)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> srv</span><span style="color: #F29668;"> :=</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Server</span><span>{</span></span> <span class="giallo-l"><span> Addr:</span><span style="color: #AAD94C;"> &quot;:8080&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> Handler: handler,</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> ReadTimeout:</span><span style="color: #D2A6FF;"> 5</span><span style="color: #F29668;">*</span><span>time.Seconds,</span></span> <span class="giallo-l"><span> WriteTimeout:</span><span style="color: #D2A6FF;"> 20</span><span style="color: #F29668;">*</span><span>time.Seconds,</span></span> <span class="giallo-l"><span> IdleTimeout: time.Minute,</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> idleConnsClosed</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">chan struct</span><span>{})</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> go func</span><span>() {</span></span> <span class="giallo-l"><span> sigint</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">chan</span><span style="color: #59C2FF;"> os</span><span>.</span><span style="color: #59C2FF;">Signal</span><span>,</span><span style="color: #D2A6FF;"> 1</span><span>)</span></span> <span class="giallo-l"><span> signal.</span><span style="color: #FFB454;">Notify</span><span>(sigint, os.Interrupt)</span></span> <span class="giallo-l"><span style="color: #F29668;"> &lt;-</span><span>sigint</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> srv.</span><span style="color: #FFB454;">Shutdown</span><span>(context.</span><span style="color: #FFB454;">Background</span><span>())</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> logger.</span><span style="color: #FFB454;">Log</span><span>(logger.ERROR,</span><span style="color: #AAD94C;"> &quot;server shutdown&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;error&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FFB454;"> close</span><span>(idleConnsClosed)</span></span> <span class="giallo-l"><span> }()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> :=</span><span> srv.</span><span style="color: #FFB454;">ListenAndServe</span><span>()</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span> http.ErrServerClosed {</span></span> <span class="giallo-l"><span> logger.</span><span style="color: #FFB454;">Log</span><span>(logger.ERROR,</span><span style="color: #AAD94C;"> &quot;server stopped listening&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;error&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;"> &lt;-</span><span>idleConnsClosed</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Based on some configurations we set up each of the <code>Logger</code>, <code>DataStore</code>, <code>BlobStore</code>, and <code>AuthStore</code>. Since <code>NewServer()</code> only cares about interfaces we can compose many different permutations on dependencies our API can use and decouples the HTTP request / response lifecycle from the core business logic in each of the inferface implementations.</p> <p>Each layer of this API can also be tested thouroughly as independent units or as one service.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> TestServer</span><span>(</span><span style="color: #D2A6FF;">t</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">testing</span><span>.</span><span style="color: #59C2FF;">T</span><span>) {</span></span> <span class="giallo-l"><span> tests</span><span style="color: #F29668;"> :=</span><span> []</span><span style="color: #FF8F40;">struct</span><span> {</span></span> <span class="giallo-l"><span> name</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> req</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span></span> <span class="giallo-l"><span> resCode</span><span style="color: #39BAE6;"> int</span></span> <span class="giallo-l"><span> }{</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span> name:</span><span style="color: #AAD94C;"> &quot;sample request&quot;</span><span>,</span></span> <span class="giallo-l"><span> req: httptest.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodGet,</span><span style="color: #AAD94C;"> &quot;/articles&quot;</span><span>,</span><span style="color: #D2A6FF;"> nil</span><span>),</span></span> <span class="giallo-l"><span> resCode: http.StatusOk,</span></span> <span class="giallo-l"><span> },</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Many more requests to test</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> _, test</span><span style="color: #F29668;"> :=</span><span style="color: #FF8F40;"> range</span><span> tests {</span></span> <span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Run</span><span>(test.name,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">t</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">testing</span><span>.</span><span style="color: #59C2FF;">T</span><span>) {</span></span> <span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Parellel</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> logger</span><span style="color: #F29668;"> :=</span><span> logger.</span><span style="color: #FFB454;">NewNoOp</span><span>()</span></span> <span class="giallo-l"><span> ds</span><span style="color: #F29668;"> :=</span><span> logger.</span><span style="color: #FFB454;">NewInMemory</span><span>()</span></span> <span class="giallo-l"><span> bs</span><span style="color: #F29668;"> :=</span><span> logger.</span><span style="color: #FFB454;">NewLocalFileSystem</span><span>()</span></span> <span class="giallo-l"><span> as</span><span style="color: #F29668;"> :=</span><span> logger.</span><span style="color: #FFB454;">NewBypass</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> handler</span><span style="color: #F29668;"> :=</span><span> handlers.</span><span style="color: #FFB454;">NewServer</span><span>(logger, ds, bs, as)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> w</span><span style="color: #F29668;"> :=</span><span> httptest.</span><span style="color: #FFB454;">NewRecorder</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> handler.</span><span style="color: #FFB454;">ServeHTTP</span><span>(w, test.req)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> w.StatusCode</span><span style="color: #F29668;"> !=</span><span> test.resCode {</span></span> <span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Fail</span><span>(</span><span style="color: #AAD94C;">&quot;failed to get expected status code&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>We set up our test harness to test the entire server with no logging, in-memory data store, a local filesystem blob store, and we bypass auth all together. We also run each test within its own <code>t.Run()</code> so we can then call <code>t.Parallel()</code> indicating all subtests should run concurrently. This will drastically speed up our tests. Take notice that we set up everything inside each subtests so we do not share resources among them. By doing this we ensure test isolation and each subtests gets its own instance. This can be expanded to set up different states for each subtests as well if some requests require existing data in a database or file on disk too. Since this is all composable the options are pretty endless.</p> <h2 id="server-shutdown-ctx"><code>server.Shutdown(ctx)</code></h2> <p>This concludes this post on how I write my HTTP servers. They are not always the same every time, but these are the goals I try to strive for. You can even use these ideas when refactoring, adding features, or bug fixing. If you focus on trying to always return <code>http.Handler</code> and pass in your dependencies to those handlers your life and you're team's lives will be much easier.</p> <p>Feedback is always welcome so feel free to send me a message or even a patch to my <a rel="external" href="https://lists.sr.ht/~blainsmith/public-inbox">public inbox</a>!</p> I Don't Care 2025-10-28T00:00:00+00:00 2025-10-28T00:00:00+00:00 Unknown https://blainsmith.com/articles/i-dont-care/ <p>There's been a recurring thought I've been having as I've gotten older and I'm coming up on being in tech for 30 years. Granted, that would make me 14 when I started, but yes, I was that young when I realized I wanted to make this my career.</p> <p>As I've seen a lot change in those three decades, I've come to care less and less about most of the things mainstream tech blows the megaphone for on a daily basis. Most of this thought process has brought me peace and reduced my anxiety, and allows me to spend more time with things that actually matter to me.</p> <h2 id="all-of-ai">All of AI</h2> <p>I don't care what good it does. I don't care what harm it does. If anyone brings it up from now on, I will most likely disassociate and ignore it. I honestly do not give a shit anymore.</p> <p>The hype cycle is exhausting. The think pieces are exhausting. The doom prophecies are exhausting. The utopian promises are exhausting. I've watched enough tech hype cycles to know how this plays out. Some useful tools will emerge. Most of it will be noise. The world will keep turning.</p> <p>I'm not interested in debating whether it's transformative or destructive. I'm not interested in your hot takes on consciousness or job displacement or copyright law. I've already spent my allocated lifetime bandwidth on this topic. It's zero now.</p> <h2 id="big-product-launches-and-innovation">Big Product Launches and "Innovation"</h2> <p>It's all hype and ego-driven bullshit. We all see through your facade and what you actually want is to raise money, cash in, and dump whether the product succeeds or fails. I will not join you on your hype wagon. I do not care.</p> <p>I've seen too many world-changing platforms collapse. I've watched too many revolutionary products disappear overnight. I've sat through too many launch events where someone in a black turtleneck tells me they're reinventing something that didn't need reinventing.</p> <p>Most "innovation" is just normal iteration with a marketing budget attached. The truly innovative stuff rarely comes with a press release. It just quietly works and slowly gets adopted because it's actually useful. Everything else is theater.</p> <h2 id="language-wars">Language Wars</h2> <p>I don't care which language is faster than the other. Which one is easier to read than the other. Which one is compiled or not. They all suck in their own way and they all are good in their own way. It is a subjective argument and I am not wasting my breath over it.</p> <p>Use what works for you. Use what works for your team. Use what solves the actual problem in front of you. Preferably use something boring and well-understood so the next person doesn't have to rewrite it in six months.</p> <p>I've written enough code in enough languages to know that the language is rarely the bottleneck. The bottleneck is usually the problem itself, or the team's understanding of it, or the architecture, or the requirements that keep changing. Arguing about syntax and compile times is a distraction from the real work.</p> <h2 id="operating-systems">Operating Systems</h2> <p>Operating systems are different. I care about Linux only. Mac and Windows are dead to me. I don't care what folks think they are good at. Enjoy it yourself. Your arguments will fall on deaf ears to me.</p> <p>This isn't about purity or ideology. It's about what I need to get work done. Linux gives me control. It doesn't hide things from me. It doesn't force updates at inconvenient times. It doesn't treat me like a consumer who needs protection from myself.</p> <p>If you love macOS or Windows, genuinely, that's fine. Use them. Be productive. I'm not here to convert you. I'm just done pretending I care about the debate. I made my choice. You made yours. Let's move on.</p> <h2 id="process-and-productivity-theater">Process and Productivity Theater</h2> <p>All organizations are slow and that isn't changing anytime soon. Management, process, production, planning, marketing, etc. are all big giant experiments that are all different in every organization because every organization is made up of completely different people.</p> <p>Yet most think "kanban" or "agile" or "waterfall" planning with Jira, Notion, GitHub issues, etc. are "the way" every project should work. I don't care anymore. I will use whatever and be as slow as your planning decisions make me, provided you're delusional enough to think it's improving productivity. No more arguments from me.</p> <p>I've seen too many process improvements that slowed things down. Too many tools that created more work than they solved. Too many meetings to discuss how to have fewer meetings. Too many retrospectives that resulted in no actual change.</p> <p>The uncomfortable truth is that productivity comes from giving smart people clear problems and getting out of their way. Everything else is overhead. Some overhead is necessary. Most of it isn't. But organizations will keep adding it because doing something feels better than doing nothing, even when doing nothing would be more effective.</p> <p>I'll follow your process. I'll fill out your tickets. I'll attend your standups. But I've stopped believing it matters as much as everyone pretends it does.</p> <h2 id="what-i-do-care-about">What I Do Care About</h2> <p>Since this has turned into a list of things I've given up caring about, it's worth saying what I still care about deeply.</p> <p>I care about writing software that works. Not software that's clever or impressive or uses the latest framework. Software that solves a real problem for real people and keeps working when you're not looking at it.</p> <p>I care about making things simple. Not easy, not dumbed down, but genuinely simple. Taking a complex problem and finding the straightforward solution that doesn't require a PhD to maintain.</p> <p>I care about my time and the time of the people around me. Every hour spent in a pointless meeting or debugging someone's overengineered abstraction is an hour I'm not spending with my family or working on something that matters.</p> <p>I care about being honest. Not brutally honest or performatively honest, just plainly honest about what works, what doesn't, what we know, and what we're guessing at.</p> <h2 id="the-peace-of-not-caring">The Peace of Not Caring</h2> <p>Letting go of these things has been liberating. I don't feel pressure to have an opinion on every new JavaScript framework. I don't feel obligated to weigh in on the latest tech controversy. I don't feel like I'm missing out when I skip the conference or ignore the launch event.</p> <p>It took me almost three decades to learn what actually matters in this industry and what's just noise. The noise is louder now than it's ever been. But I've turned down the volume.</p> <p>You're welcome to keep caring about all of it. You're welcome to passionately debate these things. You're welcome to think I'm wrong, or bitter, or checked out. That's fine. I genuinely hope you find fulfillment in whatever you care about.</p> <p>As for me? I'm going to keep writing code that works, using tools that make sense, and spending the rest of my time on things that actually matter. The hype train can leave without me.</p> <h2 id="the-end-of-caring">The End...of Caring</h2> <p>I'm well aware this all comes off as elitist, dismissive, and mean. Guess what? I still don't care. I can hold these thoughts while also working with folks who think the opposite. That is life. You co-exist.</p> <p>I don't care.</p> Excitement is Relative 2025-09-30T00:00:00+00:00 2025-09-30T00:00:00+00:00 Unknown https://blainsmith.com/articles/excitement-is-relative/ <p>Does this happen to you?</p> <p>Someone shows you something they built. You look at it carefully, think through the implications, and give specific, considered feedback. They walk away deflated. Later, someone tells you that you "seemed dismissive" or "came across as negative." You were not dismissive. You were engaged and thinking. But because your face did not change, your voice stayed level, and you did not perform excitement in the expected way, it read as indifference or criticism.</p> <p>This happens to me constantly.</p> <h2 id="the-excitement-gap">The Excitement Gap</h2> <p>Since I am autistic, I have a hard time meeting people at their excitement level. This applies both to things I find interesting and things others find exciting. When I do not match their energy, I look and sometimes sound mean, like I am raining on their parade. My face stays neutral. My voice stays flat. Over time, people think I am generally abrasive and too blunt because of my lack of visible excitement or happiness about something. When these situations come up in text chats I find myself needing to constantly add emojis and "haha" to the end of things I say so people don't read into it that I am being mean.</p> <p>The problem is not that I lack enthusiasm. The problem is that my enthusiasm does not perform the way neurotypical social scripts expect it to. When I care deeply about something, it shows up as sustained focus, careful consideration, and methodical engagement. I read documentation thoroughly. I think through implications. I build things with discipline and precision. That is what excitement looks like for me.</p> <p>But in real-time interactions, people are not reading for depth of engagement. They are reading for facial expressions, vocal inflection, and energy levels. When those signals are absent or muted, they assume disinterest.</p> <h2 id="constantly-trapped">Constantly Trapped</h2> <p>This creates a confusing set of traps:</p> <p><strong>Trap 1:</strong> I share something I find exciting. Because I do not appear visually excited, people assume I am not actually interested or that I am being sarcastic. They respond with confusion, mild concern, or just don't respond at all.</p> <p><strong>Trap 2:</strong> Someone shares something they are excited about with me. They are animated, smiling, speaking quickly. I am engaged and listening carefully, but my face and voice do not mirror their energy. They interpret this as rejection or judgment of their interest.</p> <p><strong>Trap 3:</strong> My interests are niche - plain text protocols, minimal tooling, systems programming, powerlifting/strongman, cars, heavy metal music. When I share these, people often just do not respond at all. The silence feels like confirmation that sharing was not worth it.</p> <p>No matter which trap I land in, the result is the same: I look like I do not care, even when I care deeply.</p> <h2 id="what-this-costs">What this Costs</h2> <p>Over time, I have learned to share less. If I cannot predict how my flat affect will be misread, it feels safer to just not volunteer interests or opinions. This is not ideal for anyone. People lose out on perspectives and ideas. I lose out on connection and collaboration. Everyone ends up working in a narrower space than necessary.</p> <p>The cost shows up in code reviews where I hold back useful observations because I know they will land wrong. It shows up in meetings where I do not advocate for technical approaches I believe in because I cannot perform the enthusiasm that makes ideas seem worth considering. It shows up in casual conversations where I do not mention what I have been working on or learning because I do not know how to handle the mismatch when someone responds with more visible excitement than I can return.</p> <p>All of this could be addressed by masking and faking the enthusiasm, but then that will cost me mental health and leads to burnout. How do I know? Trust me, I've done it many times.</p> <h2 id="what-i-have-learned">What I Have Learned</h2> <p>Some contexts are better than others. Written communication tends to work in my favor. Email, pull request descriptions, documentation, and blog posts let my care and precision come through without requiring real-time emotional performance. People can read my thoroughness as engagement rather than watching my face for cues I am not giving.</p> <p>Niche communities help too. The folks on Mastodon, people working in Hare, anyone deep in the smallweb space - they share enough context that my signal can land without needing to be wrapped in performative energy. When I say "this plain text protocol is elegant," they understand that is genuine appreciation, not sarcasm or indifference.</p> <p>I have also started being more explicit about this pattern with people I work closely with. "I am really interested in this, even though I probably do not look excited in the way you would expect." It is work I should not have to do, and it does not always help, but sometimes it short-circuits the misinterpretation before it morphs into "Blain does not care about anything."</p> <h2 id="for-other-autistic-engineers">For Other Autistic Engineers</h2> <p>If this sounds familiar, you are not imagining it. The mismatch is real, and it is exhausting to navigate. You are not too blunt or too negative or too detached. You are engaged in ways that do not fit the script people expect, and that is their limitation, not yours.</p> <p>Find the contexts where your signal lands clearly. Lean into written communication when it works better. Seek out communities where substance matters more than performance. You do not owe anyone a particular flavor of enthusiasm, and the people worth working with will learn to read your actual engagement rather than expecting you to perform it their way.</p> <h2 id="for-neurotypical-colleagues">For Neurotypical Colleagues</h2> <p>If you work with autistic engineers, consider that engagement might not look like what you expect. Someone can be deeply invested in your work without smiling or raising their voice. Careful, specific feedback is often a sign of respect and interest, not dismissiveness. Flat affect is not the same as flat engagement.</p> <p>When someone shares something and does not seem "excited enough" about it, ask yourself: are they actually disengaged, or are they just not performing engagement in a way that reads clearly to you? Sometimes the most enthusiastic person in the room is the one quietly taking notes and thinking through implications while everyone else is performing energy and hyping for hype's sake.</p> <h2 id="true-to-myself">True to Myself</h2> <p>I am not going to suddenly start matching energy levels I do not feel. I am not going to perform enthusiasm I do not experience in the shape people expect. What I can do is keep showing up, keep engaging with precision and care, and keep building things that matter. That has to be enough.</p> <p>If you read my code, my documentation, or my writing and see discipline and thoroughness, that is my excitement showing. If you do not see it, that is fine. But do not mistake my flat affect for apathy. I care deeply. I just do not look like I do.</p> AI Coding 2025-09-12T00:00:00+00:00 2025-09-12T00:00:00+00:00 Unknown https://blainsmith.com/articles/ai-coding/ <p>It had occurred to me that I haven't really solidified my position on coding with "AI" so I thought I would jot down some thoughts on the subject.</p> <p>Come to think of it... It's the exact opposite of... Hmmm...</p> <p>Wait, I already have <a href="/articles/software-engineering-discipline-and-posture/">written about it</a>!</p> Allergic to Unnecessary Complexity 2025-08-18T00:00:00+00:00 2025-08-18T00:00:00+00:00 Unknown https://blainsmith.com/articles/allergic-to-unnecessary-complexity/ <p>I don't sit down to design systems with complexity as the point. But I've met some that engineers do. They'll never say it outright, but you can see it in their work. They write layers on top of layers, abstractions stacked on abstractions, and clever tricks that only they can explain. Complexity makes them feel smart. It's designed to impress peers in the code review, not to make the system better. Over time this cleverness fades and complexity is left in its wake.</p> <p>Most of the time complexity shows up quietly. It presents itself as a workaround here, a "just in case" abstraction there, or a premature optimization that felt necessary in the moment. At first glance none of it seems bad on its own, but fast forward a year later and the design looks like a maze, spaghetti, or a ball of yarn that was played with by a cat. That's the problem with complexity: it doesn't announce itself. It grows like cancer in the code if no one is watching out for it and by the time noticeable to everyone it is probably too late.</p> <p>Personally, I’ve learned to treat complexity like an allergen. My first reaction is avoidance. Do we really need this extra piece of code? What happens if we don't add it yet? What's the simplest or most straight forward way to get this done without painting ourselves into an immediate corner? This posture isn't about being lazy. It's about respecting the potential cost of every moving part added. Every part has to be maintained, debugged, explained to the next engineer, and kept running long after the excitement of launch has worn off.</p> <p>The tricky part is pointing out complexity when you see it. If you say "this is too complicated" in a code review then it just sounds like a complaint. Instead, ask specific questions. "Can you walk me through why we need all three layers here? I wonder if we could collapse them." Another approach is to tie it to outcomes. "If this broke in the middle of the night, would we have a clear way to debug it?" Or another way, "How long would it take a new hire to grok this?" Framing it with curiosity and outcomes cut through defensiveness and turn what sounds like nitpicking into something the team can act on.</p> <p>Another driver of complexity that doesn't get talked about much is apathy. When engineers stop caring, complexity thrives. Apathy says, "ship it, someone else will figure it out later." Apathy leaves tangled dependencies in place because "it's good enough." It accepts bad abstractions without asking questions, because pushing back takes effort. It's easier to add another patch than to clean up what's already there because there is only ever a task or a ticket for bugs and features, not quality and improvements, despite what engineering cultures claim. Apathy doesn't fight for simplicity. That silence is exactly what lets unnecessary complexity take root. Apathy is the catalyst which leads to complexity.</p> <p>Fortunately, you can spot apathy in small ways:</p> <ul> <li>Code reviews that boil down to "LGTM" without a single comment or question.</li> <li>TODOs left to rot in the codebase for months or years.</li> <li>A reflex to add "just another service" instead of asking if the existing ones could be extended.</li> <li>Shrugs when inconsistent naming or half-finished abstractions are pointed out.</li> <li>Teams normalizing duct-tape fixes because "we’ll clean it up later," and later never comes.</li> <li>Missing automated tests in favor of "QA will test it for us".</li> </ul> <p>Each one is a signal that the guardrails are down and apathy is up. If it is left unchecked, those signals turn into systems that nobody fully understands, where adding anything new feels like wading through mud. That is what apathy buys you and now you are going to pay for it with poorly running systems and aggravated engineers.</p> <p>Sometimes, of course, complexity is unavoidable. Certain domains might warrant it like finance, compliance, distributed consensus. In those cases, the job is not to run from complexity but to embrace it, understand it, and contain it. This is where leadership really comes into play. A lead engineer's role is not just shipping tickets. It is their job to keep the whole system from going off the rails. It requires stepping back from the churn of bugs and features and focusing on the bigger picture.</p> <ul> <li>How do the parts fit together?</li> <li>Where are the risks when services are modified?</li> <li>What are the long-term costs if today's choices go unchecked?</li> <li>Do we have documentation that supports the behavior of the system?</li> <li>How do we observe the system behavior to build confidence it's working as designed?</li> </ul> <p>This work doesn't always feel productive in the short term because it is never tied to a bug or feature ticket in [INSERT SHITTY PRODUCTIVITY APP HERE]. It is, however, what keeps a team from drowning in its own cleverness and has a direct impact on fewer bugs, faster feature turnaround, and more confident engineers that what they build is, in fact, correct.</p> <p>True seniority isn't proven by how much complexity or code you can build into a system. It's proven by how much of it you can keep out. Simple is not naïve. Simple is discipline, restraint, and the willingness to say "no" when adding another abstraction would make you look clever but leave the system brittle.</p> <p>Software shouldn't be built as monuments to our cleverness since it will undoubtedly change in the future. It should be built to solve problems and stand the test of time until there comes a time to change it. Complexity is a destination software will sometimes reach, but it should never be the purpose. So stay allergic to it. Fight it when you see it. However, when you do have to accept it, make sure it is because the problem demands it and not because your ego, or your apathy, let it in.</p> Bare Essentials Development Tools 2025-08-01T00:00:00+00:00 2025-08-01T00:00:00+00:00 Unknown https://blainsmith.com/articles/bare-essentials-development-tools/ <p>As a software engineer who's been through the cycle of tooling bloat and complexity creep, I've come full circle to embrace radical simplicity. After years of wrestling with heavyweight IDEs, managing dozens of extensions, and debugging my development environment more than my actual code, I've distilled my entire setup to just four tools: a compiler, git, Helix editor, and tmux.</p> <h2 id="the-unix-philosophy-still-rings-true">The Unix Philosophy Still Rings True</h2> <p>The Unix philosophy has always resonated with me: "Write programs that do one thing and do it well." This principle doesn't just apply to the software we build—it should guide the tools we choose to build it with. Every additional tool in your arsenal is another dependency, another configuration to maintain, another potential point of failure.</p> <p>When I look at modern development environments, I see a concerning trend toward complexity. IDEs that consume gigabytes of RAM, plugin ecosystems that require their own package managers, and configuration files that span hundreds of lines. We've somehow convinced ourselves that more features equal better productivity, but I've found the opposite to be true.</p> <h2 id="my-four-tool-philosophy">My Four-Tool Philosophy</h2> <h3 id="compiler">Compiler</h3> <p>I most work in compiled languates whether it is Go, Rust, or C, but the majority of my work involves Go. Using the languages official toolchain and mastering the features it offers is essential to understanding how your program will run in production and make it much easier to debug.</p> <p>Go, specifically, embodies the same minimalist philosophy that drives my tooling choices. The compiler is fast, the language is simple, and the standard library is comprehensive. I don't need multiple language runtimes, dependency managers, or build systems cluttering my environment. The Go toolchain handles formatting, testing, and building with built-in commands that just work.</p> <h3 id="git">Git</h3> <p>While others reach for GUI clients or IDE integrations, I stick with git on the command line. It's available everywhere, works identically across all platforms, and forces me to understand what's actually happening with my code history. No visual noise, no abstraction layers, but simply direct, predictable version control.</p> <h3 id="helix">Helix</h3> <p>Helix struck the perfect balance for me. It is more capable than traditional editors like vim or nano, but without the complexity of modern IDEs. It provides syntax highlighting, basic language server support, and efficient text manipulation without requiring hours of configuration. I can install it on any system and be productive immediately.</p> <h3 id="tmux">tmux</h3> <p>Tmux gives me everything I need for managing multiple terminal sessions without the overhead of a desktop environment or complex window manager. I can detach from sessions, work across multiple panes, and maintain my workflow whether I'm on a local machine or SSH'd into a remote server.</p> <h2 id="the-power-of-minimal-configuration">The Power of Minimal Configuration</h2> <p>Each of these tools requires virtually no configuration to be useful. My entire setup can be replicated on any Unix-like system in under ten minutes:</p> <ol> <li>Install Go, git, Helix, and tmux</li> <li>Copy over a handful of configuration lines (less than 50 total across all tools)</li> <li>Clone my repositories and start coding</li> </ol> <p>This portability has been a game-changer. Whether I'm working on my laptop, a colleague's machine, a cloud instance, or a production server, my environment feels identical. There's no vendor lock-in, no licensing concerns, and no compatibility issues between operating systems.</p> <h2 id="what-i-ve-gained-by-subtracting">What I've Gained by Subtracting</h2> <p>By embracing this minimalist approach, I've discovered several unexpected benefits:</p> <p><strong>Mental clarity</strong>: With fewer tools to think about, I can focus entirely on the problem I'm solving rather than fighting with my environment.</p> <p><strong>Reduced maintenance</strong>: No more time wasted updating plugins, resolving configuration conflicts, or debugging tool interactions.</p> <p><strong>Universal accessibility</strong>: I can be productive on any machine, anywhere, without needing specific software versions or licenses.</p> <p><strong>Deeper understanding</strong>: Working closer to the metal has improved my understanding of how compilation, version control, and text processing actually work.</p> <h2 id="the-resistance-to-simplicity">The Resistance to Simplicity</h2> <p>I've encountered pushback from developers who argue that modern IDEs provide essential features like advanced debugging, intelligent code completion, and integrated testing. While these features can be valuable, I've found that they often become crutches that prevent us from truly understanding our code and development process.</p> <p>When you can't rely on an IDE to auto-import packages, you learn your dependencies better. When you run tests from the command line, you understand your test structure more clearly. When you debug with print statements and logs instead of graphical debuggers, you develop better intuition about program flow.</p> <h2 id="rediscovering-the-joy-of-simple-tools">Rediscovering the Joy of Simple Tools</h2> <p>Software development is fundamentally about solving problems with code. Every tool that doesn't directly contribute to that goal is overhead. By stripping away the unnecessary complexity and embracing the Unix philosophy of simple, composable tools, programming is full of joy.</p> <p>My four-tool setup isn't about being contrarian or nostalgic. It's about optimizing for what actually matters, which is, writing good code efficiently and reliably. In a world of increasing complexity, sometimes the most radical thing you can do is subtract rather than add.</p> <p>If you're feeling overwhelmed by your development environment or spending more time configuring tools than using them, I challenge you to try the minimalist approach. You might be surprised by how much you can accomplish with so little.</p> Software Engineering Discipline and Posture 2025-07-23T00:00:00+00:00 2025-07-23T00:00:00+00:00 Unknown https://blainsmith.com/articles/software-engineering-discipline-and-posture/ <p>There's something quietly dignified about a clean commit history, well written patch/pull requests, and documentation.</p> <p>It tells you someone gave a damn. That they weren't just slinging code to pass tests, but writing with others, including you, in mind. They knew they were stepping into a shared space, and they acted accordingly. That's what engineering discipline looks like. And it's what posture looks like when you're working in a team.</p> <h2 id="respect-is-written-into-the-code">Respect is Written Into the Code</h2> <p>You don’t respect your teammates by slapping a thumbs-up emoji in Slack. You respect them by naming your variables well. You respect them by writing a <code>README.md</code> that doesn't assume someone already knows what you know and <code>DEPLOY.md</code>/<code>DESIGN.md</code> files that explain how the system gets deployed and runs. You respect them by writing documentation as <strong>plain text</strong> and not assume everyone uses MacOS with the expensive MacOS-only diagram visualizer and editor. You respect them by explaining the <em>why</em>, not just the <em>what</em>, in your pull request description.</p> <p>Discipline isn't some grueling formality. It's kindness in disguise.</p> <p>When you write clear code, when you separate concerns, when you squash the "fixes typo" commits into the thing you meant to say in the first place then you are making life easier for the person coming after you. You are creating room for someone else to think clearly.</p> <h2 id="commits-are-a-conversation">Commits Are a Conversation</h2> <p>Commits are not just little logs of activity. They are sentences in an ongoing conversation across time. If your message is "wip" or "fix" or "asdf", you're not holding up your end.</p> <p>Write a commit message like someone else is going to read it on a dark night, months from now, debugging a production issue you never saw coming. Because they probably will.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>...</span></span> <span class="giallo-l"><span>809de69 Blain Smith May 22 09:06:40 2025 Cache results per user session</span></span> <span class="giallo-l"><span>ccc4b9d Blain Smith May 20 10:09:02 2025 Fix off-by-one pagination</span></span> <span class="giallo-l"><span>538ea53 Blain Smith May 19 17:39:54 2025 Retry logic for external API</span></span> <span class="giallo-l"><span>...</span></span></code></pre> <p>That's not verbosity. That's posture. That's thinking of your team, your future self, and the next engineer who clones the repo to start working.</p> <h2 id="pull-requests-are-invitations">Pull Requests Are Invitations</h2> <p>A pull request is not a demand this code needs to get merged. It's an invitation to start a conversation about finding the best solution as a team. It says: <em>Here is something I've built. Here's what I was thinking. Here's what I need help reviewing.</em> It should come with context. A clear title. A summary. Maybe some notes on what to look at closely, which parts are up for discussion, how it was tested, or how it improves a previous version with benchmarks.</p> <p>When you open a pull request with care, you're saying: <em>I respect your time. I've thought this through. I want your perspective.</em></p> <p>Compare that to the dump-and-run: 400 changed files, vague title, no description. That's not collaboration. That's cowardice hiding behind automation.</p> <h2 id="leave-the-place-better-than-you-found-it">Leave the Place Better Than You Found It</h2> <p>You don't need to overhaul the whole system. But you <em>can</em> write one more docstring. You <em>can</em> rename one more unclear function. You <em>can</em> add a test that wasn't there before. You don't need a Jira ticket or persmission from some "manager" to leave the place better than you found it. It is easier to ask for forgivness later than permission right now.</p> <p>Every time you touch a shared repo, you have a choice: contribute to the mess or clarify the system.</p> <p>Discipline is what makes that second choice possible. Posture is what makes it visible.</p> <h2 id="in-closing">In Closing</h2> <p>Engineering isn't just about shipping features. It's about shaping the experience of others. Our colleagues, our future selves, and those who will maintain the code long after we’re gone will appreciate it.</p> <p>If you care about your craft, it shows. If you care about your team, it shows even more.</p> <p>Write and code like someone else will read it. Because they will. Work like you're part of something larger. Because you are.</p> <p>Be disciplined. Be respectful. If you can't, learn. If you won't, GTFO.</p> Towards Teaching 2025-07-10T00:00:00+00:00 2025-07-10T00:00:00+00:00 Unknown https://blainsmith.com/articles/towards-teaching/ <p>I’ve been writing code for about thirty years now. I say that with no small amount of gratitude and a touch of disbelief. Thirty years of debugging, designing, deleting, rewriting. Of shipping things that broke and patching things that probably should not have seen the light of day. Of staying up way too late for launches that no one remembers but me. Of being woken up at 2AM because of a service that went down for a mere 10 seconds only to come back just time, but long enough to trip a critical alert. If you’ve been in this field long enough, you know the feeling. You build. You burn out. You move on. Repeat.</p> <p>But lately, something has changed. I've crossed into my 40s, and while I am still writing code, still solving problems, I feel a pull in a different direction. I am being pulled towards teaching and spreading the knowledge I've gained.</p> <p>I don't mean this by brand-building. Not some content grind that churns out bite-sized wisdom for likes on LinkedIn with animated GIFs showing AWS architecture diagrams or super pretty image code snippets that tend to nerd snipe the best of us. I am talking about teaching the craft. The real thing. Teaching students how to think clearly, write software that's correct, sustainable, and ethical. Teaching them to write code they can be proud of ten years down the road, not just code that makes a slick demo on launch day because management fucked up planning the timeline and now it is "all hands on deck" to finish the vaporware demo.</p> <p>I'm not interested in helping anyone "crack the algorithm interview" or "launch their SaaS in 30 days." We've got enough of that. What we are short on is people who can build systems that respect users, that last, that are not just glued together with libraries stacked five deep and a prayer to the CI gods. We're short on people who know that "clean architecture" is far more than a buzzword, it's a form of respect for your coworkers, for your future self, and for the users who trust you.</p> <p>I have nil interest in teaching students how to get rich. I want to teach them how to do their best to get it correct.</p> <p>And that means we do not start with frameworks. We start with thinking. With curiosity. With constraints. I want them to know what it is like to really understand the stack, to trace through a function call without needing multiple browser tabs open. I want them to know what good failure modes look like. What accountability in code looks like. What it means to write something that can still be read and trusted years later.</p> <p>Most of all, I want to pass down a vision of software that isn't about conquest. Not every line of code needs to scale to millions. Some of the best systems I've seen run quietly for years with no fanfare. That's the kind of work I want them to know exists.</p> <p>If you're a certain kind of person there's a weird freedom that comes with time in this industry. You stop needing to prove so much. You start wanting to give more than you get. You realize your legacy will not be the startups or the codebases since they’ll be gone, eventually. But the people you help? The ones you teach how to think deeply and build responsibly? They carry that forward.</p> <p>So that is the direction I am facing now. Slower. More thoughtful. More human. No AI shortcuts. No "move fast and break things." Just clear thinking, solid systems, and the hope that a few people will build software that serves others without manipulating them.</p> <p>I am <a rel="external" href="https://limeleaf.coop">not quitting the keyboard anytime soon</a>. I will still be feeding my family by coding. But if you're someone who's been in this field a while and you feel that same pull to give back then maybe it's time.</p> <p>Let's stop <a href="/articles/fuck-your-hustle">hustling</a>. Let's start teaching.</p> Introducting: Hare by Example 2025-07-02T00:00:00+00:00 2025-07-02T00:00:00+00:00 Unknown https://blainsmith.com/articles/introducing-hare-by-example/ <p>Whenever someone asks me about learning <a rel="external" href="https://go.dev">Go</a> and what online resources I would recommend they check out I always point them to <em><a rel="external" href="https://gobyexample.com">Go by Example</a></em>. This quick reference style site lets someone who is already good at programming in another language get up to speed on the basic semantics and idioms without having to read the offical language specification or verbose tutorials.</p> <blockquote> <p>How do I loop over items?</p> </blockquote> <p>Pull up the site and head to <a rel="external" href="https://gobyexample.com/range-over-built-in-types">Rage over Built-in Types</a> and there is your answer. No need to read lengthy prose explaining anything. Just an example and some annotations to get you your answer.</p> <p>If you know me at all you also probably know I've been enjoying spare time using <a rel="external" href="https://harelang.org">Hare</a> because it reminds me a lot of Go, but also C. While Hare does offer great documentation and tutorials on the language I wanted something even more concise like <em>Go by Example</em> for Hare. Nothing existed so I decided to make it myself.</p> <p>Introducing, <em><a rel="external" href="https://harebyexample.org">Hare by Example</a></em> for all your quick Hare needs.</p> <p>I would strongly recommend you still check out the other great resources the Hare website has to offer.</p> <p>As always, my <a rel="external" href="https://lists.sr.ht/~blainsmith/public-inbox">inbox</a> is open and I am happy to accept patches to the <a rel="external" href="https://git.sr.ht/~blainsmith/harebyexample.org">project</a>.</p> Block Non-Human Crawlers with Go 2025-04-23T00:00:00+00:00 2025-04-23T00:00:00+00:00 Unknown https://blainsmith.com/articles/block-non-human-crawlers-with-go/ <p>Recently, <a rel="external" href="https://adele.pages.casa/md/blog/block-non-human-crawlers-with-lighttpd.md">Adële wrote</a> about how to block non-human crawlers by setting a cookie to prove a human is visiting a website. I won't go into details here about how the mechanics work, but I wanted to see if I could adapt this to <a rel="external" href="https://go.dev">Go</a> middleware to provide the same result.</p> <h2 id="middleware">Middleware</h2> <p>This is the configurable middleware <a rel="external" href="https://pkg.go.dev/net/http#Handler"><code>http.Handler</code></a> you can use to wrap other <code>http.Handler</code>s when building a Go web app when it serves HTML.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// The template used to render the form with placeholder for the hashed</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// User-Agent value when inserted at runtime</span></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> form</span><span style="color: #39BAE6;"> string</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> `&lt;!DOCTYPE html&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;">&lt;html lang=&quot;en&quot;&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;head&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;meta name=&quot;color-scheme&quot; content=&quot;light dark&quot; /&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;title&gt;Human check&lt;/title&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;/head&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;body&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;main&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;form method=&quot;GET&quot;&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;p&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;input type=&quot;checkbox&quot; name=&quot;hash&quot; value=&quot;</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">&quot; /&gt;&lt;span&gt; I am a human&lt;/span&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;/p&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;p&gt;&lt;em&gt;this will set a dummy cookie (AI crawlers do not manage cookies)&lt;/em&gt;&lt;/p&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;/form&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;/main&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &lt;/body&gt;</span></span> <span class="giallo-l"><span style="color: #AAD94C;">&lt;/html&gt;`</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Protect is the middleware func used to wrap other http.Handlers</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> Protect</span><span>(</span><span style="color: #D2A6FF;">next</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span>,</span><span style="color: #D2A6FF;"> cookieName</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> hasher</span><span style="color: #59C2FF;"> hash</span><span>.</span><span style="color: #59C2FF;">Hash</span><span>)</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Handler</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(</span><span style="color: #FF8F40;">func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Skip if the request is not asking for HTML content</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>strings.</span><span style="color: #FFB454;">Contains</span><span>(r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;Accept&quot;</span><span>),</span><span style="color: #AAD94C;"> &quot;text/html&quot;</span><span>) {</span></span> <span class="giallo-l"><span> next.</span><span style="color: #FFB454;">ServeHTTP</span><span>(w, r)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Set the cookie if the hash value is set then continue to the next</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // handler in the chain</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> hash</span><span style="color: #F29668;"> :=</span><span> r.URL.</span><span style="color: #FFB454;">Query</span><span>().</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;hash&quot;</span><span>)</span><span style="color: #BFBDB6B3;">;</span><span> hash</span><span style="color: #F29668;"> !=</span><span style="color: #AAD94C;"> &quot;&quot;</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">SetCookie</span><span>(w,</span><span style="color: #F29668;"> &amp;</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Cookie</span><span>{</span></span> <span class="giallo-l"><span> Name: cookieName,</span></span> <span class="giallo-l"><span> Value: hash,</span></span> <span class="giallo-l"><span> SameSite: http.SameSiteLaxMode,</span></span> <span class="giallo-l"><span> Path:</span><span style="color: #AAD94C;"> &quot;/&quot;</span><span>,</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> next.</span><span style="color: #FFB454;">ServeHTTP</span><span>(w, r)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Hash the User-Agent value with the configured hash.Hash as a</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Base64-encoded string value for safe use in the form and cookie</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // value</span></span> <span class="giallo-l"><span> ua</span><span style="color: #F29668;"> :=</span><span> r.Header.</span><span style="color: #FFB454;">Get</span><span>(</span><span style="color: #AAD94C;">&quot;User-Agent&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> _, err</span><span style="color: #F29668;"> :=</span><span> hasher.</span><span style="color: #FFB454;">Write</span><span>([]</span><span style="color: #39BAE6;">byte</span><span>(ua))</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w,</span><span style="color: #AAD94C;"> &quot;failed to hash cookie value&quot;</span><span>, http.StatusInternalServerError)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> uahash</span><span style="color: #F29668;"> :=</span><span> fmt.</span><span style="color: #FFB454;">Sprintf</span><span>(</span><span style="color: #AAD94C;">&quot;</span><span style="color: #95E6CB;">%x</span><span style="color: #AAD94C;">&quot;</span><span>, hasher.</span><span style="color: #FFB454;">Sum</span><span>(</span><span style="color: #D2A6FF;">nil</span><span>))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Get the cookie by the configured name and if it does not exist</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // then render the form for the human to prove themselves worthy</span></span> <span class="giallo-l"><span> cookie, err</span><span style="color: #F29668;"> :=</span><span> r.</span><span style="color: #FFB454;">Cookie</span><span>(cookieName)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> _, err</span><span style="color: #F29668;"> :=</span><span> fmt.</span><span style="color: #FFB454;">Fprintf</span><span>(w, form, uahash)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w,</span><span style="color: #AAD94C;"> &quot;failed to render form&quot;</span><span>, http.StatusInternalServerError)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // If the cookie does exist, but the value of it does not match the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // hashed value of the User-Agent header then render the form for</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // the human to prove themselves worthy</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> cookie.Value</span><span style="color: #F29668;"> !=</span><span> uahash {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> _, err</span><span style="color: #F29668;"> :=</span><span> fmt.</span><span style="color: #FFB454;">Fprintf</span><span>(w, form, uahash)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Error</span><span>(w,</span><span style="color: #AAD94C;"> &quot;failed to render form&quot;</span><span>, http.StatusInternalServerError)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Finally, if we reach this point then the cookie is set with the</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // correct hashed value of the User-Agent proving the worthiness of</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // the human who submitted the form</span></span> <span class="giallo-l"><span> next.</span><span style="color: #FFB454;">ServeHTTP</span><span>(w, r)</span></span> <span class="giallo-l"><span> })</span></span> <span class="giallo-l"><span>}</span></span></code></pre><h2 id="example-usage">Example Usage</h2> <p>This middleware can be used to protect any single or group of routes in your app as long as it is an <code>http.Handler</code>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Decide on a cookie name that you want to use</span></span> <span class="giallo-l"><span> cookieName</span><span style="color: #F29668;"> :=</span><span style="color: #AAD94C;"> &quot;bot-blocker-5000&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Decide on a `hash.Hash` type to use</span></span> <span class="giallo-l"><span> hash</span><span style="color: #F29668;"> :=</span><span> hmac.</span><span style="color: #FFB454;">New</span><span>(sha256.New, []</span><span style="color: #39BAE6;">byte</span><span>(</span><span style="color: #AAD94C;">&quot;s3cr3t!&quot;</span><span>))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Sample unprotected route with a normal `http.Handler`</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">HandleFunc</span><span>(</span><span style="color: #AAD94C;">&quot;/unprotected&quot;</span><span>, unprotected)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Sample protected route wrapped with the `Protect()` func</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;/protected&quot;</span><span>,</span><span style="color: #FFB454;"> Protect</span><span>(http.</span><span style="color: #FFB454;">HandlerFunc</span><span>(protected), cookieName, hash))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">ListenAndServe</span><span>(</span><span style="color: #AAD94C;">&quot;:8080&quot;</span><span>,</span><span style="color: #D2A6FF;"> nil</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> unprotected</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> _</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span> fmt.</span><span style="color: #FFB454;">Fprint</span><span>(w,</span><span style="color: #AAD94C;"> &quot;I am unprotected&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> protected</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> _</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span> fmt.</span><span style="color: #FFB454;">Fprint</span><span>(w,</span><span style="color: #AAD94C;"> &quot;I am protected&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span></code></pre><h2 id="notable-differences">Notable Differences</h2> <p>The core principal is the same as Adële's version, but I made some changes to make it configurable for whoever wants to use it.</p> <ol> <li>Specify what the name of the cookie is</li> <li>Specify what <a rel="external" href="https://pkg.go.dev/hash#Hash">hashing method</a> to use</li> <li>Ensures this only protects content when the <code>Accept: text/html</code> header is so it won't break images, RSS feeds, or any other non-HTML content</li> </ol> <h2 id="outro">Outro</h2> <p>Sure, this isn't perfect from protecting all content from non-human crawlers, but its pretty good at it if your writing a web app in Go and you don't want to configure you proxy to do this or stand up an instance of <a rel="external" href="https://anubis.techaro.lol">Anubis</a>, <a rel="external" href="https://zadzmo.org/code/nepenthes/">Nepenthes</a>, etc.</p> <p>I also have no intention of publishing this as an official Go module because:</p> <ol> <li>I don't want to deal with managing versions of this</li> <li>Its not that hard to copy/pasta this into your project and change it as needed</li> <li>K.I.S.S., smolweb, and all that stuff</li> </ol> <p>If you want to give me feedback on this you can hit me up via any of the links in the footer of this site.</p> <p>Consider this another option in the fight against "AI" and LLM bots!</p> Limeleaf Co-op 2025-01-17T00:00:00+00:00 2025-01-17T00:00:00+00:00 Unknown https://blainsmith.com/articles/limeleaf-co-op/ <p>This past January 13th I turned 43, and as I approached that day, I spent a lot of time over winter break reflecting on the past decades I worked in the tech industry. About a year ago I had a similar reflection when I <a href="/articles/done">rage-quit</a>, but I eventually came around to doing some contract work to make some money and then took a job to get health benefits. This was before my wife and I got married and before she found her career job with amazing people and a great place that offered her benefits as well.</p> <p>So, I quit!</p> <p>I came to realize that I am no longer cut out for working at a company in the traditional sense in the tech industry. I just do not care about any company's product. There are some cool gems out there being worked on, but most of them are useless, wasteful, and wouldn't be missed if they did not exist since they are not actually needed. Not to mention the capitalistic money grab craziness that is tearing through the industry with "AI" right now. Being a software engineer working on hard problems over 10 years ago was novel. It was challenging. It was deliberate. It was purposeful. It was fun. Now, it isn't, and that is fine, but I am choosing not to engage or take part in a lot of it. Instead, I took steps over the past year to <a rel="external" href="https://start.coop">create and learn about co-ops</a>.</p> <p><a rel="external" href="https://limeleaf.net">Limeleaf</a> has legally existed for almost a year now, and while it isn't self-sustaining just yet, we have started to network and get people to notice us, especially in the co-op movement. We feel this is our chance to challenge the status-quo about earn a living without selling our souls (and your data) to big tech.</p> <p>For me, personally, this is the next phase of my career where I can be "the master of my fate.". This will give me the opportunity to not only be selective about what I want to work on but also give me the freedom to focus on my family that is growing up as my wife and I enter our 40s and our kids enter their teenage years.</p> <p>I am extremely passionate about what the co-op movement will afford my family and me, and so if you have any questions about the movement, then do not hesitate to reach out and ask!</p> <p>"Be excellent to each other." - Bill S. Preston, Esq.</p> "Rot Economy" and Software Bloat is Increasing the Digital Divide 2024-03-25T00:00:00+00:00 2024-03-25T00:00:00+00:00 Unknown https://blainsmith.com/articles/rot-economy-software-bloat-digital-divide/ <p>I have been involved in writing software professionally for almost 3 decades. A lot has changed since the days of <code>&lt;font&gt;</code> tags and cgi-bin scripts. A lot of the changes have redefined how software is written, organized, collaborated on, and delivered to its users. As more people came online with the commoditization of internet access and the increasing thread count of CPUs the software tools have adapted to those changes. We are seeing a lot of critical software that was once written in C being re-written in Rust to drastically improve memory safety and decrease security flaws. We have virtual cloud servers capable of simulating 256+ CPU cores per machine in large cluster thus making massive high performance cluster computations available by folks with a credit card to sign up and use. We have Go stepping in and becoming the networked cloud programming language to write these complex distributed system tools the world depends on. We have browsers now that act more like operating systems capable of loading complex HTML and enhancing it with even more complex JavaScript to give users desktop-like experiences on the web. While all of this was inevitable given varying applications of Moore's Law and layering in the tech world's obsession with hustle culture, grow quick, ship fast, raise more money, approach to building software we see the digital divide grow even wider. Once we had (still have) "developing countries" struggle to gain access to the internet, but now we have "developing states" in the US that have such sub-par internet access, even if it is better than developing countries, they struggle to keep up with the rest of the country, let alone the world.</p> <p>The Middle-East, India, Africa, and parts of rural China are well known for having limited or even no internet access. However, we are now seeing rural parts of the US, mainly in the midwest and the south, start to show signs of lagging access to internet speeds that the rest of the country takes for granted. I am currently writing this paper on my 8 core CPU, 32 GB RAM, and 1 TB HD laptop connected to a 1 GB symmetrical speed internet connection with Google Docs opened in my web browser. The experience of this is nothing short of amazing considering only 20 years ago I would have to do this by paying a lot of money for a license to Microsoft Office to use Word. However, there are a lot of hidden costs to this seemingly free online document editor now. This site had to loa ~25 MB of data over a network connection consisting of ~250 requests in which my browser is now consuming ~2 GB RAM in order for me to type black text characters onto a white screen. Windows 95 came available on 13 3.5" floppy disks so I essentially needed about 18 of those floppy disks just to run a document editor in a browser. Punishing all that data across a 1 GB network connection is fine, but what about those with connections in the low MB or even high KB range? It would be effectively useless. While the advancements and tools created to make systems and experiences like this possible the hubris and elitist stance these advancements also tend to create is also undeniable.</p> <p>To address these concerns the Single-Page App (SPA) was introduced to give users a more native experience using a web app, but also to enable background processing and caching techniques to deliver a better experience. The same experience can be delivered by offering a native build mobile or desktop app, but without the need to write it in multiple native languages. Writing a SPA requires only the use of modern web technology and tools to write a single codebase usable cross-platform on mobile and desktop browsers. The promise of convenience, usability, and consistency is usually met with over complexity, bloat, and rotten experience. The PWA movement has led software engineers into laziness by picking up tools and frameworks for quick fixes to ship faster and more often instead of being deliberate, concerned, and sympathetic software developers to their user's hardware and internet connection status in this world. What good is a SPA if the user still has to download 5 MB of React JavaScript framework bloat that takes up hard drive space on cheap hardware over a 2 MB internet connection only to be met with a SPA to consume GBs of precious RAM because it needs Google Chrome to run it which is well known for being a RAM hog. Google even recognized this and shipped a "Memory Saver" feature in Chrome that users can adjust. Why would anyone not want this enabled all the time? Most users of this wouldn't even know where to begin to set this appropriately. SPAs are not addressing the root cause of slow and poor app experiences on slow internet connections. The root cause is <a rel="external" href="https://pca.st/pb64psy2">"rot economy"</a> thinking forcing software engineers to build bloated tools and software to ship more things faster to show their company is growing to a board of investors and directors.</p> <p>Over the last 30 years I have experienced some amazing changes in the software world. It used to be a small group of us that understood and used computers on a daily basis, but now large portions of people own smartphones, tablets, multiple TVs, and multiple computers requiring increasingly better hardware and internet connections in order to sustain the growth and features of the software companies that have made tech so commonplace it's amazing how we lived without some of these things. However, those of us with the luxury, affordance, education, and understanding to keep up we still have our counterparts in not only developing countries, but developing states who do not have all of those things. There is an increasing digital divide not only technically, but also educationally since software has become pervasive and commonplace in most everyone's lives. Local municipalities, school districts, small business, and rural families are being left behind and forgotten when it comes to the software they are required to depend on for daily life. They don't care about SPAs, multi-core cloud computers, nuances of internet speeds, and the frameworks and languages used to build apps. They just want their kid's school bus app to work on their 4 year old phone because it gives them a sense of comfort that their children got back and forth to school safely. They want the local pizza shop's app that doesn't crash, load slowly, or mis-charge them when they order delivery for Friday movie night. They want to be able to conveniently pay their municipal bills and fees safely online so they don't have to wait for checks to clear or take precious time off work to run down to the town hall to pay fees. This is the population of people the digital divide is also affecting and PWAs written in the new hot JavaScript cross-platform web framework is not going to fix it and software engineers should take a step back before using them and think who their audience could be. An HTML page with good compression with a table of data is enough for a slow internet connection. Extra time making an Android and iOS app that runs on the last 4 versions of mobile phones to be able to make deliberate use of the phone's amazing hardware to ensure the experience is delightful, correct, and comforting should be a priority. These choices lead to longer term sustainable software because they are built on foundational technology and simple ideas.</p> Done 2024-03-19T00:00:00+00:00 2024-03-19T00:00:00+00:00 Unknown https://blainsmith.com/articles/done/ <p>Well, I think I finally hit a breaking point and I am officially done with the bullshit spreading through the "big" tech industry lately and I don't want to be a part of it anymore.</p> <p>I've been doing some sort of tech work for almost 30 years (27 to be exact) at this point and I am only 42 years old. I had my birthday on <a href="/articles/40">January 13th</a>. As I come to terms with moving through my 40s as a father of two great kids I find myself giving less and less fucks about the traditional work and culture in "big" tech. What I mean by "big" tech is essentially what you expect to find in mainstream media, LinkedIn, Twitter (I am not calling it X, deal with it), and other garbage and fake "social media" platforms. The vomit inducing "greater good" mission statements about changing the world and making it a better place while systematically collecting data on everything to justify the lies companies are telling themselves about what they are building is novel, innovative, and creating synergy in the world. It is all crap.</p> <p>Behind the veil of fakeness is a house of cards that constantly breaks under the weight of its own hubris and needless complexity. Hundreds of engineering hours spent on "agile" sprints doing story points and making plans that change on a weekly basis anyway. Infrastructure, tools, and architectures that are so overengineered because the industry celebrates clever and unique solutions over simple and proven ones. All of this leading towards products that are either crap or serve no real purpose and are only existing to make a play at VC money and "play" running a company.</p> <p>Software engineers as a whole are WILDLY overpaid because of all of the above. Companies need to pay to deal with the death by a thousand cuts and the sunk cost fallacy in order to retain their extremely smart folks. Folks are literally paid to babysit the computers that run products because shit is so unpredictable and unstable because they chose that complexity and think a MOMENT of downtime is life or death. Did I mention hubris yet?</p> <p>Fortunately, there is a world of tech that is actively against all of this drivel. The world of hackers, open source enthusiasts, digital activists, technology generalists, and tech co-ops is real and they are making a living at it. They are not falling for the overly positive marketing lies and "cold call" sales pitching techniques people spend money on LinkedIn classes to learn. This other tech world actually gives a shit about climate change, DEI, privacy, ethics, and socially good tech choices. There are even a few VC funded companies that actually give a shit about what they are doing. They take the privilege of being handed millions of dollars in cash very seriously and don't squander it.</p> <p>This is where I am going. I am done with the other world. I will see you in the smol, humane, solarpunk tech world from now on.</p> static-httpd 2024-03-01T00:00:00+00:00 2024-03-01T00:00:00+00:00 Unknown https://blainsmith.com/articles/static-httpd/ <p>Today I get to release the first working version of <a rel="external" href="https://git.sr.ht/~blainsmith/static-httpd"><code>static-httpd</code></a>.</p> <p>I have always found value in using an HTTP static file server to serve up some quick sites out of the current working directory. I've even written an <a href="/articles/single-command-static-file-web-server/">article way in the beforetimes</a> about some options to spin up a quick server. Since then there has been a flood of them to hit the open source scene and so I wanted to build one myself, but use <a rel="external" href="https://harelang.org">Hare</a> to do it since I've been spending a lot of time using this new language.</p> <h2 id="design-goals">Design Goals</h2> <p>My aim was to keep the final binary small and dependancy free as much as possible. By keeping <code>simple-httpd</code> single-threaded I was able to achieve that. There are options to support multi-threaded and multi-connections with either forking the process or using <a rel="external" href="https://git.sr.ht/~sircmpwn/hare-ev"><code>hare-ev</code></a> to do evented I/O. I decided against this given the use cases I was aiming to support.</p> <p>Since only needed the Hare standard libary the final compiled binary weights in at ~700kb. I think this is pretty nice considering the other options are significantly higher or need an interpreter. I could probably get this smaller, but staying under the 1mb limit is good enough for me.</p> <h2 id="future-features">Future Features</h2> <ul> <li>If there is a lot of interest in evented I/O support I could be convinced to pull in <a rel="external" href="https://git.sr.ht/~sircmpwn/hare-ev"><code>hare-ev</code></a>.</li> <li><a rel="external" href="https://letsencrypt.org">Let's Encrypt</a> or local certificates would be a nice to have leveraging <a rel="external" href="https://git.sr.ht/~apreiml/hare-tls"><code>hare-tls</code></a>.</li> </ul> <p>If you feel so inclined my <a rel="external" href="https://lists.sr.ht/~blainsmith/public-inbox">public inbox</a> is open for patches.</p> Writing Bitcask in Hare 2023-10-06T00:00:00+00:00 2023-10-06T00:00:00+00:00 Unknown https://blainsmith.com/articles/writing-bitcask-in-hare/ <p>TL;DR Read the source code for <a rel="external" href="https://git.sr.ht/~blainsmith/hare-bitcask">https://git.sr.ht/~blainsmith/hare-bitcask</a>.</p> <p>I have been <a rel="external" href="https://sr.ht/~blainsmith/hare/">writing modules</a> for <a rel="external" href="https://harelang.org">Hare</a> for a while now. It has been a lot of fun to write and helping expand the open source ecosystem of modules for this language has made me feel very fulfilled.</p> <p>Up until this point I've been mostly focused on small modules, but I really wanted to tackle something much more ambitious. I found that opportunity when I started my Fall 2023 semester at Georgia Tech and joined a <a rel="external" href="https://www.vip.gatech.edu/teams/vw9">Virtically Integrated Projects team</a> with some undergraduate students. Since a lot of my career I have spent in gaming I thought this was perfect to be able to focus an entire semester using a modern language to make a game. I got permission from the professor to use Hare to make a MUD/text adventure accessible online. There will be a blog post about that towards the end of the semester, but this post and work covered below was the result of needing to save game state for players.</p> <p>As work on the game itself progressed it was finally time to decide how to save game state. At first I considered just writing a file per player to disk based on their chosen player handle and updating that periodically. While that would be good enough for this style of game I wanted to try my hand at making a database from scratch since I have a lot more time this semester.</p> <p>Since I didn't want to spend a lot of time designing a database itself I wanted to find a pre-existing database that would not only fit my needs for the game, but also make for an interesting experience writing it in Hare. After searching around research papers and articles I finally found the perfect candidate. The <a rel="external" href="https://docs.riak.com/riak/kv/latest/setup/planning/backend/bitcask/index.html">Bitcask</a> backend for Riak KV. The <a rel="external" href="https://riak.com/assets/bitcask-intro.pdf">design paper</a> was even available cover some of the implementation details.</p> <h2 id="bitcask-api">Bitcask API</h2> <p>I wanted the API to match as closely as possible to the original so what I ended up with was:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>export type db = struct { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn put(db: *db, key: str, val: []u8) (void | error) = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn get(db: *db, key: str) ([]u8 | void | error) = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn del(db: *db, key: str) (void | error) = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn keys(db: *db) ([]str | void | error) = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn merge(db: *db) (void | error) = { ... };</span></span></code></pre> <p>Most of the functions are self-explanitory. However, <code>merge(...)</code> is used to merge and compact all of the underlying data files on disk to clean up history of old values and deleted keys since the data files are append-only. Without this the files would grow indefinately. Some implementations of Bitcask in other languages might call merge in a background job when data files reach a configurable max size. I chose to omit this functionality for now and put the responsibility on the programmer using this library to call <code>merge(...)</code> at any interval they need to.</p> <h2 id="data-file-encoding-decoding">Data File Encoding/Decoding</h2> <p>All of the files written to disk following the original design document so in theory this implementation should be able to encode/decide those same files. I have not tested this and there might be some minor differences in field sizes, but that should be pretty straightforward to accomplish. The <a rel="external" href="https://docs.harelang.org/endian"><code>endian</code></a> modules make it really easy to perform this work, but I do with there was something like <a rel="external" href="https://pkg.go.dev/encoding/binary#Read"><code>binary.Read(...)</code></a> and <a rel="external" href="https://pkg.go.dev/encoding/binary#Write"><code>binary.Write(...)</code></a> for Hare to be able to read/write binary data to a file handle instead of needing to use <code>[]u8</code> as intermediate data types. Maybe it will come in the future.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>// Make an array of 2 bytes</span></span> <span class="giallo-l"><span>let klen: [2]u8 = [0...];</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>// Encode the length of the key as a u16 into that array</span></span> <span class="giallo-l"><span>endian::little.putu16(klen, len(key): u16);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>// Write the array as a slice to the file handle</span></span> <span class="giallo-l"><span>io::write(w, klen[0..])?;</span></span></code></pre><h2 id="hash-maps">Hash Maps</h2> <p>I make use of a few hash maps in order to have quick lookups of keys and data files. Unfortunately, Hare does not natively have hash maps as a built-in data type like other languages do. Fortunately, it was trivial to implment one for the key/values I needed.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>// Meta contains information about the record and file id location.</span></span> <span class="giallo-l"><span>type meta = struct { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>// keydir is a hash map of 64 buckets with a str key and a *meta value tuple.</span></span> <span class="giallo-l"><span>type keydir = [64][](str, *meta);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>fn keydir_get(kd: *keydir, key: str) (*meta | void) = {</span></span> <span class="giallo-l"><span> let bkt = fnv::string(key) % len(kd);</span></span> <span class="giallo-l"><span> for (let i = 0z; i &lt; len(kd[bkt]); i += 1) {</span></span> <span class="giallo-l"><span> if (kd[bkt][i].0 == key) {</span></span> <span class="giallo-l"><span> return kd[bkt][i].1;</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> return;</span></span> <span class="giallo-l"><span>};</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>fn keydir_set(kd: *keydir, key: str, val: *meta) void = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>fn keydir_del(kd: *keydir, key: str) void = { ... };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>fn keydir_finish(kd: *keydir) void = { ... };</span></span></code></pre> <p>This gives me similar functionality of a Go map that would be <code>map[string]*meta</code> that I can use throughout Bitcask. The <code>keydir_finish</code> function is used to free allocated memory of all of the items in the map and then the actual map itself since I have to allocate and deallocate my own memory. The <code>TYPE_finish(...)</code> pattern you will see a lot in Hare to do exactly that kind of work. The basis of this implementation was inspired by one of Hare's author's, <a rel="external" href="https://drewdevault.com">Drew DeVault</a>, <a rel="external" href="https://paste.sr.ht/~sircmpwn/6459060150a94c81461f0ba1f3e4cb3b853b9334">public gist</a> he showed me.</p> <h2 id="what-s-next">What's next?</h2> <p>Now that I have a working database for my game I can move forward with the game itself, but I will certainly come back to Bitcask to add more features from the design paper. Most notably they would be:</p> <ul> <li>Setting an expiry on keys</li> <li>Auto-merge on a configurable interval</li> <li>1:1 encoding parity with the <a rel="external" href="https://github.com/basho/bitcask">official implementation from Basho</a></li> </ul> <p>Let me know what you think about it and if you have any questions or would like to send me patches feel free to reach out to me using my public inbox at <a rel="external" href="https://lists.sr.ht/~blainsmith/public-inbox">https://lists.sr.ht/~blainsmith/public-inbox</a> or email me directly.</p> Auto-Generating an OpenAPI Specification for gRPC and gRPC Gateway 2022-07-29T00:00:00+00:00 2022-07-29T00:00:00+00:00 Unknown https://blainsmith.com/articles/go-grpc-gateway-openapi/ <p>When building new services, <a rel="external" href="https://grpc.io">gRPC</a> can be very handy to not only define those services, but also generate a lot of the boilerplate code for server implementations and client libraries. We get the added benefit of a more efficient wire protocol, <a rel="external" href="https://developers.google.com/protocol-buffers">Protocol Buffers</a>, because it's in binary and a lot smaller than JSON. However, sometime we still need to support HTTP and some REST-ful behavior of our services. It would be nice to still be able to leverage all the benefits of using gRPC and Protocol Buffers without sacrificing or needing to write a lot of wrapper/proxy code to expose those services over HTTP. Taking this even further, it would be even better to leverage auto-generated documentation of our HTTP endpoints with the <a rel="external" href="https://swagger.io/specification/">OpenAPI Specification</a> and allow developers to interact with it with a hosted version of the <a rel="external" href="https://generator.swagger.io">Swagger UI</a>.</p> <p>Fortunately, the gRPC ecosystem has plugins to support this setup, and requires minimal code to wire everything together.</p> <h2 id="defining-a-grpc-service">Defining a gRPC Service</h2> <p>Here we define a simple gRPC CRUD <code>ExampleService</code>. For the sake of brevity, we'll just use <code>google.protobuf.Empty</code> messages for the requests and responses. Typically, we would define <code>message CreateRequest {...}</code> and <code>message CreateResponse {...}</code> types with the appropriate fields.</p> <p><strong>protos/service.proto</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="proto"><span class="giallo-l"><span style="color: #FF8F40;">syntax</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> &quot;proto3&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #AAD94C;"> protos</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> &quot;google/protobuf/empty.proto&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">option</span><span> go_package</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> &quot;github.com/blainsmith/grpc-gateway-openapi-example/gen/proto/go/protos;protos&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">service</span><span style="color: #59C2FF;"> ExampleService</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Create</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Read</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Update</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Delete</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {}</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span></code></pre><h2 id="generating-and-implementing-the-server-in-go">Generating and Implementing the Server in Go</h2> <p>Now that we have our service defined, we can generate the Go server interface and client using the Protocol Buffer compiler and the appropriate plugins. We'll be using <code>buf</code> to do this work, which uses <code>protoc</code> under the hood. We'll create two configuration files for <code>buf</code> so it knows what to generate.</p> <p>This is a basic <code>buf</code> configuration that defines our service name and the dependencies we need to build our <code>protos/service.proto</code> file. We need to define <code>buf.build/googleapis/googleapis</code> as a dependency in order to use <code>google.protobuf.Empty</code> as our request and response messages.</p> <p><strong>buf.yaml</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #39BAE6;">version</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> v1</span></span> <span class="giallo-l"><span style="color: #39BAE6;">name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> buf.build/blainsmith/grpc-gateway-openapi-example</span></span> <span class="giallo-l"><span style="color: #39BAE6;">deps</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> buf.build/googleapis/googleapis</span></span> <span class="giallo-l"><span style="color: #39BAE6;">breaking</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> use</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> FILE</span></span> <span class="giallo-l"><span style="color: #39BAE6;">lint</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> use</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> DEFAULT</span></span> <span class="giallo-l"></span></code></pre> <p>The second configuration file tells <code>buf</code> how to generate our Go code and where to put the output files.</p> <p><strong>buf.gen.yaml</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #39BAE6;">version</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> v1</span></span> <span class="giallo-l"><span style="color: #39BAE6;">plugins</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go-grpc</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> paths=source_relative</span></span></code></pre> <p>After running <code>buf build</code> and <code>buf generate</code> we end up with 2 new files. The first file contains our generate wire Protocol Buffer messages, and the second contains the gRPC server and client.</p> <p><strong>gen/protos/go/protos/service.pb.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Code generated by protoc-gen-go. DO NOT EDIT.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// versions:</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// protoc-gen-go v1.25.0</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// protoc (unknown)</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// source: protos/service.proto</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> protos</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre> <p><strong>gen/protos/go/protos/service_grpc.pb.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Code generated by protoc-gen-go-grpc. DO NOT EDIT.</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> protos</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ExampleServiceClient</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Create</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> in</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #D2A6FF;"> opts</span><span style="color: #F29668;"> ...</span><span style="color: #59C2FF;">grpc</span><span>.</span><span style="color: #59C2FF;">CallOption</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Read</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> in</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #D2A6FF;"> opts</span><span style="color: #F29668;"> ...</span><span style="color: #59C2FF;">grpc</span><span>.</span><span style="color: #59C2FF;">CallOption</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Update</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> in</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #D2A6FF;"> opts</span><span style="color: #F29668;"> ...</span><span style="color: #59C2FF;">grpc</span><span>.</span><span style="color: #59C2FF;">CallOption</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Delete</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> in</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #D2A6FF;"> opts</span><span style="color: #F29668;"> ...</span><span style="color: #59C2FF;">grpc</span><span>.</span><span style="color: #59C2FF;">CallOption</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ExampleServiceServer</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Create</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Read</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Update</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> Delete</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>)</span></span> <span class="giallo-l"><span style="color: #FFB454;"> mustEmbedUnimplementedExampleServiceServer</span><span>()</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre> <p>Now we can implement the server in our own Go file.</p> <h2 id="implementing-the-grpc-server">Implementing the gRPC Server</h2> <p>We now need to define the logic that implements <code>protos.ExampleServiceServer</code>, and to do that, we just make our own file and import the generated <code>protos</code> package. We create our source file, define our own <code>struct</code>, and implement the CRUD functions that are required by <code>protos.ExampleServiceServer</code>.</p> <p><strong>service/example.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> service</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span> (</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;context&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;github.com/blainsmith/grpc-gateway-openapi-example/gen/protos/go/protos&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;google.golang.org/grpc/codes&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;google.golang.org/grpc/status&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;google.golang.org/protobuf/types/known/emptypb&quot;</span></span> <span class="giallo-l"><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ExampleService</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">protos</span><span>.</span><span style="color: #59C2FF;">UnimplementedExampleServiceServer</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #59C2FF;">ExampleService</span><span>)</span><span style="color: #FFB454;"> Create</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, status.</span><span style="color: #FFB454;">Errorf</span><span>(codes.Unimplemented,</span><span style="color: #AAD94C;"> &quot;method Create not implemented&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #59C2FF;">ExampleService</span><span>)</span><span style="color: #FFB454;"> Read</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, status.</span><span style="color: #FFB454;">Errorf</span><span>(codes.Unimplemented,</span><span style="color: #AAD94C;"> &quot;method Read not implemented&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #59C2FF;">ExampleService</span><span>)</span><span style="color: #FFB454;"> Update</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, status.</span><span style="color: #FFB454;">Errorf</span><span>(codes.Unimplemented,</span><span style="color: #AAD94C;"> &quot;method Update not implemented&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #59C2FF;">ExampleService</span><span>)</span><span style="color: #FFB454;"> Delete</span><span>(</span><span style="color: #59C2FF;">context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">emptypb</span><span>.</span><span style="color: #59C2FF;">Empty</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, status.</span><span style="color: #FFB454;">Errorf</span><span>(codes.Unimplemented,</span><span style="color: #AAD94C;"> &quot;method Delete not implemented&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span></code></pre><h2 id="starting-the-grpc-server">Starting the gRPC Server</h2> <p>Now that we have an implemented service, we need to start it by listening on a port to expose it.</p> <p><strong>cmd/example.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> main</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span> lis, err</span><span style="color: #F29668;"> :=</span><span> net.</span><span style="color: #FFB454;">Listen</span><span>(</span><span style="color: #AAD94C;">&quot;tcp&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;:9090&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatalf</span><span>(</span><span style="color: #AAD94C;">&quot;failed to listen: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> </span></span> <span class="giallo-l"><span> grpcServer</span><span style="color: #F29668;"> :=</span><span> grpc.</span><span style="color: #FFB454;">NewServer</span><span>()</span></span> <span class="giallo-l"><span> protos.</span><span style="color: #FFB454;">RegisterExampleServiceServer</span><span>(grpcServer,</span><span style="color: #F29668;"> &amp;</span><span style="color: #59C2FF;">service</span><span>.</span><span style="color: #59C2FF;">ExampleService</span><span>{})</span></span> <span class="giallo-l"><span> </span></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> =</span><span> grpcServer.</span><span style="color: #FFB454;">Serve</span><span>(lis)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatalf</span><span>(</span><span style="color: #AAD94C;">&quot;failed to serve: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Now we have our gRPC server running so clients can connect and call our CRUD functions.</p> <h2 id="annotate-the-grpc-service-with-http-routes">Annotate the gRPC Service with HTTP Routes</h2> <p>Now comes the interesting part of exposing an HTTP server to handle REST calls to the same <code>ExampleService</code> implementation we defined in <code>service/example.go</code> without changing that code.</p> <p>We start by modifying our <code>.proto</code> file an annotate the RPC calls to include optional HTTP routes to be attached to.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="proto"><span class="giallo-l"><span style="color: #FF8F40;">syntax</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> &quot;proto3&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #AAD94C;"> protos</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> &quot;google/api/annotations.proto&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> &quot;google/protobuf/empty.proto&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">option</span><span> go_package</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> &quot;github.com/blainsmith/grpc-gateway-openapi-example/gen/proto/go/protos;protos&quot;</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">service</span><span style="color: #59C2FF;"> ExampleService</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Create</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> option</span><span> (google.api.http)</span><span style="color: #F29668;"> =</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> post</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;/create&quot;</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Read</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> option</span><span> (google.api.http)</span><span style="color: #F29668;"> =</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> get</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;/read&quot;</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Update</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> option</span><span> (google.api.http)</span><span style="color: #F29668;"> =</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> put</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;/update&quot;</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> rpc</span><span style="color: #FFB454;"> Delete</span><span>(</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) </span><span style="color: #FF8F40;">returns</span><span> (</span><span style="color: #59C2FF;">google.protobuf.Empty</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> option</span><span> (google.api.http)</span><span style="color: #F29668;"> =</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> delete</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;/delete&quot;</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre><h2 id="generate-the-grpc-gateway">Generate the gRPC Gateway</h2> <p>Now that we have our service annotated with routes, we can use another gRPC plugin to generate a new server which will accept HTTP calls and proxy them to gRPC calls. We modify our <code>buf.gen.yaml</code> file to include a new plugin.</p> <p><strong>buf.gen.yaml</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #39BAE6;">version</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> v1</span></span> <span class="giallo-l"><span style="color: #39BAE6;">plugins</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go-grpc</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> grpc-gateway</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> paths=source_relative</span></span></code></pre> <p>Now we can run <code>buf generate</code> again, which will output another file.</p> <p><strong>gen/protos/go/protos/service.pb.gw.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// source: protos/service.proto</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">/*</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">Package protos is a reverse proxy.</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">It translates gRPC into RESTful JSON APIs.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">*/</span></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> protos</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// RegisterExampleServiceHandlerServer registers the http handlers for service ExampleService to &quot;mux&quot;.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// UnaryRPC :call ExampleServiceServer directly.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterExampleServiceHandlerFromEndpoint instead.</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> RegisterExampleServiceHandlerServer</span><span>(</span><span style="color: #D2A6FF;">ctx</span><span style="color: #59C2FF;"> context</span><span>.</span><span style="color: #59C2FF;">Context</span><span>,</span><span style="color: #D2A6FF;"> mux</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">runtime</span><span>.</span><span style="color: #59C2FF;">ServeMux</span><span>,</span><span style="color: #D2A6FF;"> server</span><span style="color: #59C2FF;"> ExampleServiceServer</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span></span> <span class="giallo-l"><span style="color: #F29668;"> ...</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre><h2 id="starting-the-http-server">Starting the HTTP Server</h2> <p>We have to add two things to our <code>main()</code> function now to connect the HTTP server to the gRPC server. The first thing is we need a gRPC client, which our HTTP server will use to talk to the gRPC server.</p> <ol> <li>The HTTP Server accepts incoming requests</li> <li>It converts the request to a gRPC call</li> <li>It uses the gRPC Client that has an open connection to the gRPC Server</li> <li>the gRPC Server invokes the ExampleService function</li> <li>The result is returned to the gRPC Client</li> <li>The HTTP Server transforms the result to a response back to the caller</li> </ol> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span> ┌────────────────────────┐ ┌────────────────────────┐</span></span> <span class="giallo-l"><span> Request │ HTTP │ │ gRPC │</span></span> <span class="giallo-l"><span>──────────► Server │ │ Server │</span></span> <span class="giallo-l"><span> │ │ │ │</span></span> <span class="giallo-l"><span> │ │ │ │</span></span> <span class="giallo-l"><span> │ ┌──────────┐ │ │ │</span></span> <span class="giallo-l"><span> │ │ gRPC ◄─┼───────────► ┌────────────────┐ │</span></span> <span class="giallo-l"><span> Response │ │ Client │ │ │ │ ExampleService │ │</span></span> <span class="giallo-l"><span>◄─────────┤ └──────────┘ │ │ └────────────────┘ │</span></span> <span class="giallo-l"><span> │ │ │ │</span></span> <span class="giallo-l"><span> └────────────────────────┘ └────────────────────────┘</span></span></code></pre> <p><strong>cmd/example.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>ctx</span><span style="color: #F29668;"> :=</span><span> context.</span><span style="color: #FFB454;">Background</span><span>()</span></span> <span class="giallo-l"><span>ctx, cancel</span><span style="color: #F29668;"> :=</span><span> context.</span><span style="color: #FFB454;">WithCancel</span><span>(ctx)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">defer</span><span style="color: #FFB454;"> cancel</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// start the gRPC server</span></span> <span class="giallo-l"><span>lis, err</span><span style="color: #F29668;"> :=</span><span> net.</span><span style="color: #FFB454;">Listen</span><span>(</span><span style="color: #AAD94C;">&quot;tcp&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;:9090&quot;</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatalf</span><span>(</span><span style="color: #AAD94C;">&quot;failed to listen: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span>grpcServer</span><span style="color: #F29668;"> :=</span><span> grpc.</span><span style="color: #FFB454;">NewServer</span><span>()</span></span> <span class="giallo-l"><span>protos.</span><span style="color: #FFB454;">RegisterExampleServiceServer</span><span>(grpcServer,</span><span style="color: #F29668;"> &amp;</span><span style="color: #59C2FF;">service</span><span>.</span><span style="color: #59C2FF;">ExampleService</span><span>{})</span></span> <span class="giallo-l"><span style="color: #FF8F40;">go</span><span> grpcServer.</span><span style="color: #FFB454;">Serve</span><span>(lis)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// dial the gRPC server above to make a client connection</span></span> <span class="giallo-l"><span>conn, err</span><span style="color: #F29668;"> :=</span><span> grpc.</span><span style="color: #FFB454;">Dial</span><span>(</span><span style="color: #AAD94C;">&quot;:9090&quot;</span><span>, grpc.</span><span style="color: #FFB454;">WithInsecure</span><span>())</span></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatalf</span><span>(</span><span style="color: #AAD94C;">&quot;fail to dial: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #FF8F40;">defer</span><span> conn.</span><span style="color: #FFB454;">Close</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// create an HTTP router using the client connection above</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// and register it with the service client</span></span> <span class="giallo-l"><span>rmux</span><span style="color: #F29668;"> :=</span><span> runtime.</span><span style="color: #FFB454;">NewServeMux</span><span>()</span></span> <span class="giallo-l"><span>client</span><span style="color: #F29668;"> :=</span><span> protos.</span><span style="color: #FFB454;">NewExampleServiceClient</span><span>(conn)</span></span> <span class="giallo-l"><span>err</span><span style="color: #F29668;"> =</span><span> protos.</span><span style="color: #FFB454;">RegisterExampleServiceHandlerClient</span><span>(ctx, rmux, client)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatal</span><span>(err)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// create a standard HTTP router</span></span> <span class="giallo-l"><span>mux</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewServeMux</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// mount the gRPC HTTP gateway to the root</span></span> <span class="giallo-l"><span>mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;/&quot;</span><span>, rmux)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// start a standard HTTP server with the router</span></span> <span class="giallo-l"><span>err</span><span style="color: #F29668;"> =</span><span> http.</span><span style="color: #FFB454;">ListenAndServe</span><span>(</span><span style="color: #AAD94C;">&quot;:8080&quot;</span><span>, mux)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatal</span><span>(err)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Now we have a service listening on 2 different ports. The gRPC server port is <code>:9090</code> and the HTTP server port is <code>:8080</code>. The routes we have defined in our <code>.proto</code> file can be hit with Postman, cURL, or your favorite REST API tool.</p> <p>The problem now is what do these HTTP request and responses look like in JSON? How do we know what request body to create, and what do the responses contain? This is where another plugin and the Swagger UI comes in to play.</p> <h2 id="generating-the-openapi-specification">Generating the OpenAPI Specification</h2> <p>The last few steps are to pull in another gRPC plugin to generate the OpenAPI Specification JSON file, and then expose that file and the Swagger UI in our HTTP server. Back in the <code>buf.gen.yaml</code> file we add in the last plugin for <code>openapiv2</code>.</p> <p><strong>buf.gen.yaml</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #39BAE6;">version</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> v1</span></span> <span class="giallo-l"><span style="color: #39BAE6;">plugins</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> go-grpc</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> grpc-gateway</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen/protos/go</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> opt</span><span style="color: #BFBDB6B3;">:</span></span> <span class="giallo-l"><span> -</span><span style="color: #AAD94C;"> paths=source_relative</span></span> <span class="giallo-l"><span> -</span><span style="color: #39BAE6;"> name</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> openapiv2</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> gen</span></span></code></pre> <p>Running <code>buf generate</code> again will output a JSON file that contains the OpenAPI Specification of our annotated gRPC service.</p> <p><strong>gen/protos/service.swagger.json</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="json"><span class="giallo-l"><span>{</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;swagger&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;2.0&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;info&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;title&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;protos/protos/service.proto&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;version&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;version not set&quot;</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;tags&quot;</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span> <span class="giallo-l"><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;name&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;ExampleService&quot;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> ]</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;consumes&quot;</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;application/json&quot;</span></span> <span class="giallo-l"><span> ]</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;produces&quot;</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;application/json&quot;</span></span> <span class="giallo-l"><span> ]</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;paths&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;/create&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;post&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;operationId&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;ExampleService_Create&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;responses&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;200&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;description&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;A successful response.&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;schema&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;type&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;object&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;properties&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {}</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;default&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;description&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;An unexpected error response.&quot;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;schema&quot;</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;$ref&quot;</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;#/definitions/rpcStatus&quot;</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #39BAE6;"> &quot;tags&quot;</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;ExampleService&quot;</span></span> <span class="giallo-l"><span> ]</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"><span style="color: #D95757;">...</span></span></code></pre> <p>Now we can use this and render the Swagger UI for it, similar to <a rel="external" href="https://generator.swagger.io/">https://generator.swagger.io</a>, but for our generated specification file. To do that, we need to grab the entire <code>dist</code> folder from <a rel="external" href="https://github.com/swagger-api/swagger-ui/tree/master/dist">https://github.com/swagger-api/swagger-ui/tree/master/dist</a> and add it to our repository as <code>swagger-ui</code>.</p> <p>Next, we will modify our HTTP server to have 2 new routes. One for the specification file, and the other for the Swagger UI.</p> <p><strong>cmd/example.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// mount the gRPC HTTP gateway to the root</span></span> <span class="giallo-l"><span>mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;/&quot;</span><span>, rmux)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// mount a path to expose the generated OpenAPI specification on disk</span></span> <span class="giallo-l"><span>mux.</span><span style="color: #FFB454;">HandleFunc</span><span>(</span><span style="color: #AAD94C;">&quot;/swagger-ui/swagger.json&quot;</span><span>,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">w</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">ResponseWriter</span><span>,</span><span style="color: #D2A6FF;"> r</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>) {</span></span> <span class="giallo-l"><span> http.</span><span style="color: #FFB454;">ServeFile</span><span>(w, r,</span><span style="color: #AAD94C;"> &quot;./gen/protos/service.swagger.json&quot;</span><span>)</span></span> <span class="giallo-l"><span>})</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// mount the Swagger UI that uses the OpenAPI specification path above</span></span> <span class="giallo-l"><span>mux.</span><span style="color: #FFB454;">Handle</span><span>(</span><span style="color: #AAD94C;">&quot;/swagger-ui/&quot;</span><span>, http.</span><span style="color: #FFB454;">StripPrefix</span><span>(</span><span style="color: #AAD94C;">&quot;/swagger-ui/&quot;</span><span>, http.</span><span style="color: #FFB454;">FileServer</span><span>(http.</span><span style="color: #FFB454;">Dir</span><span>(</span><span style="color: #AAD94C;">&quot;./swagger-ui&quot;</span><span>))))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// start a standard HTTP server with the router</span></span> <span class="giallo-l"><span>err</span><span style="color: #F29668;"> =</span><span> http.</span><span style="color: #FFB454;">ListenAndServe</span><span>(</span><span style="color: #AAD94C;">&quot;localhost:8080&quot;</span><span>, mux)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span> log.</span><span style="color: #FFB454;">Fatal</span><span>(err)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre> <p>Lastly, we will change the Swagger initializer <code>url</code> to load the <code>swagger.json</code> path we added to our HTTP server.</p> <p><strong>swagger-ui/swagger-initializer.js</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="javascript"><span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>window</span><span style="color: #F29668;">.</span><span>ui</span><span style="color: #F29668;"> =</span><span style="color: #FFB454;"> SwaggerUIBundle</span><span>({</span></span> <span class="giallo-l"><span> url</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &quot;swagger.json&quot;</span><span style="color: #BFBDB6B3;">,</span><span style="color: #5A6673;font-style: italic;"> // this should be</span></span> <span class="giallo-l"><span> dom_id</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> &#39;#swagger-ui&#39;</span><span style="color: #BFBDB6B3;">,</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre><h2 id="play-the-api-with-the-swagger-ui">Play the API with the Swagger UI</h2> <p>Now that everything is wired up, all we need to do is start our service again and then open up <a rel="external" href="http://localhost:8080/swagger-ui">http://localhost:8080/swagger-ui</a> in a browser, and we get an interactive Swagger UI to play with.</p> <p><img src="/assets/grpc_gateway_swagger-ui.png" alt="gRPC Gateway Swagger UI" /></p> <h2 id="closing-thoughts">Closing Thoughts</h2> <p>Now that you have the foundation set up, you can expand upon this to build a full fledged gRPC service and continually re-generate the OpenAPI Specification to support HTTP calls, all without compromising or leaving the comfort of your <code>service.proto</code> definition. This stays as the source of truth as you implement the server itself in Go as normal gRPC function calls.</p> <p>You can find the complete example at <a rel="external" href="https://git.sr.ht/~blainsmith/grpc-gateway-openapi-example">https://git.sr.ht/~blainsmith/grpc-gateway-openapi-example</a> and if you have any questions feel free to file an issue or contact me any way you're comfortable with.</p> Network Programming with Hare 2022-05-10T00:00:00+00:00 2022-05-10T00:00:00+00:00 Unknown https://blainsmith.com/articles/network-programming-with-hare/ <p>I've been reading <a rel="external" href="https://drewdevault.com/">Drew Devault's blog</a> for quite some time. Recently, he and a small team <a rel="external" href="https://harelang.org/blog/2022-04-25-announcing-hare/">released a new programming language</a> called <a rel="external" href="https://harelang.org/">Hare</a> that is laser focused on systems programming and meant to compete with C, Rust, Zig, but have some of the simple design choices of Go. The biggest differences from Go I can see is Hare's lack of a garbage collector so memory management like C, Rust, and Zig is completely up to you, the programmer and lack of built-in concurrency support.</p> <p>It has a fairly small <a rel="external" href="https://docs.harelang.org/">standard library</a> with some additional batteries included for doing a lot of common tasks and so I wanted to see what it could do when compared to Go specifically since that is my main programming language these days. I like to create simple UDP and TCP servers in new languages so see how they stack up against Go in terms of readability and conciseness.</p> <p>Features of each server include:</p> <ul> <li>Listen on an IP and port</li> <li>Accept incoming connections</li> <li>Accept incoming data from those connection</li> <li>Log out what data came in and what was the client's IP and port</li> <li>Echo the data back to the client</li> <li>For TCP it should allow for multiple simultaneous client connections</li> </ul> <h2 id="udp-server">UDP Server</h2> <p>Since UDP is connectionless the server is pretty straightforward and I don't have to do any multiplexing of client connections. The server just sets up a listener and waits for incoming data, logs it, and writes it back to the client. TCP, however, is much more complicated to handle multiple simultaneous connections.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>use fmt;</span></span> <span class="giallo-l"><span>use net::udp;</span></span> <span class="giallo-l"><span>use net::ip;</span></span> <span class="giallo-l"><span>use strings;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn main() void = {</span></span> <span class="giallo-l"><span> // create a udp listener on the local v4 ip and a port</span></span> <span class="giallo-l"><span> const listener = udp::listen(ip::LOCAL_V4, 8000)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> for (true) {</span></span> <span class="giallo-l"><span> // define buffer to hold incoming data</span></span> <span class="giallo-l"><span> let buf: [1024]u8 = [0...];</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // define ip address to hold source of data</span></span> <span class="giallo-l"><span> let src: ip::addr = ip::ANY_V4;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // define port to hold source&#39;s port of data</span></span> <span class="giallo-l"><span> let port: u16 = 0;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // block and wait for incoming data on the listener</span></span> <span class="giallo-l"><span> // and write the buf, src, port accordingly</span></span> <span class="giallo-l"><span> let n = udp::recvfrom(listener, &amp;buf, &amp;src, &amp;port)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // re-slice the buffer to the length of the</span></span> <span class="giallo-l"><span> // data that came in</span></span> <span class="giallo-l"><span> let buf = buf[..n];</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // print out the variables and format the buffer</span></span> <span class="giallo-l"><span> // as a string</span></span> <span class="giallo-l"><span> fmt::printfln(&quot;{}:{} says {}&quot;, ip::string(src), port, strings::fromutf8(buf))!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // echo it back to the client</span></span> <span class="giallo-l"><span> udp::sendto(listener, buf, src, port)!;</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span>};</span></span></code></pre><h2 id="tcp-server">TCP Server</h2> <p>The same basic idea is the same as UDP, but in order to handle multiple simultaneous I need to store and hold onto those and periodically check when data is incoming from them. There is a lot of bookkeeping of these connections, but Hare does support <code>poll</code> which allows us to check for activity on those connections.</p> <p>I set up a listener as before, but then I add it to a list of file descriptors inside <code>poll</code> and start checking for events on all of them. If an event comes in from the <code>listener</code> file descriptor I know it is a new client connection so I add it to the file descriptors for <code>poll</code> to watch. If an event happens on one of the client file descriptors I know it is data incoming so I can read from it, log it, and then write it back to the client. The biggest differences from Go I can see is Hare's lack of a garbage collector so memory management like C, Rust, and Zig is completely up to you, the programmer and lack of built-in concurrency support.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>use net::tcp;</span></span> <span class="giallo-l"><span>use net::ip;</span></span> <span class="giallo-l"><span>use strings;</span></span> <span class="giallo-l"><span>use unix::poll;</span></span> <span class="giallo-l"><span>use io;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>def MAX_CLIENTS: u8 = 10;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>export fn main() void = {</span></span> <span class="giallo-l"><span> // create a udp listener on the local v4 ip and a port</span></span> <span class="giallo-l"><span> const listener = tcp::listen(ip::LOCAL_V4, 8000)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // create a bunch of poll file descriptors and set the first one to the</span></span> <span class="giallo-l"><span> // listener created above</span></span> <span class="giallo-l"><span> let fds: [MAX_CLIENTS]poll::pollfd = [</span></span> <span class="giallo-l"><span> poll::pollfd {</span></span> <span class="giallo-l"><span> fd = listener,</span></span> <span class="giallo-l"><span> events = (poll::event::POLLIN | poll::event::POLLPRI),</span></span> <span class="giallo-l"><span> ...</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> ...</span></span> <span class="giallo-l"><span> ];</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // fill the rest of the polling file descriptors with empty ones for</span></span> <span class="giallo-l"><span> // clients that want to connect</span></span> <span class="giallo-l"><span> for (let i: size = 1; i &lt; MAX_CLIENTS; i+=1) {</span></span> <span class="giallo-l"><span> fds[i] = poll::pollfd {</span></span> <span class="giallo-l"><span> fd = 0,</span></span> <span class="giallo-l"><span> events = 0,</span></span> <span class="giallo-l"><span> ...</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> for (true) {</span></span> <span class="giallo-l"><span> // poll all of the file descriptors and block until there is</span></span> <span class="giallo-l"><span> // activity on one of them</span></span> <span class="giallo-l"><span> poll::poll(fds, poll::INDEF)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // once we get activity check if it was from the first one which</span></span> <span class="giallo-l"><span> // is the listener which means we have a new incoming client connection</span></span> <span class="giallo-l"><span> if (fds[0].revents == poll::event::POLLIN) {</span></span> <span class="giallo-l"><span> // accept the client connection from the listener</span></span> <span class="giallo-l"><span> let conn = net::accept(listener)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> log::println(&quot;client conn accepted&quot;);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // loop over the remaining file descriptors and find and empty</span></span> <span class="giallo-l"><span> // slot for the client and set the appropriate events to poll for</span></span> <span class="giallo-l"><span> for (let i: size = 1; i &lt; MAX_CLIENTS; i+=1) {</span></span> <span class="giallo-l"><span> if (fds[i].events == 0) {</span></span> <span class="giallo-l"><span> log::printfln(&quot;client conn added to position {}&quot;, i);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> fds[i].fd = conn;</span></span> <span class="giallo-l"><span> fds[i].events = (poll::event::POLLIN | poll::event::POLLPRI);</span></span> <span class="giallo-l"><span> </span></span> <span class="giallo-l"><span> break;</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // lastly loop over all the client file descriptors to check if any</span></span> <span class="giallo-l"><span> // of them have written data</span></span> <span class="giallo-l"><span> for (let i: size = 1; i &lt; MAX_CLIENTS; i+=1) {</span></span> <span class="giallo-l"><span> if (fds[i].events &gt; 0 &amp;&amp; (fds[i].revents == poll::event::POLLIN || fds[i].revents == poll::event::POLLPRI)) {</span></span> <span class="giallo-l"><span> // set up a read/write buffer with the client&#39;s file</span></span> <span class="giallo-l"><span> // descriptor</span></span> <span class="giallo-l"><span> let rbuf: [1024]u8 = [0...];</span></span> <span class="giallo-l"><span> let wbuf: [1024]u8 = [0...];</span></span> <span class="giallo-l"><span> let buffered = bufio::buffered(fds[i].fd, rbuf, wbuf);</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // attempt to read the line</span></span> <span class="giallo-l"><span> match (bufio::scanline(&amp;buffered)) {</span></span> <span class="giallo-l"><span> </span></span> <span class="giallo-l"><span> // if a line is read then grab the client&#39;s ip/port and log out</span></span> <span class="giallo-l"><span> // the data they sent and also echo it back to the client</span></span> <span class="giallo-l"><span> case let line: []u8 =&gt;</span></span> <span class="giallo-l"><span> let peer: (ip::addr, u16) = tcp::peeraddr(fds[i].fd) as (ip::addr, u16);</span></span> <span class="giallo-l"><span> fmt::printfln(&quot;{}:{} says {}&quot;, ip::string(peer.0), peer.1, strings::fromutf8(line))!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> io::writeall(&amp;buffered, line)!;</span></span> <span class="giallo-l"><span> bufio::flush(&amp;buffered)!;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // if reading the line returns and EOF then we can assume the</span></span> <span class="giallo-l"><span> // client disconnected so we close the file descriptor and reset</span></span> <span class="giallo-l"><span> // the events to open up a slot for a new client to connect to</span></span> <span class="giallo-l"><span> case io::EOF =&gt;</span></span> <span class="giallo-l"><span> io::close(fds[i].fd)!;</span></span> <span class="giallo-l"><span> fds[i].events = 0;</span></span> <span class="giallo-l"><span> fds[i].revents = 0;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // any other error we reset the events for a new client slot</span></span> <span class="giallo-l"><span> case let err: io::error =&gt;</span></span> <span class="giallo-l"><span> fds[i].events = 0;</span></span> <span class="giallo-l"><span> fds[i].revents = 0;</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"><span> };</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> // shut down the listener if we ever get here</span></span> <span class="giallo-l"><span> net::shutdown(listener);</span></span> <span class="giallo-l"><span>};</span></span></code></pre><h2 id="thoughts">Thoughts</h2> <p>Overall, I like Hare. It writes like Go since a lot of it was inspired by Go, but it's target audience is C, Rust, and Zig programmers who are doing more low level systems programming like sockets, device drivers, and kernels.</p> <p>The standard library is pretty complete with all the tools you need to do sorting, crypto, basic encoding, working with files, etc. Some of the extended library packages will get SQL/database support, additional encoding, but they might just stop there. While you could write a REST HTTP API with Hare, it would take a monumental effort to support the HTTP protocol so if this is what you're thinking of doing I would just use Go instead. However, if CPU and safe memory matter then Hare might be a good fit if the other systems programming languages are too unsafe (C) or complex (Rust/Zig) and you need a simple language with fast compile times and good tooling like Go.</p> 40 2022-01-13T00:00:00+00:00 2022-01-13T00:00:00+00:00 Unknown https://blainsmith.com/articles/40/ <p>I guess society celebrates turning 40 as a big milestone birthday. I might have some wisdom, anecdotes, and pithy quotes to share that might serve others well since I've been alive on this big ball of rock for 4 decades. Here are 40 things I've learned during my time here in no particular order:</p> <ol> <li>Mental and physical health form a symbiotic relationship. Make both a priority and take care of them.</li> <li>People will move in and out of your life. The ones that stay are the ones worth your time.</li> <li>You can't know everything no matter how much you read and research. Trust the expertise in others.</li> <li>You can disagree with someone, but still be civil and acquaintances. You don't have to be close friends.</li> <li>Social media connections are not friends.</li> <li>Social media feeds off negativity and contention. You are a product and a means to an end for them.</li> <li>A pandemic is a good way to force you to take stock in your life and find out who you really are. You can then change or stay the same.</li> <li>Introverts are not anti-social, they are "pro-quiet".</li> <li>The advice you give when it is asked for isn't required to be heeded, but you can choose to stop giving it.</li> <li>Prioritize your time. There are do-overs, but they take additional time.</li> <li>40 doesn't have to feel old if you don't want it to be. I am in the best shape of my life competing in powerlifting and strongman with a 600lbs deadlift, 500lbs squat, 300lbs bench, and 200lbs shoulder press. I can carry 200lbs in each hand and go for a walk. All of this strength makes daily life a piece of cake.</li> <li>Stop giving a fuck about things you can't change.</li> <li>Just because you can doesn't mean you should. Solutions that solve pre-existing problems are worth while. Solutions that create artificial problem to solve are meaningless. (See cryptocurrencies, NFT, etc.)</li> <li>"Crypto" is short for "cryptography", not "cryptocurrencies".</li> <li>Social media influencers are just another form of ads.</li> <li>You cannot have a meaningful debate with an ignorant person.</li> <li>Low bar squats and high bar squats serve different purposes and one isn't better than the other.</li> <li>Conventional deadlifts and sumo deadlifts serve different purposes and one isn't better than the other.</li> <li>Training and exercising are different things. Training is planned exercising with a goal. You can exercise all you want, but without a plan it will take longer to reach your goal.</li> <li>Typed languages are better than untyped dynamic languages.</li> <li>JavaScript is a necessary evil.</li> <li>Simpler is better than complex. Start with the former and move to the latter only when needed.</li> <li>SQLite is grossly underestimated.</li> <li>Unless you're Facebook, Google, Amazon, Apple, Netflix, etc. you don't need to promise your users <a rel="external" href="https://uptime.is/three-nines">99.9% or above</a> uptime. Be realistic.</li> <li>Software is rarely every done and left alone. There is always maintenance, patch, and/or feature work to do.</li> <li>Using software as someone in the software field is a rollercoaster of frustration and bliss.</li> <li>Software is a means to an end. Rarely is it center stage anymore since it is becoming a tool to solve problems in other disciplines.</li> <li>Anyone can be a software developer if you write code. Some write more than others in their day to day.</li> <li>Take some time and learn the tools and underlying technology you depend on to do your work. It will bring a new perspective and appreciation to work.</li> <li>Some people are better suited to be managers and some are better suited to be individual contributors.</li> <li>Tools should reduce time and overheard to accomplish work. If they don't then throw it away for something else.</li> <li>"eth" is short for "Ethernet", not "Ethereum".</li> <li>You can't be a good person and work for a bad company.</li> <li>Today's web sucks to browse ever since ad companies got their hands on it with tracking and bullshit JavaScript.</li> <li>Cable TV isn't worth the cost. The content of the shows and the commercials are pure garbage.</li> <li>Help others as much as you can, but if you start feeling used and taken for granted, drop them like a bad habit. People can be very selfish.</li> <li>Coffee should be drank black.</li> <li>Shop the perimeter of grocery stores to get the best possible food to eat. Avoid the aisle. Only exception to this rule is frozen vegetables since they are considerably cheaper than fresh.</li> <li>Problem solving and critical thinking are paramount to any one specific discipline, trade, or profession.</li> <li>40 years is a long time. 1 year is a long time. A lot can happen in those time frames if your life needs to change.</li> </ol> <p>Onto the next 40 years.</p> Software I'm Thankful For 2021-11-29T00:00:00+00:00 2021-11-29T00:00:00+00:00 Unknown https://blainsmith.com/articles/software-im-thankful-for/ <p>I read <a rel="external" href="https://twitter.com/davidcrawshaw">David Crawshaw</a>'s <a rel="external" href="https://crawshaw.io/blog/thankful-for-technology">recent post about the same topic</a> and I thought it would be nice to do the same.</p> <h2 id="go">Go</h2> <p>I've always tried to be a polyglot programmer to allow myself to learn and be a well rounded software engineer, but as time went on I realized that specializing in a small set areas is also important. I made a choice to really focus on Go back in 2015 when I was primarily doing Node.js at the time and even though it was a relatively new language and it was risky to bank on it being as popular and as marketable as it is today I felt it was the right move. I am thankful that I was right. While becoming more proficient in the language itself I also became more a more proficient distributed and systems software engineer as well. I was exposed to a lot more complex topics such as lower level CPU and memory management, consensus algorithms, software teams productivity, fundamental networking protocols, and the list goes on. <a href="https://blainsmith.com/articles/go-made-me-return-college/">Go even made me return to college</a> to build up my academic understanding of computer science itself.</p> <h2 id="ip-udp">IP &amp; UDP</h2> <p>I read the <a rel="external" href="https://datatracker.ietf.org/doc/rfc760/">IP</a> &amp; <a rel="external" href="https://datatracker.ietf.org/doc/rfc768/">UDP</a> RFCs periodically to remind myself where parts of the internet started and how simple technology can have such a significant impact. So much of the internet today still relies on these two pillars of technology to allow you to do what you do on the internet today. If you haven't read the RFCs I urge you to do so.</p> <h2 id="redis">Redis</h2> <p>I always enjoy pulling Redis into a project since it feels like a blank canvas when I need to reason about what data I need to store. Relational databases always forced me to think about objects, but Redis always makes me think about data structures. Having the flexibility to store and manipulate data structures felt more freeing when designing a new system or a feature. Connect over a socket and away you go playing with keys, lists, sets, and graphs.</p> <h2 id="sqlite">SQLite</h2> <p>When I do need a relational database I like to use SQLite to start off with given its simplicity. Since it is just a file on disk there is no network issues or operational overheard that come with running a dedicated database server. The database file can even be backed up via <a rel="external" href="https://litestream.io">Litestream</a> which is another fantastic piece of technology.</p> <h2 id="git-github-gitlab-sourcehut-et-al">Git, GitHub, GitLab, sourcehut, et al.</h2> <p>My background in source control many moons ago was <a rel="external" href="https://en.wikipedia.org/wiki/Microsoft_Visual_SourceSafe">Visual SourceSafe</a> and most recently I've been exposed to <a rel="external" href="https://www.perforce.com/">Perforce</a> and <a rel="external" href="https://www.plasticscm.com/">PlasticSCM</a> which are heavily used in game studios. Git and the code sharing sites still continues to shine when it comes to building software as a group of software engineers with it's distributed model and its diffing capabilities.</p> <h2 id="elementary-os">elementary OS</h2> <p>elementary OS has been a fantasic operating system that reminds me of using a wonderful UI from the macOS days while still having a full Linux system at my fingertips to do anything I need to. I get the best of both worlds with a well supported and great ecosystem of native apps with thoughtful attention to details in their designs while still spending most of my data in Terminal working on what I need to do to ship software. I even start to install it on older systems to revive them and give them to family members instead of recommending they buy a new machine. I really hope this distro has a bright future and is around for a long time as the poster child for what a Linux laptop/deskop should be like for the masses.</p> <h2 id="twitter">Twitter</h2> <p>Despite the toxicity that can arise there is still some great parts to it to connect with other like-minded people. I have learned a lot from people I've carefully curated into lists and I even landed a job after seeing a job posting come from a person a friend of mine and I were mutually following. At the end of the day here it has been a great social tool since I've worked at making sure it is a positive environment for myself.</p> How I Write HTTP Clients 2021-11-09T00:00:00+00:00 2021-11-09T00:00:00+00:00 Unknown https://blainsmith.com/articles/how-i-write-http-clients/ <p>I have written a lot of HTTP clients that talk to 3rd party APIs over the years and there have been some patterns that have emerged and so I thought I would share them in hopes they help others. The major concepts I want to highlight are:</p> <ul> <li>Composing Client Options</li> <li>JSON Encoding/Decoding to Go Types</li> <li>HTTP Request Signing</li> <li>Convenience Methods</li> <li>Binary Readers</li> </ul> <p>To illustrate these concepts I am going to pick through the Infogram Go client I created specifically for this article since one did not exist.</p> <h2 id="composing-client-options">Composing Client Options</h2> <p>Most HTTP clients share 3 things in common with each other.</p> <ol> <li>They use <a rel="external" href="https://pkg.go.dev/net/http#Client"><code>http.Client</code></a> to make HTTP calls</li> <li>They talk to a base endpoint URL of some kind</li> <li>They require some sort of credentials so the API can authenticate requests</li> </ol> <p>Starting with the <a rel="external" href="https://pkg.go.dev/git.sr.ht/~blainsmith/infogram-go#Client"><code>Client</code></a> struct we can define what is needed:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Client</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> HTTPClient</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Client</span></span> <span class="giallo-l"><span> Endpoint</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> APIKey</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> APISecret</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>This is a pretty common starting point for all HTTP clients and this also allows the user to supply their own <a rel="external" href="https://pkg.go.dev/net/http#Client"><code>http.Client</code></a> and endpoint based on their needs.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>httpClient</span><span style="color: #F29668;"> :=</span><span style="color: #59C2FF;"> http</span><span>.</span><span style="color: #59C2FF;">Client</span><span>{</span></span> <span class="giallo-l"><span> Timeout:</span><span style="color: #D2A6FF;"> 10</span><span style="color: #F29668;"> *</span><span> time.Second,</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>client</span><span style="color: #F29668;"> :=</span><span> infogram.</span><span style="color: #FFB454;">Client</span><span>(</span></span> <span class="giallo-l"><span> HTTPClient:</span><span style="color: #F29668;"> &amp;</span><span>httpClient,</span></span> <span class="giallo-l"><span> Endpoint:</span><span style="color: #AAD94C;"> &quot;https://example.com/infogram&quot;</span><span>,</span></span> <span class="giallo-l"><span> APIKey:</span><span style="color: #AAD94C;"> &quot;api-key&quot;</span><span>,</span></span> <span class="giallo-l"><span> APISecret:</span><span style="color: #AAD94C;"> &quot;api-secret&quot;</span><span>,</span></span> <span class="giallo-l"><span>)</span></span></code></pre> <p>For convenience, I use <a rel="external" href="https://pkg.go.dev/sync#One"><code>sync.Once</code></a> to ensure the first time to <code>Do()</code> is called there is a configured <code>HTTPClient</code> and <code>Endpoint</code> set to avoid nil pointer exceptions and missing endpoints.</p> <p>Another benefit of this approach is making the package easily testable without needing to make HTTP calls over the internet to the Infogram API. I can create a new <a rel="external" href="https://pkg.go.dev/net/http#Client"><code>http.Client</code></a> from a <a rel="external" href="https://pkg.go.dev/net/http/httptest#Server"><code>httptest.Server</code></a>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>mockInfogramAPIServer</span><span style="color: #F29668;"> :=</span><span> httptest.</span><span style="color: #FFB454;">NewServer</span><span>(</span><span style="color: #F29668;">...</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;">defer</span><span> mockInfogramAPIServer.</span><span style="color: #FFB454;">Close</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>client</span><span style="color: #F29668;"> :=</span><span> infogram.</span><span style="color: #FFB454;">Client</span><span>(</span></span> <span class="giallo-l"><span> HTTPClient: mockInfogramAPIServer.</span><span style="color: #FFB454;">Client</span><span>(),</span></span> <span class="giallo-l"><span> Endpoint: mockInfogramAPIServer.URL,</span></span> <span class="giallo-l"><span> APIKey:</span><span style="color: #AAD94C;"> &quot;api-key&quot;</span><span>,</span></span> <span class="giallo-l"><span> APISecret:</span><span style="color: #AAD94C;"> &quot;api-secret&quot;</span><span>,</span></span> <span class="giallo-l"><span>)</span></span></code></pre><h2 id="json-encoding-decoding-to-go-types">JSON Encoding/Decoding to Go Types</h2> <p>JSON encoding/decoding is relatively straightforward if you can utilize struct tags, but some times there are other Go types you want to support that do not support JSON encoding/decoding via struct tags out of the box. However, we can implement the JSONMarshaler and JSONUnmarshaler interfaces to support those types. In the case of Infograms API there are some URLs that get returned in the JSON and instead of just keeping them as <code>string</code> types I would like them to be <a rel="external" href="https://pkg.go.dev/net/url#URL"><code>url.URL</code></a> types.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Theme defines the type returned by the Infogram API</span></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Theme</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Id</span><span style="color: #39BAE6;"> int</span></span> <span class="giallo-l"><span> Title</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> Thumbnail</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">url</span><span>.</span><span style="color: #59C2FF;">URL</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// MarshalJSON implements json.Marshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">t </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Theme</span><span>)</span><span style="color: #FFB454;"> MarshalJSON</span><span>() ([]</span><span style="color: #39BAE6;">byte</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> data</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">map</span><span>[</span><span style="color: #39BAE6;">string</span><span>]</span><span style="color: #FF8F40;">interface</span><span>{})</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> data[</span><span style="color: #AAD94C;">&quot;id&quot;</span><span>]</span><span style="color: #F29668;"> =</span><span> t.Id</span></span> <span class="giallo-l"><span> data[</span><span style="color: #AAD94C;">&quot;title&quot;</span><span>]</span><span style="color: #F29668;"> =</span><span> t.Title</span></span> <span class="giallo-l"><span> data[</span><span style="color: #AAD94C;">&quot;thumbnail_url&quot;</span><span>]</span><span style="color: #F29668;"> =</span><span> t.Thumbnail.</span><span style="color: #FFB454;">String</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> json.</span><span style="color: #FFB454;">Marshal</span><span>(data)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// UnmarshalJSON implements json.Unmarshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">t </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Theme</span><span>)</span><span style="color: #FFB454;"> UnmarshalJSON</span><span>(</span><span style="color: #D2A6FF;">bytes</span><span> []</span><span style="color: #39BAE6;">byte</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span></span> <span class="giallo-l"><span> data</span><span style="color: #F29668;"> :=</span><span style="color: #FFB454;"> make</span><span>(</span><span style="color: #FF8F40;">map</span><span>[</span><span style="color: #39BAE6;">string</span><span>]</span><span style="color: #FF8F40;">interface</span><span>{})</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> val, found</span><span style="color: #F29668;"> :=</span><span> data[</span><span style="color: #AAD94C;">&quot;id&quot;</span><span>]</span><span style="color: #BFBDB6B3;">;</span><span> found {</span></span> <span class="giallo-l"><span> v, ok</span><span style="color: #F29668;"> :=</span><span> val.(</span><span style="color: #39BAE6;">int</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> errors.</span><span style="color: #FFB454;">New</span><span>(</span><span style="color: #AAD94C;">&quot;id needs to be an int&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> t.Id</span><span style="color: #F29668;"> =</span><span> v</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> val, found</span><span style="color: #F29668;"> :=</span><span> data[</span><span style="color: #AAD94C;">&quot;title&quot;</span><span>]</span><span style="color: #BFBDB6B3;">;</span><span> found {</span></span> <span class="giallo-l"><span> v, ok</span><span style="color: #F29668;"> :=</span><span> val.(</span><span style="color: #39BAE6;">string</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> errors.</span><span style="color: #FFB454;">New</span><span>(</span><span style="color: #AAD94C;">&quot;title needs to be an string&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> t.Title</span><span style="color: #F29668;"> =</span><span> v</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> val, found</span><span style="color: #F29668;"> :=</span><span> data[</span><span style="color: #AAD94C;">&quot;thumbnail_url&quot;</span><span>]</span><span style="color: #BFBDB6B3;">;</span><span> found {</span></span> <span class="giallo-l"><span> v, ok</span><span style="color: #F29668;"> :=</span><span> val.(</span><span style="color: #39BAE6;">string</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> errors.</span><span style="color: #FFB454;">New</span><span>(</span><span style="color: #AAD94C;">&quot;thumbnail_url needs to be an string&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> err</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"><span> t.Thumbnail, err</span><span style="color: #F29668;"> =</span><span> url.</span><span style="color: #FFB454;">Parse</span><span>(v)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> errors.</span><span style="color: #FFB454;">New</span><span>(</span><span style="color: #AAD94C;">&quot;thumbnail_url needs to be a parsable URL&quot;</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>The <a rel="external" href="https://pkg.go.dev/git.sr.ht/~blainsmith/infogram-go#Infographic"><code>Infographic</code></a> type is similar, but I omitted it for brevity.</p> <h2 id="http-request-signing">HTTP Request Signing</h2> <p>One of the most common ways to authorize HTTP requests to an API is to specify a token of some kind that the API provides to you. This token is set in one of two places.</p> <ol> <li>Query Parameter</li> <li>Authorization HTTP Header</li> </ol> <h3 id="query-parameter">Query Parameter</h3> <p>Merely attaching a predefined query parameter to every call that the API specifies is a simple way to authorize requests.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>http://exmaple.com/api/v1?api_key=&lt;token&gt;</span></span></code></pre> <p>The Go way:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>req, _</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodGet,</span><span style="color: #AAD94C;"> &quot;http://exmaple.com/api/v1?api_key=&lt;token&gt;&quot;</span><span>,</span><span style="color: #D2A6FF;"> nil</span><span>)</span></span></code></pre><h3 id="authorization-http-header">Authorization HTTP Header</h3> <p>The other, more common, method is setting the Authorization HTTP Header with the same token.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>GET /api/v1 HTTP/1.1</span></span> <span class="giallo-l"><span>Host: example.com</span></span> <span class="giallo-l"><span>Authorization: Bearer &lt;token&gt;</span></span></code></pre> <p>The Go way:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>req, _</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodGet,</span><span style="color: #AAD94C;"> &quot;http://exmaple.com/api/v1&quot;</span><span>,</span><span style="color: #D2A6FF;"> nil</span><span>)</span></span> <span class="giallo-l"><span>req.Header.</span><span style="color: #FFB454;">Add</span><span>(</span><span style="color: #AAD94C;">&quot;Authorization&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;Bearer &lt;token&gt;&quot;</span><span>)</span></span></code></pre> <p>While these methods work just fine, there is another method you might come across called request signing. I specifically like to implement this step in its own method to make building and performing requests more composable if the user wants to manipulate more of the <a rel="external" href="https://pkg.go.dev/net/http#Request"><code>http.Request</code></a> itself.</p> <p>The specific rules for request signing depends on the API, but Infogram lists their <a rel="external" href="https://developers.infogr.am/rest/request-signing.html">implmentation</a> to be recreated.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// SignRequest adds the `api_key` and `api_sig` query parameter in accordance with https://developers.infogr.am/rest/request-signing.html</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> SignRequest</span><span>(</span><span style="color: #D2A6FF;">req</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> data</span><span style="color: #59C2FF;"> url</span><span>.</span><span style="color: #59C2FF;">Values</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> req.Method {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span> http.MethodGet, http.MethodDelete:</span></span> <span class="giallo-l"><span> data</span><span style="color: #F29668;"> =</span><span> req.URL.</span><span style="color: #FFB454;">Query</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> default</span><span>:</span></span> <span class="giallo-l"><span> req.</span><span style="color: #FFB454;">ParseForm</span><span>()</span></span> <span class="giallo-l"><span> data</span><span style="color: #F29668;"> =</span><span> req.Form</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> query.</span><span style="color: #FFB454;">Set</span><span>(</span><span style="color: #AAD94C;">&quot;api_key&quot;</span><span>, c.apiKey)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> sig</span><span style="color: #59C2FF;"> bytes</span><span>.</span><span style="color: #59C2FF;">Buffer</span></span> <span class="giallo-l"><span> sig.</span><span style="color: #FFB454;">WriteString</span><span>(req.Method)</span></span> <span class="giallo-l"><span> sig.</span><span style="color: #FFB454;">WriteByte</span><span>(</span><span style="color: #AAD94C;">&#39;</span><span style="color: #95E6CB;">&amp;</span><span style="color: #AAD94C;">&#39;</span><span>)</span></span> <span class="giallo-l"><span> sig.</span><span style="color: #FFB454;">WriteString</span><span>(req.URL.</span><span style="color: #FFB454;">EscapedPath</span><span>())</span></span> <span class="giallo-l"><span> sig.</span><span style="color: #FFB454;">WriteByte</span><span>(</span><span style="color: #AAD94C;">&#39;</span><span style="color: #95E6CB;">&amp;</span><span style="color: #AAD94C;">&#39;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #F29668;"> ...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> h</span><span style="color: #F29668;"> :=</span><span> hmac.</span><span style="color: #FFB454;">New</span><span>(sha1.New, []</span><span style="color: #39BAE6;">byte</span><span>(c.APISecret))</span></span> <span class="giallo-l"><span> h.</span><span style="color: #FFB454;">Write</span><span>(sig.</span><span style="color: #FFB454;">Bytes</span><span>())</span></span> <span class="giallo-l"><span> signature</span><span style="color: #F29668;"> :=</span><span> h.</span><span style="color: #FFB454;">Sum</span><span>(</span><span style="color: #D2A6FF;">nil</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> data.</span><span style="color: #FFB454;">Add</span><span>(</span><span style="color: #AAD94C;">&quot;api_sig&quot;</span><span>, base64.StdEncoding.</span><span style="color: #FFB454;">EncodeToString</span><span>(signature))</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> switch</span><span> req.Method {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> case</span><span> http.MethodGet, http.MethodDelete:</span></span> <span class="giallo-l"><span> req.URL.RawQuery</span><span style="color: #F29668;"> =</span><span> data.</span><span style="color: #FFB454;">Encode</span><span>()</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> default</span><span>:</span></span> <span class="giallo-l"><span> req.Form</span><span style="color: #F29668;"> =</span><span> data</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>The result of calling this adds a <code>api_sig</code> query parameter with the signature of the request data signed by the API secret provided by Infogram.</p> <h2 id="convenience-methods">Convenience Methods</h2> <p>Now all that is left is the API calls themselves and for that we need to create methods to invoke them. What I like to do is to keep these as small as possible and move as much of the boilerplate logic into their own functions.</p> <p>The main endpoints we need for Infogram follow the same pattern.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches the list of infographics</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Infographics</span><span>() ([]</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // 1. Create Infographics request</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // 2. Sign the request</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // 3. Send the request</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // 4. Decode the response and handle API errors</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // 5. Return the resulting Go type</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches a single infographic by identification number</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Infographic</span><span>(</span><span style="color: #D2A6FF;">id</span><span style="color: #39BAE6;"> string</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches a available themes to use for infographics</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Themes</span><span>() ([]</span><span style="color: #59C2FF;">Theme</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span></code></pre> <p>Instead of putting most of the duplicated logic into each function we can create more generic function to handle them like we did with <code>SignRequest()</code> and just call it with the appropriate parameters.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Do</span><span>(</span><span style="color: #D2A6FF;">req</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>,</span><span style="color: #D2A6FF;"> v</span><span style="color: #FF8F40;"> interface</span><span>{}) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Response</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> req.RequestURI</span><span style="color: #F29668;"> =</span><span style="color: #AAD94C;"> &quot;&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> res, err</span><span style="color: #F29668;"> :=</span><span> c.HTTPClient.</span><span style="color: #FFB454;">Do</span><span>(req)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> defer</span><span> res.Body.</span><span style="color: #FFB454;">Close</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> res.StatusCode</span><span style="color: #F29668;"> &gt;</span><span style="color: #D2A6FF;"> 299</span><span> {</span></span> <span class="giallo-l"><span> _, err</span><span style="color: #F29668;"> :=</span><span> io.</span><span style="color: #FFB454;">ReadAll</span><span>(res.Body)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> res, fmt.</span><span style="color: #FFB454;">Errorf</span><span>(</span><span style="color: #AAD94C;">&quot;http error code: </span><span style="color: #95E6CB;">%d</span><span style="color: #AAD94C;">&quot;</span><span>, res.StatusCode)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> v</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> w, ok</span><span style="color: #F29668;"> :=</span><span> v.(</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Writer</span><span>)</span><span style="color: #BFBDB6B3;">;</span><span> ok {</span></span> <span class="giallo-l"><span> io.</span><span style="color: #FFB454;">Copy</span><span>(w, res.Body)</span></span> <span class="giallo-l"><span> }</span><span style="color: #FF8F40;"> else</span><span> {</span></span> <span class="giallo-l"><span> decErr</span><span style="color: #F29668;"> :=</span><span> json.</span><span style="color: #FFB454;">NewDecoder</span><span>(res.Body).</span><span style="color: #FFB454;">Decode</span><span>(v)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> decErr</span><span style="color: #F29668;"> ==</span><span> io.EOF {</span></span> <span class="giallo-l"><span> decErr</span><span style="color: #F29668;"> =</span><span style="color: #D2A6FF;"> nil</span><span style="color: #5A6673;font-style: italic;"> // ignore EOF errors caused by empty response body</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> decErr</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, decErr</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> res,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p><code>Do()</code> now handle most of the heavy lifting of preparing requests, sending them, and decoding the API responses. With these functions our convenience methods for each endpoint becomes small and easily repeatable for adding additional endpoints. This also allows to ensure good test coverage over each of the three methods used inside since the convenience methods just call them in a specific order. Notice it also returns the raw <code>http.Response</code> to the caller as well in case that is needed for any reason. I did not want to limit the caller's ability to inspect that response.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches the list of infographics</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Infographics</span><span>() ([]</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> req, err</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodGet, fmt.</span><span style="color: #FFB454;">Sprintf</span><span>(</span><span style="color: #AAD94C;">&quot;</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">/</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">/</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">&quot;</span><span>, c.Endpoint,</span><span style="color: #AAD94C;"> &quot;infographics&quot;</span><span>, id),</span><span style="color: #D2A6FF;"> nil</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, fmt.</span><span style="color: #FFB454;">Errorf</span><span>(</span><span style="color: #AAD94C;">&quot;new infographic request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> =</span><span> c.</span><span style="color: #FFB454;">SignRequest</span><span>(req)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> infographic</span><span style="color: #59C2FF;"> Infographic</span></span> <span class="giallo-l"><span> _, err</span><span style="color: #F29668;"> =</span><span> c.</span><span style="color: #FFB454;">Do</span><span>(req,</span><span style="color: #F29668;"> &amp;</span><span>infographic)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, fmt.</span><span style="color: #FFB454;">Errorf</span><span>(</span><span style="color: #AAD94C;">&quot;performing infographics request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #F29668;"> &amp;</span><span>infographic,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre><h2 id="binary-readers">Binary Readers</h2> <p>Infogram offers more than just JSON responses to their API. When interacting with the <code>/infographics</code> endpoint specifically you can request to have a PDF, PNG, or HTML version of the infographic returned to you. We can create an <code>io.Reader</code> for each one of these formats for the <code>Infographic</code> type to make this downloading possible.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Infographic</span><span style="color: #FF8F40;"> struct</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> reader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>,</span><span style="color: #D2A6FF;"> format</span><span style="color: #39BAE6;"> string</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span> req, err</span><span style="color: #F29668;"> :=</span><span> http.</span><span style="color: #FFB454;">NewRequest</span><span>(http.MethodGet, fmt.</span><span style="color: #FFB454;">Sprintf</span><span>(</span><span style="color: #AAD94C;">&quot;</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">/</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">/</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">?format=</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">&quot;</span><span>, client.Endpoint,</span><span style="color: #AAD94C;"> &quot;infographics&quot;</span><span>, i.Id, format),</span><span style="color: #D2A6FF;"> nil</span><span>)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, fmt.</span><span style="color: #FFB454;">Errorf</span><span>(</span><span style="color: #AAD94C;">&quot;new infographic PDF reader request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">&quot;</span><span>, err)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> err</span><span style="color: #F29668;"> =</span><span> client.</span><span style="color: #FFB454;">SignRequest</span><span>(req)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> res, err</span><span style="color: #F29668;"> :=</span><span> client.HTTPClient.</span><span style="color: #FFB454;">Do</span><span>(req)</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span><span>, err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> res.Body,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// PDFReader returns an io.Reader of the Infographic in PDF format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> PDFReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> i.</span><span style="color: #FFB454;">reader</span><span>(client,</span><span style="color: #AAD94C;"> &quot;pdf&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// PNGReader returns an io.Reader of the Infographic in PNG format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> PNGReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> i.</span><span style="color: #FFB454;">reader</span><span>(client,</span><span style="color: #AAD94C;"> &quot;png&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// HTMLReader returns an io.Reader of the Infographic in HTML format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> HTMLReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> i.</span><span style="color: #FFB454;">reader</span><span>(client,</span><span style="color: #AAD94C;"> &quot;html&quot;</span><span>)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Since these functions are flexible and composable I can do this and still use the <code>SignRequest()</code> after than and then perform a standard <code>http.Client.Do()</code> call and just return the <code>http.Response.Body</code> back to the call to complete the reading of the response to a file, buffer, or anything else that accepts a <code>io.Reader</code>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// get infographic 100 from the API</span></span> <span class="giallo-l"><span>ig100 _</span><span style="color: #F29668;"> :=</span><span> client.</span><span style="color: #FFB454;">Infographic</span><span>(</span><span style="color: #D2A6FF;">100</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// get the PDF from the API as an io.Reader</span></span> <span class="giallo-l"><span>ig100reader, _</span><span style="color: #F29668;"> :=</span><span> ig100.</span><span style="color: #FFB454;">PDFReader</span><span>(client)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// create a local file for the PDF infographic</span></span> <span class="giallo-l"><span>pdf, _</span><span style="color: #F29668;"> :=</span><span> os.</span><span style="color: #FFB454;">Create</span><span>(</span><span style="color: #AAD94C;">&quot;infographic-100.pdf&quot;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// copy the results from the API to the local file</span></span> <span class="giallo-l"><span>io.</span><span style="color: #FFB454;">Copy</span><span>(pdf, ig100reader)</span></span></code></pre><h2 id="conclusion">Conclusion</h2> <p>Now I have a complete and composable Infogram API client in Go with a clear surface area.</p> <p><strong>client.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Client is used to interact with the Infogram API</span></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Client</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> HTTPClient</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Client</span></span> <span class="giallo-l"><span> Endpoint</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> APIKey</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span> APISecret</span><span style="color: #39BAE6;"> string</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Do performs the *http.Request and decodes the http.Response.Body into v and return the *http.Response. If v is an io.Writer it will copy the body to the writer.</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Do</span><span>(</span><span style="color: #D2A6FF;">req</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>,</span><span style="color: #D2A6FF;"> v</span><span style="color: #FF8F40;"> interface</span><span>{}) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Response</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// SignRequest adds the `api_key` and `api_sig` query parameter in accordance with https://developers.infogr.am/rest/request-signing.html</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> SignRequest</span><span>(</span><span style="color: #D2A6FF;">req</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">http</span><span>.</span><span style="color: #59C2FF;">Request</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches the list of infographics</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Infographics</span><span>() ([]</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches a single infographic by identification number</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Infographic</span><span>(</span><span style="color: #D2A6FF;">id</span><span style="color: #39BAE6;"> int</span><span>) (</span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// UserInfographics fetches the list of infographics for the user&#39;s identification number</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> UserInfographics</span><span>(</span><span style="color: #D2A6FF;">id</span><span style="color: #39BAE6;"> string</span><span>) ([]</span><span style="color: #59C2FF;">Infographic</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographics fetches a available themes to use for infographics</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">c </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Client</span><span>)</span><span style="color: #FFB454;"> Themes</span><span>() ([]</span><span style="color: #59C2FF;">Theme</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span></code></pre> <p><strong>types.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Infographic defines the type returned by the Infogram API</span></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Infographic</span><span style="color: #FF8F40;"> struct</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// PDFReader returns an io.Reader of the Infographic in PDF format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> PDFReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// PNGReader returns an io.Reader of the Infographic in PNG format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> PNGReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// HTMLReader returns an io.Reader of the Infographic in HTML format</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> HTMLReader</span><span>(</span><span style="color: #D2A6FF;">client</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">Client</span><span>) (</span><span style="color: #59C2FF;">io</span><span>.</span><span style="color: #59C2FF;">Reader</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// MarshalJSON implements json.Marshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> MarshalJSON</span><span>() ([]</span><span style="color: #39BAE6;">byte</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// UnmarshalJSON implements json.Unarshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">i </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Infographic</span><span>)</span><span style="color: #FFB454;"> UnmarshalJSON</span><span>(</span><span style="color: #D2A6FF;">bytes</span><span> []</span><span style="color: #39BAE6;">byte</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Theme defines the type returned by the Infogram API</span></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Theme</span><span style="color: #FF8F40;"> struct</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// MarshalJSON implements json.Marshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">t </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Theme</span><span>)</span><span style="color: #FFB454;"> MarshalJSON</span><span>() ([]</span><span style="color: #39BAE6;">byte</span><span>,</span><span style="color: #39BAE6;"> error</span><span>) {</span><span style="color: #F29668;">...</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// UnmarshalJSON implements json.Unmarshaler</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">t </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">Theme</span><span>)</span><span style="color: #FFB454;"> UnmarshalJSON</span><span>(</span><span style="color: #D2A6FF;">bytes</span><span> []</span><span style="color: #39BAE6;">byte</span><span>)</span><span style="color: #39BAE6;"> error</span><span> {</span><span style="color: #F29668;">...</span><span>}</span></span></code></pre> <p>I've had really good luck with designing HTTP clients in Go this way and I hope this helps others as well. You can find the full implementation of the <a rel="external" href="https://git.sr.ht/~blainsmith/infogram-go">Infogram API Go client</a> on GitHub. I plan to keep iterating on the client more and more so if there is something you see missing or there is a problem feel free to file an issue or submit a change.</p> Why Do You Lift? 2021-08-25T00:00:00+00:00 2021-08-25T00:00:00+00:00 Unknown https://blainsmith.com/articles/why-do-you-lift/ <p>I get this question a decent amount from people who don't lift and my answers usually consist of:</p> <blockquote> <p>"I spend all day staring at a computer screen and it offers a nice balance to work life behind a desk."</p> </blockquote> <blockquote> <p>"It facinates me what the human body is capable of doing if it is trained and taken care of so I want to see how far I can push it with measureable results."</p> </blockquote> <blockquote> <p>"Because being strong as fuck makes normal life a lot easier like picking up my son, carrying groceries, or any other physical demand life throws at me."</p> </blockquote> <blockquote> <p>"Heart disease runs in both sides of my family so I am stressing my body to adapt to potentially avoid that future."</p> </blockquote> <p>While all of these answers are true I've come to realize another and deeper reason why...If something fails in my training it is entirely my fault.</p> <p>Considering what I do for a living and the dependancies I rely on to do their job whether it is tools or people there is a high probability they will fail and it is out of my control.</p> <ul> <li>Laptop blue screens</li> <li>Internet outages</li> <li>Semver contract violations</li> <li>Fragile build systems</li> <li>False promises by people</li> <li>Customer's unplanned context switching</li> <li>Forced inefficient tooling and processes</li> <li>It's not DNS. There's no way it's DNS. It was DNS.</li> </ul> <p>Software engineering is a dicipline of solving hard problems while also dealing with it's own self imposed problems that make things even harder. All of these are out of my control. These systems of dependancies remind me of a <a rel="external" href="https://www.merriam-webster.com/dictionary/house%20of%20cards">house of cards</a> and yet the momentary bliss of solving that hard problem overshadows the days and weeks and months of consistant failures and setbacks. Unfortunately, I cannot fix most of these systems to make my progress more efficient and more enjoyable so I just deal with it...with some venting here and there.</p> <p>Now looking at all of the systems of dependancies involved when I lift their probabolity of failure is effectively zero which makes the only system that would cause a failure is me. I am the only system in control of the entirety of my progress, efficiency, and results. The dependancies I rely on for results out of my control are</p> <ul> <li>a barbell made of steel and ball bearings,</li> <li>a steel cage of welded steel posts and cross members,</li> <li>cast iron weights,</li> <li>triple-ply canvas fill with sand,</li> <li>vulcanized rubber weights,</li> <li>and the list goes on.</li> </ul> <p>The failure rate of any of these dependancies is extremely low not only because of their manufacturing process, but also because of their analog and mechanical nature. Since the probabily of failure of these dependancies is zero I can eliminate the cognitive load worrying about these failing and focus solely on myself. I can concern myself with reducing failure in myself by using proper form when lifting, eating appropriately, sleeping consistantly, and recovering accordingly. If any of these are off then my training suffers and the only thing to blame is myself. I and I alone can control and modify my behavior to fix things to get back on track to progress and see results.</p> <p>Spending my professional life wrought with constantly experiencing failures and overcoming them by problems solving it begins to make more sense as to why I lift as much as I do and make it such a priority in my life.</p> <blockquote> <p>"The Iron is the best antidepressant I have ever found. There is no better way to fight weakness than with strength. Once the mind and body have been awakened to their true potential, it’s impossible to turn back.</p> <p>The Iron never lies to you. You can walk outside and listen to all kinds of talk, get told that you’re a god or a total bastard. The Iron will always kick you the real deal. The Iron is the great reference point, the all-knowing perspective giver. Always there like a beacon in the pitch black.</p> <p>I have found the Iron to be my greatest friend. It never freaks out on me, never runs. Friends may come and go. But 200 pounds is always 200 pounds."</p> <p>– Henry Rollins <a rel="external" href="https://www.artofmanliness.com/articles/henry-rollins-iron-and-soul/">"Iron and the Soul"</a>.</p> </blockquote> Programming is Easy, Software Engineering is Hard 2021-06-15T00:00:00+00:00 2021-06-15T00:00:00+00:00 Unknown https://blainsmith.com/articles/programming-is-easy-software-engineering-is-hard/ <p>Over the years I've been programming and people ask me what I do for a living, I try to explain to them as best I can. Inevitably, they respond with something along the lines of "that sounds very hard and complicated". To someone without programming skills, this sure sounds hard. To me, performing open heart surgery sounds hard, but to a seasoned surgeon, I am sure it is like riding a bike. Sure, there may be challenges in the middle of these tasks that need to be addressed, but the training acquired should be enough to work through those challenges.</p> <p>Anyone with the proper skills can:</p> <ul> <li>implement Quicksort in any language;</li> <li>read and understand the SHA-512 algorithm;</li> <li>write a REST or GraphQL API that connects to a SQL or NoSQL database with a proper caching strategy</li> <li>utilize a matchmaking algorithm, like TrueSkill or Openskill, to properly match people together based on skill level;</li> <li>track statistics that are computed in near real time to update a leaderboard;</li> <li>write an authorization service which validates some combination of credentials using security industry best practices such as password hashing, randomization, and data encryption.</li> </ul> <p>These are varying degrees of difficulty based on skill level, but at the end of the day, programming by itself is largely an easy thing for most of us to do. We are good at it and we educated ourselves by learning new techniques, languages, and concepts to solve these complex problems. If left to our own devices, we all could build a very stable and successful solution. However, programming does not become stressful, frustrating, and downright hard until we factor in two more variables...time and other people.</p> <blockquote> <p>"Software engineering is what happens to programming when you add time and other programmers." -<em>Russ Cox on <a rel="external" href="https://research.swtch.com/vgo-eng">"What is Software Engineering?"</a></em></p> </blockquote> <p>Russ hit the nail on the head when he was using this to explain the reason why Go needed module support, but this also holds true in the larger context of what we all do day in and day out.</p> <p>I would like to amend that quote to be:</p> <blockquote> <p>Software engineering is what happens to programming when you add time and other PEOPLE.</p> </blockquote> <p>The people I am referring to here are managers, stakeholders, investors, SCRUM masters, sales people, marketing people, the list goes on. So now, we should circle back and address the solutions above. We can take into account the plethora of other people involved who impose time constraints on problems of which they do not have complete understanding. Not only are we no longer just programming, we are engineering software systems that need to fit neatly within an arbitrary box, that was not chosen by us even if we may be able to influence that box size. 9 times out 10, our influence over those constraints will not help if we are up against another agenda driven by time, other people, and money.</p> <p>Can you create a websocket service to accept 1 million concurrent connections to broker messages to multiple internal services? I am sure you can, but now consider doing it within 3 weeks and having to sit in on 5 hours of meetings per week over those same 3 weeks.</p> <p>Can you get a 50 node Kubernetes cluster up and running on bare metal? Sure, but how about you also do sprint planning and retro meetings every 2 weeks while also onboarding a new hire that joined your team?</p> <p>What we do now is no longer focused on technical problems, which we know fully well how to solve, but now there are other non-deterministic and uncontrollable factors mixed into that task. Now things are stressful, frustrating, and downright hard. The daily juggling of context switching, meetings, cost/benefit analysis, personality differences, time constraints, priority changes, and anything not involved in programming is what makes what we do hard. The sooner everyone involved (not just programmers) understand this, the better off we all will be.</p> Hugo to Zola 2021-03-06T00:00:00+00:00 2021-03-06T00:00:00+00:00 Unknown https://blainsmith.com/articles/hugo-to-zola/ <p>I managed to switch my site over to using <a rel="external" href="https://www.getzola.org">Zola</a>. It was very tempting since the template engine looked a lot simpler than <a rel="external" href="https://gohugo.io">Hugo</a>. It took me about 3 hours to figure out how to map each of the Hugo features to the Zola equivalent.</p> <p>I've included the diff from the both versions for comparison. You can see that zola requires a lot less boilerplate and template files to achieve the same result which I was very happy with.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>&gt; git diff hugo..zola --compact-summary</span></span> <span class="giallo-l"><span> .build.yml | 5 ++--</span></span> <span class="giallo-l"><span> LICENSE.md =&gt; LICENSE | 0</span></span> <span class="giallo-l"><span> Makefile (gone) | 5 ----</span></span> <span class="giallo-l"><span> README.md (mode -x) | 0</span></span> <span class="giallo-l"><span> config.toml | 53 +++++++++++++----------------------------</span></span> <span class="giallo-l"><span> content/articles/3-years-at-madglory.md | 13 ++++------</span></span> <span class="giallo-l"><span> content/articles/_index.md (new) | 7 ++++++</span></span> <span class="giallo-l"><span> content/articles/build-it-live-nasa-video-app.md | 9 ++++---</span></span> <span class="giallo-l"><span> content/articles/case-insensitive-sorting-in-mongo-db.md (gone) | 46 ------------------------------------</span></span> <span class="giallo-l"><span> content/articles/convention-over-configuration-web-servers.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/easy-profiling-go-benchmark-tests-with-bash.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/encoding-images-with-go-for-the-ssd1306-oled.md (gone) | 34 --------------------------</span></span> <span class="giallo-l"><span> content/articles/farewell-to-harvard-and-onward-to-madglory.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/fuck-your-hustle.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/go-go-import-container-list.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/go-made-me-return-college.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/gophercon-2017-generating-hundreds-of-video-catalog-feeds-in-seconds.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/i-love-my-fear.md (gone) | 21 -----------------</span></span> <span class="giallo-l"><span> content/articles/linux-system-metrics-with-collectd.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/plain-text-protocols.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/quick-and-dirty-local-domain-names-for-mamp.md (gone) | 73 --------------------------------------------------------</span></span> <span class="giallo-l"><span> content/articles/reverting-back-to-a-simpler-time.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/sharatoga-techtalks-git-a-free-highly-scaled-web-host.md (gone) | 14 -----------</span></span> <span class="giallo-l"><span> content/articles/side-projects.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/signing-jwts-with-gos-crypto-ed25519.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/single-command-static-file-web-server.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/tedxalbany-the-master-of-my-fate.md | 9 ++++---</span></span> <span class="giallo-l"><span> content/articles/the-20-year-5-school-bachelors-degree.md | 8 +++----</span></span> <span class="giallo-l"><span> content/articles/vpscheapnet-review-for-the-server-savvy.md (gone) | 71 -------------------------------------------------------</span></span> <span class="giallo-l"><span> content/articles/writing-a-web-server-in-go-without-dependancies.md | 10 ++++----</span></span> <span class="giallo-l"><span> content/book-recs/_index.md (new) | 26 ++++++++++++++++++++</span></span> <span class="giallo-l"><span> content/book-recs/a-philosophy-of-software-design.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/a-programmers-introduction-to-mathematics.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/neurotribes.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/the-atheists-guide-to-reality.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/the-coddling-of-the-american-mind.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/the-elements-of-programming-style.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/book-recs/zen-and-the-art-of-motorcycle-maintenance.md (gone) | 5 ----</span></span> <span class="giallo-l"><span> content/favicon.ico (gone) | Bin 1150 -&gt; 0 bytes</span></span> <span class="giallo-l"><span> content/go/_index.md (new) | 5 ++++</span></span> <span class="giallo-l"><span> content/go/baconian-cipher.md | 13 ++++++----</span></span> <span class="giallo-l"><span> content/go/goreds.md | 13 ++++++----</span></span> <span class="giallo-l"><span> content/go/seahash.md | 13 ++++++----</span></span> <span class="giallo-l"><span> content/go/shardcache.md | 13 ++++++----</span></span> <span class="giallo-l"><span> layouts/book-recs/list.html (gone) | 22 -----------------</span></span> <span class="giallo-l"><span> layouts/for-sale/list.html (gone) | 15 ------------</span></span> <span class="giallo-l"><span> layouts/for-sale/single.html (gone) | 6 -----</span></span> <span class="giallo-l"><span> layouts/go/list.html (gone) | 17 -------------</span></span> <span class="giallo-l"><span> layouts/go/single.html (gone) | 12 ----------</span></span> <span class="giallo-l"><span> {themes/text-only/static =&gt; static}/favicon.png | Bin</span></span> <span class="giallo-l"><span> templates/article.html (new) | 14 +++++++++++</span></span> <span class="giallo-l"><span> templates/articles.html (new) | 9 +++++++</span></span> <span class="giallo-l"><span> templates/base.html (new) | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++</span></span> <span class="giallo-l"><span> templates/go-package.html (new) | 12 ++++++++++</span></span> <span class="giallo-l"><span> templates/go-packages.html (new) | 13 ++++++++++</span></span> <span class="giallo-l"><span> templates/index.html (new) | 10 ++++++++</span></span> <span class="giallo-l"><span> templates/section.html (new) | 11 +++++++++</span></span> <span class="giallo-l"><span> themes/text-only/archetypes/default.md (gone) | 2 --</span></span> <span class="giallo-l"><span> themes/text-only/layouts/404.html (gone) | 5 ----</span></span> <span class="giallo-l"><span> themes/text-only/layouts/_default/single.html (gone) | 17 -------------</span></span> <span class="giallo-l"><span> themes/text-only/layouts/index.html (gone) | 14 -----------</span></span> <span class="giallo-l"><span> themes/text-only/layouts/index.rss.xml (gone) | 26 --------------------</span></span> <span class="giallo-l"><span> themes/text-only/layouts/partials/footer.html (gone) | 13 ----------</span></span> <span class="giallo-l"><span> themes/text-only/layouts/partials/header.html (gone) | 33 --------------------------</span></span> <span class="giallo-l"><span> themes/text-only/layouts/partials/styles.html (gone) | 100 -----------------------------------------------------------------------------</span></span> <span class="giallo-l"><span> themes/text-only/static/favicon.ico (gone) | Bin 1150 -&gt; 0 bytes</span></span> <span class="giallo-l"><span> themes/text-only/theme.toml (gone) | 6 -----</span></span> <span class="giallo-l"><span> 67 files changed, 374 insertions(+), 727 deletions(-)</span></span></code></pre> Easy Profiling Go Benchmark Tests with Bash 2021-02-18T00:00:00+00:00 2021-02-18T00:00:00+00:00 Unknown https://blainsmith.com/articles/easy-profiling-go-benchmark-tests-with-bash/ <p>Update: This idea got popular enough that I decided to turn it into a tool. You can grab it from <a rel="external" href="http://pprof.sh">http://pprof.sh</a>.</p> <p>There are a lot of great articles on the interwebs about profiling entire Go applications for CPU and memory usage, however I can never remember the syntax for doing the same thing for specific Benchmark-prefixed tests. Editors have great support for running these tests, but sometimes I want to be able to produce binary and profile output files to open in the visualizer for further inspection. So like any lazy programmer I made a few scripts to do all the work for me.</p> <p><strong>bench.sh</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">#!/bin/sh</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> [</span><span style="color: #39BAE6;font-style: italic;"> $#</span><span style="color: #F29668;"> -eq</span><span style="color: #D2A6FF;"> 0</span><span> ]</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> then</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Runs a specified benchmark test func and opens the profile in Chrome.\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Usage:\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;\t./bench.sh &lt;cpu|mem&gt; &lt;dir&gt; &lt;function&gt;\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Example:\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;\t./bench.sh cpu ./internal BenchmarkBinaryEncoding\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> exit</span><span style="color: #D2A6FF;"> 0</span></span> <span class="giallo-l"><span style="color: #FF8F40;">fi</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #59C2FF;">go</span><span style="color: #AAD94C;"> test</span><span style="color: #95E6CB;"> -bench=</span><span style="color: #D2A6FF;">${3}</span><span style="color: #95E6CB;"> -benchmem -</span><span style="color: #D2A6FF;">${1}</span><span style="color: #95E6CB;">profile</span><span style="color: #AAD94C;"> profile.out</span><span style="color: #95E6CB;"> -o</span><span style="color: #AAD94C;"> bench.test</span><span style="color: #D2A6FF;"> ${2}</span></span> <span class="giallo-l"><span style="color: #59C2FF;">go</span><span style="color: #AAD94C;"> tool pprof</span><span style="color: #95E6CB;"> -http=:6060</span><span style="color: #AAD94C;"> bench.test profile.out</span></span></code></pre> <p><strong>trace.sh</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">#!/bin/sh</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">if</span><span> [</span><span style="color: #39BAE6;font-style: italic;"> $#</span><span style="color: #F29668;"> -eq</span><span style="color: #D2A6FF;"> 0</span><span> ]</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> then</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Runs a specified benchmark test func and opens the trace profile in Chrome.\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Usage:\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;\t./trace.sh &lt;dir&gt; &lt;function&gt;\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;Example:\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> &quot;\t./trace.sh ./internal BenchmarkBinaryEncoding\n\n&quot;</span></span> <span class="giallo-l"><span style="color: #F07178;"> exit</span><span style="color: #D2A6FF;"> 0</span></span> <span class="giallo-l"><span style="color: #FF8F40;">fi</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #59C2FF;">go</span><span style="color: #AAD94C;"> test</span><span style="color: #95E6CB;"> -bench=</span><span style="color: #D2A6FF;">${2}</span><span style="color: #95E6CB;"> -trace</span><span style="color: #AAD94C;"> trace.out</span><span style="color: #95E6CB;"> -o</span><span style="color: #AAD94C;"> trace.test</span><span style="color: #D2A6FF;"> ${1}</span></span> <span class="giallo-l"><span style="color: #59C2FF;">go</span><span style="color: #AAD94C;"> tool trace</span><span style="color: #95E6CB;"> -http=:6060</span><span style="color: #AAD94C;"> trace.test trace.out</span></span></code></pre> <p>Lets consider a sample Go project layout that looks something like the following:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-go-project/</span></span> <span class="giallo-l"><span>├─ cmd/</span></span> <span class="giallo-l"><span>│ ├─ server/</span></span> <span class="giallo-l"><span>│ │ ├─ main.go</span></span> <span class="giallo-l"><span>├─ internal/</span></span> <span class="giallo-l"><span>│ ├─ encoding.go</span></span> <span class="giallo-l"><span>│ ├─ encoding_bench_test.go</span></span> <span class="giallo-l"><span>│ ├─ encoding_test.go</span></span> <span class="giallo-l"><span>├─ scripts/</span></span> <span class="giallo-l"><span>│ ├─ bench.sh</span></span> <span class="giallo-l"><span>│ ├─ tests.sh</span></span> <span class="giallo-l"><span>│ ├─ trace.sh</span></span> <span class="giallo-l"><span>├─ go.mod</span></span> <span class="giallo-l"><span>├─ go.sum</span></span> <span class="giallo-l"><span>├─ README.md</span></span></code></pre> <p>Inside the internal directory there is encoding_bench_test.go which has a benchmark function called BenchmarkBinaryEncoding that we want to profile.</p> <p><strong>./internal/encoding_bench_test.go</strong></p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> internal_test</span></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> BenchmarkBinaryEncoding</span><span>(</span><span style="color: #D2A6FF;">b</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">testing</span><span>.</span><span style="color: #59C2FF;">B</span><span>) {</span></span> <span class="giallo-l"><span style="color: #F29668;"> ...</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"><span style="color: #F29668;">...</span></span></code></pre> <p>You can see we placed <strong>bench.sh</strong> and <strong>trace.sh</strong> inside the scripts folder so we can run it from the root of the project and see the help menu.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-go-project on master via Go v1.16</span></span> <span class="giallo-l"><span>&gt; ./scripts/bench.sh</span></span> <span class="giallo-l"><span>Runs a specified benchmark test in app&#39;s package and opens the profile in Chrome.</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>Usage:</span></span> <span class="giallo-l"><span> ./bench.sh &lt;cpu|mem&gt; &lt;dir&gt; &lt;function&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>Example:</span></span> <span class="giallo-l"><span> ./bench.sh cpu ./internal BenchmarkBinaryEncoding</span></span></code></pre> <p>Running it with the correct arguments will perform the following:</p> <ol> <li>Run the benchmark test BenchmarkBinaryEncoding within the internal package and capture the cpu|memory profile</li> <li>Write a test binary and profile file to disk</li> <li>Launch the profiler visualizer on port :6060 with test binary and profile</li> </ol> <p>Now let's run the BenchmarkBinaryEncoding function in the internal package.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-go-project on master via Go v1.16</span></span> <span class="giallo-l"><span>&gt; ./scripts/bench.sh cpu ./internal BenchmarkBinaryEncoding</span></span> <span class="giallo-l"><span>goos: linux</span></span> <span class="giallo-l"><span>goarch: amd64</span></span> <span class="giallo-l"><span>pkg: my-go-project/internal</span></span> <span class="giallo-l"><span>cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz</span></span> <span class="giallo-l"><span>BenchmarkBinaryEncoding-8 567564 1916 ns/op 979 B/op 3 allocs/op</span></span> <span class="giallo-l"><span>PASS</span></span> <span class="giallo-l"><span>ok my-go-project/internal 2.047s</span></span> <span class="giallo-l"><span>Serving web UI on http://localhost:6060</span></span></code></pre> <p>At this point the browser should open automatically so you can poke around the CPU profile. Once you are done you can close the browser and quit the bash process and go about your business.</p> <p>The <strong>trace.sh</strong> script operates the same way as <strong>bench.sh</strong>, but it opens the trace visualizer in the browser.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>my-go-project on master via Go v1.16</span></span> <span class="giallo-l"><span>&gt; ./scripts/trace.sh ./internal BenchmarkBinaryEncoding</span></span> <span class="giallo-l"><span>goos: linux</span></span> <span class="giallo-l"><span>goarch: amd64</span></span> <span class="giallo-l"><span>pkg: my-go-project/internal</span></span> <span class="giallo-l"><span>cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz</span></span> <span class="giallo-l"><span>BenchmarkBinaryEncoding-8 567564 1916 ns/op 979 B/op 3 allocs/op</span></span> <span class="giallo-l"><span>PASS</span></span> <span class="giallo-l"><span>ok my-go-project/internal 2.047s</span></span> <span class="giallo-l"><span>2021/02/18 13:17:50 Parsing trace...</span></span> <span class="giallo-l"><span>2021/02/18 13:17:51 Splitting trace...</span></span> <span class="giallo-l"><span>2021/02/18 13:17:51 Opening browser. Trace viewer is listening on http://[::]:6060</span></span></code></pre> <p>I found these scripts to be super helpful to reduce the feedback loop of changing code and running a benchmark to see if things changed. I hope this helps you as much as it has helped me in my day-to-day work and if there are things that can be improved please reach out and let me know.</p> <p>Happy pprof-ing!</p> Plain Text Protocols 2020-09-01T00:00:00+00:00 2020-09-01T00:00:00+00:00 Unknown https://blainsmith.com/articles/plain-text-protocols/ <p>If you haven't noticed already I really love plain text so I thought I would find some plain text protocols that are used in different software systems. The Redis Protocol specification states it best by describing its own protocol, but this applies to all of the plain text protocols I will cover.</p> <ul> <li>Simple to implement.</li> <li>Fast to parse.</li> <li>Human readable.</li> </ul> <p>The next time you are building a software system and have to exchange data start with using a simple plain text protocol and then add in the complexity for optimization later. All of these protocols can be sent as plain text data over a TCP or UDP. They are easily inspected, small, and direct.</p> <h1 id="graphite-plaintext-protocol">Graphite plaintext protocol</h1> <p>Starting off with the simplest one in the list is the Graphite plaintext protocol for delivering metrics into Graphite via Carbon.</p> <p>The protocol:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>&lt;metics_path&gt; &lt;metics_value&gt; &lt;metrics_timestamp&gt;</span></span></code></pre> <p>The example:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>/cpu_load/core_0 0.08 1599009388</span></span> <span class="giallo-l"><span>/cpu_load/core_1 0.07 1599009388</span></span> <span class="giallo-l"><span>/cpu_load/core_2 0.07 1599009388</span></span> <span class="giallo-l"><span>/cpu_load/core_3 0.05 1599009388</span></span></code></pre> <p><a rel="external" href="https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol">Full Specification</a></p> <h1 id="influx-line-protocol">Influx Line Protocol</h1> <p>The next simplest one in the list is the Influx Line Protocol. While this is similar to Graphite it does add some additional metadata to its specification.</p> <p>The protocol:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>&lt;measurement&gt;[,&lt;tag_set&gt;...] &lt;field_set&gt;... &lt;timestamp&gt;</span></span></code></pre> <p>The example:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>cpu,core=0 load=0.08 1599009388</span></span> <span class="giallo-l"><span>cpu,core=1 load=0.07 1599009388</span></span> <span class="giallo-l"><span>cpu,core=2 load=0.07 1599009388</span></span> <span class="giallo-l"><span>cpu,core=3 load=0.05 1599009388</span></span></code></pre> <p><a rel="external" href="https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/">Full Specification</a></p> <h1 id="redis-serialization-protocol-resp">Redis Serialization Protocol (RESP)</h1> <p>Here is where things get more complex, but the same 3 characteristics from above apply still. Simplifying the protocol for the sake of brevity in this article we see that the protocol is a single line of text and the initial command dictates what argument data types are allowed after it.</p> <p>The protocol:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>COMMAND arguments...</span></span></code></pre> <p>The example:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>GET key1</span></span> <span class="giallo-l"><span>SET key2 100</span></span> <span class="giallo-l"><span>HSET hash key1 value1</span></span> <span class="giallo-l"><span>HGETALL hash</span></span></code></pre> <p>There is more to this particular protocol to denote strings, numbers, arrays, etc in a text format which is outlined in the official specification.</p> <p><a rel="external" href="https://redis.io/topics/protocol">Full Specification</a></p> <h1 id="hypertext-transfer-protocol-http">Hypertext Transfer Protocol (HTTP)</h1> <p>Yes, HTTP is just plain text. Since the protocol itself has lots of permutations of metadata lets just look at some examples of an HTTP request and response.</p> <p>The request example:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>GET /articles/plain-text-protocols/ HTTP/1.1</span></span> <span class="giallo-l"><span>Host: blainsmith.com</span></span> <span class="giallo-l"><span>Accept-Language: en-us</span></span> <span class="giallo-l"><span>Accept-Encoding: gzip, deflate</span></span></code></pre> <p>The response example:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>HTTP/1.1 200 OK</span></span> <span class="giallo-l"><span>Accept-Ranges: bytes</span></span> <span class="giallo-l"><span>Content-Length: 6262</span></span> <span class="giallo-l"><span>Content-Type: text/html; charset=utf-8</span></span> <span class="giallo-l"><span>Date: Wed, 02 Sep 2020 01:41:35 GMT</span></span> <span class="giallo-l"><span>Last-Modified: Wed, 02 Sep 2020 01:37:33 GMT</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>&lt;THE HTML OF THIS ARTICLE&gt;</span></span></code></pre> <p>Your browser constructs the request when you visit http://blainsmith.com/articles/plain-text-protocols/ and parses the response when it comes back from the server. There are a lot more allowable headers, but the basics of this protocol are pretty straightforward.</p> <p><a rel="external" href="https://tools.ietf.org/html/rfc2616">Full Specification</a></p> <h1 id="keep-it-simple">Keep it simple</h1> <p>The world runs on these types of plain text protocol so don't reinvent the wheel if you find yourself building a software system and there is an existing protocol available. If there is an existing plain text protocol that you can adapt to your purposes then do that since there might be a library available already to serialize and deserialize the protocol so you don't have to write your own. If any of these protocols do not fit your needs and you need to write your own then keep it simple. If everyone's web browsers are using HTTP which has worked for decades there is no reason for you to create a complicated protocol unless your can prove the need to otherwise.</p> <h1 id="additional-protocols">Additional Protocols</h1> <ul> <li><a rel="external" href="https://github.com/statsd/statsd/wiki/Protocol">StatsD</a></li> <li><a rel="external" href="https://www.collectd.org/wiki/index.php/Plain_text_protocol">collectd</a></li> <li><a rel="external" href="https://tools.ietf.org/html/rfc959">File Transfer Protocol (FTP)</a></li> <li><a rel="external" href="https://tools.ietf.org/html/rfc2821">Simple Mail Transfer Protocol (SMTP)</a></li> <li><a rel="external" href="https://tools.ietf.org/html/rfc1288">Finger user information protocol</a></li> <li><a rel="external" href="https://github.com/memcached/memcached/wiki/Protocols">Memcached</a></li> </ul> Reverting Back to a Simpler Time 2020-06-23T00:00:00+00:00 2020-06-23T00:00:00+00:00 Unknown https://blainsmith.com/articles/reverting-back-to-a-simpler-time/ <p>Every since the world started shutting down when COVID-19 hit I've been finding myself reverting back to simpler things. With the constant firehose of information from the news channels, social feeds, and just general information overload I've been burning out on almost a weekly basis. It sucks and I am tired. I recognize that my personal issues with all of this pales in comparison with what other people are going through in this world. However, if I am not operating at a healthy mental level then not only can I not help myself, but I cannot help others. At the end of day all that really matters is being happy and trying to help others as we spin around this ball of water and rock to make a life worth living with the time we have.</p> <p>My approach to fixing this for myself is essentially reverting everything I do to simpler versions to reduce the cognitive load and allow my brain to recover.</p> <ol> <li>Leaving social media, except Instagram (I interact with my powerlifting coach there)</li> <li>Reducing my public profile on GitHub/Gitlab and moving to sourcehut</li> <li>Leaving most Slack groups I am in</li> <li>Leaving my computer off outside of 8:00 AM - 4:00 PM EST</li> <li>Catching up on news a few times a week to still be a well informed citizen</li> <li>Read non-technical books for pleasure</li> <li>Spend more time with human beings in person</li> </ol> <p>Social media like Twitter and LinkedIn have been great for so long, but now they have just become overly political streams of consciousness that is just overwhelming. The software we all write has direct political impact and we need to recognize that, but Twitter swings to the extremes a lot and I find it hard to follow so it is personally best for me to remove myself from it. LinkedIn generally doesn't suffer from too much politics, but it does suffer from overly marketed and fake bullshit that just gets on my nerves. The "visionaries", "thought leaders", and "startup hustlers" are far too common now and it is best we agree to disagree and part ways. These anti-social media platforms have now done more harm than good and they are not worth it anymore.</p> <p>GitHub/Gitlab provide a great platform for code sharing and fostering the open source idea. There was a time in my life where I wanted to contribute to open source as much as possible, but as time went on and I starting having more important things in my life outside of coding I realized a commitment to open source would eat into the rest of my life. This prompted me to start archiving and moving my public repos. I will still hack around on things when I feel like it, but I will not advertise I am doing so as much anymore so I can keep them personal.</p> <p>I know I have a big dependency on Google for my phone, email, etc. so this will come off a hypocritical, but I also feel like I need to move to more ethical software choices from here on out. Someday I hope to look back on my software choices and my uses for them and be happy not regret supporting ones that were involved in questionable ethical and political practices. So far I am happy with my choices of Linux for my daily operating system, Signal for messaging, and now sourcehut for source control. Everything else is pure fluff that I haven't gotten value out of in a long time so it is time to remove it.</p> <p>This isn't a goodbye, but is it is me reverting back to simpler times. Email me, call me, text me, and read this blog (text only with no tracking) for tech things I experiment with. Any code worth sharing will end up on sourcehut, but I make not promises to accept patches or support bug fixes in any way if you choose to use it.</p> <p>Talk to you later.</p> The 20 Year 5 School Bachelor's Degree 2020-05-10T00:00:00+00:00 2020-05-10T00:00:00+00:00 Unknown https://blainsmith.com/articles/the-20-year-5-school-bachelors-degree/ <p>Well, my undergraduate career is finally over.</p> <h2 id="rensselaer-polytechnic-institute">Rensselaer Polytechnic Institute</h2> <p>I was fortunate enough to get accepted to my first choice back in 2000 when I graduated high school. Unfortunately, however, it wasn't without its speed bumps. I had received the Rensselaer Medal prior to receiving my acceptance letter so when I got notified I was awarded the scholarship I assumed my acceptance letter was just around the corner. As it turned out, I was wrong. I actually got officially rejected at first. Naturally I was very confused since I was awarded an academic scholarship to a school that rejected my admission. My mother ended up calling the school while I was in my high school and they told her they had made a mistake. They sent me the wrong letter. I had been accepted all along.</p> <p>I spent about a year attending Rensselaer until I came to the realization I was not personally ready for college life. I was still mentally unprepared and immature to be on my own 3 hours away from my home town with no mode of transportation for myself. I ended up leaving Rensselaer at the end of my freshman year to move back home and chose a school closer to home in Worcester, MA.</p> <h2 id="worcester-polytechnic-institute">Worcester Polytechnic Institute</h2> <p>My next attempt at college was closer to home and with being allowed a vehicle on campus I had the freedom to move around as I pleased. Home was now only 45 minutes away so I could head back whenever I needed to. Knowing that choice was available made studying a lot easier to focus on. However, this college attempt was met with not only going through 9/11 my first year there, but also contracting mononucleosis, commonly known as "mono", my second year there. This put me behind in school work and required me to undergo surgery over the summer to have my tonsils removed.</p> <p>After playing lots of catch up and realizing I was paying a lot of money per course for content I was just buying books on Amazon to learn I decided to end pursuing my degree in Computer Science and focus on a degree that justified the high-priced tuition to be taught in a classroom.</p> <h2 id="bentley-college">Bentley College</h2> <p>Third time is a charm right? Bentley College is more known for its business and information technology curriculum which sounded like a better choice given the state of the world at this point. I thought I would focus more on a business oriented technical degree since at lot of the statistics for graduates with Computer Science degree showed their salaries dropped significantly. With mounting student load debt it made more sense to change to another degree to be more marketable when I graduated to pay back the ever increasing loans. As it turns out I hated the business side of technology. My heart was more in the scientific side, but it wasn't marketable in my area so I made a very hard to decision to drop out of college completely. I couldn't justify taking out more money for an education I hated doing.</p> <h2 id="harvard-university">Harvard University</h2> <p>After working for a few years for a local technology startup I ended up being laid off for financial reasons. Interview after interview yielded nothing I cared for or I was not the right fit. Eventually I came across an ad in Craigslist that ended up being posted by a recruiter. They told me it was for Harvard University. After my last position I thought it would be a smart move to focus on this one as much as I could. They would certainly not go out of business for lack of finances. On top of that I learned that employees got the added perk of taking courses for a ridiculous discount. Sounded like a win/win. Work for one of the most prestigious universities in the US and continue my college career part time at a discount. So that is what I did. I spent almost 5 years chipping away at my degree one course at a time each semester. Unfortunately, on two different occasions, the graduation requirements changed which forced me to take more classes than I anticipated to meet the graduation requirements. During this whole time I was getting older and my life was changing. I had moved to upstate New York and start building a life there while trying to finish online classes. A new job opportunity presented itself to work for a company locally and really put down roots where I lived so, again, I made the choice to drop out of college completely to work and live my life accepting the fact I will never end up with my degree.</p> <h2 id="suny-empire-state-college">SUNY Empire State College</h2> <p>Fast forwarding a lot of years and a few different jobs I ended up leaning a new programming language, Go, which piqued my interest in going <a href="/articles/go-made-me-return-college">back to college for Computer Science</a>. At this stage of my life as a father to an autistic son and a working professional as a software engineer I didn't have the time or the means to just stop all of that and return to college full time taking classes during the day. I set out to find a college that accommodated working adult professionals. As it turns out SUNY Empire State College is essentially in my back yard only 10 minutes from my house and their focus is offering degree programs online for adults. This sounded like a perfect choice for someone like me.</p> <p>I spent the next 3 years taking all the final classes I needed to achieve a Bachelor's Degree in Computer Science. While there were some classes I could have taken in my sleep I pushed through since I could see the light at the end of the very very long tunnel I entered 20 years ago as an 18 year old kid. Now that this part of my life has finally come to an end I have come out the other end with a renewed vigor for academic learning to augment the Computer Science knowledge I have.</p> <h2 id="master-s-degree-certificates-courses">Master's Degree, Certificates, Courses?</h2> <p>I am still doing as much research a possible, but I do think that I won't stop taking courses in some fashion. The more I pay attention to where the world is headed with respect to technology and computers the more I realize that coding, software engineering, and computer science is only part of the puzzle. Picking up topics in mathematics, physics, material sciences, and other engineering disciplines will only strengthen my understanding and capacity to solve more complex problems in my career as a software engineer. Physicists, mathematicians, and engineers are starting to leverage coding and software engineering principals to do their job so it would be advantageous of me to learn parts of their primary disciplines to do mine. While I do spend most of my time solving problems in the video gaming and entertainment space; having other points of view would make me a more well rounded software engineer to apply those problems solving skills to new and exciting projects.</p> Signing JWTs with Go's crypto/ed25519 2019-12-07T00:00:00+00:00 2019-12-07T00:00:00+00:00 Unknown https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/ <p>The <a rel="external" href="https://pkg.go.dev/crypto/ed25519">crypto/ed25519</a> package was added to the standard library in <a rel="external" href="https://golang.org/doc/go1.13#crypto/ed25519">Go 1.13</a>. This package implements the <a rel="external" href="https://ed25519.cr.yp.to/">Ed25519</a> Edwards-curve Digital Signature Algorithm. It offers significant speed and security improvements over RSA and it makes for a perfect signing method for JWTs. Unfortunately, the <a rel="external" href="https://pkg.go.dev/github.com/dgrijalva/jwt-go">most popular JWT library for Go</a> does not natively support it yet. It only supports ECDSA, HMAC, RSA, and RSAPSS, however, it is trivial to extend the package and satisfy the interface <a rel="external" href="https://pkg.go.dev/github.com/dgrijalva/jwt-go#SigningMethod">(github.com/dgrijalva/jwt-go).SigningMethod</a> for EdDSA.</p> <h2 id="implementing-github-com-dgrijalva-jwt-go-signingmethod">Implementing (github.com/dgrijalva/jwt-go).SigningMethod</h2> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> SigningMethodEd25519</span><span style="color: #FF8F40;"> struct</span><span>{}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">m </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">SigningMethodEd25519</span><span>)</span><span style="color: #FFB454;"> Alg</span><span>()</span><span style="color: #39BAE6;"> string</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #AAD94C;"> &quot;EdDSA&quot;</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">m </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">SigningMethodEd25519</span><span>)</span><span style="color: #FFB454;"> Verify</span><span>(</span><span style="color: #D2A6FF;">signingString</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> signature</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> key</span><span style="color: #FF8F40;"> interface</span><span>{})</span><span style="color: #39BAE6;"> error</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> err</span><span style="color: #39BAE6;"> error</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> sig []</span><span style="color: #39BAE6;">byte</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> sig, err</span><span style="color: #F29668;"> =</span><span> jwt.</span><span style="color: #FFB454;">DecodeSegment</span><span>(signature)</span><span style="color: #BFBDB6B3;">;</span><span> err</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span> {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> err</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> ed25519Key</span><span style="color: #59C2FF;"> ed25519</span><span>.</span><span style="color: #59C2FF;">PublicKey</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> ok</span><span style="color: #39BAE6;"> bool</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> ed25519Key, ok</span><span style="color: #F29668;"> =</span><span> key.(</span><span style="color: #59C2FF;">ed25519</span><span>.</span><span style="color: #59C2FF;">PublicKey</span><span>)</span><span style="color: #BFBDB6B3;">;</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> jwt.ErrInvalidKeyType</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #FFB454;"> len</span><span>(ed25519Key)</span><span style="color: #F29668;"> !=</span><span> ed25519.PublicKeySize {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> jwt.ErrInvalidKey</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> ok</span><span style="color: #F29668;"> :=</span><span> ed25519.</span><span style="color: #FFB454;">Verify</span><span>(ed25519Key, []</span><span style="color: #39BAE6;">byte</span><span>(signingString), sig)</span><span style="color: #BFBDB6B3;">;</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> ErrEd25519Verification</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span> (</span><span style="color: #D2A6FF;">m </span><span style="color: #F29668;">*</span><span style="color: #59C2FF;">SigningMethodEd25519</span><span>)</span><span style="color: #FFB454;"> Sign</span><span>(</span><span style="color: #D2A6FF;">signingString</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> key</span><span style="color: #FF8F40;"> interface</span><span>{}) (</span><span style="color: #D2A6FF;">str</span><span style="color: #39BAE6;"> string</span><span>,</span><span style="color: #D2A6FF;"> err</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> ed25519Key</span><span style="color: #59C2FF;"> ed25519</span><span>.</span><span style="color: #59C2FF;">PrivateKey</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> var</span><span> ok</span><span style="color: #39BAE6;"> bool</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span> ed25519Key, ok</span><span style="color: #F29668;"> =</span><span> key.(</span><span style="color: #59C2FF;">ed25519</span><span>.</span><span style="color: #59C2FF;">PrivateKey</span><span>)</span><span style="color: #BFBDB6B3;">;</span><span style="color: #F29668;"> !</span><span>ok {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #AAD94C;"> &quot;&quot;</span><span>, jwt.ErrInvalidKeyType</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;"> if</span><span style="color: #FFB454;"> len</span><span>(ed25519Key)</span><span style="color: #F29668;"> !=</span><span> ed25519.PrivateKeySize {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span style="color: #AAD94C;"> &quot;&quot;</span><span>, jwt.ErrInvalidKey</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Sign the string and return the encoded result</span></span> <span class="giallo-l"><span> sig</span><span style="color: #F29668;"> :=</span><span> ed25519.</span><span style="color: #FFB454;">Sign</span><span>(ed25519Key, []</span><span style="color: #39BAE6;">byte</span><span>(signingString))</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> jwt.</span><span style="color: #FFB454;">EncodeSegment</span><span>(sig),</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>In order to work with the JWT methods above we need to have a <a rel="external" href="https://pkg.go.dev/crypto/ed25519/#PrivateKey">(crypto/ed25119).PrivateKey</a> and <a rel="external" href="https://pkg.go.dev/crypto/ed25519/#PublicKey">(crypto/ed25119).PublicKey</a> loaded into our application to sign and verify tokens. For this case we will assume these keys are in PEM format and we can load them in from a file or environment varible.</p> <h2 id="private-and-public-keys-in-pem-format">Private and Public Keys in PEM Format</h2> <p>These are strings converted to []byte which is needed by <a rel="external" href="https://pkg.go.dev/encoding/pem/#Decode">(encoding/pem).Decode()</a>.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span>privateKeyPEM</span><span style="color: #F29668;"> :=</span><span> []</span><span style="color: #39BAE6;">byte</span><span>(</span><span style="color: #AAD94C;">`-----BEGIN PRIVATE KEY-----</span></span> <span class="giallo-l"><span style="color: #AAD94C;">MC4CAQAwBQYDK2VwBCIEIEFMEZrmlYxczXKFxIlNvNGR5JQvDhTkLovJYxwQd3ua</span></span> <span class="giallo-l"><span style="color: #AAD94C;">-----END PRIVATE KEY-----`</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>publicKeyPEM</span><span style="color: #F29668;"> :=</span><span> []</span><span style="color: #39BAE6;">byte</span><span>(</span><span style="color: #AAD94C;">`-----BEGIN PUBLIC KEY-----</span></span> <span class="giallo-l"><span style="color: #AAD94C;">MCowBQYDK2VwAyEAWH7z6hpYqvPns2i4n9yymwvB3APhi4LyQ7iHOT6crtE=</span></span> <span class="giallo-l"><span style="color: #AAD94C;">-----END PUBLIC KEY-----`</span><span>)</span></span></code></pre> <p>We have to parse these from their string format into actual (crypto/ed25119).PrivateKey and (crypto/ed25119).PublicKey types. We only have to do this once when our application starts up and we can pass these keys around to the functions that need them to do signing and verifying.</p> <h2 id="decoding-the-private-key">Decoding the Private Key</h2> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ed25519PrivKey</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> Version</span><span style="color: #39BAE6;"> int</span></span> <span class="giallo-l"><span> ObjectIdentifier</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> ObjectIdentifier</span><span style="color: #59C2FF;"> asn1</span><span>.</span><span style="color: #59C2FF;">ObjectIdentifier</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> PrivateKey []</span><span style="color: #39BAE6;">byte</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> block</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">pem</span><span>.</span><span style="color: #59C2FF;">Block</span></span> <span class="giallo-l"><span>block, _</span><span style="color: #F29668;"> =</span><span> pem.</span><span style="color: #FFB454;">Decode</span><span>(privateKeyPEM)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> asn1PrivKey</span><span style="color: #59C2FF;"> ed25519PrivKey</span></span> <span class="giallo-l"><span>asn1.</span><span style="color: #FFB454;">Unmarshal</span><span>(block.Bytes,</span><span style="color: #F29668;"> &amp;</span><span>asn1PrivKey)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>privateKey</span><span style="color: #F29668;"> :=</span><span> ed25519.</span><span style="color: #FFB454;">NewKeyFromSeed</span><span>(asn1PrivKey.PrivateKey[</span><span style="color: #D2A6FF;">2</span><span>:])</span></span></code></pre><h2 id="decoding-the-public-key">Decoding the Public Key</h2> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> ed25519PubKey</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> OBjectIdentifier</span><span style="color: #FF8F40;"> struct</span><span> {</span></span> <span class="giallo-l"><span> ObjectIdentifier</span><span style="color: #59C2FF;"> asn1</span><span>.</span><span style="color: #59C2FF;">ObjectIdentifier</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> PublicKey</span><span style="color: #59C2FF;"> asn1</span><span>.</span><span style="color: #59C2FF;">BitString</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> block</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">pem</span><span>.</span><span style="color: #59C2FF;">Block</span></span> <span class="giallo-l"><span>block, _</span><span style="color: #F29668;"> =</span><span> pem.</span><span style="color: #FFB454;">Decode</span><span>(publicKeyPEM)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> asn1PubKey</span><span style="color: #59C2FF;"> ed25519PubKey</span></span> <span class="giallo-l"><span>asn1.</span><span style="color: #FFB454;">Unmarshal</span><span>(block.Bytes,</span><span style="color: #F29668;"> &amp;</span><span>asn1PubKey)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>publicKey</span><span style="color: #F29668;"> :=</span><span> ed25519.</span><span style="color: #FFB454;">PublicKey</span><span>(asn1PubKey.PublicKey.Bytes)</span></span></code></pre> <p>Now that we have implemented the (github.com/dgrijalva/jwt-go).SigningMethod and have a (crypto/ed25519).PrivateKey and (crypto/ed25519).PublicKey we can put this all together to sign and verify JWTs.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #FF8F40;">var</span><span> Ed25519SigningMethod</span><span style="color: #59C2FF;"> SigningMethodEd25519</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>jwt.</span><span style="color: #FFB454;">RegisterSigningMethod</span><span>(Ed25519SigningMethod.</span><span style="color: #FFB454;">Alg</span><span>(),</span><span style="color: #FF8F40;"> func</span><span>()</span><span style="color: #59C2FF;"> jwt</span><span>.</span><span style="color: #59C2FF;">SigningMethod</span><span> {</span><span style="color: #FF8F40;"> return</span><span style="color: #F29668;"> &amp;</span><span>Ed25519SigningMethod })</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>token</span><span style="color: #F29668;"> :=</span><span> jwt.</span><span style="color: #FFB454;">NewWithClaims</span><span>(</span><span style="color: #F29668;">&amp;</span><span>Ed25519SigningMethod,</span><span style="color: #59C2FF;"> jwt</span><span>.</span><span style="color: #59C2FF;">StandardClaims</span><span>{</span></span> <span class="giallo-l"><span> IssuedAt: time.</span><span style="color: #FFB454;">Now</span><span>().</span><span style="color: #FFB454;">Unix</span><span>(),</span></span> <span class="giallo-l"><span> Issuer:</span><span style="color: #AAD94C;"> &quot;urn:ed25519-jwt&quot;</span><span>,</span></span> <span class="giallo-l"><span> Subject:</span><span style="color: #AAD94C;"> &quot;[email protected]&quot;</span><span>,</span></span> <span class="giallo-l"><span>})</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>jwtstring</span><span style="color: #F29668;"> :=</span><span> token.</span><span style="color: #FFB454;">SignedString</span><span>(privateKey)</span></span> <span class="giallo-l"><span>fmt.</span><span style="color: #FFB454;">Println</span><span>(jwtstring)</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Outputs: eyJhbGciOiJFRDI1NTE5IiwidHlwI...</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>token, _</span><span style="color: #F29668;"> :=</span><span> jwt.</span><span style="color: #FFB454;">Parse</span><span>(jwtstring,</span><span style="color: #FF8F40;"> func</span><span>(</span><span style="color: #D2A6FF;">token</span><span style="color: #F29668;"> *</span><span style="color: #59C2FF;">jwt</span><span>.</span><span style="color: #59C2FF;">Token</span><span>) (</span><span style="color: #FF8F40;">interface</span><span>{},</span><span style="color: #39BAE6;"> error</span><span>) {</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> return</span><span> publicKey,</span><span style="color: #D2A6FF;"> nil</span></span> <span class="giallo-l"><span>})</span></span> <span class="giallo-l"><span>fmt.</span><span style="color: #FFB454;">Println</span><span>(token.Issuer, token.Subject)</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// Outputs: urn:ed25519-jwt [email protected]</span></span></code></pre> <p>The <code>jwtstring</code> can be used by clients to verify their identity as any other JWT, but now we are using Ed25519 for signing and verifying tokens like you can with RSA. The <a rel="external" href="https://jwt.io">https://jwt.io</a> website is a great tool for working with JWTs and you can see other libraries that support different signing methods. Since Ed25519 is fairly new you will notice that a lot are missing the "EdDSA" algorithm, but now you can at least implement this yourself with not a lot of code or importing a 3rd party dependency.</p> Linux System Metrics with collectd 2018-08-03T00:00:00+00:00 2018-08-03T00:00:00+00:00 Unknown https://blainsmith.com/articles/linux-system-metrics-with-collectd/ <p>This tutorial assumes you have the following software installed.</p> <ul> <li><a rel="external" href="https://collectd.org/">https://collectd.org</a></li> <li><a rel="external" href="https://www.docker.com/">https://www.docker.com</a></li> </ul> <p>collectd is a general purpose system metrics collection tool that supports various plugins for <strong>reading</strong> metrics from <strong>sources</strong> and <strong>writing</strong> results to <strong>destinations</strong>. Multiple sources can be read and then written to multiple destinations which makes collectd very flexible. For example, you can collect CPU, Memory, and Load information and write those results every 10ms to CSV, Redis, and RDD (we will get to that).</p> <p>For this tutorial we will be collecting very basic system metrics and writing them to a few output formats. We will set up collectd to monitor and store the following metrics.</p> <ul> <li>CPU usage</li> <li>CPU temperature</li> <li>Load</li> <li>Memory (virtual and swap)</li> <li>Disk</li> <li>Battery</li> </ul> <p>Each of these metrics above represent a <strong>read plugin</strong> in collectd and each can be configured separately. We will assume the defaults though. Then we will set up collectd to write to the following output formats.</p> <ul> <li>RDD (TL;DR efficient binary data format made for collecting time series data in a ring buffer)</li> <li>CSV</li> </ul> <p>Each of these metrics above represent a <strong>write plugin</strong> in collectd and each can be configured separately.</p> <p>There is a <a rel="external" href="https://collectd.org/wiki/index.php/Table_of_Plugins">full list of plugins</a> available on the collectd wiki.</p> <h2 id="configure-collectd">Configure collectd</h2> <p>The default path collectd keeps its configuration file is in <code>/etc/collectd/collectd.conf</code>. We need to edit this file to set up the plugins we want to use.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>$ cd /etc/collectd</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Create a backup of the default configuration</span></span> <span class="giallo-l"><span>$ cp collectd.conf collectd.default.conf</span></span></code></pre> <p>Open <code>/etc/collectd/collectd.conf</code> in your preferred editor and overwrite it with the following:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span># This tells collectd to lookup the hostname for storing data</span></span> <span class="giallo-l"><span># in a folder with that name</span></span> <span class="giallo-l"><span>FQDNLookup true</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># This is the read interval in milliseconds</span></span> <span class="giallo-l"><span>Interval 5000</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Load the syslog plugin</span></span> <span class="giallo-l"><span>LoadPlugin syslog</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Configure the syslog plugin to log only errors</span></span> <span class="giallo-l"><span>&lt;Plugin syslog&gt;</span></span> <span class="giallo-l"><span> LogLevel error</span></span> <span class="giallo-l"><span>&lt;/Plugin&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Load the following plugins for collectd to read/write with</span></span> <span class="giallo-l"><span>LoadPlugin battery</span></span> <span class="giallo-l"><span>LoadPlugin cpu</span></span> <span class="giallo-l"><span>LoadPlugin csv</span></span> <span class="giallo-l"><span>LoadPlugin disk</span></span> <span class="giallo-l"><span>LoadPlugin load</span></span> <span class="giallo-l"><span>LoadPlugin memory</span></span> <span class="giallo-l"><span>LoadPlugin rrdtool</span></span> <span class="giallo-l"><span>LoadPlugin swap</span></span> <span class="giallo-l"><span>LoadPlugin thermal</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Configure the csv plugin</span></span> <span class="giallo-l"><span>&lt;Plugin csv&gt;</span></span> <span class="giallo-l"><span> DataDir &quot;/var/lib/collectd/csv&quot;</span></span> <span class="giallo-l"><span> StoreRates true</span></span> <span class="giallo-l"><span>&lt;/Plugin&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Configure the rrdtool plugin</span></span> <span class="giallo-l"><span>&lt;Plugin rrdtool&gt;</span></span> <span class="giallo-l"><span> DataDir &quot;/var/lib/collectd/rrd&quot;</span></span> <span class="giallo-l"><span>&lt;/Plugin&gt;</span></span></code></pre> <p>Save the file and now we have a minimal configuration.</p> <h2 id="running-collectd">Running collectd</h2> <p>If you installed collectd via a package manager then chances are there will be a SystemD service available to use.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span># Start the collecd system service</span></span> <span class="giallo-l"><span>$ sudo systemctl start collectd</span></span></code></pre> <p>collectd should now be running and collecting metrics and writing data to the folders specified in the configuration file.</p> <h2 id="verify-collectd">Verify collectd</h2> <p>We set up the CSV plugin to verify we are collecting information since the RDD plugin can't be read by humans. CSV is easier to read via the command line.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span># View some metrics</span></span> <span class="giallo-l"><span>$ cd /var/lib/collectd</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># List the host folders, the folder name will differ on your machine</span></span> <span class="giallo-l"><span>$ ls -lah</span></span> <span class="giallo-l"><span>total 12K</span></span> <span class="giallo-l"><span>drwxr-xr-x 3 root root 4.0K Aug 13 11:13 ./</span></span> <span class="giallo-l"><span>drwxr-xr-x 4 root root 4.0K Aug 13 11:13 ../</span></span> <span class="giallo-l"><span>drwxr-xr-x 44 root root 4.0K Aug 13 11:14 pop-os.localdomain/</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>$ cd pop-os.localdomain</span></span> <span class="giallo-l"><span>$ ls -lah</span></span> <span class="giallo-l"><span>total 176K</span></span> <span class="giallo-l"><span>drwxr-xr-x 44 root root 4.0K Aug 13 11:14 ./</span></span> <span class="giallo-l"><span>drwxr-xr-x 3 root root 4.0K Aug 13 11:13 ../</span></span> <span class="giallo-l"><span>drwxr-xr-x 2 root root 4.0K Aug 13 11:13 battery-0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-3/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-4/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-5/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-6/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 cpu-7/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:15 disk-dm-0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:15 disk-dm-1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-dm-2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop3/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop4/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop5/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-loop6/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-nvme0n1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-nvme0n1p1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-nvme0n1p2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:15 disk-nvme0n1p3/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:14 disk-nvme0n1p4/</span></span> <span class="giallo-l"><span>drwxr-xr-x 2 root root 4.0K Aug 13 11:13 load/</span></span> <span class="giallo-l"><span>drwxr-xr-x 2 root root 4.0K Aug 13 11:13 memory/</span></span> <span class="giallo-l"><span>drwxr-xr-x 2 root root 4.0K Aug 13 11:13 swap/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device3/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device4/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device5/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device6/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device7/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-cooling_device8/</span></span> <span class="giallo-l"><span>drwxr-xr-x 2 root root 4.0K Aug 13 11:13 thermal-cooling_device9/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-thermal_zone0/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-thermal_zone1/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-thermal_zone2/</span></span> <span class="giallo-l"><span>d--xrwxrwx 2 root root 4.0K Aug 13 11:13 thermal-thermal_zone3/</span></span></code></pre> <p>You can see how the metrics are actually being stored on disk. The RDD plugin has a similar folder structure under <code>/var/lib/collectd/pop-os.localdomain/rdd</code> which you can browse as well.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span># View system load</span></span> <span class="giallo-l"><span>$ cd load/</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># You will have different filenames here with a different date</span></span> <span class="giallo-l"><span>$ cat load-2018-08-13</span></span> <span class="giallo-l"><span>epoch,shortterm,midterm,longterm</span></span> <span class="giallo-l"><span>1534182073.078,0.500000,0.550000,0.430000</span></span> <span class="giallo-l"><span>1534182083.078,0.660000,0.580000,0.440000</span></span> <span class="giallo-l"><span>1534182093.078,0.560000,0.560000,0.440000</span></span> <span class="giallo-l"><span>1534182103.078,0.550000,0.560000,0.440000</span></span> <span class="giallo-l"><span>1534182113.078,0.700000,0.590000,0.450000</span></span> <span class="giallo-l"><span>1534182123.078,0.830000,0.620000,0.460000</span></span> <span class="giallo-l"><span>1534182133.078,0.700000,0.600000,0.460000</span></span> <span class="giallo-l"><span>1534182143.078,0.670000,0.600000,0.460000</span></span> <span class="giallo-l"><span>1534182153.078,0.560000,0.580000,0.450000</span></span> <span class="giallo-l"><span>1534182163.078,0.480000,0.560000,0.450000</span></span> <span class="giallo-l"><span>1534182173.078,0.640000,0.590000,0.460000</span></span> <span class="giallo-l"><span>1534182183.078,0.540000,0.570000,0.450000</span></span> <span class="giallo-l"><span>1534182193.078,0.780000,0.620000,0.470000</span></span> <span class="giallo-l"><span>1534182203.079,0.660000,0.600000,0.470000</span></span> <span class="giallo-l"><span>1534182213.078,0.640000,0.590000,0.470000</span></span> <span class="giallo-l"><span>1534182223.078,0.540000,0.570000,0.460000</span></span></code></pre> <p>Now we see the load information collectd has been collecting on the interval we have specified. For this example I have 10ms specified for the interval to show more data. The corresponding RDD binary file is at <code>/var/lib/collectd/rdd/pop-os.localdomain/load/load.rdd</code> which we will use for the UI in the next section.</p> <h2 id="viewing-collectd-metrics-with-charts">Viewing collectd metrics with charts</h2> <p>The CSV files are all well and good, but we can actually generate charts from the raw binary RDD files with a simple docker container running Collectd Graph Panel. This web UI read RDD files from disk and displays charts for each host it has data for.</p> <p>Now that you have the metrics on your local machine you can simply run the docker container to run a local web app that will allow you to browse the results.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span># Run the container and mount a volume of the RDD data</span></span> <span class="giallo-l"><span>$ docker run -v /var/lib/collectd/rrd:/var/lib/collectd/rrd -p 8080:80 konjak/cgp</span></span></code></pre> <p>Open a web browser and go to the following address: http://localhost:8080 and you should see something like this.</p> <p><img src="https://s3.amazonaws.com/dev.beautifulatlas.com/uploads/56a5df1a-939c-4915-8a9b-d1e0b6543224/ee2772f3-2467-4fab-9e97-7c90133ec31f/Screenshot%20from%202018-08-13%2014-11-21.png" alt="" /></p> <p>You can see I have multiple hosts so it will list every host (device) it has data for. Click on the host you want to view charts for and you should see something like this. The multiple hosts are just multiple folders listed on disk.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>$ cd /var/lib/collectd/rrd </span></span> <span class="giallo-l"><span>$ ls -lah</span></span> <span class="giallo-l"><span>total 20K</span></span> <span class="giallo-l"><span>drwxr-xr-x 5 root root 4.0K Aug 13 14:10 ./</span></span> <span class="giallo-l"><span>drwxr-xr-x 4 root root 4.0K Aug 13 11:13 ../</span></span> <span class="giallo-l"><span>drwxr-xr-x 44 root root 4.0K Aug 13 14:10 78dsJIOLHJK78bnjk.localdomain/</span></span> <span class="giallo-l"><span>drwxr-xr-x 44 root root 4.0K Aug 13 14:10 dasKH90321KJsaa8.localdomain/</span></span> <span class="giallo-l"><span>drwxr-xr-x 44 root root 4.0K Aug 13 11:14 pop-os.localdomain/</span></span></code></pre> <p>You can continue to drop host folders in here and the UI will pick them up and parse them.</p> <p><img src="https://s3.amazonaws.com/dev.beautifulatlas.com/uploads/56a5df1a-939c-4915-8a9b-d1e0b6543224/528c0d0d-3695-48bb-819b-13ad8cd758f8/Screenshot%20from%202018-08-13%2014-12-32.png" alt="" /></p> <p>Oooooo charts! Now you can click on the left menu to drill down. You will also notice that the URL has a predictable pattern so you can even add these links to other systems you have to link directly to these charts. Adding a link in the form of <code>https://cgp.example.com/host.php?h=&lt;HOST_FOLDER&gt;</code>.</p> <p>Enjoy and reach out to me if you have any questions.</p> Go Made Me Return to College 2017-09-12T00:00:00+00:00 2017-09-12T00:00:00+00:00 Unknown https://blainsmith.com/articles/go-made-me-return-college/ <p>A long time ago I gave a <a href="/articles/tedxalbany-the-master-of-my-fate/">TEDx talk</a> about how I ended up where I am and the choices I made. Most notably, the choices I made to transfer to many different colleges and eventually drop out. As I got more comfortable in my developer career at Harvard and then MadGlory I convinced myself that I never needed a formal education in Computer Science to do well. Convincing myself of that helped minimize the annoying itch I had that reminded me I never graduated, specifically from the school I worked my ass off to get into when I was a senior in high school, <a rel="external" href="http://rpi.edu">Rensselaer</a>.</p> <p>I started with using <a rel="external" href="http://golang.org">Go</a> back in the Summer of 2016 for a small project. A co-worker of mine, <a rel="external" href="https://twitter.com/dogboystudios">Patrick</a>, suggested we try it out since he was showing interest in it as well. So we took the chance on it and used Go to write it. We finished if fairly quickly while learning the language and got it into production. It performed better than we imagined it would and I was immediately hooked after that. I started spending more and more time learning the language and also, more importantly, the reasons why it was created and why it works at a conceptual level. It felt deliberate. Well thought out. Intentional. I liked those feelings, especially in world of being forced to ship software too fast.</p> <p>And now GopherCon 2017...</p> <p>Talk about being a small stupid guppy fish in a big ocean full of really smart dolphins! I have never been hit so hard with imposter syndrome in my life and to make it even worse I agreed to <a href="/articles/gophercon-2017-generating-hundreds-of-video-catalog-feeds-in-seconds/">give a lightning talk</a> at the end of the conference. The first two days had a lot of tough acts to follow. However, after that weekend that annoying itch returned about never graduating.</p> <p>I spent over a year with Go and being a part of the community. Not only did I fall hard for the language, but I fell even harder for the message it conveys. The community's outlook on writing software has pushed me to educate myself more and how to build better software. This sparked the idea of returning to school to finish my degree since a lot of the concepts I was hearing from Go programmers was stuff I was never taught or I just happened to stumble upon accidentally. I felt like I needed more formal education around these concepts. So, I started to check out Rensselaer to see if that was a viable option to return to and complete my degree since I only live 30 minutes away. Unfortunately, given my lifestyle of doing dad and full time work things I couldn't make that work. However, I did end up finding <a rel="external" href="http://esc.edu">Empire State College</a> basically in my back yard that prides itself in educating students in a non-traditional way. For me this meant I could complete my degree entirely online from and accredited <a rel="external" href="http://suny.edu">SUNY</a> school. Sold!</p> <p>After being accepted I was pleasantly surprised to find out I am a mere 4-5 courses shy of a bachelor's degree in Computer Science and after speaking with my advisor it sounds like I may end up on a graduate degree path afterwards. Perhaps graduating from Rensselaer will actually happen if I am crazy enough to actually pursue it. The thought of that does sound very exciting though.</p> <p>Now that I have begun classes I am experiencing the value of the knowledge that comes from a formal education. The upcoming courses on Operating Systems, Software Engineering, and Systems Analysis and Design are really piquing my interests. These areas will certainly put me where I want to be in my career as a software engineer. This is going to be fun!</p> <p>Thank you, Patrick, for recommending Go and thank you, Gophers, for making me yearn for more and to go back to school.</p> <p>Now I leave you with Rob Pike's 2015 dotGo: Simplicity is Complicated talk to give you a taste of what contributed to my decision to return back to school.</p> <iframe width="700" height="394" src="https://www.youtube.com/embed/rFejpH_tAHM" frameborder="0" allowfullscreen></iframe> Fuck Your Hustle 2017-02-06T00:00:00+00:00 2017-02-06T00:00:00+00:00 Unknown https://blainsmith.com/articles/fuck-your-hustle/ <p>This word is used throughout the startup and tech worlds and it annoys the shit out of me. Not only does it seem to stroke the egos using it, but it is also being used in completely the wrong context. Both of these definitions indicate no decisions being made on the person being hustled. How does this have anything to do with successfully launching a startup, product, or completing any task that is worth a damn?</p> <p><strong>hustle (verb)</strong></p> <ol> <li>force (someone) to move hurriedly or unceremoniously in a specified direction.<br /> "they hustled him into the back of a horse-drawn wagon"</li> <li>obtain by forceful action or persuasion.<br /> "the brothers headed to New York to try and hustle a record deal"</li> </ol> <blockquote> <p>Haste makes waste.</p> </blockquote> <p>When taking on a job or a task does telling the world at 10pm at night that you are working on it and "hustling" actually help get the work done right? How does rushing to get the job done make it any better? Wouldn't you prefer to take your time and methodically execute on it so its done right the first time?</p> <p>I work at a startup now and I am proud to say that none of us fuckin' hustle on a daily basis. Under certain conditions there may be a deadline that needs to be met, but that is on a rare occasion. The rest of the time is spent building quality systems that will stand up to future demand. Why? Because we didn't fuckin' hustle.</p> <p>Sometimes I feel sorry for those who feel the need they have to hustle to be successful. Shame on those who preach that as the way to achieve that success. Those who do preach it remind me of religious con men who make their money by spewing falsehoods to the masses so they pay for their books and conferences.</p> <blockquote> <p>Whatever is worth doing at all, is worth doing well.</p> <ul> <li>Philip Stanhope, 4th Earl of Chesterfield,<br /> Letters to His Son on the Art of Becoming<br /> a Man of the World and a Gentleman (1774)</li> </ul> </blockquote> <p>Passion and planning yields success so stop fuckin' hustling and do it right.</p> Go, Go import "container/list"! 2017-02-01T00:00:00+00:00 2017-02-01T00:00:00+00:00 Unknown https://blainsmith.com/articles/go-go-import-container-list/ <p>On a few different occasions I've needed to keep a registry of interface implementations to iterate over later on. The first time I did it was to have define an <code>EventWriter</code> interface.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// writers/writers.go</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> writers</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Event stuct</span><span> {</span></span> <span class="giallo-l"><span> Action</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;action&quot;`</span></span> <span class="giallo-l"><span> Details</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;details&quot;`</span></span> <span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;createdAt&quot;`</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> EventWriter</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> WriteEvent</span><span>(</span><span style="color: #D2A6FF;">event</span><span style="color: #59C2FF;"> Event</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> Writers []</span><span style="color: #59C2FF;">EventWriter</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> Add</span><span>(</span><span style="color: #D2A6FF;">writer</span><span style="color: #59C2FF;"> EventWriter</span><span>) {</span></span> <span class="giallo-l"><span> Writers</span><span style="color: #F29668;"> =</span><span style="color: #FFB454;"> append</span><span>(Writers, writer)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Elsewhere, in my program I initialized a few implementations of <code>EventWriter</code> and added them to the list of available ones. This is a stupid use, but for the sake of explaining I show it this way. My actual use was handling HTTP POST requests and so this really lives in a handler.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// main.go</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> main</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span> (</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;sync&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;writer-app/writers&quot;</span></span> <span class="giallo-l"><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> init</span><span>() {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Add a bunch of EventWriter implementations to the registry</span></span> <span class="giallo-l"><span> writers.</span><span style="color: #FFB454;">Add</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">mySQLWriter</span><span>{})</span></span> <span class="giallo-l"><span> writers.</span><span style="color: #FFB454;">Add</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">s3Writer</span><span>{})</span></span> <span class="giallo-l"><span> writers.</span><span style="color: #FFB454;">Add</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">boltDBWriter</span><span>{})</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span> event</span><span style="color: #F29668;"> := &amp;</span><span style="color: #59C2FF;">writers</span><span>.</span><span style="color: #59C2FF;">Event</span><span>{</span><span style="color: #AAD94C;">&quot;load&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;the app loaded&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;2017-02-01T00:00:00Z&quot;</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Loop over the slice that is the registry and call WriteEvent on each one</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> _, writer</span><span style="color: #F29668;"> :=</span><span style="color: #FF8F40;"> range</span><span> writers.Writers {</span></span> <span class="giallo-l"><span> writer.</span><span style="color: #FFB454;">WriteEvent</span><span>(event)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>While this proved effective and worked perfectly fine it isn't very idiomatic in Go. It also felt very dirty to me so I looked around for a better way to handle this. I immediately went into the Go standard library because it seems to always have what I need. In this cases I needed something like a slice, but with some methods to make working with the underlying elements nicer. As it turns out Go has a <code>container/list</code> package that does exactly what I wanted to have this registry pattern I could work with.</p> <p>Refactoring the interface file I now have:</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// writers/writers.go</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> writers</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span> (</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;container/list&quot;</span></span> <span class="giallo-l"><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">var</span><span> Registry</span><span style="color: #F29668;"> =</span><span> list.</span><span style="color: #FFB454;">New</span><span>()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> Event stuct</span><span> {</span></span> <span class="giallo-l"><span> Action</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;action&quot;`</span></span> <span class="giallo-l"><span> Details</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;details&quot;`</span></span> <span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:&quot;createdAt&quot;`</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">type</span><span style="color: #59C2FF;"> EventWriter</span><span style="color: #FF8F40;"> interface</span><span> {</span></span> <span class="giallo-l"><span style="color: #FFB454;"> WriteEvent</span><span>(</span><span style="color: #D2A6FF;">event</span><span style="color: #59C2FF;"> Event</span><span>)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>And now for using the <code>Registry</code> list in my main program I have just a standard <code>list.List</code> provided by the standard library, which is an implementation of a doubly linked list data structure. I also get all the documentation around this as well so that the next developer that looks at this can understand this a lot quicker.</p> <p>One thing to notice when I call <code>WriteEvent</code> within the loop is that I need to cast the <code>writer.Value</code>, into an actual <code>Writer</code>. That is because <code>list.List</code> works with <code>interface{}</code> types as you can see from the documentation.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="go"><span class="giallo-l"><span style="color: #5A6673;font-style: italic;">// main.go</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">package</span><span style="color: #59C2FF;"> main</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">import</span><span> (</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;sync&quot;</span></span> <span class="giallo-l"><span style="color: #AAD94C;"> &quot;writer-app/writers&quot;</span></span> <span class="giallo-l"><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> init</span><span>() {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Add a bunch of EventWriter implementations to the registry</span></span> <span class="giallo-l"><span> writers.Registry.</span><span style="color: #FFB454;">PushFront</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">mySQLWriter</span><span>{})</span></span> <span class="giallo-l"><span> writers.Registry.</span><span style="color: #FFB454;">PushFront</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">s3Writer</span><span>{})</span></span> <span class="giallo-l"><span> writers.Registry.</span><span style="color: #FFB454;">PushFront</span><span>(</span><span style="color: #F29668;">&amp;</span><span style="color: #59C2FF;">boltDBWriter</span><span>{})</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FF8F40;">func</span><span style="color: #FFB454;"> main</span><span>() {</span></span> <span class="giallo-l"><span> event</span><span style="color: #F29668;"> := &amp;</span><span style="color: #59C2FF;">writers</span><span>.</span><span style="color: #59C2FF;">Event</span><span>{</span><span style="color: #AAD94C;">&quot;load&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;the app loaded&quot;</span><span>,</span><span style="color: #AAD94C;"> &quot;2017-02-01T00:00:00Z&quot;</span><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Loop over the list that is the registry and call WriteEvent on each one</span></span> <span class="giallo-l"><span style="color: #FF8F40;"> for</span><span> writer</span><span style="color: #F29668;"> :=</span><span> Registry.</span><span style="color: #FFB454;">Front</span><span>()</span><span style="color: #BFBDB6B3;">;</span><span> writer</span><span style="color: #F29668;"> !=</span><span style="color: #D2A6FF;"> nil</span><span style="color: #BFBDB6B3;">;</span><span> writer</span><span style="color: #F29668;"> =</span><span> writer.</span><span style="color: #FFB454;">Next</span><span>() {</span></span> <span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // Convert the Value to an actual Writer</span></span> <span class="giallo-l"><span> writer.Value.(</span><span style="color: #59C2FF;">Writer</span><span>).</span><span style="color: #FFB454;">WriteEvent</span><span>(event)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>This seems like a trivial example, but it shows how nice the Go standard library is by providing this out of the box instead of having to implement my own every time.</p> <p><strong>References</strong></p> <ul> <li>container/list GoDoc: https://golang.org/pkg/container/list/</li> <li>Doubly Linked List: https://en.wikipedia.org/wiki/Doubly_linked_list</li> </ul> 3 Years at MadGlory 2015-10-07T00:00:00+00:00 2015-10-07T00:00:00+00:00 Unknown https://blainsmith.com/articles/3-years-at-madglory/ <p>This may have been the first job that when I look back over the years I am absolutely shocked with how much I have been exposed to and learned. I couldn't be more grateful towards everyone I work with. After all, MadGlory would be nothing if it wasn't for the people that work there and I am glad to call them my family.</p> <p>MadGlory opened its doors only <a href="/articles/farewell-to-harvard-and-onward-to-madglory/">3 years ago</a>, but over those years we have gone from a 4 person team with no office and a few clients to a team of 20+ in our now 2nd office. We are starting to outgrow that now too. I think the next step is just just build our own office campus, but that is just my opinion. ;-)</p> <p>I remember when I started that I was just this ColdFusion developer with some WordPress, Drupal, and PHP experience. Barely used git and GitHub or any source control for that matter. Apache was the web server of choice and manually configured VMWare machines was the architecture of choice. I thought I had a good handle on engineering on the web. I was sadly mistaken. Fast forwarding today the list things I've learned is almost exhausting to talk about and it is growing every day. Never in a million years would I have thought that I would have a chance to write and successfully launch an Xbox 360 app driven by a Node.js API I helped design, engineer, and deploy, all in 5 months, but that just happened this week. By the way, there is also an <a rel="external" href="https://itunes.apple.com/us/app/poker-central/id1039048620?mt=8">iOS</a>, <a rel="external" href="https://play.google.com/store/apps/details?id=com.oddworks.poker">Android</a>, <a rel="external" href="http://www.amazon.com/Poker-Central-LLC/dp/B014JRYJP6">FireTV</a>, <a rel="external" href="https://channelstore.roku.com/details/68165/poker-central">Roku</a>, and <a rel="external" href="https://www.pokercentral.com/find-us/xbox-one">XboxOne</a> app that was also launched powered by the same API as well.</p> <p><iframe width="480" height="360" src="https://www.youtube.com/embed/4BQLE_RrTSU" frameborder="0" allowfullscreen></iframe></p> <p>Things are only going to get bigger and better from here on out.</p> Side Projects 2015-02-27T00:00:00+00:00 2015-02-27T00:00:00+00:00 Unknown https://blainsmith.com/articles/side-projects/ <p>I've been hearing a lot of chatter about <a rel="external" href="https://twitter.com/search?q=%23sideprojects&amp;src=typd">side projects</a> lately. First was from fellow Glory Lord, <a rel="external" href="https://twitter.com/mubashariqbal">Mubs</a>, when he gave a talk at <a rel="external" href="http://sharatogatechtalks.com">Sharatoga TechTalks</a> in February 2015. He is basically the king of side projects north of Albany, NY as far as I am concerned. So what he had to say was very inspiring.</p> <p>A few weeks later Twitter started getting more activity about side projects and then one very popular dev's tweet came into my timeline about his side projects. <a rel="external" href="https://twitter.com/antirez">Salvatore Sanfilippo</a> aka "Antirez", the creator of <a rel="external" href="http://redis.io">Redis</a>, wrote about his list of <a rel="external" href="http://antirez.com/news/86">side projects</a>. Yes Redis started as a side project and now look at where it is today. Pretty amazing.</p> <p>In the spirit of side projects I decided to add a new <a href="/projects">"Side Projects"</a> page to my site that listed some of the bigger efforts I have completed. There is still a bunch of other projects I have on my <a rel="external" href="https://github.com/blainsmith">GitHub profile</a>, but these are the ones I felt deserved to be featured on my side.</p> <blockquote> <p>"Side projects are the projects making your bigger projects possible. Moreover they are often the start of new interesting projects."</p> <p>- Salvatore Sanfilippo</p> </blockquote> Single Command Static File Web Server 2014-03-12T00:00:00+00:00 2014-03-12T00:00:00+00:00 Unknown https://blainsmith.com/articles/single-command-static-file-web-server/ <p>I have been building a lot of Javascript apps lately. I started to prototype one recently, but I did not want to set up an Apache host or a connect server to run it. I found a little gem of a command that was already installed on OSX.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>~/Projects/static-app &gt; python -m SimpleHTTPServer</span></span></code></pre> <p>This was the perfect solution for me. <code>SimpleHTTPServer</code> is a Python library and when you send that to Python with the <code>-m</code> flag it tells Python to run the library as a script. Running this within the root of your static file project just fires up a web server at <a href="http://localhost:8000">http://localhost:8000</a>.</p> <p>There is also a PHP version that will actually read a <code>.htaccess</code> file so you don't have to use Apache.</p> <pre class="giallo" style="color: #BFBDB6; background-color: #0D1017;"><code data-lang="plain"><span class="giallo-l"><span>~/Projects/static-app &gt; php -S localhost:8000</span></span></code></pre> <p>This is perfect for:</p> <ul> <li>Wireframing and protoyping</li> <li>Javascript apps</li> <li>Designers converting their PSDs into static templates who don't want to deal with build tools right away</li> </ul> <p>The next time you need something really basic to run a HTML/CSS/JS app try it out for a quick win.</p> Farewell to Harvard and Onward to MadGlory 2012-10-15T00:00:00+00:00 2012-10-15T00:00:00+00:00 Unknown https://blainsmith.com/articles/farewell-to-harvard-and-onward-to-madglory/ <p>Well believe it or not after 7 memorable years working in the <a rel="external" href="http://www.hsph.harvard.edu/it">Harvard School of Public Health Information Technology Department</a> I have decided to leave. I will be taking up my new post at <a rel="external" href="http://madglory.com">MadGlory Interactive</a> as Chief Architect. MadGlory is a web software company that creates awesome stuff for the video gaming and entertainment industry. This new direction is bitter sweet, but it was an opportunity I couldn't pass up. The guys that are working in the company are some of the smartest and creative people I know. I needed to become apart of their, now our, growing company.</p> <p>Over the past years at Harvard I have had the pleasure of working with some really great people throughout the Harvard community. A few noteable friendships are Kevin Wnek who I run <a rel="external" href="http://adjacentconcepts.com">Adjacent Concepts</a> with, Tina Haupert who blogs over at <a rel="external" href="http://carrotsncake.com">Carrots N' Cake</a>, and my boss, Deane Eastwood. Kevin and I actually redesigned his <a rel="external" href="http://bellevuegolfclub.com">golf club's website</a> so I will always be in touch with him.</p> <p>On top of meeting so many great people I had numerous opportunities to expand my skillset. I entered knowing a few programming languages, but I am leaving knowing a lot more. My writing, communication, and project management skills improved dramatically. I was also able to get down and dirty with server admin work which I never thought I would get into.</p> <p>Harvard gave me a lot over my years so I wanted to take this opportunity to thank them very much and wish them the best on all of their future projects. I may be leaving, but I will never be gone.</p> Convention Over Configuration Web Servers 2012-07-13T00:00:00+00:00 2012-07-13T00:00:00+00:00 Unknown https://blainsmith.com/articles/convention-over-configuration-web-servers/ <p>I am seeing more and more bootstrap projects for HTML, CSS, PHP, and the like. This is great because it really removes a lot of the configuration time when starting a new project so you can get down to business. Even frameworks for Ruby and PHP have basic web app skeletons in place before you being working.</p> <p>With both front end and middleware levels covered its time we focus our efforts on the backend servers that power these apps. Too often I find myself manually installing and configuring a server.</p> <ol> <li>Create the VM</li> <li>Install Linux</li> <li>Install Apache</li> <li>Install MySQL</li> <li>Install PHP</li> <li>Remove mod_php and replace with mod_fcgi</li> <li>Install PHP accelerator (XCache, APC, etc.)</li> <li>Install content caching (Redis, Memcache, etc.)</li> <li>Finally install WordPress, Drupal, or being working on an app</li> </ol> <p>Seems like a lot of shit to deal with when a lot of these programs and settings are becoming standard, especially for high traffic websites. Why would we build a website expecting low traffic anyways? Some sites will not need the more advanced caching settings, but why not have them there and ready to go if needed.</p> <p>A few places have started to embrace the need for a single install web server. <a rel="external" href="http://www.turnkeylinux.org">TurnKey Linux</a> and <a rel="external" href="http://bitnami.org">BitNami</a> offer a wide range server stacks that you can download, install, and a lot of the configurations done.</p> <h3 id="turnkey-linux-lamp-stack"><a rel="external" href="http://www.turnkeylinux.org/lampstack">TurnKey Linux LAMP Stack</a></h3> <ul> <li>SSL support out of the box.</li> <li>PHP, Python and Perl support for Apache2 and MySQL.</li> <li>XCache - PHP opcode caching acceleration.</li> <li>PHPMyAdmin administration frontend for MySQL (listening on port 12322 - uses SSL).</li> <li>Postfix MTA (bound to localhost) to allow sending of email from web applications (e.g., password recovery)</li> <li>Webmin modules for configuring Apache2, PHP, MySQL and Postfix.</li> </ul> <p>This stack is also available with <a rel="external" href="http://www.turnkeylinux.org/lapp">PostgreSQL</a> instead of MySQL.</p> <p>There are also many others for Ruby on Rails, Tomcat, and Java. More specific web app versions for Drupal, WordPress, Redmine, etc. are also available.</p> <p>Once you get up and running with the configuration you need then you can certainly take a snapshop of that web server and deploy it as many times as you need. However, for us jack of all trade types it would be nice to see this taken a step further to include other emerging technologies and best practices. My wish list includes:</p> <ul> <li>Nginx</li> <li>Node.js</li> <li>Redis and Memcache</li> <li>Optimized WordPress/Drupal with caching and tuned PHP</li> <li>GitLabHQ</li> </ul> <p>It only makes you a better programmer when you understand the underlying powerhouse of your app, but sometimes you don't give a shit and just want to build an app without messing around in web server configuration hell. I hope to see TurnKey Linux and BitNami thrive that more and more flavors become offered.</p> <p>Happy Friday the 13th!</p>