Blain SmithZola2026-04-16T00:00:00+00:00https://blainsmith.com/atom.xmlUnbounded Concurrency is a Foot Gun2026-04-16T00:00:00+00:002026-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;"> // <-- 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'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;"> <</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'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;"> <-</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'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;"> <-</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 "no more work." 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'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;"> := <-</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 Fairness2026-04-13T00:00:00+00:002026-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., & 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. & 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., & 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., &
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., & 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., & 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 Goose2026-04-07T00:00:00+00:002026-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;"> 'active'</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;"> "2"</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;"> "postgresql"</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> queries</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "sql/queries"</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> schema</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "sql/migrations"</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;"> "db"</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> out</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "db"</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> sql_package</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "pgx/v5"</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;"> "time"</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;"> "context"</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;">&</span><span>i.ID,</span><span style="color: #F29668;"> &</span><span>i.Name,</span><span style="color: #F29668;"> &</span><span>i.Status,</span><span style="color: #F29668;"> &</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:"id" doc:"Widget ID" example:"42"`</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:"id" example:"42" doc:"Widget ID"`</span></span>
<span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"name" example:"Sprocket" doc:"Widget name"`</span></span>
<span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"status" example:"active" doc:"Widget status"`</span></span>
<span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"created_at" example:"2025-04-07T12:00:00Z" doc:"Creation timestamp"`</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:"name" required:"true" maxLength:"255" doc:"Widget name"`</span></span>
<span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"status" required:"true" enum:"active,inactive" doc:"Widget status"`</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:"id"`</span></span>
<span class="giallo-l"><span> Name</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"name"`</span></span>
<span class="giallo-l"><span> Status</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"status"`</span></span>
<span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"created_at"`</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;"> "context"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "fmt"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "log"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "net/http"</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "github.com/danielgtaylor/huma/v2"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "github.com/danielgtaylor/huma/v2/adapters/humago"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "github.com/go-chi/chi/v5"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "github.com/jackc/pgx/v5/pgxpool"</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span> gendb</span><span style="color: #AAD94C;"> "my-api/db"</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;"> "postgres://localhost:5432/myapi"</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;">"My API"</span><span>,</span><span style="color: #AAD94C;"> "1.0.0"</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;"> "/widgets/{id}"</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;">"widget not found"</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;"> := &</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;">"2006-01-02T15:04:05Z"</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;"> "/widgets"</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;">"creating widget: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">"</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;"> := &</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;">"2006-01-02T15:04:05Z"</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;"> "/widgets"</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;"> "/widgets/{id}"</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;">"listening on :8080"</span><span>)</span></span>
<span class="giallo-l"><span> http.</span><span style="color: #FFB454;">ListenAndServe</span><span>(</span><span style="color: #AAD94C;">":8080"</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 "</span><span style="color: #AAD94C;">$(</span><span>DATABASE_URL</span><span style="color: #AAD94C;">)</span><span>" 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 "</span><span style="color: #AAD94C;">$(</span><span>DATABASE_URL</span><span style="color: #AAD94C;">)</span><span>" 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 Hare2026-03-28T00:00:00+00:002026-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(&obj, "hello", "world")!;</span></span>
<span class="giallo-l"><span> json::set(&obj, "foo", "bar")!;</span></span>
<span class="giallo-l"><span> json::set(&obj, "the answer", 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 Rules2026-03-23T00:00:00+00:002026-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 Machine2026-03-13T00:00:00+00:002026-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 Person2026-02-13T00:00:00+00:002026-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 Old2026-01-13T00:00:00+00:002026-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 Manual2025-11-19T00:00:00+00:002025-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 Servers2025-10-31T00:00:00+00:002025-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;">"Content-Type"</span><span>) {</span></span>
<span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> "application/json"</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;"> "application/xml"</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;"> &</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;">"Accept"</span><span>) {</span></span>
<span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> "application/json"</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;"> "application/xml"</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;">"Content-Type"</span><span>) {</span></span>
<span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> "application/json"</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;">&</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;"> "application/xml"</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;">&</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;">"Accept"</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;">"Content-Type"</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;"> "application/json"</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;"> "application/xml"</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;"> &</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;">"Content-Type"</span><span>) {</span></span>
<span class="giallo-l"><span style="color: #FF8F40;"> case</span><span style="color: #AAD94C;"> "application/json"</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;">&</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;"> "application/xml"</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;">&</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;">&</span><span style="color: #59C2FF;">mockLogger</span><span>{},</span><span style="color: #F29668;"> &</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;">`{"title":"Article Title","contents":"This is the contents of the article"}`</span><span>)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: #5A6673;font-style: italic;"> // The "/" path does not matter since we'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;"> "/"</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;">"Content-Type"</span><span>:</span><span style="color: #AAD94C;"> "application/json"</span><span>)</span></span>
<span class="giallo-l"><span> r.Header.</span><span style="color: #FFB454;">Set</span><span>(</span><span style="color: #AAD94C;">"Accept"</span><span>:</span><span style="color: #AAD94C;"> "application/json"</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;">"expected 200 response code"</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;"> "id"</span><span>) {</span></span>
<span class="giallo-l"><span> t.</span><span style="color: #FFB454;">Fail</span><span>(</span><span style="color: #AAD94C;">"expected response to contain generated article id"</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;"> "request"</span><span>,</span><span style="color: #AAD94C;"> "method"</span><span>, r.Method,</span><span style="color: #AAD94C;"> "path"</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;">"Authorization"</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;"> " "</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;"> "not authorized"</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;"> "Bearer"</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;"> "invalid authorization method"</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;"> "not authorized"</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;">"GET /articles"</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;">"GET /articles/{id}"</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;">"POST /articles"</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;">"DELETE /articles/{id}"</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;"> "slog"</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;"> "zap"</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;"> "postgresql"</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;"> "in-memory"</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;"> "s3"</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;"> "scp"</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;"> "redis"</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;"> "in-memory"</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;"> "bypass"</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;"> ":8080"</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;"> <-</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;"> "server shutdown"</span><span>,</span><span style="color: #AAD94C;"> "error"</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;"> "server stopped listening"</span><span>,</span><span style="color: #AAD94C;"> "error"</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;"> <-</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;"> "sample request"</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;"> "/articles"</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;">"failed to get expected status code"</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 Care2025-10-28T00:00:00+00:002025-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 Relative2025-09-30T00:00:00+00:002025-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 Coding2025-09-12T00:00:00+00:002025-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 Complexity2025-08-18T00:00:00+00:002025-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 Tools2025-08-01T00:00:00+00:002025-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 Posture2025-07-23T00:00:00+00:002025-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 Teaching2025-07-10T00:00:00+00:002025-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 Example2025-07-02T00:00:00+00:002025-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 Go2025-04-23T00:00:00+00:002025-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;"> `<!DOCTYPE html></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"><html lang="en"></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <head></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <meta name="color-scheme" content="light dark" /></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <title>Human check</title></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> </head></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <body></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <main></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <form method="GET"></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <p></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <input type="checkbox" name="hash" value="</span><span style="color: #95E6CB;">%s</span><span style="color: #AAD94C;">" /><span> I am a human</span></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <button type="submit">Submit</button></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> </p></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> <p><em>this will set a dummy cookie (AI crawlers do not manage cookies)</em></p></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> </form></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> </main></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> </body></span></span>
<span class="giallo-l"><span style="color: #AAD94C;"></html>`</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;">"Accept"</span><span>),</span><span style="color: #AAD94C;"> "text/html"</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;">"hash"</span><span>)</span><span style="color: #BFBDB6B3;">;</span><span> hash</span><span style="color: #F29668;"> !=</span><span style="color: #AAD94C;"> ""</span><span> {</span></span>
<span class="giallo-l"><span> http.</span><span style="color: #FFB454;">SetCookie</span><span>(w,</span><span style="color: #F29668;"> &</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;"> "/"</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;">"User-Agent"</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;"> "failed to hash cookie value"</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;">"</span><span style="color: #95E6CB;">%x</span><span style="color: #AAD94C;">"</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;"> "failed to render form"</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;"> "failed to render form"</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;"> "bot-blocker-5000"</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;">"s3cr3t!"</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;">"/unprotected"</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;">"/protected"</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;">":8080"</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;"> "I am unprotected"</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;"> "I am protected"</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-op2025-01-17T00:00:00+00:002025-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 Divide2024-03-25T00:00:00+00:002024-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><font></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>
Done2024-03-19T00:00:00+00:002024-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-httpd2024-03-01T00:00:00+00:002024-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 Hare2023-10-06T00:00:00+00:002023-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 < 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 Gateway2022-07-29T00:00:00+00:002022-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;"> "proto3"</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;"> "google/protobuf/empty.proto"</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;"> "github.com/blainsmith/grpc-gateway-openapi-example/gen/proto/go/protos;protos"</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;"> "context"</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "github.com/blainsmith/grpc-gateway-openapi-example/gen/protos/go/protos"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "google.golang.org/grpc/codes"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "google.golang.org/grpc/status"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "google.golang.org/protobuf/types/known/emptypb"</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;"> "method Create not implemented"</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;"> "method Read not implemented"</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;"> "method Update not implemented"</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;"> "method Delete not implemented"</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;">"tcp"</span><span>,</span><span style="color: #AAD94C;"> ":9090"</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;">"failed to listen: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">"</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;"> &</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;">"failed to serve: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">"</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;"> "proto3"</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;"> "google/api/annotations.proto"</span><span style="color: #BFBDB6B3;">;</span></span>
<span class="giallo-l"><span style="color: #FF8F40;">import</span><span style="color: #AAD94C;"> "google/protobuf/empty.proto"</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;"> "github.com/blainsmith/grpc-gateway-openapi-example/gen/proto/go/protos;protos"</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;"> "/create"</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;"> "/read"</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;"> "/update"</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;"> "/delete"</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 "mux".</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;">"tcp"</span><span>,</span><span style="color: #AAD94C;"> ":9090"</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;">"failed to listen: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">"</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;"> &</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;">":9090"</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;">"fail to dial: </span><span style="color: #95E6CB;">%v</span><span style="color: #AAD94C;">"</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;">"/"</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;">":8080"</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;"> "swagger"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "2.0"</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "info"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "title"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "protos/protos/service.proto"</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "version"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "version not set"</span></span>
<span class="giallo-l"><span> }</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "tags"</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span>
<span class="giallo-l"><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "name"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "ExampleService"</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;"> "consumes"</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "application/json"</span></span>
<span class="giallo-l"><span> ]</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "produces"</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "application/json"</span></span>
<span class="giallo-l"><span> ]</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "paths"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "/create"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "post"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "operationId"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "ExampleService_Create"</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "responses"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "200"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "description"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "A successful response."</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "schema"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "type"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "object"</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "properties"</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;"> "default"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "description"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "An unexpected error response."</span><span style="color: #BFBDB6B3;">,</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "schema"</span><span style="color: #BFBDB6B3;">:</span><span> {</span></span>
<span class="giallo-l"><span style="color: #39BAE6;"> "$ref"</span><span style="color: #BFBDB6B3;">:</span><span style="color: #AAD94C;"> "#/definitions/rpcStatus"</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;"> "tags"</span><span style="color: #BFBDB6B3;">:</span><span> [</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "ExampleService"</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;">"/"</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;">"/swagger-ui/swagger.json"</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;"> "./gen/protos/service.swagger.json"</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;">"/swagger-ui/"</span><span>, http.</span><span style="color: #FFB454;">StripPrefix</span><span>(</span><span style="color: #AAD94C;">"/swagger-ui/"</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;">"./swagger-ui"</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;">"localhost:8080"</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;"> "swagger.json"</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;"> '#swagger-ui'</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 Hare2022-05-10T00:00:00+00:002022-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'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, &buf, &src, &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("{}:{} says {}", 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 < 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("client conn accepted");</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 < 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("client conn added to position {}", 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 < MAX_CLIENTS; i+=1) {</span></span>
<span class="giallo-l"><span> if (fds[i].events > 0 && (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'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(&buffered)) {</span></span>
<span class="giallo-l"><span> </span></span>
<span class="giallo-l"><span> // if a line is read then grab the client'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 =></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("{}:{} says {}", ip::string(peer.0), peer.1, strings::fromutf8(line))!;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span> io::writeall(&buffered, line)!;</span></span>
<span class="giallo-l"><span> bufio::flush(&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 =></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 =></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>
402022-01-13T00:00:00+00:002022-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 For2021-11-29T00:00:00+00:002021-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 & UDP</h2>
<p>I read the <a rel="external" href="https://datatracker.ietf.org/doc/rfc760/">IP</a> & <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 Clients2021-11-09T00:00:00+00:002021-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;"> &</span><span>httpClient,</span></span>
<span class="giallo-l"><span> Endpoint:</span><span style="color: #AAD94C;"> "https://example.com/infogram"</span><span>,</span></span>
<span class="giallo-l"><span> APIKey:</span><span style="color: #AAD94C;"> "api-key"</span><span>,</span></span>
<span class="giallo-l"><span> APISecret:</span><span style="color: #AAD94C;"> "api-secret"</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;"> "api-key"</span><span>,</span></span>
<span class="giallo-l"><span> APISecret:</span><span style="color: #AAD94C;"> "api-secret"</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;">"id"</span><span>]</span><span style="color: #F29668;"> =</span><span> t.Id</span></span>
<span class="giallo-l"><span> data[</span><span style="color: #AAD94C;">"title"</span><span>]</span><span style="color: #F29668;"> =</span><span> t.Title</span></span>
<span class="giallo-l"><span> data[</span><span style="color: #AAD94C;">"thumbnail_url"</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;">"id"</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;">"id needs to be an int"</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;">"title"</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;">"title needs to be an string"</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;">"thumbnail_url"</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;">"thumbnail_url needs to be an string"</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;">"thumbnail_url needs to be a parsable URL"</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=<token></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;"> "http://exmaple.com/api/v1?api_key=<token>"</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 <token></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;"> "http://exmaple.com/api/v1"</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;">"Authorization"</span><span>,</span><span style="color: #AAD94C;"> "Bearer <token>"</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;">"api_key"</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;">'</span><span style="color: #95E6CB;">&</span><span style="color: #AAD94C;">'</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;">'</span><span style="color: #95E6CB;">&</span><span style="color: #AAD94C;">'</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;">"api_sig"</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;"> ""</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;"> ></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;">"http error code: </span><span style="color: #95E6CB;">%d</span><span style="color: #AAD94C;">"</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;">"</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;">"</span><span>, c.Endpoint,</span><span style="color: #AAD94C;"> "infographics"</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;">"new infographic request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">"</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;"> &</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;">"performing infographics request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">"</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;"> &</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;">"</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;">"</span><span>, client.Endpoint,</span><span style="color: #AAD94C;"> "infographics"</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;">"new infographic PDF reader request: </span><span style="color: #95E6CB;">%w</span><span style="color: #AAD94C;">"</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;"> "pdf"</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;"> "png"</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;"> "html"</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;">"infographic-100.pdf"</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'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:002021-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 Hard2021-06-15T00:00:00+00:002021-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 Zola2021-03-06T00:00:00+00:002021-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>> 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 => 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 -> 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 => 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 -> 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 Bash2021-02-18T00:00:00+00:002021-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;"> "Runs a specified benchmark test func and opens the profile in Chrome.\n\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "Usage:\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "\t./bench.sh <cpu|mem> <dir> <function>\n\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "Example:\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "\t./bench.sh cpu ./internal BenchmarkBinaryEncoding\n\n"</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;"> "Runs a specified benchmark test func and opens the trace profile in Chrome.\n\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "Usage:\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "\t./trace.sh <dir> <function>\n\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "Example:\n"</span></span>
<span class="giallo-l"><span style="color: #F07178;"> printf</span><span style="color: #AAD94C;"> "\t./trace.sh ./internal BenchmarkBinaryEncoding\n\n"</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>> ./scripts/bench.sh</span></span>
<span class="giallo-l"><span>Runs a specified benchmark test in app'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 <cpu|mem> <dir> <function></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>> ./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>> ./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 Protocols2020-09-01T00:00:00+00:002020-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><metics_path> <metics_value> <metrics_timestamp></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><measurement>[,<tag_set>...] <field_set>... <timestamp></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><THE HTML OF THIS ARTICLE></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 Time2020-06-23T00:00:00+00:002020-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 Degree2020-05-10T00:00:00+00:002020-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/ed255192019-12-07T00:00:00+00:002019-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;"> "EdDSA"</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;"> ""</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;"> ""</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;"> &</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;"> &</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;"> &</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;">&</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;"> "urn:ed25519-jwt"</span><span>,</span></span>
<span class="giallo-l"><span> Subject:</span><span style="color: #AAD94C;"> "[email protected]"</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 collectd2018-08-03T00:00:00+00:002018-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><Plugin syslog></span></span>
<span class="giallo-l"><span> LogLevel error</span></span>
<span class="giallo-l"><span></Plugin></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><Plugin csv></span></span>
<span class="giallo-l"><span> DataDir "/var/lib/collectd/csv"</span></span>
<span class="giallo-l"><span> StoreRates true</span></span>
<span class="giallo-l"><span></Plugin></span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span># Configure the rrdtool plugin</span></span>
<span class="giallo-l"><span><Plugin rrdtool></span></span>
<span class="giallo-l"><span> DataDir "/var/lib/collectd/rrd"</span></span>
<span class="giallo-l"><span></Plugin></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=<HOST_FOLDER></code>.</p>
<p>Enjoy and reach out to me if you have any questions.</p>
Go Made Me Return to College2017-09-12T00:00:00+00:002017-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 Hustle2017-02-06T00:00:00+00:002017-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:002017-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:"action"`</span></span>
<span class="giallo-l"><span> Details</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"details"`</span></span>
<span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"createdAt"`</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;"> "sync"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "writer-app/writers"</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;">&</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;">&</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;">&</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;"> := &</span><span style="color: #59C2FF;">writers</span><span>.</span><span style="color: #59C2FF;">Event</span><span>{</span><span style="color: #AAD94C;">"load"</span><span>,</span><span style="color: #AAD94C;"> "the app loaded"</span><span>,</span><span style="color: #AAD94C;"> "2017-02-01T00:00:00Z"</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;"> "container/list"</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:"action"`</span></span>
<span class="giallo-l"><span> Details</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"details"`</span></span>
<span class="giallo-l"><span> CreatedAt</span><span style="color: #39BAE6;"> string</span><span style="color: #AAD94C;"> `json:"createdAt"`</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;"> "sync"</span></span>
<span class="giallo-l"><span style="color: #AAD94C;"> "writer-app/writers"</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;">&</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;">&</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;">&</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;"> := &</span><span style="color: #59C2FF;">writers</span><span>.</span><span style="color: #59C2FF;">Event</span><span>{</span><span style="color: #AAD94C;">"load"</span><span>,</span><span style="color: #AAD94C;"> "the app loaded"</span><span>,</span><span style="color: #AAD94C;"> "2017-02-01T00:00:00Z"</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 MadGlory2015-10-07T00:00:00+00:002015-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 Projects2015-02-27T00:00:00+00:002015-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&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 Server2014-03-12T00:00:00+00:002014-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 > 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 > 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 MadGlory2012-10-15T00:00:00+00:002012-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 Servers2012-07-13T00:00:00+00:002012-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>