<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://theothermattm.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://theothermattm.github.io//" rel="alternate" type="text/html" /><updated>2026-04-28T01:59:51+00:00</updated><id>https://theothermattm.github.io//feed.xml</id><title type="html">Matt @ Work</title><subtitle>Clean Code + Coffee == Happy Developers </subtitle><entry><title type="html">Admitting Fault and the AITA? Reflex</title><link href="https://theothermattm.github.io//admitting-fault-aita" rel="alternate" type="text/html" title="Admitting Fault and the AITA? Reflex" /><published>2024-06-13T00:00:00+00:00</published><updated>2024-06-13T00:00:00+00:00</updated><id>https://theothermattm.github.io//admitting-fault</id><content type="html" xml:base="https://theothermattm.github.io//admitting-fault-aita"><![CDATA[<p>A while back, I was having a conversation with a fellow engineering manager about a particularly tense encounter he had with a coworker. The details of that encounter have faded, but I remember someone was throwing blame at my peer. During his story, he said this, which resonated with me:</p>

<blockquote>
  <p>“Maybe I’m crazy, but in those type of situations, I immediately step back and see if I did something wrong.”</p>
</blockquote>

<p>I made my agreement visible by nodding vigorously. My colleague accurately described my reflex in many situations where something goes wrong or I’m being blamed.</p>

<p><strong>I call it the “Am I the A**shole? Reflex”</strong> (The AITA? Reflex). There is (somewhat) famous subreddit called <a href="https://www.reddit.com/r/AmItheAsshole/">r/AmITheA**hole?</a> where anyone can post about an experience where someone was being an a**hole to them and reflect on the question “…But am I the a**hole here?”. The stories range from hilarious to genuinely intriguing, sometimes resulting in thought-provoking responses that cause the poster to realize that they are, indeed, the a**hole.</p>

<p>My own experience with the AITA? Reflex has been an emotional rollercoaster. For a long, long time, I didn’t even think about it. Then, when I became a manager for the first time, I became concerned that this was because of a lack of self-confidence and that this instinct needed to be overcome.</p>

<p><strong>With time, I’ve realized that asking, “Is this my fault?” before reacting is extremely important when leading any team or project.</strong></p>

<p>It may take seconds to answer, <em>“Most definitely, not my fault.”</em> But if you have a hint of doubt that it could be your fault, you need to pull on that thread and start asking questions and self-reflecting.</p>

<p>The last thing you want to do to peers and talent is show that you can’t own up to your mistakes. If you give into the fear of admitting fault, you set a toxic example for your organization: shifting blame is the norm. You encourage a culture of covering up mistakes rather than figuring out how to prevent them.</p>

<p>To avoid this, I often ask myself, <em>“Could I have done something to prevent this?”</em> If the answer is yes, I ask, <em>“Was doing that thing reasonable to expect of myself?”</em></p>

<p>Say something immediately if it was your fault and it was reasonable to prevent it. If you know what could have prevented it, say it. Say what you’ll do to prevent it from happening again, or say that you’ll follow up with that information soon.</p>

<p>In a group setting, admitting that a situation went south because of you can immediately de-escalate a tense room. At the same time, it sets an example that it’s okay to admit fault and that we, as a team, are less concerned about blame and more about learning and moving on.</p>

<p>When you admit fault one on one, you can have a potent conversation about the nuances of why you may have screwed up. You can use it as a learning moment to underscore what you’ll do to remedy the situation and prevent it from happening again. It can be an essential moment to bond with a peer or report and build rapport.</p>

<p>If I couldn’t have prevented something bad from happening, or it wasn’t reasonable to do so, I start reflecting a little deeper: )<em>“Is it possible that others could simply perceive this as your fault even if it wasn’t? Why?”</em></p>

<p>When you’re in leadership, perception is often reality. So, knowing how your actions are perceived is just as important as the actions themselves. In these situations, it’s important not to blame someone else but to calmly give as much context as possible.</p>

<p>What you shouldn’t do is point the blame. <strong>Pointing the blame elsewhere is the only thing worse than not admitting fault.</strong> If after giving that context, others don’t understand that it’s not your fault, then you have the choice of agreeing to disagree or <a href="https://en.wiktionary.org/wiki/fall_on_one%27s_sword">falling on the sword</a> in order to move on. Taking the blame when it’s not your fault requires a careful cost/benefit analysis; you don’t want to hurt yourself. That’s a subject for another post…</p>

<h1 id="tips-on-admitting-fault">Tips on Admitting Fault</h1>

<p>If you struggle with admitting fault, knowing your audience is critical. For some situations and people, humor works. Sometimes, specific actions are necessary to show it won’t happen again, especially if you’re managing up. Sometimes, just being sincere is the best default.</p>

<p>Here’s some ideas that might help.</p>

<h2 id="simple-sincerity">Simple sincerity</h2>

<p>“I’m feeling pretty embarrassed right now. That was my fault, I didn’t [y] and should have [y], my apologies. Let me figure out how I can prevent that from happening again.”</p>

<h2 id="start-humorously-pivot-to-sincerity">Start humorously, pivot to sincerity</h2>

<p>“Well, I seem to have done it again. I’ve failed you! Can you forgive me?! [… pivot to sincerity ] But, really, I am sorry, I won’t let that happen again. Can we talk about how I can help fix it?”</p>

<h2 id="specificity">Specificity</h2>

<p>“This is definitely my fault. I did not [a] and should have [b]. I’m going to figure out how to fix this, but I believe that [doing c, d, and e] will probably help. In the future, I will [f, g and h] to make sure it doesn’t happen again.”</p>

<h2 id="empathy">Empathy</h2>

<p>“This was my fault. I can tell that you’re really [upset/frustrated/insert emotion here] because of it and I’m really sorry. I’m going to try to fix it by [a, b and c]. How does that sound? Do you want to talk about it more?”</p>

<h1 id="go-forth-and-check-yourself">Go forth and check yourself</h1>

<p>Especially right now, in a bad tech job market where people are tense and worried about getting laid off, we need to remember to admit fault when we screwed up and set a good example for others.</p>

<p>Always question your actions in a healthy way. Don’t perseverate on it or let it get in the way of taking action, but also… Don’t be an a**hole.</p>]]></content><author><name>Matt</name></author><category term="management" /><category term="career" /><category term="fault" /><summary type="html"><![CDATA[A while back, I was having a conversation with a fellow engineering manager about a particularly tense encounter he had with a coworker. The details of that encounter have faded, but I remember someone was throwing blame at my peer. During his story, he said this, which resonated with me:]]></summary></entry><entry><title type="html">In Praise Of Logging (A Node.js/Javascript Logging Guide)</title><link href="https://theothermattm.github.io//in-praise-of-logging" rel="alternate" type="text/html" title="In Praise Of Logging (A Node.js/Javascript Logging Guide)" /><published>2023-02-23T00:00:00+00:00</published><updated>2023-02-23T00:00:00+00:00</updated><id>https://theothermattm.github.io//in-praise-of-logging</id><content type="html" xml:base="https://theothermattm.github.io//in-praise-of-logging"><![CDATA[<p>I often find myself leaving comments in pull requests about logging. Usually about adding logging where they might be helpful for production troubleshooting, or changing their levels to be more appropriate for production. Understandably, when you’re under pressure to hit a deadline, logging can fall to the wayside. I find that most people don’t share my love of logging and why it can be great. Done well, it can save you a ton of time and headaches.</p>

<p>In this post, I’m going to outline some good practices I’ve learned along the way and (hopefully) convince you why and how you should love 🪵 logging 🪵 and why you should consider using a logging framework immediately instead of <code class="language-plaintext highlighter-rouge">console.log</code> statements.</p>

<p>I’ll also show by example what some of the most popular node logging libraries do well and how they stack up against what I’m suggesting.</p>

<p>I am going to be referring to code snippets and output from a sample project I put together called <a href="https://github.com/theothermattm/node-logging-examples">node-logging-examples</a>.  You can run this project over on <a href="https://replit.com/@theothermattm/node-logging-examples#README.md">Replit</a> by using the “Shell” button at the bottom right.</p>
<h1 id="lessons-from-the-frontline---aka-why-should-i-listen-to-this-guy">Lessons from the Frontline - Aka “Why should I listen to this guy?”</h1>

<p>I’ve supported several server side applications. Including being on call for those applications. When you get woken up in the middle of the night for an alert, you want to make sure that your logs are watertight, otherwise, you’ll be water <em>logged</em> (GET IT!?). So watertight you can read them squinting in a dreamy haze! I know what to do, and probably more importantly what not to do.</p>

<p>It’s also worth knowing that I spent a lot of my career in the Java world (though not recently). Since this post is targeted at Javascript developers, reserve your judgment when I say: Java does logging <em>very</em> well. There are several battle-tested libraries that allow for out of the box ease, as well as endless customizability.</p>

<p>Okay, on with the show…</p>

<h2 id="use-a-framework-built-for-logging-not-consolelog">Use a framework built for logging, not <code class="language-plaintext highlighter-rouge">console.log</code></h2>

<p>Table stakes - The rest of this article will hopefully convince you why! I know some people will disagree, and for really really simple apps you can get away with it, but I can’t recall a case in my career where I’ve started with simple console.log statements and didn’t regret it later.</p>
<h2 id="separate-logging-configuration-from-logging-code">Separate logging configuration from logging code</h2>

<p>Whenever possible, you want the ability to dial logging levels up and down at granular levels without performing a code change. This could be via environment variables, or even through external configuration files or databases.</p>

<p>Either way, during a production incident, or even while debugging, being able to dial up or down logging levels with minimal risk is a lifesaver.</p>

<p>At a very minimal level, you can simply set a <code class="language-plaintext highlighter-rouge">LOG_LEVEL</code> environment variable which your logging setup inspects and sets the appropriate level.</p>

<p>Going to the more advanced levels, you might have an external configuration file that defines log levels.  Log4js is the only framework I saw with a specific <a href="https://log4js-node.github.io/log4js-node/api.html">option to configure logging with an external source</a> right out of the box.</p>

<p>You may be thinking: Of course, we set our log levels outside of code! However, I would ask if you can do it only for the areas of the application which need the logging tweaked?</p>

<p>Read on…</p>

<h2 id="configuring-logging-on-a-per-filemodule-basis">Configuring logging on a per file/module basis</h2>

<p>Being able to dial up logging levels in very specific parts of your application can help reduce a lot of noise when troubleshooting. Of course, modern log ingestion/viewing tools like Elasticsearch or Splunk allow you to do really advanced filtering, but what about when you just want to trace through what’s happening for a given user in your application? It’d be nice to see just a bit of logging for the parts of your application that aren’t having issues, but a ton for the areas of concern.</p>

<p>A lot of node.js frameworks do not have the ability to do this out of the box, instead relying on you to roll your own configuration.  Which has its drawbacks and benefits.</p>

<p>I’ve been programming in Node.js for years now, but still, one of the best features of <a href="https://logging.apache.org/log4j/2.x/">Log4j</a> (the de-facto standard Java logging library) I miss the most while programming in Node is being able to easily customize which modules are logging to what logging level.</p>

<p>As an example, these Java classes:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
  <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">Main</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
  
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Begin app ..."</span><span class="o">);</span>

<span class="nc">ExternalService</span><span class="o">.</span><span class="na">makeACall</span><span class="o">(</span><span class="s">"test request info"</span><span class="o">);</span>
    <span class="nc">FailingService</span><span class="o">.</span><span class="na">makeACall</span><span class="o">(</span><span class="s">"something that will fail"</span><span class="o">);</span>
    <span class="nc">InternalBusinessLogic</span><span class="o">.</span><span class="na">wickedImportantRules</span><span class="o">(</span><span class="s">"test input"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Completed app!"</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExternalService</span> <span class="o">{</span>
  <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">ExternalService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">makeACall</span><span class="o">(</span><span class="nc">String</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Making external service call"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"External Service Call Request {}"</span><span class="o">,</span> <span class="n">request</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FailingService</span> <span class="o">{</span>
  <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">FailingService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">makeACall</span><span class="o">(</span><span class="nc">String</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Making failing external service call"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Something went wrong in the call!"</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">InternalBusinessLogic</span> <span class="o">{</span>
  <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">InternalBusinessLogic</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>

  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">wickedImportantRules</span><span class="o">(</span><span class="nc">String</span> <span class="n">arg</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Running business rules"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Fixing Johnson rod"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">trace</span><span class="o">(</span><span class="s">"Arguments for business logic: {}"</span><span class="o">,</span> <span class="n">arg</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">trace</span><span class="o">(</span><span class="s">"More information here that's really low level"</span><span class="o">);</span>
    <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Done running business rules"</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Will output like this on the console (configuration dependent):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>22:20:44.283 [main] INFO  FailingService - Making failing external service call
22:20:44.285 [main] ERROR FailingService - Something went wrong in the call!
22:20:44.286 [main] INFO  InternalBusinessLogic - Running business rules
22:20:44.286 [main] DEBUG InternalBusinessLogic - Fixing Johnson rod
22:20:44.294 [main] TRACE InternalBusinessLogic - Arguments for business logic: test input
22:20:44.294 [main] TRACE InternalBusinessLogic - More information here that's really low level
22:20:44.294 [main] DEBUG InternalBusinessLogic - Done running business rules
</code></pre></div></div>
<p>You can dial logging levels and even separate outputs for individual files/classes. See <a href="https://replit.com/@theothermattm/JavaLoggingExample">this ReplIt</a> for a working example you can run.</p>

<p>I haven’t found any Javascript frameworks that allow this type of customizability, out of the box, however, some logging frameworks have the concept of categories or child loggers that allow you to do something <em>similar</em>.</p>

<p>For example, I was able to get Log4js to do this with a little bit of help from their <a href="https://log4js-node.github.io/log4js-node/categories.html">categories</a> API.</p>

<p>This is the type of output you see with my approach, which results in this type of output where the file name is listed before the message.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2023-02-13T10:58:00.168] [INFO] log4js/example-log4js.js - Syncing clients...
[2023-02-13T10:58:00.172] [DEBUG] log4js/log4jssubfolder/clientservice.js - Getting last run
[2023-02-13T10:58:00.174] [DEBUG] log4js/log4jssubfolder/clientservice.js - Getting clients updated since 2023-02-13T10:58:00
[2023-02-13T10:58:00.174] [INFO] log4js/example-log4js.js - Fetching clients updated since 2023-02-13T10:58:00
[2023-02-13T10:58:00.174] [DEBUG] log4js/log4jssubfolder/clientservice.js - Doing something really complicated
[2023-02-13T10:58:00.174] [ERROR] log4js/example-log4js.js - ERROR Calling Remote Service
[2023-02-13T10:58:00.174] [ERROR] log4js/example-log4js.js - Service Call Result []
[2023-02-13T10:58:00.174] [DEBUG] log4js/log4jssubfolder/anotherservice.js - Doing something really fun!
[2023-02-13T10:58:00.174] [DEBUG] log4js/log4jssubfolder/clientservice.js - Setting last run

</code></pre></div></div>

<p>And you can dial the level up and down with a glob pattern (see the <code class="language-plaintext highlighter-rouge">categories</code> block):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="dl">"</span><span class="s2">appenders</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">stdout</span><span class="dl">"</span> <span class="p">}</span> <span class="p">},</span>
  <span class="dl">"</span><span class="s2">categories</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">default</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">appenders</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span><span class="p">],</span> <span class="dl">"</span><span class="s2">level</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">info</span><span class="dl">"</span> <span class="p">},</span>
    <span class="dl">"</span><span class="s2">log4js/log4jssubfolder/**.js</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">appenders</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span><span class="p">],</span>
      <span class="dl">"</span><span class="s2">level</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">debug</span><span class="dl">"</span>
    <span class="p">},</span>
    <span class="dl">"</span><span class="s2">perf</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">appenders</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span><span class="p">],</span> <span class="dl">"</span><span class="s2">level</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">trace</span><span class="dl">"</span><span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>It would be great if this were built into log4js, but it’s fairly easy to see how it’s done (<a href="https://github.com/theothermattm/node-logging-examples/blob/main/log4js/log4jslogger.js">source here</a>). Here are the highlights:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">fileURLToPath</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">url</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">glob</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">glob</span><span class="dl">'</span><span class="p">;</span>


<span class="kd">const</span> <span class="nx">defaultOptions</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">ignore</span> <span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">],</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">getLog4JOptionsUnGlobbed</span><span class="p">(</span><span class="nx">globbedLog4JsOptions</span><span class="p">,</span> <span class="nx">globOptions</span><span class="p">)</span> <span class="p">{</span>

  <span class="kd">const</span> <span class="nx">mergedGlobOptions</span> <span class="o">=</span> <span class="p">{...</span><span class="nx">defaultOptions</span><span class="p">,</span> <span class="nx">globOptions</span><span class="p">};</span>
  <span class="kd">const</span> <span class="nx">unglobbedOptions</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">globbedLog4JsOptions</span><span class="p">));</span>
  <span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">category</span> <span class="k">in</span> <span class="nx">globbedLog4JsOptions</span><span class="p">.</span><span class="nx">categories</span> <span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">matchingFiles</span> <span class="o">=</span> <span class="nx">glob</span><span class="p">.</span><span class="nf">sync</span><span class="p">(</span><span class="nx">category</span><span class="p">,</span> <span class="nx">mergedGlobOptions</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span> <span class="nx">matchingFiles</span> <span class="o">&amp;&amp;</span> <span class="nx">matchingFiles</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span>
      <span class="nx">matchingFiles</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">file</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">unglobbedOptions</span><span class="p">.</span><span class="nx">categories</span><span class="p">[</span><span class="nx">file</span><span class="p">]</span> <span class="o">=</span> <span class="nx">globbedLog4JsOptions</span><span class="p">.</span><span class="nx">categories</span><span class="p">[</span><span class="nx">category</span><span class="p">];</span>
      <span class="p">})</span>
      <span class="k">delete</span> <span class="nx">unglobbedOptions</span><span class="p">.</span><span class="nx">categories</span><span class="p">[</span><span class="nx">category</span><span class="p">]</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">unglobbedOptions</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">createModuleLogger</span><span class="p">(</span><span class="nx">fileName</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// you can just use __filename if using commonjs modules! (eg require() format)</span>
  <span class="kd">const</span> <span class="nx">__filename</span> <span class="o">=</span> <span class="nf">fileURLToPath</span><span class="p">(</span><span class="nx">fileName</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">relativeModuleName</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">relative</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="nx">__filename</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">log4js</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">(</span><span class="nx">relativeModuleName</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can create new loggers like this in each of your modules/files:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// again, you don't need import.meta.url if you're using commonjs</span>
<span class="kd">const</span> <span class="nx">logger</span> <span class="o">=</span> <span class="nf">createModuleLogger</span><span class="p">(</span><span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
</code></pre></div></div>

<p>After that, we’re using log4js categories to add the filename for output and configuration. With a little help with some preprocessing the log4js config file with the <a href="https://www.npmjs.com/package/glob">glob</a> library, we can dial those levels up and down on a per file (or folder) basis.</p>

<h2 id="use-low-level-log-statements-instead-of-comments-to-document-code">Use low-level log statements instead of comments to document code</h2>

<p>You can use logging statements to simultaneously document your code <em>and</em> provide useful logging output that you can turn on or shut off with a logging framework.</p>

<p>Consider this simple example, ignore the details, they’re purposely opaque so you can pretend you’re in a new codebase:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">syncClientsWithoutLoggingFramework</span> <span class="o">=</span> <span class="k">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Syncing clients...</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">lastRun</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getLastRun</span><span class="p">();</span>
  <span class="kd">const</span> <span class="k">from</span> <span class="o">=</span> <span class="nx">lastRun</span> <span class="o">||</span> <span class="nf">moment</span><span class="p">().</span><span class="nf">subtract</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">days</span><span class="dl">'</span><span class="p">).</span><span class="nf">format</span><span class="p">(</span><span class="nx">DATE_FORMAT</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">timeOfLastFetch</span> <span class="o">=</span> <span class="nf">moment</span><span class="p">().</span><span class="nf">format</span><span class="p">(</span><span class="nx">DATE_FORMAT</span><span class="p">)</span>
  <span class="kd">const</span> <span class="nx">clients</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">clientsUpdatedSince</span><span class="p">(</span><span class="nx">timeOfLastFetch</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Fetching clients updated since </span><span class="p">${</span><span class="nx">timeOfLastFetch</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">clients</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">No updated clients.</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// call out to another service to modify the clients list</span>
  <span class="c1">// with some _really important business logic_</span>
  <span class="kd">const</span> <span class="nx">modifiedClients</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">doSomethingReallyComplicatedInAnotherService</span><span class="p">(</span><span class="nx">clients</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span> <span class="nx">modifiedClients</span> <span class="o">&lt;</span> <span class="mi">1</span> <span class="o">||</span> <span class="p">(</span><span class="nx">modifiedClients</span><span class="p">.</span><span class="nx">errors</span> <span class="o">&amp;&amp;</span> <span class="nx">modifiedClients</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">ERROR Calling Remote Service</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`Service Call Result </span><span class="p">${</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">modifiedClients</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="c1">// Store the last time we updated the clients.</span>
  <span class="k">await</span> <span class="nf">setLastRun</span><span class="p">(</span><span class="nf">moment</span><span class="p">())</span>
  <span class="k">return</span> <span class="nx">modifiedClients</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That results in this logging output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Syncing clients...
Fetching clients updated since 2023-01-23T17:25:18
ERROR Calling Remote Service
Service Call Result []
</code></pre></div></div>

<p>Well, it’s better than nothing! But I’m still kinda confused about what’s going on.</p>

<p>If we add a logging framework (here I’m going with <a href="https://github.com/pinojs/pino">Pino</a>) and use the lowest log level (in this case, <code class="language-plaintext highlighter-rouge">trace</code>), this is what the code looks like as we run it:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">logger</span> <span class="o">=</span> <span class="nf">pino</span><span class="p">({</span><span class="na">level</span><span class="p">:</span> <span class="dl">'</span><span class="s1">trace</span><span class="dl">'</span><span class="p">});</span>

<span class="kd">const</span> <span class="nx">syncClientsWithLoggingFramework</span> <span class="o">=</span> <span class="k">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="dl">'</span><span class="s1">Syncing clients...</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">CLIENT_LAST_RUN_KEY</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">CLIENT_LAST_RUN_DATE</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">lastRun</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getLastRun</span><span class="p">();</span>
  <span class="kd">const</span> <span class="k">from</span> <span class="o">=</span> <span class="nx">lastRun</span> <span class="o">||</span> <span class="nf">moment</span><span class="p">().</span><span class="nf">subtract</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">days</span><span class="dl">'</span><span class="p">).</span><span class="nf">format</span><span class="p">(</span><span class="nx">DATE_FORMAT</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">timeOfLastFetch</span> <span class="o">=</span> <span class="nf">moment</span><span class="p">().</span><span class="nf">format</span><span class="p">(</span><span class="nx">DATE_FORMAT</span><span class="p">)</span>
  <span class="kd">const</span> <span class="nx">clients</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">clientsUpdatedSince</span><span class="p">(</span><span class="nx">timeOfLastFetch</span><span class="p">);</span>

  <span class="nx">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Fetching clients updated since %s</span><span class="dl">"</span><span class="p">,</span> <span class="nx">timeOfLastFetch</span><span class="p">);</span>
  <span class="nx">logger</span><span class="p">.</span><span class="nf">trace</span><span class="p">(</span><span class="dl">"</span><span class="s2">Fetching clients with parameters from: %s and timeOfLastFetch: %s</span><span class="dl">"</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">timeOfLastFetch</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">clients</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">logger</span><span class="p">.</span><span class="nf">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">No updated clients.</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">logger</span><span class="p">.</span><span class="nf">debug</span><span class="p">(</span><span class="dl">"</span><span class="s2">Calling out to another service to do some really complicated business logic</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">modifiedClients</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">doSomethingReallyComplicatedInAnotherService</span><span class="p">(</span><span class="nx">clients</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span> <span class="nx">modifiedClients</span> <span class="o">&lt;</span> <span class="mi">1</span> <span class="o">||</span> <span class="p">(</span><span class="nx">modifiedClients</span><span class="p">.</span><span class="nx">errors</span> <span class="o">&amp;&amp;</span> <span class="nx">modifiedClients</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
    <span class="nx">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">ERROR Calling Remote Service</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`Service Call Result </span><span class="p">${</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">modifiedClients</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
  <span class="p">}</span>
  <span class="nx">logger</span><span class="p">.</span><span class="nf">trace</span><span class="p">(</span><span class="dl">"</span><span class="s2">Done calling out and doing important things.</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">logger</span><span class="p">.</span><span class="nf">trace</span><span class="p">(</span><span class="dl">"</span><span class="s2">Setting last run date so we know where to pick up next time.</span><span class="dl">"</span><span class="p">)</span>
  <span class="k">await</span> <span class="nf">setLastRun</span><span class="p">(</span><span class="nf">moment</span><span class="p">())</span>
  <span class="k">return</span> <span class="nx">modifiedClients</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is what the output looks like, and before you judge, hold up:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"level"</span><span class="p">:</span><span class="mi">30</span><span class="p">,</span><span class="nl">"time"</span><span class="p">:</span><span class="mi">1674503301467</span><span class="p">,</span><span class="nl">"pid"</span><span class="p">:</span><span class="mi">918</span><span class="p">,</span><span class="nl">"hostname"</span><span class="p">:</span><span class="s2">"f6572868d2b5"</span><span class="p">,</span><span class="nl">"msg"</span><span class="p">:</span><span class="s2">"Syncing clients..."</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="nl">"level"</span><span class="p">:</span><span class="mi">30</span><span class="p">,</span><span class="nl">"time"</span><span class="p">:</span><span class="mi">1674503301469</span><span class="p">,</span><span class="nl">"pid"</span><span class="p">:</span><span class="mi">918</span><span class="p">,</span><span class="nl">"hostname"</span><span class="p">:</span><span class="s2">"f6572868d2b5"</span><span class="p">,</span><span class="nl">"msg"</span><span class="p">:</span><span class="s2">"Fetching clients updated since 2023-01-23T19:48:21"</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="nl">"level"</span><span class="p">:</span><span class="mi">50</span><span class="p">,</span><span class="nl">"time"</span><span class="p">:</span><span class="mi">1674503301469</span><span class="p">,</span><span class="nl">"pid"</span><span class="p">:</span><span class="mi">918</span><span class="p">,</span><span class="nl">"hostname"</span><span class="p">:</span><span class="s2">"f6572868d2b5"</span><span class="p">,</span><span class="nl">"msg"</span><span class="p">:</span><span class="s2">"ERROR Calling Remote Service"</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="nl">"level"</span><span class="p">:</span><span class="mi">50</span><span class="p">,</span><span class="nl">"time"</span><span class="p">:</span><span class="mi">1674503301469</span><span class="p">,</span><span class="nl">"pid"</span><span class="p">:</span><span class="mi">918</span><span class="p">,</span><span class="nl">"hostname"</span><span class="p">:</span><span class="s2">"f6572868d2b5"</span><span class="p">,</span><span class="nl">"msg"</span><span class="p">:</span><span class="s2">"Service Call Result []"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You might think “But when I’m developing, this looks awful!” And you’re right.</p>

<p>But with a logging framework, you can easily configure your logs to be prettified and colorized for development, along with dialing the logging level up to get more information:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">logger</span> <span class="o">=</span> <span class="nf">pino</span><span class="p">(</span>
  <span class="p">{</span>
  <span class="na">level</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">trace</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">transport</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">pino-pretty</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">options</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">colorize</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Alternatively, Pino lets you use <code class="language-plaintext highlighter-rouge">pino-pretty</code> as a command line utility like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm start | pino-pretty
</code></pre></div></div>

<p>You can even put a separate <code class="language-plaintext highlighter-rouge">pino-prettyrc</code> config to customize the output as desired.</p>

<p>This is what the colorized, prettified output looks like:</p>

<p><img src="https://user-images.githubusercontent.com/392778/214134946-4a4fa674-7f9a-4376-afa1-9f89f094ff4b.png" alt="Color output from logger" /></p>

<p>With the addition of a couple of lines of configuration, you have extremely useful JSON objects for production logging, as well as nicely readable, colorized log statements for my command line development with a timestamp to know when things were logged.</p>

<p>Using environment variables, you can very easily drive these logging configurations for local versus production.  There is an example of this with Pino in my example project <a href="https://github.com/theothermattm/node-logging-examples/blob/main/pino/pinologger.js#L8">here</a>. Using console logging makes this extremely hard.</p>
<h2 id="bonus-having-a-logging-adapter">Bonus: Having a logging <em>Adapter</em></h2>

<p>This one is a nice to have, but a lot of people don’t really think about it. Let’s go back to Java for a minute to illustrate: Generally, most Java applications reach for the <a href="https://logging.apache.org/log4j/2.x/manual/api.html">Log4J Api</a> to log information. This is an <em>interface</em> for logging in Java. Meaning that other libraries can implement that interface. In Javascript, we don’t have interfaces, in Typescript we kinda do, but the point is to have an adapter that delegates the act of logging to a library you configure.</p>

<p>Why is this nice?  Well, let’s say you start with a logging framework that serves you well while your app is small, but then you switch to using Elasticsearch to ingest logs. Maybe there’s another framework that provides really easy integration to ingest Elasticsearch logs. You can switch out that implementation without changing your actual code. This also makes it easy for library maintainers to have a consistent way to log output.</p>

<p>(As a side note, most people in the Java world just use the Log4J implementation for everything, but it’s nice to have options.)</p>

<p>Compare this approach against Node, where there are a number of methods for logging, but no standard interface or adapter. The best we can do is use the traditional <code class="language-plaintext highlighter-rouge">console.log</code>, <code class="language-plaintext highlighter-rouge">console.error</code> (and so on) statements and then use another tool to scrape the console output to ingest in a different way. Certainly nothing wrong with that, but it can get kinda complicated depending on your deployment setup. However, Log4js, one of the logging frameworks I’ll go over, does have an <a href="https://www.npmjs.com/package/@log4js-node/log4js-api">API only library</a> that you can use.</p>

<h1 id="node-logging-frameworks---an-selected-overview">Node Logging Frameworks - An selected overview</h1>

<p>This is by no means an extensive list of logging frameworks for Node, but rather a specially selected list based on popularity and my own use and interests based on my recommendations above.</p>

<p>For another great analysis, check out <a href="https://geshan.com.np/blog/2021/01/nodejs-logging-library/">this article</a>.</p>

<h2 id="bunyan"><a href="https://www.npmjs.com/package//bunyan">Bunyan</a></h2>

<p>⭐7k, 2 million weekly downloads on NPM</p>

<p>This library has been around since the early days of Node and is well respected. Unfortunately, the GitHub project hasn’t been updated in 2 years as of this writing, and it is also somewhat of a laggard in the performance area as you’ll see below.  There is no ability to do external configuration that I see, and it would be fairly difficult to roll your own.</p>

<p>For this reason, I don’t recommend it.</p>

<h2 id="log4js"><a href="https://www.npmjs.com/package/log4js">Log4js</a></h2>

<p>⭐5.6k, 3.7 million weekly downloads on NPM</p>

<p>I came into this article never having used this library. Probably because I thought “Why would I want to use a java-like library in node?” As the README itself states:</p>

<blockquote>
  <p>Although it’s got a similar name to the Java library log4j, thinking that it will behave the same way will only bring you sorrow and confusion.</p>
</blockquote>

<p>While it’s a bit slow on the performance end of things (which you can see in the bonus section at the end of this article) its out of the box configuration settings are very easy to use and extremely useful. Pino also allows a huge amount of customization and much, much better performance, but since it works with streams it can be harder to reason about. Through writing this article, I became a big fan of log4js, despite its name.</p>

<h2 id="loglevel"><a href="https://www.npmjs.com/package/loglevel">LogLevel</a></h2>

<p>⭐2.4k, 9.3 million weekly downloads on NPM</p>

<p>It’s so simple! Definitely worth a look, but I think LogLevel’s brilliance lies in having a logging framework that works with your browser’s console <em>or</em> your server’s console. Out of the box, you can’t even get timestamps without some gymnastics.</p>

<h2 id="pino"><a href="https://www.npmjs.com/package/pino">Pino</a></h2>

<p>️⭐10.9k, 4.5million weekly downloads on NPM</p>

<p>This framework is newer than the others, having gone 1.0 in 2016. It has been making the rounds in Node projects at DEPT® recently to good fanfare amongst our developers. As it claims, it is <em>fast</em> and very customizable. To me its best feature is the ability to perform logging on a separate thread asynchronously with the flip of a configuration switch. Very cool. On any project that required some <em>serious</em> log gymnastics and high performance, I’d definitely go with Pino.</p>

<h2 id="winston"><a href="https://www.npmjs.com/package/winston">Winston</a></h2>

<p>⭐21.1k, 12.6million weekly downloads on NPM.</p>

<p>One of the more established and popular options, having been around since 2011, I’ve used Winston personally in many past projects. My opinion of it before going into this analysis was that it was … fine. A little wonky to set up, and the <code class="language-plaintext highlighter-rouge">silly</code> logging level is cute, but leaves me scratching my head why they didn’t use <code class="language-plaintext highlighter-rouge">trace</code> like almost every other logging framework.</p>

<p>Winston allows for good customization of logging transports and the ability to customize formats quite well. Its configuration API is a bit wonky though. I’ve used it in production applications for years and never really liked it, but rather just accepted its wonkiness. Now that I know there are better options, I don’t think I’d go back to it.</p>

<h2 id="my-takeaways">My Takeaways</h2>

<p>First, I don’t think I would ever start a project with plain console logs again after knowing what kind of benefits you can get from a logging framework over time. I know some will disagree with me, and that’s fine. But, I’d ask that you consider it. Logging can save you a <em>lot</em> of headaches in an application that’s operational.</p>

<p>For Node.js, there are lots of great logging frameworks available. If low level customizability and performance are your utmost concern, go with Pino. Its ability to take streams and log asynchronously out of the box is amazing.</p>

<p>For everything else, I can honestly recommend log4js. I hadn’t used it (or even heard of it!) before I started writing this, and after working with it, I really loved its ability to get up and running fast and the ease of customization. As you saw here, it’s a bit slower, but for the majority of applications I work on, this isn’t much of an issue, especially since most production logging levels are dialed way back to info and above level log output. For my next project, I plan on using log4js if I have the choice.</p>

<p>Go forth and Log!</p>
<h1 id="what-about-logging-in-the-frontend-client">What About Logging in the Frontend Client?</h1>

<p>By <a href="https://www.linkedin.com/in/jakerainis/">Jake Rainis</a></p>

<p>Historically, it could be argued this sort of complexity on the server is far greater than on the client since the front-end has traditionally been more responsible for layout and aesthetic than it has for business logic. However, the behavioral complexity of modern front-end applications continues to increase, particularly in framework-driven front-ends such as React, Angular, or Vue. And depending on the context of how the front-end application operates functionally, logging mechanisms could provide a great deal of insight into behavior resulting in an easier way to identify and isolate bugs.  \</p>

<p>So, where does that leave us on the front-end?</p>

<p>Of course, we all know that we can drop <code class="language-plaintext highlighter-rouge">console.log</code>s anywhere we please to debug an issue during development, but this isn’t a mature solution. These transient log statements are isolated to a single user’s browser session, and then they’re gone forever. Front-end applications run in the browser and don’t have the same luxuries as applications running on a server where events can be captured. \</p>

<h2 id="what-about-my-nextjs-app">What About My NextJS App?</h2>

<p>You might be wondering about full-stack frameworks such as a <a href="https://nextjs.org/">Next</a>, <a href="https://remix.run/">Remix</a>, or <a href="https://nuxtjs.org/">Nuxt</a>. These too run on a Node server, so they _do _enable logging solutions… But only on the server-side aspects of the application.</p>

<p>For example, let’s consider a Next application that uses the <a href="https://github.com/pinojs/pino">Pino</a> library (<a href="https://nextjs.org/docs/going-to-production#logging">Next recommends Pino</a>). Dropping a Pino log within a Next API route, a middleware, or even in one of Next’s server helpers such as <code class="language-plaintext highlighter-rouge">[getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props)</code> will result in the type of log we’re after here. On the other hand, dropping a Pino log into one of the application’s React components would <em>only</em> result in a log to the browser’s console. Once again, this is helpful for debugging throughout the development cycle, but it doesn’t help engineers when the application is running in production since the log is never captured!</p>

<h2 id="front-end-logging-solutions">Front-End Logging Solutions</h2>

<p>As you’ve likely surmised by now, we can’t effectively capture a log from a client-side application without some additional effort. But it is entirely possible! After all, this is a common conundrum for application developers.</p>

<p>The gist of front-end logging lies in sending the log events <em>somewhere</em> to be captured and this could even be accomplished through a custom hand-rolled solution that sends events to your back-end (though, be cautious of the overhead this might introduce). However, there are also third-party solutions out there to make the job easier.</p>

<p><a href="https://sentry.io/welcome/">Sentry</a> is perhaps the most battle-tested error and performance monitoring service that plugs seamlessly into <a href="https://sentry.io/platforms/">any application</a> — even front-end applications. But there are others too, like <a href="https://www.bugsnag.com/">BugSnag</a> and <a href="https://logflare.app/">Logflare</a> (recently acquired by <a href="https://supabase.com/">Supabase</a>).</p>

<p>One aspect of the front-end that we don’t have to worry as much about when monitoring a back-end are the browsers themselves. _What browser/version is the end-user viewing our app from? Oh jeez… what if it’s Internet Explorer? <a href="https://www.wired.com/story/microsoft-internet-explorer-is-finally-really-fully-dead/">Just kidding</a>.  What device are they on? What user flow did they follow to trigger this event/error? _One one the great selling points about the third-party services listed above is that they capture this information. This additional context can make bug reproduction and squashing much easier for application engineers.</p>

<p>These tools will come with a price tag when used at scale, but all have generous free tiers that might be sufficient for smaller teams and applications. And since they also work for back-end technologies, logging for all portions of an application can be captured, evaluated, and triaged in one place.</p>

<h2 id="does-every-front-end-need-logging">Does Every Front-End Need Logging?</h2>

<p>In general, logging and monitoring is a best practice and should not be an afterthought when it comes to a back-end application. After all, it’s simple and quick to implement.</p>

<p>But is it absolutely necessary on the front-end? Well, any front-end app would benefit, but the right answer is subjective. A larger and/or more complex front-end that manages a good deal of business logic would be a better candidate than a relatively static front-end that doesn’t have much complex functionality — particularly if it’s well-tested. Ultimately, it should be a discussion with the product and engineering team to determine what monitoring solutions make sense for your application. \</p>

<p>Speaking of, we here at DEPT® have quite a bit of experience in the realm of application monitoring. We’d love to chat more about it, so don’t hesitate to reach out!</p>

<h1 id="bonus-section-logging-framework-performance-analysis">BONUS SECTION: Logging Framework Performance Analysis</h1>

<p>If you’ve made it this far, I appreciate you and welcome, fellow logging nerd!</p>

<p>Now how about performance?  Pino claims to be super fast.  Let’s find out!</p>

<p>To test, I ran a loop of 100,000 simple statements like this (with varying syntax for each logging system):</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kd">const</span> <span class="nx">numberOfLoops</span> <span class="o">=</span> <span class="mi">100000</span>
  <span class="kd">let</span> <span class="nx">startTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">();</span>
  <span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="nx">numberOfLoops</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">`Test Log Message </span><span class="p">${</span><span class="nx">i</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
  <span class="p">}</span>
  <span class="kd">let</span> <span class="nx">endTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">`Time to execute </span><span class="p">${</span><span class="nx">numberOfLoops</span><span class="p">}</span><span class="s2"> log messages: </span><span class="p">${(</span><span class="nx">endTime</span><span class="o">-</span><span class="nx">startTime</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">}</span><span class="s2"> seconds`</span><span class="p">);</span>
</code></pre></div></div>

<p>To get a better sample, I ran the node process using the GNU <code class="language-plaintext highlighter-rouge">time</code> command 20 times each using <a href="https://github.com/theothermattm/node-logging-examples/blob/main/scripts/perf-test.sh">a bash script</a>. I purposely wrote the output to a file so my tty was not the bottleneck. This is a sample of the output of each command in the output files:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/time -a -o pino-times-output.txt node index.js -f pino -p &gt;&gt; pino-output.txt
&lt;&lt; snip ... &gt;&gt;
{"level":30,"time":1675893207318,"pid":54614,"hostname":"Matts-16-Macbook-Pro.local","msg":"Time to execute 100000 log messages: 517 ms"}
        0.62 real         0.39 user         0.15 sys
</code></pre></div></div>

<p>This was running on my Intel 2.6ghz 6 core i7 Macbook with 16gb of ram. I used the “real” time for comparison.</p>

<p>Of course, this probably isn’t a perfect test, but let’s see what happened:</p>

<p><img src="https://user-images.githubusercontent.com/392778/218817393-7d7321cd-0e37-4424-8e2d-b8e3096ba272.png" alt="Results in a Table" /></p>

<p><em>And it’s console winning the race by a nose!</em></p>

<p>Let’s take a deeper look.</p>

<h2 id="the-laggard-log4js">The Laggard: Log4js</h2>

<p>The laggard in the race was log4js, coming in almost 200ms longer than any other framework at an average of 1.54 seconds.</p>

<h2 id="the-middle-tier">The middle tier</h2>

<p>Second to last was Bunyan, doing slightly better than Log4js at 1.44 seconds. Third to last was Winston at 1.42 seconds, then LogLevel at 1.38 seconds.</p>

<h2 id="runner-up-pino">Runner Up: Pino</h2>

<p>As promised, Pino did very well, coming in only about 80 milliseconds slower than console logging. And that’s with <em>synchronous</em> logging, not <a href="https://getpino.io/#/docs/asynchronous">async logging</a>, which offloads logging onto worker threads to make logging a non-blocking operation, which is awesome. Of course, the whole command would likely take just as long with the worker thread, but it’s nice to know that for an online application your logging won’t be blocking your application’s threads.</p>

<h2 id="console">Console</h2>

<p>Probably not a surprise, but good old console.log came in first by about 80 milliseconds. Given that there’s nothing else happening with the logs, this makes total sense.  If performance is your concern, console.log can’t be beat!  But, since it’s not nearly as customizable, there are plenty of tradeoffs to consider.</p>

<p><strong>NOTE:</strong> This article was originally written for my employer, DEPT® Agency, you can find the original article on <a href="https://engineering.deptagency.com/in-praise-of-logging-a-node-js-javascript-logging-guide">DEPT®’s engineering blog</a>.</p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="logging" /><summary type="html"><![CDATA[I often find myself leaving comments in pull requests about logging. Usually about adding logging where they might be helpful for production troubleshooting, or changing their levels to be more appropriate for production. Understandably, when you’re under pressure to hit a deadline, logging can fall to the wayside. I find that most people don’t share my love of logging and why it can be great. Done well, it can save you a ton of time and headaches.]]></summary></entry><entry><title type="html">We asked ChatGPT to architect our application and this is what happened</title><link href="https://theothermattm.github.io//we-asked-chatgpt-about-architecture" rel="alternate" type="text/html" title="We asked ChatGPT to architect our application and this is what happened" /><published>2023-02-03T00:00:00+00:00</published><updated>2023-02-03T00:00:00+00:00</updated><id>https://theothermattm.github.io//we-asked-chatgpt-about-architecture</id><content type="html" xml:base="https://theothermattm.github.io//we-asked-chatgpt-about-architecture"><![CDATA[<p>I opened up my laptop Monday morning and saw this email from the CEO:</p>

<blockquote>
  <p>We’re pivoting. We need a new app, fast! New idea is basically Uber for dogs. If you need a cute dog, quick, this app will get you a dog to rent, FAST. It has to have an excellent native iOS and Android app. Need it by next monday.</p>
</blockquote>

<p>After wiping up the coffee I spit out on my desk and gathered my sanity, I was blanking on where to start. Then I remembered that ChatGPT is here to help me get started.</p>

<p>So, I asked ChatGPT to help us get started with our app architecture. This is what happened.</p>

<p><img src="https://user-images.githubusercontent.com/392778/215508631-0b20b481-0d16-4962-9d43-1a63622314e2.png" alt="First Interaction in which ChatGPT actually does a pretty good job architecting an app" width="100%" /></p>

<p>Huh, well, I’m a little put off by the level of confidence, but that’s actually … Better than I expected.</p>

<p>I’m not just gonna sit here and let AI take over my job though, I can still do better.</p>

<p>A queue will definitely work, but what about “dogs” screams “queuing” to ChatGPT? I’m not quite sure why dog rentals need a queue based system, but I’m not against it either.</p>

<p>And more importantly, what’s the justification for using a NoSQL database over a relational database? That’s questionable, in this engineer’s opinion!</p>

<p><img src="https://user-images.githubusercontent.com/392778/215508744-4547758c-05bd-444e-96ab-43fbea16246a.png" alt="Second Interaction, in which ChatGPT defends its decision to use NoSQL" width="100%" /></p>

<p>Ok well, I definitely need to handle a large number of dog rental requests. This idea is going to take off like crazy so I’m glad NoSQL is webscale.</p>

<p>The rest of this is a load of baloney, ChatGPT!</p>

<p>Who’s to say NoSQL will be faster at querying than PostGresSQL!? What if I need to get at my dog rentals by joining user information. What if I want to give free dog rentals on birthdays? That’s gonna be hard to do!</p>

<p>And NoSQL “can be more cost effective than relational databases” Phoey! Running my own datacenter <em>can be</em> more effective than using the cloud but I’m not gonna do that!</p>

<p>I also don’t think storing my dog rental data in unstructured form is a good thing. This is a mission critical app and I want the utmost data integrity.</p>

<p>Ok, let’s move on.</p>

<p>What about those Microservices? That seems like overkill to me.</p>

<p><img src="https://user-images.githubusercontent.com/392778/215509612-744c95a4-06b0-4a17-9a13-76875673a119.png" alt="Third interaction, where ChatGPT tells us why microservices are better" width="100%" /></p>

<p>That… Makes a lot of sense. Fair point on the pros and cons, ChatGPT. While I watched this answer type out I was full of judgment but then it ended on that note and I don’t know if I could have come up with a better answer myself.</p>

<p><em>Well done, ChatGPT, point for you.</em></p>

<p><img src="https://media.giphy.com/media/5gYkTDtYSTqeHajMoQ/giphy.gif" alt="Reluctantly Cheering for ChatGPT" width="100%" /></p>

<p>Ok, so how about the deployment. Using “cloud services” is pretty vague.  Let’s clear this up.</p>

<p><img src="https://user-images.githubusercontent.com/392778/215510714-12dd89cf-ff00-4d9e-9d1c-be60c1898c6d.png" alt="Fourth Interaction, asking about cloud deployment" width="100%" /></p>

<p><img src="https://media.giphy.com/media/so8KXAphERsre/giphy.gif" alt="Suspicious" width="50%" /></p>

<p>This is all suspiciously vague. This type of answer seems like it was written by a non-technical MBA student in their first year. Not even a mention of containers. Blasphemy!</p>

<p>Take that point away!</p>

<p>Ok, how about React Native. Sure, we’ll save some money and effort not having to code two different apps but the experience won’t be as good!</p>

<p><img src="https://user-images.githubusercontent.com/392778/215512800-98bdcb9b-c176-47fb-b001-e0ca9b5bbb92.png" alt="Fourth Interaction, why React Native?" width="100%" /></p>

<p>Hmmm… Ok, another good overview.</p>

<p>Point back, ChatGPT.</p>

<p><img src="https://user-images.githubusercontent.com/392778/215513184-693e4d96-b640-4688-915f-dab0d2df2768.png" alt="Thanks, ChatGPT" width="100%" /></p>

<p>Finally, let’s ask ChatGPT about the CEO’s idea, after all, the best engineers know when NOT to build things, right?</p>

<p><img src="https://user-images.githubusercontent.com/392778/215513780-c6721e5d-55a7-40d2-82d8-b4cc438003df.png" alt="Would you use uber for dogs?" width="100%" /></p>

<p>Sounds like a winner to me! Off to build that app…</p>

<h1 id="what-did-we-learn">What did we learn?</h1>

<p>AI is a good actor!</p>

<p>But seriously: despite seeing lots of posts/articles about how great ChatGPT is at various things, it <em>still</em> did quite a bit better than I thought it would with such a vague question about design/architecture. I was not expecting such well-rounded answers, even with caveats about weighing pros and cons.</p>

<p>It also does well with very specific information. You can see how diving into NoSQL gave many more specifics and even gave those nice caveats about when it might not be applicable.</p>

<p>The overly-confident tone it presents is off putting, though. For a new tool that is scraping internet articles for its answers, it sounds <em>way</em> more confident than it should be. And not <em>all</em> of the answers presented caveats about weighing pros and cons.</p>

<p>Using the tool is a great way to get started if you’re stuck or just learning. It does a great job of amalgamating information that might take you a while to collect with various internet searches.</p>

<p>However, I do hope that the confident tone can be scaled back before unknowing newbies use the tool to direct their software development efforts, whether it be for code or for architecture and design.</p>

<p>There is a great article I recently read called <a href="https://castlebridge.ie/insights/llms-and-the-enshittening-of-knowledge/">ChatGPT and the Enshittening of Knowledge</a> that sums this up perhaps better than I can:</p>

<blockquote>
  <p>[…] in order to ensure that its answer fits what its model tells it is expected to come next, ChatGPT is also making shit up. So, we have a generic non-committal middle of the road representation of knowledge coupled with what, in a human, we’d class as “A-Grade Bullshitter” levels of self-confidence so they make shit up to support their argument.</p>
</blockquote>

<blockquote>
  <p>The merit or trust rating of a book or journal or newspaper that cites the A-Grade Bullshit then increases the truthiness of the bullshit, resulting the next iteration of the question to the AI having EVEN MORE CONFIDENCE in their bullshit. […] This results in a classic data quality spiral where the incorrect data becomes accepted as fact and decisions or outputs that disagree are discounted. The Enshittening of Knowledge gathers momentum.</p>
</blockquote>

<p>We’re still not even close to the eyes, ears and brain of a discriminating engineer.</p>

<p>You heard it here first: Learning the old fashioned way by trying and failing is still the way to go.</p>

<p><img src="https://user-images.githubusercontent.com/392778/215564712-5b64fe74-4ac1-41e7-ae62-859960c10350.png" alt="Why are you so confident, ChatGPT?" width="100%" /></p>

<p><em>NOTE: The original version of this post appeared on <a href="https://engineering.deptagency.com/p/fb0ab9c7-14a2-4712-84fb-4c012f461135">DEPT® Agency’s Engineering Blog</a>.</em></p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="architecture" /><summary type="html"><![CDATA[I opened up my laptop Monday morning and saw this email from the CEO:]]></summary></entry><entry><title type="html">I believe I can fly.io [A fly.io review]</title><link href="https://theothermattm.github.io//a-fly-io-review" rel="alternate" type="text/html" title="I believe I can fly.io [A fly.io review]" /><published>2023-01-17T00:00:00+00:00</published><updated>2023-01-17T00:00:00+00:00</updated><id>https://theothermattm.github.io//i-believe-i-can-fly.io</id><content type="html" xml:base="https://theothermattm.github.io//a-fly-io-review"><![CDATA[<p>I remember when I heard that Heroku was getting acquired by Salesforce - To say I was disappointed is an understatement.  Heroku pioneered developer-centric, easy-to-use webapp hosting and they did it very well.  But, as expected, things started slowly going downhill after the Salesforce acquisition - Culminating in them <a href="https://blog.heroku.com/next-chapter">removing their free hobbyist hosting option</a>.</p>

<p>At my employer, we still use Heroku to host an internal project tracking application. Last year their <a href="https://thestack.technology/heroku-outage-github-breach/">Github integration broke</a> and wasn’t fixed for almost two months, with very little communication from the company, that’s when I decided I would no longer recommend it or use it as an option for deployments.</p>

<p>A few months ago, a colleague enthusiastically recommended <a href="https://fly.io/">Fly.io</a> to me.  I trust his opinion a lot, and I gave it a shot and stood up a sample Node.js app on it. I was seriously impressed!  An amazing CLI and I got an app with a database up and running in less than 30 minutes.  AND I could do all the wonderful things I could usually do with Docker containers. Oh, and it still has a free option, unlike some others (looking at you, Heroku!)</p>

<p>I was so impressed, that when we had to host another internal app, I immediately reached for it.  It’s worth noting that these types of <a href="https://en.wikipedia.org/wiki/Platform_as_a_service">Platform as a Service (PaaS)</a> tools are not for every use case, but ours was perfect since it wasn’t a mission-critical app.  That said, we still wanted some basic things for it like monitoring and being able to view logs, etc.  Things that Heroku does splendidly.</p>

<p>To make a long story short: My experience was that though Fly.io has amazing promise, it doesn’t yet have the necessary features for operational use on a team. I’ll outline why in this article.  It is great, however, for hobby projects, single developer applications.  <strong>It’s especially great if you just need a (free!) place to start and plan on migrating to a more mature platform at some point soon.</strong></p>

<p>This article may come off as critical, but useful software often has critics. I’m one of them. But, let me make it 100% clear: <strong>I am a Fly.io fan!</strong> I wouldn’t take the time to write this if I wasn’t excited by the promise it has.</p>

<p>Ok, on we go….</p>

<h1 id="why-fly-is-awesome">Why Fly is awesome</h1>

<p>The whole platform is based on Docker containers.  You can do everything from creation of projects to managing secrets via <a href="https://fly.io/docs/hands-on/install-flyctl/">command line</a>. This means everything can be scripted. It can use Heroku build packs.  It can be as simple or as complicated (within reason, see below) as you want or need it to be.</p>

<p>Think of it as Kubernetes or AWS Elastic Container Service on easy mode.  Though this ease of use brings with it a restriction the customization abilities of your deployments, and, as you’ll see as you read, some limitations.</p>

<p>Oh, and did I mention that <strong>it has a free option to get started?</strong></p>

<h1 id="setting-up-an-app">Setting up an app</h1>

<p>It’s very, very easy:</p>
<ol>
  <li>Download the CLI.</li>
  <li>Authenticate with <code class="language-plaintext highlighter-rouge">flyctl auth login</code></li>
  <li>Run <code class="language-plaintext highlighter-rouge">flyctl launch</code> or <code class="language-plaintext highlighter-rouge">flyctl apps create</code> and point it to your Dockerfile or Docker image.</li>
</ol>

<p>That’s it!</p>

<h1 id="plaintext-configs">Plaintext Configs</h1>

<p>Fly uses a <code class="language-plaintext highlighter-rouge">fly.toml</code> file that is used to <a href="https://fly.io/docs/reference/configuration/">define your application</a>.  All of your configs are here, at a glance and able to be version controlled.</p>

<p>You can also provide multiple definitions for different environments or variations as well using the <code class="language-plaintext highlighter-rouge">--config</code> flag on the CLI.</p>

<p>One thing to note is that there is no “override” option (<a href="https://docs.docker.com/compose/extends/">a-la docker-compose</a>), so you will have to copy/paste your definitions for now. No big deal.</p>

<h1 id="deploys--free-builders">Deploys / Free Builders</h1>

<p>One of the nicest things of the developer experience is how easy it is to deploy your app for testing.  You run <code class="language-plaintext highlighter-rouge">flyctl deploy</code> and it ships off your code to a free builder which builds your container image on their servers and uploads it to a secure private repo, then deploys it.  It’s really awesome that they provide this for free, even as you scale beyond the initial free option.  Other CI/CD offerings like Github actions cap you at a certain number of builder minutes a month then charge you afterwards.</p>

<h1 id="apps-and-organizations">Apps and Organizations</h1>

<p>Fly has the concept of organizations, which are collections of individual apps.  Individual apps are essentially Docker containers that can run and scale independently. This is also how you grant access to users to manage your applications.</p>

<h1 id="secrets-management--environment-variables">Secrets Management / Environment Variables</h1>

<p><a href="https://open.spotify.com/track/1gVFwk7WUdhdmD7YBSaGxI?si=0bedd9198ce34db4&amp;nd=1">Adding and managing secrets</a> in Fly is very easy and a pleasure to use.  You run a CLI <code class="language-plaintext highlighter-rouge">add</code> or <code class="language-plaintext highlighter-rouge">update</code> command and the variable is present to your app’s container automatically. That’s it.  There’s even a very easy way to <a href="https://fly.io/docs/reference/configuration/#the-env-variables-section">define non-sensitive environment variables in your fly.toml</a>. This was probably the easiest and best way of managing environment vars I’ve used, bar none.</p>

<h1 id="user-roles-and-permissioning">User Roles and Permissioning</h1>

<p>There are NO user permissions beyond having access to an organization.  You have organization access, you have the keys to the kingdom.</p>

<p>Additionally, your API tokens have access to <em>everything you as an end user have access to</em>.  There is no way to narrow them down so they only have deploy access to one application.  Most importantly, in order to hook up the command line utility to any deployment pipelines, you have to give your CI/CD tool full access to <em>everything</em> your user has access to 😵, including across organizations if the user is part of more than one. I would really like to see fine grained permissioning so that an access token only has deploy access to one application and nothing else.</p>

<p>To get around this, we created a separate “non production” organization where a whole development team can have permissions to freely configure and deploy the app.  We locked our production organization down to a small subset of trusted committers/deployers.</p>

<h1 id="monitoringalerting">Monitoring/Alerting</h1>

<p>Fly gives you <a href="https://fly.io/docs/reference/metrics/">Prometheus metrics, displayed via a Grafana instance</a> out of the box that are super useful.  I really love that they used a well-known, open source tool to do this.  It is a version of Grafana with features disabled, though.</p>

<p>Crucially, <em>it does not have alerting enabled</em>.  I had to stand up my own instance of Grafana to be able to set up real-time alerts for errors on my app.</p>

<p>It was fairly easy for me to set up Grafana, and the Prometheus metrics from each app were available to plug into any Grafana instance.</p>

<p>I do like that the metrics are exposed and you can do what you want with it, but I had to sink about half a day into getting Grafana stood up with an SMTP provider.</p>

<p>Also, there is no way I’m aware of to export logs to a more digestible place like Splunk/Elasticsearch.  You can look at logs via the web console or the command line only.</p>

<h1 id="databases">Databases</h1>

<p>Fly does support running <a href="https://fly.io/docs/database-storage-guides/">Postgres and other databases</a> but they are really just running docker containers on volumes on fly itself.  It doesn’t give you out-of-the-box High Availability or monitoring or easy snapshotting and restoring. To set up Postgres, you have to fork one of their repos.</p>

<p>Fly themselves claim that “<a href="https://fly.io/docs/postgres/getting-started/what-you-should-know/">Fly Postgres is not managed Postgres</a>.” Managed means things like replication and automated restores (among other things). They even point you to Heroku for a fully managed version of Postgres.  I commend them for being upfront about it, but it sure would be nice to be able to have a managed database alongside your app without having to worry about this. What they provide is certainly workable though!</p>

<h1 id="scheduled-tasksjobs">Scheduled Tasks/Jobs</h1>

<p>Surprisingly, there is no great way to run a Docker container as a scheduled task.  There is a way to do it using the newer “Machines” API which is a bit buried in their <a href="https://fly.io/docs/machines/working-with-machines/">documentation (search for schedule)</a>, but it only supports limited scheduling options and not cron expressions.</p>

<p>Do a quick <a href="https://www.google.com/search?q=fly.io+scheduled+jobs">Google search for Fly.io scheduled jobs</a>, and you’ll see that many users have been rallying for this feature for a while.</p>

<h1 id="running-multiple-processes--sidecar-containers">Running Multiple Processes / Sidecar Containers</h1>

<p>Related to the Jobs portion, but zooming out a little: Fly does not yet have support for sidecar containers, so running multiple processes requires <a href="https://fly.io/docs/app-guides/multiple-processes/">adding a process manager into your Docker container</a>. The addition of this feature might solve a lot of operational challenges we had!</p>

<p>If we could run a sidecar container, we could probably run our jobs or have a logging exporter to output logs to another service for us.</p>

<h1 id="security">Security</h1>

<p>One of the big draws of Fly is its ease of use on the command line.  However, this comes at a cost - Because of the lack of fine-grained permissions noted above, it is incredibly easy to run a command to change the wrong application.  And if your auth token is accidentally shared or exposed to a bad actor, they will have access to change a lot. Rotating keys also becomes problematic because you have to replace just about everything.</p>

<p>Speaking for myself, I would love to have an authentication configuration just for my non-productions applications to make sure I don’t inadvertently take down my production app.</p>

<h1 id="docker-limitations">Docker Limitations</h1>

<p>Some things you take for granted in Docker may not be available, like for instance the ability to mount multiple volumes in a container.  When I was setting up Grafana for alerting, I needed to mount two volumes to configure the app’s secrets securely, but only one volume was supported.  Because of this, I had to work around it by copying files over and doing environment variable substitution.</p>

<h1 id="shifting-product-features">Shifting Product Features</h1>

<p>Fly is still in the early days, and they are adjusting the product quickly.  This is great because you know that there’s a passionate group of developers working on this and making the product better.  It’s not so great in that features are shifting and not being supported as well anymore.  For instance, we created a Postgres database, and when we went to create a staging version of it, the option to create it was no longer available.   When we went to the documentation page, we saw this:</p>

<p><img src="/img/fly-database-warning.png" alt="Fly changed their database support and showed this warning" /></p>

<p>When you dive into that, you find out that their new “Apps V2” architecture is supplanting their original Hashicorp Nomad architecture, which everything we built was based on.  We stood up the app in June, and somewhere in the October/November timeframe, this announcement happened.
Unfortunately, at the time of this writing (January 2023), the Machines API is just that: literally a REST API you have to send commands to.  There is no support in their CLI for it or at least minimal support</p>

<p>In the course of writing this, I stumbled on a lot of information about how Fly’s Machines-based architecture will likely solve a LOT of these limitations.</p>

<p>One such example is that there is a <a href="https://fly.io/docs/machines/guides-examples/terraform-machines/">Terraform provider for the machines API</a>!  That’s really exciting to see and hear.</p>

<h1 id="in-summary-onward-and-upwards">In Summary: Onward and Upwards</h1>

<p>I hope I’ve done a decent job of showing you the pros and cons of using Fly right now. Fly’s promise and advantage are that it is obviously run by a team of talented developers working to make a tool that they would want to use.  They don’t have the baggage of a large company like Amazon or Salesforce holding them back.  This is really exciting.</p>

<p>Very soon Fly will become a contender to fully replace Heroku for production application and will be much more simple and easy to use than something like Kubernetes or any of AWS’s offerings.
Right now, it’s pretty much the perfect option for hobbyists or a team with one or two developers.  When you need to get going fast and free, it can’t be beat.  Just be aware of the limitations I’ve mentioned and how they may limit your ability to support your application.</p>

<p>In the meantime, if you need a simple-to-use deployment option for a production application on a team, my opinion is that you should stick with Heroku, or investigate using Elastic Beanstalk on AWS.  Of course, there are other options as well (<a href="https://www.aptible.com/">Aptible</a> is a very interesting one I’ve heard about) but these are the ones my employer has experience with.</p>

<p><em>Note: I wrote the original version of this post for my employer, <a href="https://engineering.deptagency.com">DEPT® Agency</a>, you can view it <a href="https://engineering.deptagency.com/our-experience-with-fly-io">here</a></em></p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="cloud" /><category term="cloud" /><category term="aws" /><summary type="html"><![CDATA[I remember when I heard that Heroku was getting acquired by Salesforce - To say I was disappointed is an understatement. Heroku pioneered developer-centric, easy-to-use webapp hosting and they did it very well. But, as expected, things started slowly going downhill after the Salesforce acquisition - Culminating in them removing their free hobbyist hosting option.]]></summary></entry><entry><title type="html">New Year, Refreshed Blog</title><link href="https://theothermattm.github.io//new-year-refreshed-blog" rel="alternate" type="text/html" title="New Year, Refreshed Blog" /><published>2023-01-15T00:00:00+00:00</published><updated>2023-01-15T00:00:00+00:00</updated><id>https://theothermattm.github.io//new-year-refreshed-blog</id><content type="html" xml:base="https://theothermattm.github.io//new-year-refreshed-blog"><![CDATA[<p>I realized a while back that hosting my blog on Wordpress was doing me no favors. I was paying about $10 a month for WordPress hosting on DreamHost (who is fantastic, by the way) to have a site that was slow and who’s editing experience wasn’t ideal.</p>

<p>I wanted to write in Markdown, too. Preferably using my preferred editor (VSCode) with my preferred Keybindings (Vim, thankyouverymuch!). There are plugins for Markdown in Wordpress I used, but they were pretty clunky too.</p>

<p>So, I finally bit the bullet and converted over to using <a href="https://jekyllrb.com/">Jekyll</a> hosted for <em>free</em> on <a href="https://pages.github.com/">Github pages</a>. You can see the source code for the blog <a href="https://github.com/theothermattm/theothermattm.github.io">here</a>.</p>

<p>Jekyll has so many wonderful theming options to choose from for a backend engineer who has no design taste! I chose the <a href="https://github.com/StartBootstrap/startbootstrap-clean-blog-jekyll/">Clean Blog Jekyll</a> theme, which looks a <em>lot</em> like the old theme of my site, but a little more fresh with nicer typography.</p>

<p>The process of the export was very easy with help from the <a href="https://wordpress.org/plugins/jekyll-exporter/">Jekyll Exporter</a> Wordpress plugin and <a href="https://www.bawbgale.com/from-wordpress-to-jekyll/">Bob Gale’s “From Wordpress to Jekyll” post</a>. I kept all my canonical blog URL’s with barely any effort.</p>

<p>What a wonderful change: I’m writing this with my Vim keybindings, and I can source control the whole thing. I’m in heaven here, folks.</p>

<p>And you, dear reader, benefit from a crisp new design and near instantaneous loading times because the whole site is static. No more waiting for Wordpress to load folks, you just get to my content faster. Which I’m sure you’ve been dying for.</p>

<p>Happy new year!</p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="career" /><category term="writing" /><summary type="html"><![CDATA[I realized a while back that hosting my blog on Wordpress was doing me no favors. I was paying about $10 a month for WordPress hosting on DreamHost (who is fantastic, by the way) to have a site that was slow and who’s editing experience wasn’t ideal.]]></summary></entry><entry><title type="html">Why and how to create your own Mastodon server on AWS with Terraform</title><link href="https://theothermattm.github.io//mastodon-with-terraform-and-ecs" rel="alternate" type="text/html" title="Why and how to create your own Mastodon server on AWS with Terraform" /><published>2022-12-19T00:00:00+00:00</published><updated>2022-12-19T00:00:00+00:00</updated><id>https://theothermattm.github.io//mastodon-with-terraform-and-ecs</id><content type="html" xml:base="https://theothermattm.github.io//mastodon-with-terraform-and-ecs"><![CDATA[<p><em>TL;DR:</em> At my employer, <a href="https://deptagency.com">DEPT® Agency</a>, I updated and open-sourced Terraform scripts to bootstrap your own Mastodon server using AWS, Elastic Container Service, and Fargate.</p>

<p>It all started with a simple request to see how hard it would be to setup a Mastodon server for my company.</p>

<p>Sure, easy. I’ll do it!</p>

<p>So began my journey into creating a Mastodon server.</p>

<p>But, before we get into all that …</p>

<h1 id="why-would-i-create-my-own-mastodon-server-isnt-that-why-twitter-exists">Why would I create my own Mastodon server? Isn’t that why Twitter exists?</h1>

<p>It would be hard to avoid hearing about <a href="https://joinmastodon.org/">Mastodon</a> lately with all that’s happening around Elon Musk’s takeover of Twitter:  <a href="https://www.nytimes.com/2022/12/15/technology/twitter-suspends-journalist-accounts-elon-musk.html">Journalists being banned</a>, <a href="https://techcrunch.com/2022/12/18/twitter-wont-let-you-post-your-facebook-instagram-and-mastodon-handles/">accounts in violation of policies for links to other services</a>, and constant unpredictability with policies being changed daily.</p>

<p>But, even though you’ve heard of Mastodon, you may not know how you use it - <a href="https://buffer.com/resources/mastodon-social/">here’s a good resource</a> to learn a little more.  Also, if you’re not already signed up: You have to find and choose a Mastodon server to use, a key difference from the centralized approach of Twitter.  To get you going faster, I’d recommend creating an account on <a href="Mastodon.social">Mastodon.social</a> (one of the biggest servers).  Then, once you get that going, use <a href="https://www.movetodon.org/">Movetodon.org</a> to import your Twitter following list into Mastodon.</p>

<p>But why on earth would you want to create your own Mastodon server?  I can’t state it any better than Julien Deswaef in his article <a href="https://martinfowler.com/articles/your-org-run-mastodon.html">Your organization should run its own Mastodon server</a>:</p>

<blockquote>
  <p>… just as with email, you can send a message to anyone in your organization, and at the same time have a conversation with anyone outside of your organization. All this is transparent and your correspondent’s email address lets you know that you are actually talking to someone as part of a particular organization.</p>
</blockquote>

<blockquote>
  <p>By using your own domain name, your brand, you are creating a recognizable social presence in the Fediverse, without the need to associate it with facebook.com, twitter.com or instagram.com. No need to worry about someone else squatting your name either. Your domain name is what will get people to trust that the Mastodon accounts under it are legitimate and official.</p>
</blockquote>

<blockquote>
  <p>And once you run your own corner of a social network, you can decide who you invite there, what are your rules of engagement and code of conduct. At Thoughtworks, we’ve allowed all our employees to open an account on our Mastodon server as this is aligned with our culture and our practice of being quite vocal and transparent about what we are passionate about.</p>
</blockquote>

<p>Having some piece of mind for your company’s brand to exist on a social network that you control fully is huge.</p>

<p><em>Ok, now that we’ve gotten that out of the way…</em></p>

<h1 id="the-context">The Context</h1>

<p>Jumping ahead a bit, one thing that I couldn’t really find was a nice, introductory article explaining the architecture of Mastodon.  I found myself struggling to set up infrastructure I had no context in. So here’s a crash course that will help you understand things a bit better before diving in.</p>

<p>Mastodon is comprised of just a few main components, all within the same <a href="https://github.com/mastodon/mastodon">open-source codebase</a>:</p>

<p><img src="/img/mastodon-architecture.webp" alt="Mastodon Architecture" /></p>

<p>(<a href="https://gist.github.com/theothermattm/0740f982316e3b87e8efc9adbc87b7fe">Diagram Source</a>)</p>

<p><strong>The main web application.</strong> This is a Ruby on Rails application that runs on <a href="https://puma.io/">Puma</a>. This includes web pages themselves as well as APIs.</p>

<p><strong>Background Job Processing.</strong>  This runs using <a href="https://sidekiq.org/">Sidekiq</a>, a Ruby job processing system. These jobs handle things like handling requests from other Mastodon servers (known as federated servers) for messages and follow requests, handling notifications, and other things.</p>

<p><strong>The streaming API.</strong>  This is a Node.js application that has Websocket API’s for real-time updates for the various parts of Mastodon’s data model.  Think of things like getting notifications in the app for new follows.</p>

<p>Another important thing to know is that Mastodon created and implements the <a href="https://docs.joinmastodon.org/spec/activitypub/">ActivityPub spec</a>.  This is a specification managed by the W3C for decentralized social networking.  I won’t go into the details about ActivityPub here, but it’s super important to know about the concepts in it to help you with setup, more on that later.</p>

<h1 id="how-federated-servers-communicate">How Federated Servers Communicate</h1>

<p>I think perhaps the most confusing thing is understanding the flow between different Mastodon servers.  So, let’s take the example of Following someone on a different server:</p>

<ol>
  <li>A user clicks “Follow” for someone on a different Mastodon server.</li>
  <li>The Puma web application will take the federation information and lookup how to contact the other server and send a follow request to that server.  The endpoint that’s called on the other end is something like <code class="language-plaintext highlighter-rouge">POST /users/{followed_remote_username}/inbox</code>.  This puts a message in the remote user’s “inbox that they have a follow request”.</li>
  <li>On the remote end, the API endpoint puts a message in the <a href="https://docs.joinmastodon.org/admin/scaling/#sidekiq">Sidekiq <code class="language-plaintext highlighter-rouge">ingress</code> queue</a>. When the job processor picks up that task, if the user doesn’t need to approve follow, the server will respond with a corresponding call back to the original users inbox that the follow request is accepted.</li>
</ol>

<p>This is all part of the ActivityPub spec but is kind of hard to follow, and if I’m being honest, I’m still not sure I got it completely right (comments welcome!).</p>

<p>The takeaway is that background jobs are really important in Mastodon, and so is the ability to securely connect between servers through HTTPS connections.  This process is fraught with SSL errors and layers you’ll need to debug if the connections don’t just work.</p>

<h1 id="the-infrastructure-needed">The Infrastructure Needed</h1>

<p>To host this beast (ha ha) here are a few things that need to be set up, and a couple optional things:</p>

<p><strong>An online web application container.</strong> Something that can run Ruby processes.  Could be Docker, could be a Virtual Machine.  Pick your poison.</p>

<p><strong>A PostGresSQL database.</strong> This is the heart of your Mastodon instance storing user accounts, messages, etc.</p>

<p><strong>A Redis data store.</strong> This is used for the job queueing system described above as well as caching.</p>

<p><strong>A static file store.</strong> This is used to store uploaded photos/files for posts.  Think AWS S3, or similar.</p>

<p><strong>A reverse proxy or load balancer.</strong> You really don’t want your web application directly hosting requests.  You’ll want some way to throttle and distribute those requests and also put in some security checks as well.  You could use something like <a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/">NGinx</a> for a reverse proxy, or in our case, we used an AWS Application Load Balancer.</p>

<p><strong>Optional: An ElasticSearch cluster.</strong>  This is used for advanced searching capabilities.  I didn’t set this up so it’s not included in this write-up. If I end up setting one up I’ll update the post.</p>

<h1 id="choosing-a-path">Choosing a path</h1>

<p>There are affordable hosted Mastodon server offerings, but really part of this exercise was learning more about the architecture of Mastodon and what goes into setting up a server.  After all, we’re engineers.  We can do it better ourselves, right? 🫠 Kidding…</p>

<p>If you have the knowledge, it makes a lot of sense to stand up Mastodon yourself on cloud infrastructure instead of relying on a hosting provider: You can be assured you’re in control of your data and treat Mastodon just like any other app you develop. It’s also advantageous to be able to really tweak your cloud resources to your number of users to save money or scale up.  whereas hosting providers charge for the <a href="https://masto.host/pricing/">price of Mastohost is $89 a month for approximately 2000 users</a>, and the price for <a href="https://toot.io/mastodon_hosting.html#pricing">toot.io’s 1000 user plan is $89 a month</a>.  Also, as of this writing, Mastohost isn’t even accepting new customers according to their page.  That’s kinda important especially with the recent influx of Mastodon users.</p>

<p><del>As for cost, I’m anticipating it will be a wash compared with hosting providers.  The AWS estimate for our first month’s bill is about $130 but that includes lots of experimentation and also a NAT Gateway, which can be avoided if you’d like to be slightly less secure (exposing your containers publicly, which is generally a no-no).</del> See my update at the end of the article. It may be more cost-effective to use one of these services unless you need to tightly control your infrastructure!</p>

<p>I’m most familiar with AWS and a . big fan of containerization, so I thought I might go in that direction.  It helps that Mastodon has <a href="https://hub.docker.com/r/tootsuite/mastodon">official Docker Images</a>! Finally, I knew if I was going to go with AWS, I wanted to use an Infrastructure as Code (IaC) tool so that I could share the setup with the world, and you, dear reader.  I’m most familiar with <a href="https://www.terraform.io/">Terraform</a> so I chose that.</p>

<p>With all my preferences, I set out to see what already existed to help me out.  One of the huge benefits of using IaC tools is being able to leverage existing open-source code to do this type of thing.  After some false starts, I landed on a project called <a href="https://github.com/r7kamura/mastodon-terraform">mastodon-terraform by r7kamura</a>. It was a beautifully laid out Terraform project with modules and a great setup for providing configuration variables. It also used Elastic Container Service which is a “just complicated enough” container orchestration engine.</p>

<p>The problem was that it was out of date, having been touched last six years ago.  Mastodon had only released its <a href="https://github.com/mastodon/mastodon/releases/tag/v1.0">first stable release in 2017</a>, so I assumed much had changed between then and now.  It also did not use <a href="https://docs.aws.amazon.com/AmazonECS/latest/userguide/what-is-fargate.html">Fargate Managed ECS Clusters</a>, but instead EC2 servers which can be cumbersome to maintain, even with Terraform.</p>

<p>That was as good a place as any to start!</p>

<h1 id="the-takeaways">The Takeaways</h1>

<p>In the course of a couple weeks of off and on work, maybe about 20 hours total, I got a server up and running, ready to scale out should our community take off. My company shared the forked repository for all to use here: <a href="https://github.com/deptagency/mastodon-terraform-aws-ecs">deptagency/mastodon-terraform-aws-ecs</a> (MIT Licensed).</p>

<p>I hit a couple bumps in the road making changes to container definitions in ECS to be in line with updates to Mastodon since 2017, but other than that <a href="https://github.com/r7kamura">r7kamura</a>’s work was a fantastic base and I thank them greatly.</p>

<p>I also hit a knowledge wall at one point where messages from other servers were not being received by ours.  I spent a lot of time with networking settings, but in the end, posted a question on the <a href="https://github.com/mastodon/mastodon/discussions/22310">Mastodon Github discussion forum</a> which was super helpful and pointed me in the right direction.</p>

<p>My biggest challenge was finding high-level information about mastodon’s architecture and infrastructure.  Mastodon has all kinds of fantastic documentation about the details of how to set up a server and how to configure it.  They also have great introductory material on how to use and administer Mastodon.  Everything in between is a bit of a black box.  I hope that this post helps someone else fill in those gaps.</p>

<p><em>Update 2/2023:</em> The cost for the past couple of months ended up being about $170 a month! The majority of that was (surprisingly) the NAT Gateway to keep the containers isolated from the public internet.  This could be avoided by allowing containers to be accessed through the internet. We also ended up dialing back the RDS instance size to save a bit.</p>

<p><img src="https://user-images.githubusercontent.com/392778/218801680-45238635-ed8f-4ef4-83c3-6454c8ed13ff.png" alt="Snapshot of AWS Charges" /></p>

<p><em>I wrote the original version of this post for my employer, <a href="https://engineering.deptagency.com">DEPT® Agency</a>, you can view it <a href="https://engineering.deptagency.com/create-your-own-mastodon-server-on-aws-with-terraform">here</a></em></p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="cloud" /><category term="cloud" /><category term="aws" /><summary type="html"><![CDATA[TL;DR: At my employer, DEPT® Agency, I updated and open-sourced Terraform scripts to bootstrap your own Mastodon server using AWS, Elastic Container Service, and Fargate.]]></summary></entry><entry><title type="html">Moving a Monolith</title><link href="https://theothermattm.github.io//moving-a-monolith" rel="alternate" type="text/html" title="Moving a Monolith" /><published>2022-03-28T00:00:00+00:00</published><updated>2022-03-28T00:00:00+00:00</updated><id>https://theothermattm.github.io//moving-a-monolith</id><content type="html" xml:base="https://theothermattm.github.io//moving-a-monolith"><![CDATA[<p>A while back, I spent about a year working on a team who wanted to move their Java based monolith into the 2020’s with a spanking new Microservice / Single Page App (SPA) architecture. Sweet! A greenfield redo! Sound a little too good to be true? Here was the catch:   We needed to keep their old monolith running while we did it, and it was a big, complicated application.</p>

<p>I learned a lot of lessons about what to do and what <em>not</em> to do during this process.</p>

<h2 id="why-microservices">Why Microservices?</h2>

<p>This particular application team wanted to rearchitect with Microservices but didn’t seem to have scale problems. They had a modestly large user base, but nothing that was really stretching the scaling boundaries of their current app.  We found this peculiar.  What was their goal in using Microservices, then?  At this point, everyone likes Microservices, but most are cognizant of the overhead they can incur as well.</p>

<p>It took me and the team a bit of time to figure out that they wanted Microservices to accelerate the pace of development. And they didn’t just want them, they wanted them NOW! Ah, this was a different beast that required a different weapon to take down.</p>

<p>Knowing their primary goal was time to market, we worked towards trying to scope new services with proper domain boundaries rather than simply just creating a new service for each portion of the domain.  Our challenge was to <em>keep things as simple as possible in a Microservices architecture</em>.</p>

<p>Mission impossible? Probably. 😃</p>

<h2 id="api-first-development">API first development</h2>

<p>The desire to quickly deliver a new feature set with this architecture forced our teams to work within an API contract driven development model.  This allowed some developers to run ahead on frontend development by agreeing on what the backend REST API would look like <em>while</em> we built it out.</p>

<p>We documented the API’s with <a href="https://www.openapis.org/">OpenAPI</a> specs from the very beginning.  This was crucial.  Our frontend team used these specs religiously.  Later, we were able to take the open API specs and use tools like <a href="https://www.npmjs.com/package/express-openapi-validator">express-openapi-validator</a> to do automatic validation and <a href="https://www.npmjs.com/package/@manifoldco/swagger-to-ts">swagger-to-ts</a> to generate Typescript interfaces from this (if you haven’t gathered by now, a bunch of our services were in Node.js and Typescript).  Other teams were using Java, and because OpenAPI is, well… open, the tooling for other languages exists as well and can easily be supported.  Our initial investment in API driven development paid off in spades by avoiding writing a lot of boilerplate code.  This all worked brilliantly and we would repeat it again in a heartbeat.</p>

<h3 id="side-note-about-apis">Side note about API’s:</h3>

<p>At the beginning of the project, we had a lot of debate about whether to use REST API’s or GraphQL API’s.  One thing that jumped out at us was that something like GraphQL didn’t <em>need</em> OpenAPI, it was built in!  However, we ended up going with RESTful API’s because we were running lean - We needed to get going fast.  We made the decision that the time necessary to setup GraphQL resolvers and other infrastructure was best spent elsewhere.  I’ll save the debate about whether or not we were right for another post. 😜</p>

<h2 id="service-boundaries-are-kinda-important">Service boundaries are kinda important</h2>

<p>Since scaling wasn’t our primary goal with this architecture, we set our sights on defining the right service boundaries to choose. Our first feature handled a somewhat broad swath of functionality, so we created a service with a larger set of capabilities than you’d typically see in a microservice.  This was instead of trying to carve off a small niche of functionality to allow it to scale independently.  We had plenty of groundwork to lay, so it was nice that we could start with just one new service and still complete a large feature set.</p>

<p>However, this time to market focused approach of determining service boundaries led to bad decisions (big surprise, huh?).  Because we were under the gun to deliver, we didn’t get a chance to fully understand the business domain before we dove in and created a new service.  This led to us (honestly) confusing where various API’s should be located:  Should they be in the new service we were creating?  Should we have created multiple services?  Should they be in the legacy application temporarily?  Should they be a completely separate service?</p>

<p>In hindsight:  We should have done more experimentation with service boundaries using <em>real working code</em>, and refactored as we went if we needed narrower service boundaries.</p>

<h2 id="using-your-cloud-provider-to-the-max">Using your cloud provider to the max</h2>

<p>One major factor of the success of our project was staying within one cloud provider’s ecosystem.  In our case it was Amazon Web Services (AWS).  This same principle applies for any of the major cloud providers though: Using their utilities whenever we could saved us time and kept things smooth and moving quickly.</p>

<p>Our domain was hosted with Cloudfront.  We used Application Load Balancers (ALB’s) to balance traffic.  We used S3 to host our SPA’s static assets.</p>

<p>On the API side, we deployed our services into Docker containers using Elastic Container Service (ECS) which stored data in databases managed by Relational Data Service (RDS).  It worked splendidly.  The one place where we strayed from pure AWS was that we configured things with Terraform instead of Cloud Formation.</p>

<p>For running containers, we toyed with the idea of using Kubernetes, but decided that seemed like using a jackhammer when only a hammer was needed.  ECS is “just complex enough” for an orchestration engine, in our experience. A year out, that still seems like the right choice.</p>

<h2 id="pay-attention-to-the-glue">Pay attention to the glue</h2>

<p>One of the executives described moving off the old system while keeping it running as: “Changing the tires on a bus while it’s moving.”  Which is an apt metaphor. So, how were we going to share data between the systems and gracefully move off over time?</p>

<p>One thing that we did right was picking an API gateway that was endlessly customizable in one central place.  Because we were working in (mostly) a Node.js stack, we chose <a href="https://www.express-gateway.io/">express-gateway</a> as our entrypoint to our Microservices.</p>

<p>In the year at this client, this has been the biggest question:  “If you’re so bought into AWS, why not just use AWS API Gateway?”  There’s a few reasons:</p>

<p>First, we were excited by the fact that express-gateway was basically just glorified E<a href="https://expressjs.com/">xpress.js</a> middleware with a pre-existing set of routing policies built in. It was open source, backed by the Linux Foundation and <a href="https://auth0.com/blog/apigateway-microservices-superglue/">recommended by Auth0</a>.  Using it, we could get the best of express along with not having to code our own proxying or filtering logic.  We were able to use existing policies like JWT authentication while also writing our own policies for those strange, unforeseen situations that inevitably come up during a major technical transition like this.</p>

<p>Second, we knew that authentication between the two systems was going to be challenging.  The old system used session based cookie authentication, and our new Single Page App needed token based authentication.  How were we going to bridge that gap while not propagating this issue to each of the underlying services?  We <em>could</em> customize the heck out of AWS API Gateway using Lambdas, but we decided that having a central point where we could keep this code and run pipelines was preferable.  We were able to use express-gateway <a href="https://www.express-gateway.io/docs/policies/">custom policies</a> to bridge this gap in authentication nicely, all in one place in code, without having a bunch of Lambda code sprinkled around that was hard to track.</p>

<p>Third, we knew that this transition state between an old and new architecture was temporary.  We constantly told everyone that if we did things right in this “semi customized” API Gateway, that someday, when the old system was completely retired, we could completely get rid of it and move to an off the shelf solution like API Gateway. This did end up happening eventually after I left.</p>

<h2 id="dont-kid-yourself-the-old-monolith-isnt-going-anywhere-soon">Don’t kid yourself:  The old Monolith isn’t going anywhere soon</h2>

<p>The tech team wants a new system, and the product team is smart enough to know (and so are you) that “The Big Rewrite” approach is a recipe for disaster! You want to be lean and move your old system into the new world gradually, delivering value along the way.</p>

<p>But, you can’t pretend like your old system is going anywhere soon, even if you really really want it to.  We spent a lot of time setting expectations around this with the client.  In our case, and I’m sure many others, the old system will still be the primary system the business runs on for a good while.  If the old system is shaky enough, it will take down the new system as well.</p>

<p>We spent a good amount of time shoring up the older system, which we had to work hard to justify.  And that <em>still</em> wasn’t enough. Moving an application to Microservices doesn’t have to be all or nothing.  You can take steps in your existing Monolith to <em>get it ready to be split up</em> into Microservices.  It’s important that your product team understands this for roadmap planning.</p>

<h2 id="dont-short-change-your-investment-in-the-foundation">Don’t short change your investment in the foundation</h2>

<p>A wise colleague once told me:</p>

<blockquote>
  <p>You can’t fill a garbage truck with cement if you want to lay a foundation.</p>
</blockquote>

<p>Along those lines, our primary learning point in this project was around foundational Microservices architecture: You cannot build an “MVP” of Microservices infrastructure.  If you have even the faintest whiff there will be more than one service, you have to go all in and invest in the necessary infrastructure to support many services.</p>

<p>Because of the desire to deliver quickly, our Continuous Integration / Continuous Deployment (CI/CD) pipelines were created using an MVP approach.  This was a mistake.  The minute it became time to create services number 2 and 3, we felt the pain of the missing capabilities we didn’t add.  An example was sharing build artifacts across environments.  These missing capabilities caused each service to take a <em>long</em> time to build, and was repeated for each environment to save a little time at the beginning.  After a couple of services, this was a nightmare.</p>

<p>We didn’t have time for things like standards on API formats from the get go, and instead learned in service 2 or 3 that we needed them.</p>

<p>The time spent up front building this foundation would have paid off in spades.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<ul>
  <li>Microservices are <em>serious business</em>.  You can’t shortchange the time and effort needed to do them right. of where the product is going and.</li>
  <li>Use your cloud provider as leverage to move faster</li>
  <li>Have a deep understanding of the business domain and the roadmap going forward in order to architect service boundaries properly</li>
</ul>

<p>And of course….</p>

<p><img src="/img/paul-a-devops.jpeg" alt="Paul Allen Yells Devops!" /></p>

<p>Happy microservicing!</p>

<p><em>Note: I wrote the original version of this post for my employer, <a href="https://engineering.deptagency.com">DEPT® Agency</a>, you can view it <a href="https://engineering.deptagency.com/a-journey-moving-the-monolith-to-microservices">here</a></em></p>]]></content><author><name>Matt</name></author><category term="coding" /><category term="cloud" /><category term="architecture" /><category term="microservices" /><summary type="html"><![CDATA[A while back, I spent about a year working on a team who wanted to move their Java based monolith into the 2020’s with a spanking new Microservice / Single Page App (SPA) architecture. Sweet! A greenfield redo! Sound a little too good to be true? Here was the catch:   We needed to keep their old monolith running while we did it, and it was a big, complicated application.]]></summary></entry><entry><title type="html">Scaling Technical Teams</title><link href="https://theothermattm.github.io//scaling-technical-teams" rel="alternate" type="text/html" title="Scaling Technical Teams" /><published>2021-08-18T00:00:00+00:00</published><updated>2021-08-18T00:00:00+00:00</updated><id>https://theothermattm.github.io//scaling-technical-teams</id><content type="html" xml:base="https://theothermattm.github.io//scaling-technical-teams"><![CDATA[<blockquote>
  <p>“How can I move faster?”</p>
</blockquote>

<p>I’m sure we’ve all heard this many times from bosses and clients. Regardless of team size, industry or maturity, I consistently see teams struggle to scale. Even the ones that do it well have some serious learning to do. In some extreme scenarios I’ve heard “I am spending more on software, yet, somehow, I’m seeing less software get released. How the hell is this happening?”</p>

<p>Those of us who’ve been in software for a while are probably very familiar with <a href="https://en.wikipedia.org/wiki/The_Mythical_Man-Month">Brooks’ Law and The Mythical Man Month</a> which states:</p>

<blockquote>
  <p>Adding manpower to a late software project makes it later.</p>
</blockquote>

<p>This is what you’re seeing when the situation described above occurs. It makes it even worse when your team isn’t set up to scale.</p>

<p>Why should you care about scaling your team? Here are some symptoms of a team that is struggling with growth:</p>

<ul>
  <li><strong>Problems organizing work</strong> - Entire teams stuck waiting on product requirements on other teams, wasting time.</li>
  <li>Too many hurdles make the <strong>slightest of changes to production take a very long time.</strong> This leads to very frustrated business partners.</li>
  <li><strong>Paralysis by analysis</strong> - Teams stuck debating on how to implement something, again, wasting time.</li>
</ul>

<p>In this post, I’ll start with the less technical ways of addressing these challenges and end with the more technical ones.</p>

<p>First up, the people!</p>

<h2 id="the-talent">The Talent</h2>

<p>This will sound cliche, but it all starts with hiring. You want to know how to scale your team <em>before it’s too late</em> . Here are some things to consider when hiring your software team.</p>

<p><strong>Start with strong technical leadership.</strong> Hire this talent first. By “technical leadership” I don’t mean “managers.” I mean player/coach talent who <em>do the work while building out a team</em> , then move to full time leadership.</p>

<p>When picking your technical leads, it’s best to <strong>prioritize communication skill and curiosity over technical ability</strong>. You want a technically sound leader, but more importantly, you want a leader who asks good questions, exposes shortcomings, and <em>knows when they are in unfamiliar territory</em>. This type of person is also someone who can communicate with you and about your business with ease. Someone who can talk tech and also business is rare, so when you find it the cost may be high but it will be worth it.</p>

<p>If you nail your tech lead(s), you’ll have a person who can ask the right questions to get projects started, who knows how to staff them with the right balance of people, and who will also contribute. To vet this person, leverage your network of technical people to help. Depending on your plans, you may want to hire a few of these people.</p>

<p>After you hire your technical leaders, you want to make sure you <strong>have a good balance between leaders and individual contributors</strong>. Trust that your tech leads will lead this hiring effort and let them know how much you want to be involved. Don’t just hire all senior engineers, or you’ll have a situation where you have stalemates on technical decisions all the time as Senior Engineers tend to have strong opinions.</p>

<p>Ideally, you have a balance of people who will want to lead the charge, and people who are more junior and eager to learn. In all of these roles, curiosity is a key trait to look for. Make sure you hire Senior Engineers who <em>want</em> to mentor and check in with them frequently to make sure their mentoring duties don’t interfere with their daily work too much. You’ll know you’re on the verge of having too many junior developers when your senior engineers say mentoring is taking up too much time.</p>

<p>Finally, make sure you <strong>have a large enough product and/or business team to back up your technical team</strong> to help drive work forward. A technical team without enough product owners and/or designers is as good as not having a software team. Of course you want independent teams who can drive work, but if you don’t have the business expertise at your disposal you’ll be unpleasantly surprised when your app or platform <em>looks like it was built by engineers</em>.</p>

<h3 id="organization">Organization</h3>

<p>After people, the next most important thing is organization - Of people and work.</p>

<p>The big theme here is: Less is more. Put some basic constraints in place and <em>trust your teams</em> to do the right things. You don’t want everyone going wild, so things like making sure everyone’s using the same language might be a good constraint. But, determining how individual teams run meetings and processes is only going to create frustration. Again, it’s amazing what an empowered software team can do if you give them goals and leave them to their own devices.</p>

<p>As a wise man once said:</p>

<blockquote>
  <p>If you want to build a ship, don’t drum up the mento gather wood, divide the work, and give orders. Instead, teach them to yearn for the vast and endless sea.</p>
</blockquote>

<ul>
  <li>Antoine de Saint-Exupéry</li>
</ul>

<h3 id="team-structure">Team Structure</h3>

<p>You don’t have to nail the team structure from the get go. Just <strong>keep in mind that you’ll have to create more teams</strong> and have a list of “things to figure out.” Make sure you’re transparent about what has and hasn’t been figured out. Figuring things out as you go and being honest about it is a lot better than trying to anticipate everything at the outset. You’ll save everyone’s sanity and         time. Share the plans for growth with your teams and ask for their input. Software teams can be extremely proficient at helping with these things. They can let you know what they’ve seen work and more importantly, <em>you want them bought in since they’ll be living with it every day.</em></p>

<h3 id="inter-team-communication">Inter Team Communication</h3>

<p>Figure out a way to communicate across teams. Sometimes this is Slack channels, sometimes this is a <a href="https://www.atlassian.com/agile/scrum/scrum-of-scrums">Scrum of Scrums</a> with your technical leads.Whatever it is, just <strong>get a basic cross-team communication framework in place</strong> and let the teams adjust it and run with it as they wish.</p>

<h3 id="organizing-work">Organizing Work</h3>

<p>Finally, I should talk about organization of <em>work</em> <strong>Strive to have about a month’s worth of work <em>shovel ready</em> for <em>all</em> of your teams.</strong> I use the term shovel ready to describe work that is well-defined enough for teams to go and execute on without being blocked. This is an art, not a science; if you over define the work, you risk alienating your team. However, if you don’t define it enough, you’ll have a bunch of developers looking at each other wondering what you meant by “I want the software to be <em>easy to use.</em>”</p>

<p>To get to this point, make sure that your technical leaders and product leaders are working together regularly to help strike the right balance and keep defining the work to keep it ahead of the individual contributors on your teams.</p>

<p>When you have a team who has enough definition and enough freedom to ask questions and try out different solutions when implementing, you’ll have a team of engaged, productive people and you’ll be pleased with the results. It’s also extremely important to have a team that feels collaborative and is part of decision-making when things change.</p>

<p>If a feature or change isn’t working out, the team shouldn’t feel like it’s a reflection of poor work, but rather that they are working <em>together</em> to make a better product.</p>

<h1 id="communication">Communication</h1>

<p>In the previous section, I discussed what can go wrong when a software team isn’t built to scale properly and some strategies around hiring and organization that can help mitigate some of those problems.</p>

<p>Next, I’ll tackle another area that’s ripe for problems relating to scaling teams:</p>

<p><strong>Talking and Stuff. Aka - Communication.</strong></p>

<h2 id="the-blessing-and-curse-of-tribal-knowledge">The Blessing and Curse of “Tribal Knowledge”</h2>

<p><a href="https://www.isixsigma.com/dictionary/tribal-knowledge/">Tribal Knowledge</a> is an often-used (and very unwoke) term for the stuff that your team knows but isn’t codified yet. When you’re starting up, there’s no time or need to write those things down or automate them. It’ll just slow you down. The fact that this knowledge is <em>just known</em> on the team makes it a well oiled machine.</p>

<p>But as you grow and new people come onto your software team, they only find out about these things by happenstance or even worse when an issue comes up and they’re not sure how to fix it. You could consider the process of gaining this knowledge a tax on every new person on your team. To become fully productive, they need to absorb all that knowledge and the only way to do it is to give it time.</p>

<p>This knowledge can be simple things like where the link to the deployment system is and what branch production is deployed from. Or, it can be skeletons (as in <em>skeletons in your closet).</em> I often find that companies that grow quickly also have skeletons in their closets that only a handful of “the old guard” know.</p>

<p>In the worst case scenario, the old guard views having this knowledge as a badge of honor and job security and holds onto this knowledge. In the best case scenario they have these issues documented and potential tickets documented knowing that they need to be fixed.</p>

<p>Regardless of where you might fall, it’s best to get ahead of this as your team is <em>starting</em> to grow.</p>

<p>Make sure that you reward transparency and information sharing, not <a href="https://bloomfire.com/blog/five-signs-company-information-hoarding-problem/">information hoarding</a>. The default reaction to a team member saving the day during a production incident or fixing a bug should be to ask “How do I make sure that doesn’t happen again? Is what you did written down somewhere? Could other developers do that if I needed them to?”</p>

<p>Make sure your technical leads set goals for their teams to codify their knowledge. Simply dumping all the knowledge into a document or wiki is not enough, this needs to be thoughtful and strategic.</p>

<p>Here are some other ideas to consider to get ahead of this as you think about scaling your teams:</p>

<ul>
  <li>Set aside some time periodically to prioritize automation of tasks that require tribal knowledge. There’s nothing better than having workingcode instead of documentation.</li>
  <li>Make sure that code comments which document the “<a href="https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/">Why? Rather than the How?</a>” are prioritized and vetted during code review processes. As the linked article explains, the code is the how, and what’s more important is the why. This is especially important when something weird is happening. Keeping this documentation as close as possible to the code itself is much better than distributing it in different places like a document or wiki.</li>
  <li>Have a [Runbook](This knowledge can be simple things like where the link to the deployment system is and what  branch production is deployed from.  Or, it can be skeletons ☠️ (as in skeletons in your closet) .  We often find that companies that grow quickly also have skeletons in their closets that only a  handful of “the old guard” know.</li>
</ul>

<h2 id="curiosity">Curiosity</h2>

<p>In our experience, the best developers on the best teams are very, very curious. They don’t just want to build the thing you ask, they want to know why what they’re building will be valuable. The best developers and teams know that if they understand the core of what a business is after, they can lend the strengths of their technical discipline to making products better.</p>

<p>Don’t treat a developer with lots of questions as a nuisance; embrace them and foster a culture  where asking those questions is not only rewarded but expected. If your software teams aren’t asking lots of questions, consider that a potential red flag. Perhaps your tech leads are hoarding information!</p>

<h2 id="organize-around-information-sharing">Organize around information sharing</h2>

<p>Make sure that between you, your technical leads and individual team members there is a clear sense of who makes what decisions, who needs to be informed of them, and who doesn’t care at all. There’s a tool for that! <a href="https://en.wikipedia.org/wiki/Responsibility_assignment_matrix">RACI matrix</a> anyone? You don’t have to use something as formal  as a RACI matrix, but be inspired by the idea. Strive to make sure your team can make as many decisions as possible independently.</p>

<p>When a team knows they’re empowered to make a decision, they will move much faster. And when they can’t make a decision, if they know exactly who to go to, then that decision will get to the right person a lot quicker. This, of course, assumes that when a decision needs to be made outside the team, that decision-maker makes it a priority to get that decision to them fast. Reward unblocking your teams throughout the organization.</p>

<p>Establishing a RACI matrix (or something like it) <em>before</em> your teams scale and updating it as you go is very low effort and will pay off in spades.</p>

<h1 id="the-technical-things">The Technical Things</h1>

<h2 id="getting-started">Getting Started</h2>

<p>Developers often have a difficult time getting an application running on their local machines. In the worst cases, it can take a developer more than a week to get the full system running so they’re ready to start developing.</p>

<p>Making sure that other developers can easily get started running a system locally is something that’s often overlooked early in a project. As your team grows and new people have to create this setup (or, existing people get new computers!) this slows you down immensely. Combine this with the amount of “Tribal Knowledge” that a new person needs to be productive and you can see how this will slow down the productivity of your team and their ability to scale.</p>

<p>So how can you get ahead of it? <strong>Prioritize automation.</strong> Whether it be through specific talent like DevOps or Site Reliability Engineers or Release Managers, or just making it one developer’s priority to script things - Make sure that a new developer can get started in a few hours at the very most. At the very best, one script does <em>all</em> thesetup for a new developer - they run the script, go have a coffee, and come back to a workstation ready to develop on.</p>

<p>Be mindful that different developers have different preferred platforms they use. Some prefer to use Mac, some prefer to use Windows, others Linux. I are not fans of dictating tooling, so prefer automation methods that work across platforms like using Docker images for development setups, or provide different scripts for different platforms.</p>

<p>Oftentimes, the effort you put into scripting a local setup can be directly translated into deploying the application to a real environment. Even if the scripts aren’t directly used, you can usually translate them into a new language/framework because <em>everything you need to set up is in code and not just written down.</em></p>

<p>Which brings us to our next point …</p>

<h3 id="make-significant-investments-in-automationdevops">Make significant investments in Automation/DevOps</h3>

<p>One surefire way to kill the ability to scale your team and software is a lack of investment in automation and “DevOps” (side note: I’m using that word even though I hate what it’s become!). I have seen many projects where it’s time intensive and risky to get software into production because processes are manual. In the worst cases, nobody fully understands how to get the software into production at all.</p>

<p>In some of these same projects, there is little ongoing investment in their DevOps platforms to solve the problem. Instead, more resources go to the development team to crank out <em>more software that is risky to be deployed.</em></p>

<p>Even in the earliest phases of your project, make sure you have a fully automated Continuous Integration / Continuous Delivery (CI/CD) pipeline. Regardless of the solution, it should be as easy as pushing code to a branch or clicking a button to deploy to production. Whatever you do, <strong>don’t give into the temptation to manually push code out</strong>! It will come back to haunt you before you even get the software out to users. The investment in a CI/CD pipeline will pay off <em>even before production releases</em> because it will allowyou to get test versions of your software out faster to be QA’d. These days with so many easy to use options for CI/CD pipelines, nobody has a good excuse for not having a good CI/CD pipeline.</p>

<p>In concert with CI/CD pipelines, invest in <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Infrastructure as Code (IaC)</a>. I recommend <a href="https://www.terraform.io/">Terraform</a>. These tools let you describe your cloud environment and create it with one command. Using these types of tools not reduces your risk of environments being different, and also gives you the ability to spin up new environments very easily. New environments should ideally be button click. This will allow you to spin up a new testing environment for a new feature set, then easily tear it down, saving on cloud compute costs.</p>

<p>Make sure your environments mirror each other as closely as possible. It takes discipline to make sure that this stays in place. Make investments in anonymizing production data to copy to testing environments to make sure features work with production-like data.</p>

<h3 id="testing">Testing</h3>

<p>This one isn’t groundbreaking, but needs to be said: make investments in test automation and integrate tests into your CI/CD pipeline. Do this as early as you possibly can. It’s much harder to add tests to an existing application than to adjust them as you go.</p>

<p>I often hear from clients that they invest heavily in automated testing. Yet, when it comes down to it, their strategy is just not working and releases of software still require extensive manual testing.</p>

<p>It’s easier to start by saying what <em>doesn’t</em> work in this area:</p>

<ul>
  <li><strong>Relying solely on unit tests in each component of the application.</strong> This often just tests one particular case, but functionality of interdependent features often gets missed here. This will inevitably lead to more manual testing.</li>
  <li><strong>Having too many automated front end tests</strong>, such as in Selenium or Cypress. No matter how hard you try, these tests will be brittle and will break over time and require a lot of effort to maintain. What’s worse than no tests at all? Tests that break all the time and you can’t rely on them. Rely on these types of tests for absolutely crucial pieces of your software, main use cases and smoke testing to make sure that an app comes up after a restart. Do not try to test everything with them. I’ve yet to see an organization succeed at this over time.</li>
</ul>

<p>What does work is automating API tests. It doesn’t matter what you use, but being able to work through key functionality cases using just a series of API calls is a good way of automating tests that strikes a good balance betIen making sure your whole application works together and being <em>easier</em> (note not <em>easy</em>) to automate.</p>

<p>Having API level tests that go all the way from login, to storing data in a data database, to logout allows you to test a very wide surface of your application with even one test case. It can be difficult to catch every corner case with these types of tests though, so you have to still rely on unit tests for those situations. A good mix of API tests with unit tests for individual components is what you should strive for.</p>

<p>The challenge with automating API tests is having the necessary infrastructure in place to allow for full application testing in an environment that can be torn down and built up at will. Quick throwback to having CI/CD pipelines and IaC tools. If you have a good CI/CD pipeline in place, this <em>should</em> be easier. You can do this with quicklydeployed databases using Docker containers so you don’t even need to set up a whole cloud environment to do these tests, which will make them quicker. The sticky wicket in these tests are third party API’s which you don’t have control over. Use a network mocking tool like <a href="https://mswjs.io/">Mock ServiceWorker</a> for Javascript to make your tests as seamless as possible in this regard.</p>

<p>I also suggest having <strong>code coverage targets only for areas of the code with high <a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity">cyclomatic complexity</a></strong>. Arbitrary code coverage targets  will ensure your useless methods get covered as much as your most important ones do.  Focus on the complicated ones and let API tests cover the rest.</p>

<h3 id="code-organization--decoupling">Code Organization &amp; Decoupling</h3>

<p>One of the most significant (and obvious) pain points on large software teams is when multiple people are doing work on the same piece of code at the same time. The team ends up spending more time fixing source control conflicts and coordinating than actually producing value. There’s no perfect way to organize code, but if you give it some effort up front and adjust as you go, it’s a pretty easy discipline to get into, especially if it’s part of your code review process.</p>

<p>Make sure from the get go that you’re not putting all your code in a few modules or files (this one’s pretty obvious, but worth saying to reiterate). Once you hit a certain point, separate files will become unsustainable. You will smell the code rot from a mile away.</p>

<p>One other idea to consider is the idea of contract driven services (lowercase s) <em>within the same application.</em> Many of the benefits of a Microservice architecture are derived from the concept of <a href="https://en.wikipedia.org/wiki/Domain-driven_design">Domain Driven Design</a>, specifically the idea  of a <a href="https://www.martinfowler.com/bliki/BoundedContext.html">Bounded Context</a>. This is a fancy term for drawing smart boundaries around portions of the system, creating contracts for interacting with them and restricting the communication between boundaries to use that contract.</p>

<p>If you can come up with the right bounded contexts for services within your application, you can then work to define contracts for those contexts using simple code documentation or even going so far as to use things like <a href="https://www.openapis.org/">OpenAPI</a>. Yes, using OpenAPI even if you’re not making a separate deployed API yet.</p>

<p>The simple act of being more explicit about the contract of a set of services is the important part. By defining the contract of a bounded context, you can then split work out across teams and make sure that they work with each other’s code through those contracts.</p>

<p>Once you hit a certain point, separate files will become unsustainable. You will smell the code rot from a mile away. Once you start to sense this oncoming disaster, it probably makes sense to create a separate API or application for some portion of your system. Then, if you’ve created the right bounded context and have a well defined contract, this exercise will be a lot easier, and there will likely be a team who is already well versed in that particular service that needs to be split out.</p>

<p>Speaking of services…</p>

<h3 id="what-about-microservices">What about Microservices?</h3>

<p>The past ten years or so the buzzword “Microservices” thrown around as a panacea to fix many problems in scaling and organizing software. And of course, as with many technical buzzwords, there’s no one definition of what a Microservice architecture is. The most important thing to remember is: <strong>Don’t conflate a well organized application with Microservices.</strong> A well organized application with separate domains is always a good idea. Microservices may or may not be a good idea.</p>

<p>There <em>are</em> many situations where a microservice architecture will help, but to “Do Microservices Right” you need all kinds of foundational architecture in place. For example, have you addressed these questions?</p>

<ul>
  <li>How are you going to handle deployments across dozens or perhaps hundreds of services?</li>
  <li>How will you handle transactions betIen services?</li>
  <li>How will you handle discoverability of services?</li>
</ul>

<p>Those are just a few of them. If it’s not obvious, you can quickly get in over your head, especially if you haven’t done it before. Even the godfather of Microservices himself says “<a href="https://www.martinfowler.com/bliki/MicroservicePrerequisites.html">You must be this tall to use microservices.</a>”</p>

<p>Start with simpler options like those described above in the “Code Organization” section. It probably does make sense at a certain point to have a system with multiple services or applications. But do you need to do it for every little piece of your application? Probably not.</p>

<p>When you use the ideas I outlined in the Code Organization section, you’re effectively forcing your teams to work <em>as though they were using a Microservice</em> but without all the headaches of separate deployments, dealing with cross service transactions, etc. You’re getting yourself more than 50% of the way there, but keeping things simpler, and more importantly, letting your teams scale out more.</p>

<h2 id="parting-thoughts">Parting Thoughts</h2>

<p>You can probably tell by the sheer amount of content given to <em>non-technical</em> topics here, I feel strongly that an organization that can scale is driven by people, culture and communication, more so than technology. Remember that languages and platforms are tools to use  to get to a goal, not the goal itself.</p>

<p>Good luck growing your team!</p>

<p><em>Note: Me and other coworkers wrote a different version of this post for my employer, <a href="https://engineering.deptagency.com">DEPT® Agency</a>, you can view it <a href="https://www.deptagency.com/en-us/insight/how-to-scale-your-engineering-team/">here</a></em></p>]]></content><author><name>Matt</name></author><category term="management" /><category term="teams" /><summary type="html"><![CDATA[“How can I move faster?”]]></summary></entry><entry><title type="html">Having Kids Made Me A Better Developer</title><link href="https://theothermattm.github.io//having-kids-made-me-a-better-developer/" rel="alternate" type="text/html" title="Having Kids Made Me A Better Developer" /><published>2021-04-22T14:03:16+00:00</published><updated>2021-04-22T14:03:16+00:00</updated><id>https://theothermattm.github.io//having-kids-made-me-a-better-developer</id><content type="html" xml:base="https://theothermattm.github.io//having-kids-made-me-a-better-developer/"><![CDATA[<!-- wp:paragraph -->
<p>About 8 years ago, I started the biggest journey of my life: Having kids. Before my wife and I had our first child, to say I was anxious is an understatement.  Of course I was happy too, but a lot of the time my thoughts would clash between things like "Will she be healthy and have two arms and legs?" and "How will I function with no sleep!?". It was a constant tug of war between selfless and selfish thoughts, causing spirals of anxiety for me.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>As a software geek, I kept thinking about the amazing article Jeff Atwood wrote called "<a href="https://blog.codinghorror.com/on-parenthood/" target="_blank" rel="noreferrer noopener" title="https://blog.codinghorror.com/on-parenthood/">On Parenthood</a>" where he presents one of the best data visualizations I've ever seen related to the raising of children.


<br /><br />

<img src="../img/coding-horror-kids.png" width="350" />


<br /><br />

After our daughter arrived and we spent our maternity/paternity leave groggy and caring for a beautiful new infant,  we settled into the rhythm of having a newborn.  We got used to losing sleep and started making sacrifices for ourselves for this new bundle of joy.  We did this without a second thought.  It just happens, and when you're in it, you're in it.  Looking back on it, the anticipation of having a newborn was actually much worse than having a newborn!  </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But, after a few months, something happened that I didn't expect.  <em>Everything else got easier.</em><br /><br />Drama with friends?  Whatever, I don't have time for that, I've got a baby to take care of! Drama at work? Ergh, I'll keep my head above the fray - I need to focus on work so I can get home and take care of the baby!  Should I use NoSQL or relational databases at work? </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It's that last one I want to talk about a little more.  This is the type of decision I would pontificate endlessly over.  I'm a consensus-seeker too, so my own internal gnashing of teeth would be amplified by asking for others' opinions. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But after my first was born, the answer to these types of questions came a <em>lot</em> more quickly.  In the case of SQL vs NoSQL, for instance: "Well, this seems like a wash, so I'll just pick the one I'm familiar with and get the job done, so I can spend more time with my family."<br /><br />This decision-making process could sound like a bad thing - Like i'm cutting corners because I want to spend time with my family.  Honorable on a personal level, but negligent professionally.  Maybe, but I know that I would never be negligent about making a decision, it's just not in my nature.  But knowing that a beautiful living being was waiting for me at home really forced me to (subconsciously) make a decision a lot quicker.  </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It got even more crisp when my son was born four years later!<br /><br />With some hindsight:  This type of pressure is the best thing that has happened to me both personally and professionally.<br /><br />It has forced me to become a much more crisp and quick decision-maker.  It forces me to prioritize <em>getting things done</em> over <em>making things perfect</em>.  It has also made me more scrupulous about when attention to detail and striving for perfection is important, and when it's not.  And finally, it has forced me to become well versed in <a href="https://www.amazon.com/Essentialism-Disciplined-Pursuit-Greg-McKeown/dp/0804137404" target="_blank" rel="noreferrer noopener" title="https://www.amazon.com/Essentialism-Disciplined-Pursuit-Greg-McKeown/dp/0804137404">Essentialism </a>and the subtle art of saying "No" to things.  It's amazing how much better your life becomes when you become comfortable with saying no.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>As a Software Development manager, all of this also has positive effects for my teams as well - I'm much more comfortable delegating decisions to them. Being judicious with my time forces me to let those who are <em>closest to the problem at hand</em> make the decisions. Of course, I always want to be informed, but I make it clear that my input shouldn't hold them up. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I have found that often in the world of software, shipped is better than perfect.  Feedback from users is better than spending time and money making something that may not be used by anyone.  My kids helped me realize that and I thank them for it.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><br /><br /></p>
<!-- /wp:paragraph -->]]></content><author><name>Matt</name></author><category term="career" /><summary type="html"><![CDATA[About 8 years ago, I started the biggest journey of my life: Having kids. Before my wife and I had our first child, to say I was anxious is an understatement. Of course I was happy too, but a lot of the time my thoughts would clash between things like "Will she be healthy and have two arms and legs?" and "How will I function with no sleep!?". It was a constant tug of war between selfless and selfish thoughts, causing spirals of anxiety for me.]]></summary></entry><entry><title type="html">Announcing My New Business</title><link href="https://theothermattm.github.io//announcing-my-new-business/" rel="alternate" type="text/html" title="Announcing My New Business" /><published>2021-04-01T12:53:00+00:00</published><updated>2021-04-01T12:53:00+00:00</updated><id>https://theothermattm.github.io//announcing-my-new-business</id><content type="html" xml:base="https://theothermattm.github.io//announcing-my-new-business/"><![CDATA[<p>I’m happy to announce that I’ve started a new business.  This is a side gig right now, but once this takes off, I expect to become independently wealthy from it.</p>

<p>:drumroll: Announcing…..</p>
<h2 id="the-jvm-jargon-generator"><a href="https://kotlin-jvm-jargon-generator.fly.dev/">The JVM Jargon Generator</a></h2>

<p>Years of toiling away with Java Virtual Machine (JVM) technologies made me realize that the only thing that really matters when developing in the JVM space is that you can <em>intelligently talk about it</em>.  These complex projects rarely get deployed to production or used by users, so it’s usually sufficient to just have a headline and be able to talk circles around your businesspeople to confuse them.  Then, you can go have a beer.</p>

<p>Written in state of the art Kotlin using complex AI (if/else blocks and switch statements) it is truly a groundbreaking piece of technology.</p>

<p>Please send me copious amounts of money so I can buy a beach house and retire.  Thanks!</p>

<p><em>Postscript January,2023:</em>
Since Heroku decided to <a href="https://blog.heroku.com/next-chapter">shut down their free tier</a> 😡, I moved this app over to <a href="https://fly.io/">Fly.io</a> which has been an absolute pleasure and something I plan on writing about.</p>]]></content><author><name>Matt</name></author><category term="coding" /><summary type="html"><![CDATA[I’m happy to announce that I’ve started a new business. This is a side gig right now, but once this takes off, I expect to become independently wealthy from it.]]></summary></entry></feed>