<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Nathan Kallman</title>
    <description>The latest articles on Forem by Nathan Kallman (@kallmanation).</description>
    <link>https://forem.com/kallmanation</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F253841%2Fdc111ece-f3a9-4a9e-9863-b38609f203ae.jpg</url>
      <title>Forem: Nathan Kallman</title>
      <link>https://forem.com/kallmanation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kallmanation"/>
    <language>en</language>
    <item>
      <title>Swagger Codegen and Multiple Tags</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Mon, 09 Feb 2026 16:06:59 +0000</pubDate>
      <link>https://forem.com/kallmanation/swagger-codegen-and-multiple-tags-4n8o</link>
      <guid>https://forem.com/kallmanation/swagger-codegen-and-multiple-tags-4n8o</guid>
      <description>&lt;p&gt;Today ChatGPT lied to me, so I’m setting the record straight. This is my question: “If an endpoint in an OpenAPI spec has multiple tags, what will Swagger Codegen do?”. Because Swagger Codegen organized the generated code into &lt;code&gt;api/tag_name_api&lt;/code&gt; files. So what if there’s multiple tags? Does it take the first tag? The last tag? Or does it generate a file for each tag and give it the same behavior? ChatGPT claimed it takes only the first tag. I’ve already revealed that’s false, so I guess I don’t have any dramatic reveal, so let me just spout the details for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;I used Swagger Codegen V3 Online Generator: &lt;a href="https://swagger.io/docs/open-source-tools/swagger-codegen/codegen-v3/online-generators/" rel="noopener noreferrer"&gt;https://swagger.io/docs/open-source-tools/swagger-codegen/codegen-v3/online-generators/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I &lt;code&gt;POST&lt;/code&gt; ‘ed to &lt;a href="https://generator3.swagger.io/api/generate" rel="noopener noreferrer"&gt;https://generator3.swagger.io/api/generate&lt;/a&gt; on 2/9/2026 with a payload looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"specURL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my.code/openapi/v3/swagger.yaml"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gemName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"liar-liar-pants-on-fire"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gemRequiredRubyVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;= 3.1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gemSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Liar Liar Pants on FIRE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"gemDescription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gemAuthor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Me"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CLIENT"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"codegenVersion"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"V3"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The swagger had tag sections that looked like this (two endpoints, each with one differing tag and one common tag)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get thingy&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getThingy&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Better Grouping&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Thingies&lt;/span&gt;
&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get whatchamacallit&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getWhatchamacallit&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Better Grouping&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Whatchamacallits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The generated code had three files &lt;code&gt;api/better_grouping_api.rb&lt;/code&gt;, &lt;code&gt;api/thingies_api.rb&lt;/code&gt;, and &lt;code&gt;api/whatchamacallits_api.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And wouldn’t you know it, &lt;code&gt;better_grouping_api&lt;/code&gt; had the code to call both endpoints and the &lt;code&gt;thingies_api&lt;/code&gt; and &lt;code&gt;whatchamacallits_api&lt;/code&gt; had the duplicated code for their correspondingly tagged endpoint.&lt;/p&gt;

&lt;p&gt;So there you go, if you have OpenAPI spec with multiple tags, it looks like Swagger Codegen v3 will yield the behavior under each of the &lt;code&gt;tag_api&lt;/code&gt; files it generates for each tag.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>ai</category>
    </item>
    <item>
      <title>Has AI Taken My Job Yet?</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Tue, 19 Sep 2023 20:30:01 +0000</pubDate>
      <link>https://forem.com/kallmanation/has-ai-taken-my-job-yet-9o4</link>
      <guid>https://forem.com/kallmanation/has-ai-taken-my-job-yet-9o4</guid>
      <description>&lt;p&gt;AI will not change the world (the way you think it will). Exactly as the Internet did not change the world (the way early adopters thought it would).&lt;/p&gt;

&lt;p&gt;Of course, our daily lives have drastically shifted in the 30 years since home internet connected us. And of course, our lives 30 years from now will look even more different after consumer AI weasels its way into everything we touch.&lt;/p&gt;

&lt;p&gt;But if you don't remember the late 1900s, the internet advent came with a different optimism. The quintessential phrase of the day, "The Information Super Highway!!", encapsulates all the hopes and expectations of the technology. We still echo those hopes when we wax on about the sheer knowledge available a tap away. With all those learnings we'll all make better decisions, self-educate throughout our lives, and society will grow greener each day. Now I hear the emptiness in our echoes. This is not The Internet I was promised. And AI will not become what the sirens sing of today.&lt;/p&gt;

&lt;h1&gt;
  
  
  It all started at Babel
&lt;/h1&gt;

&lt;p&gt;You may already know, or you may be &lt;a href="https://xkcd.com/1053/"&gt;today's lucky 10,000&lt;/a&gt;, about the &lt;a href="https://libraryofbabel.info/"&gt;Library of Babel&lt;/a&gt;. Each time it makes a new round on social media there are a dozen reposts about how mind-blowing it is that everything ever written or that ever will be written already exists in the library.&lt;/p&gt;

&lt;p&gt;Nearly every introduction to the Library of Babel leaves off at this unspoken existentialism. Implying that it could somehow change our lives if only we looked in the library for all the knowledge we are missing. They have not spent enough time in the library if they believe this, and it shows.&lt;/p&gt;

&lt;p&gt;The creator of the library said &lt;a href="https://libraryofbabel.info/theory4.html"&gt;this&lt;/a&gt; after building a search function able to take us directly to the book and page for a given passage of text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interestingly, this leaves the frustration of using the library unaltered. One can find only text one has already written, and any attempt to find it in among other meaningful prose is certain to fail. The tantalizing promise of the universal library is the potential to discover what hasnt been written, or what once was written and now is lost. But there is still no way for us to find what we dont know how to look for. Unless, of course, &lt;a href="https://libraryofbabel.info/bookmark.cgi?thefrustrationofthelibrary"&gt;youre brave enough to browse&lt;/a&gt;...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason the Library of Babel has not changed your life is because any useful knowledge is buried in a hopeless mire of gibberish.&lt;/p&gt;

&lt;p&gt;Take this thought experiment: we are literally trapped in the Library of Babel, our world only consisting of bookshelf beside bookshelf with no escape, having never seen the sky outside. One day we miraculously find a page with the words "&lt;a href="https://libraryofbabel.info/bookmark.cgi?theskyisorange"&gt;The sky outside is orange&lt;/a&gt;". Astounding scientific discovery?! No, the statement "&lt;a href="https://libraryofbabel.info/bookmark.cgi?theskyisblue100"&gt;The sky outside is blue&lt;/a&gt;" must also exist within a different book, elsewhere in the library. The library contains all statements, both true and false. This is the nature of Universal Libraries. With no way to verify this statement, we can do nothing with the knowledge the library tried to bestow on us.&lt;/p&gt;

&lt;h1&gt;
  
  
  Universal Libraries
&lt;/h1&gt;

&lt;p&gt;Such is the fate of all Universal Libraries. By containing information both useful and useless, only the librarians already knowing which books are useful can find a good book. By containing statements both true and false only the researchers already knowing what's true can find veracity. By containing stories both beautiful and ugly, only already discerning eyes can find the aesthetic.&lt;/p&gt;

&lt;p&gt;Authorship bestows value to the works. Books, images, and songs are not worthwhile because they exist, but are valuable because they were authored. Out of all the sounds, of all the colors, of all the letters, this particular group was chosen and placed together; or more importantly, all the other combinations were removed, bringing focus to the worthwhile.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Internet
&lt;/h1&gt;

&lt;p&gt;It strikes me that the Internet is a Universal Library. Perhaps the most universal we've built to date. (of course, containing the entire Library of Babel makes it more universal than that library on its own). Like Bo Burnham sang in &lt;em&gt;Welcome to the Internet&lt;/em&gt; from his 2021 special, &lt;em&gt;Bo Burnham: Inside&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Obama sent the immigrants to vaccinate your kids!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wait. No, that isn't the quote I was looking for. This library is too big. Here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could I interest you in everything, all of the time? A little bit of everything, all of the time? [...] Anything and everything, all of the time&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It certainly feels as though everything is here. This article is here. The Library of Babel is here. Infinite scrolling on every page.&lt;/p&gt;

&lt;p&gt;Let me propose a test for you though; for each thing you see, ask yourself three questions, and be honest with how much of the content you consume satisfies all three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Is this mostly new to me?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is this useful to me?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is this true? Or is it misleading?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Be honest. Does 80% of the Internet satisfy just three questions? Even 20%? I was promised an "Information Super Highway" and instead I wade through an information swamp. The reins to unimaginable knowledge now bind me to a worthless merry-go-round of diversion.&lt;/p&gt;

&lt;p&gt;Google boasts millions of results for every search, and yet I find the same shallow parroted lines behind every link. Worse than the Library of Babel, which mindlessly serves me gibberish, the Algorithms of the Internet have been crafted to serve me intentionally manipulative information: attempts to sway my votes and attempts to spend my dollars, mostly attempts at my dollars; never useful for &lt;em&gt;me&lt;/em&gt; unless it's more useful to &lt;em&gt;them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And when you last searched, was there a &lt;em&gt;primary&lt;/em&gt; source listed on the first page? I do not mean Wikipedia. I do not mean an article about news coverage of a new study. I mean the &lt;em&gt;actual source of information&lt;/em&gt;. I find ten of the same rehashed summaries, probably written by a computer, and if I'm lucky one of them has linked to their source (which is paywalled and out of date).&lt;/p&gt;

&lt;p&gt;We fell for the siren song of a Universal Library. Now I hear a remix crescendoing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Generative AI
&lt;/h1&gt;

&lt;p&gt;So far, Artificial Intelligence (AI) manufactures uncanny mimicries of human intelligence. AI, like humans, &lt;em&gt;mostly&lt;/em&gt; recombines information into non-novel restatements of the same. &lt;em&gt;Occasionally&lt;/em&gt; an AI, like a human, forms a novel combination: emergent work from previous learnings. Of those emergent works, some portion are truthful (that is, the predictions drawn from them will match reality) and some portion are false.&lt;/p&gt;

&lt;p&gt;Unlike a human, an AI has no means to assess its novel works. Like the librarians of Babel, the AI is trapped within its Universal Library: the Internet. It cannot know if the sky is blue or orange outside of words and images transmitted to it via the Internet.&lt;/p&gt;

&lt;p&gt;When training human minds, our teachers take great care to bring new, useful, truthful information into the developing intelligence. Have we taken the same care when training artificial minds? Or do we blindly feast them on a Universal Library?&lt;/p&gt;

&lt;p&gt;When done blindly, the AI becomes a larger Universal Library than the content it consumed. As it becomes more universal, &lt;a href="https://futurism.com/ai-trained-ai-generated-data-interview"&gt;it paradoxically becomes less useful&lt;/a&gt;. Interactions devolve into hallucinations and false citations; how could the AI know any better? That is all it experienced on the Internet. Employing the AI only gives legs to the Universal Library of the Internet. It is no more true than before. It is no more useful than before. The difference is that now it enacts its disheveled will without the filter of external curation.&lt;/p&gt;

&lt;h1&gt;
  
  
  What will change?
&lt;/h1&gt;

&lt;p&gt;We finally come to the biggest headline (that I see) from Generative AI advancements: "Programmers around the world, out of a job". Except, that headline &lt;a href="https://dev.to/kallmanation/nolow-code-why-hasnt-it-won-56an-temp-slug-5179755"&gt;has been running for 30 years&lt;/a&gt;. And it does not sound any different than the existential dread in the shallow sharing of those discovering the &lt;a href="https://libraryofbabel.info/"&gt;Library of Babel&lt;/a&gt;. They both have it wrong.&lt;/p&gt;

&lt;p&gt;As I said, programmers have been "replaced by technology" since before I was born. How did I make a career doing something that shouldn't exist? How do I know I'll still exist despite AI "taking my job"?&lt;/p&gt;

&lt;p&gt;The answer is simple. Each time a new technology enters meant to replace the skills of a professional, a new set of skilled professionals arises to interact with that technology. We saw it again this time around, before the ink on the "out of a job" headline dried, a new job title arose: &lt;a href="https://en.wikipedia.org/wiki/Prompt_engineering"&gt;Prompt Enginee&lt;/a&gt;r.&lt;/p&gt;

&lt;p&gt;The skills we can see change; less documentation on syntax, perhaps a touch more psychology. But the jobs continue. And &lt;em&gt;why&lt;/em&gt; these classes of "technical" people always arise is because the skills we &lt;em&gt;can't see&lt;/em&gt; stay the same. A craving for learning things, tenacity when facing challenging puzzles, creatively bringing different solutions to the same situation.&lt;/p&gt;

&lt;p&gt;So, until AI does &lt;em&gt;everyone's&lt;/em&gt; jobs, there will be jobs titled &lt;em&gt;something&lt;/em&gt; engineer to take care of the rest. I'll see you all there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>Double-entry Bookkeeping for Programmers</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Mon, 30 May 2022 11:12:24 +0000</pubDate>
      <link>https://forem.com/kallmanation/double-entry-bookkeeping-for-programmers-3ok9</link>
      <guid>https://forem.com/kallmanation/double-entry-bookkeeping-for-programmers-3ok9</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@ibrahimboran?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Ibrahim Boran&lt;/a&gt; on &lt;a href="https://unsplash.com/@ibrahimboran?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;This is the article I wished existed when I needed to support accounting functions. Double-entry Bookkeeping may seem esoteric and unnecessarily complicated, but actually describes a simple framework for maintaining a correctible and auditable record of state changes.&lt;/p&gt;

&lt;p&gt;This is not a practical guide on how to actually do modern accounting. It only looks at some mechanics of Accounting, not practical operating concerns.&lt;/p&gt;

&lt;h1&gt;
  
  
  Defining words
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Credits&lt;/strong&gt; and &lt;strong&gt;Debits&lt;/strong&gt;. Forget anything you think you know about what these words "mean". One of them isn't "positive" or "negative". Neither are "good" or "bad". They may as well be called left and right. They don't "mean" anything. They are only mirrors of one another. They only have words assigned to know their chirality.&lt;/p&gt;

&lt;p&gt;Next up, &lt;strong&gt;Accounts&lt;/strong&gt;. A confusing word from thousands of years of accounting. But I'll forgive them, naming things is hard. If a programmer were inventing accounting it would probably be called a type or category or collection. An Account is just a bucket giving a name to some money (distinguishing it from the other money being accounted for... oh wait, maybe that's why they're called accounts... who knew).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts&lt;/strong&gt; come in two flavors: &lt;strong&gt;Credit-based&lt;/strong&gt; and &lt;strong&gt;Debit-based&lt;/strong&gt;. Describing which way all the Credits and Debits against the account should be added up. Credit-based Accounts subtract the sum of Debits from the sum of Credits ( Credits - Debits). And following chirality, Debit-based Accounts subtract the sum of Credits from the sum of Debits ( Debits - Credits). Note in accounting practice accountants often subdivide these two flavors into &lt;a href="https://en.wikipedia.org/wiki/Double-entry_bookkeeping#Accounting_equation_approach" rel="noopener noreferrer"&gt;~5 main accounts&lt;/a&gt; which are put into &lt;a href="https://en.wikipedia.org/wiki/Accounting_equation" rel="noopener noreferrer"&gt;the accounting equation&lt;/a&gt;. By convention, the asset-ish accounts are Debit-based and liability-ish accounts are Credit-based. Again, this article is not meant to delve into accounting practice; knowing the fundamental mechanics suffices for today.&lt;/p&gt;

&lt;p&gt;To summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Credit:&lt;/strong&gt; The opposite of a Debit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debit:&lt;/strong&gt; The opposite of a Credit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account:&lt;/strong&gt; A label for certain Credits &amp;amp; Debits to be summarized together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit-based Account:&lt;/strong&gt; Account whose Total = Credits - Debits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debit-based Account:&lt;/strong&gt; Account whose Total = Debits - Credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully Double-entry Bookkeeping just became slightly more approachable. Slightly. Three words in five concepts form our core. But now let's actually use these things...&lt;/p&gt;

&lt;h1&gt;
  
  
  Double-entry Bookkeeping is NOT a Table
&lt;/h1&gt;

&lt;p&gt;Crack open any accounting book or introductory blog post. Not long into the text there will be tables looking like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loan&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is how double-entry bookkeeping gets its name; it takes two entries to record a single action. It may also be the most unintuitive way to present financial activity.&lt;/p&gt;

&lt;p&gt;A layman's description would be that $42 was taken from Cash and given to (or paid to) the Loan. To and From... hmmm, let's rewrite that table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;From Account&lt;/th&gt;
&lt;th&gt;To Account&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;Loan&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This format hints at a new way to break our thinking free from our table-based accounting mindset. What if we thought of our record as a graph?&lt;/p&gt;

&lt;h1&gt;
  
  
  Double-entry Bookkeeping is a Graph
&lt;/h1&gt;

&lt;p&gt;Make each account a node and each credit/debit pair a single arrow (aka directed edge; credits/debits start/end the "arrow" respectively) with the amount (aka weight) and each account (aka node) involved. Here's what that weighted directed graph would look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653850418582%2F5uiDAZ26B.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653850418582%2F5uiDAZ26B.png" alt="A graph with two nodes, labeled Cash and Loan, with an arrow pointing from the former to the latter labeled $42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you may have heard the rule of Double-entry Bookkeeping: "Every Credit must have a Debit and every Debit must have a Credit". Obviously, an arrow always has a start and an end (or to and from). So the graphical form of Double-entry Bookkeeping makes following the rules inescapable.&lt;/p&gt;

&lt;p&gt;But how do we represent external movement of money? We do get paid for our work after all. Not only that, but I made five whole dollars from this blog. For a moment, let us relax that rule and let money come from nothing into our Cash account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851081168%2FzEz5Wsi56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851081168%2FzEz5Wsi56.png" alt="A graph of two nodes, Cash and Loan, with two arrows coming from nothing entering Cash labeled $1000 and $100; also still having the arrow between Cash and Loan labeled $42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This works, but there is some unhelpful lack of information here. If we back this graph into a table, we might label the "from" account of those two new actions as &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;From Account&lt;/th&gt;
&lt;th&gt;To Account&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;Loan&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We don't have to label it as &lt;code&gt;null&lt;/code&gt; though. In graph theory, we could label &lt;code&gt;null&lt;/code&gt; as anything and the graph would be equivalent. Choosing something appropriately vague, we can draw an equivalent graph of our situation looking like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851100310%2FdsG6qMSH3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851100310%2FdsG6qMSH3.png" alt="A graph of three nodes, Outside, Cash, and Loan, with two arrows between Outside and Cash labeled $1000 and $100; also still having the arrow between Cash and Loan labeled $42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While these accounts are not particularly helpful (why did I get $1000? why the $100?) we have answered the question of how to represent money moving to and from external sources. Simply make up a new node (aka account)! Instead of one, vaguely named, "Outside" account, make one for each source we need to account for. And to round out our example, buy some food and set aside some money for later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851135064%2Fat828bF-k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653851135064%2Fat828bF-k.png" alt="A graph of six nodes, labeled Employer, Side Hustle, Cash, Savings, McTaco King and Loan, with an arrow between Employer and Cash labeled $1000, an arrow between Side Hustle and Cash labeled $100, an arrow between Cash and Savings labeled $50, an arrow between Cash and McTaco King labeled $12, and an arrow between Cash and Loan labeled $42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Representing external parties as their own nodes brings some interesting qualities: Adding up our Employer and Side Hustle accounts results in total income while looking at them separately tells me which pays better. Same for expenses (McTaco King's looking a little over-visited); but also internal money maneuvering. Keeping an emergency fund, for example, can be viewed as a "loan" that is "repaid" when the fund is used and rebuilt; graphically there is no difference between a loan from an "internal" account and an "external" account (besides interest; I gave myself quite the deal).&lt;/p&gt;

&lt;h1&gt;
  
  
  But Keep the Double-entry Book in a Table Anyway
&lt;/h1&gt;

&lt;p&gt;While it is useful to &lt;em&gt;think&lt;/em&gt; about our accounting as a graph it will not be useful to &lt;em&gt;see&lt;/em&gt; our accounting as a graph after a year when we have 24 edges between &lt;code&gt;Employer&lt;/code&gt; and &lt;code&gt;Cash&lt;/code&gt; and 200 between &lt;code&gt;Cash&lt;/code&gt; and &lt;code&gt;McTaco King&lt;/code&gt; (don't judge me).&lt;/p&gt;

&lt;p&gt;How about we backtrack our graph into tabular representations and see what we get out of it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;From Account&lt;/th&gt;
&lt;th&gt;To Account&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;Loan&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Employer&lt;/td&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side Hustle&lt;/td&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;McTacoKing&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;Savings&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Being a table, shoving this data into SQL (or CSV or Excel) becomes trivial. But this format takes a little trickery to group and add up the account values correctly. If we return to the "classic" table form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loan&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Employer&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side Hustle&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;McTaco King&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Savings&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can easily total this table's accounts using either &lt;code&gt;sum(Credit) - sum(Debit)&lt;/code&gt; or &lt;code&gt;sum(Debit) - sum(Credit)&lt;/code&gt; and grouping by the account. But we have traded the easier reading of what any one action has done by needing two lines instead of one for this easier aggregation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rabbit hole: if you want some psuedo-SQL on how this second table could be made from the first, it would probably look like this:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
   FromAccount AS Account,
   Amount AS Credit,
   NULL AS Debit
FROM actions

UNION

SELECT
  ToAccount AS Account,
  NULL AS Credit,
  Amount AS Debit
FROM actions

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;And what trickery queries these two tables for aggregation? Consider these two queries, which is simpler?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- To aggregate the "classic" table for the Cash account
SELECT
  SUM(COALESCE(Debit, 0)) - SUM(COALESCE(Credit, 0)) AS CashTotal
FROM actions
WHERE Account = 'Cash'

-- To aggregate the former table for the Cash account
SELECT
  SUM(
    CASE
      WHEN ToAccount = 'Cash' THEN Amount
      ELSE -Amount
    END
  ) AS CashTotal
FROM actions
WHERE ToAccount = 'Cash'
OR FromAccount = 'Cash'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What format should your financial system store these in? The answer (like many things) is it depends. But the beauty of our modern computing machines means we don't have to choose! We can store our history in one format and automatically transform it to any other whenever we find it convenient.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Use Double-entry Bookkeeping at All?
&lt;/h1&gt;

&lt;p&gt;You've read this whole article and have a better grasp of &lt;em&gt;what&lt;/em&gt; Double-entry Bookkeeping is; but I haven't really spoken to &lt;em&gt;why&lt;/em&gt; we want to use it. Sure we need to store history to report our finances and make sure we aren't going broke, but why not just keep a snapshot of each account's value at each change? We'll even record which external thing made the change, so we should be fine and can answer all of our accountant's questions, right? I'm here to tell you &lt;strong&gt;don't do that&lt;/strong&gt;. &lt;strong&gt;Please do not do that&lt;/strong&gt;. Double-entry Bookkeeping has been independently invented and re-invented across multiple cultures spanning up to 2000 years! Those inventors found multiple benefits from writing this way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Double-entries protect against mis-writing&lt;/li&gt;
&lt;li&gt;Double-entries protect against mis-reading&lt;/li&gt;
&lt;li&gt;Double-entries protect against arithmetic mistakes&lt;/li&gt;
&lt;li&gt;Double-entries protect against damaged or incomplete records&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But all these advantages I believe are mostly erased by modern databases and computers; automatic-checksums and data integrity algorithms, automatic calculations that never mis-read or make mistakes with built-in error-correcting codes, machines counting cash, machines reading checks, machines communicating financial actions directly with one another. But I see benefits that still make record keeping this way a good idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Correcting historical mistakes&lt;/li&gt;
&lt;li&gt;Hypothetical alternate histories&lt;/li&gt;
&lt;li&gt;Hypothetical futures&lt;/li&gt;
&lt;li&gt;Flexible storage and migration options&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Breaking news: people still make mistakes. I guarantee that one day the accountants will come to you and say they've mis-recorded something and now they &lt;strong&gt;need&lt;/strong&gt; it changed and that correction &lt;strong&gt;needs to be in the past&lt;/strong&gt;. What will we do if we've only been keeping a snapshot of the account value at each step? Sure we insert the one new value in the past, but that makes &lt;strong&gt;all the subsequent history wrong!&lt;/strong&gt; So then we have to correct &lt;strong&gt;all&lt;/strong&gt; the ones following that correction; don't forget these accounts are live and being updated constantly. Good luck with that.&lt;/p&gt;

&lt;p&gt;In a double-entry system, resolution is child's play: find the mistaken action and insert another action with the exact opposite to and from (or credit and debit) and mark that inserted action as happening at the same time as the original. Poof, it's like the mistake never happened (and yet, when asked, we can confirm that indeed that mistake was made and corrected appropriately).&lt;/p&gt;

&lt;p&gt;And by its nature of never using mutation ("updates" are done with the insertion of new records), Double-entry Bookkeeping makes moving our data about simple. Have SQL and want NoSQL? Just copy the rows into documents and done. Things changed since the migration? Just check for the new records and copy those. Export to Excel; no problem. Even a text file with lines appended to it satisfies the requirements of our storage needs.&lt;/p&gt;

&lt;p&gt;By making our data easy to migrate, we can copy data into a staging environment with hypothetical records to test fixing history or to test potential futures. All made possible by Double-entry. And to those of you saying "just add X" or "modify Y" to a snapshot history, congratulations, you are the latest in a long line to re-invent double-entry bookkeeping. But go ahead, leave that comment, as if you needed my permission.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Psst, if you want to practice these accounting principles, I am accepting donations at my &lt;a href="https://www.buymeacoffee.com/kallmanation" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;. Here's a little graph of what happens: &lt;code&gt;[You]-$4-&amp;gt;[Me]&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Additional Reading
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2011/03/07/accounting-for-computer-scientists.html" rel="noopener noreferrer"&gt;Accounting for Computer Scientists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/1407.1898.pdf" rel="noopener noreferrer"&gt;On Double-Entry Bookkeeping: The Mathematical Treatment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Debits_and_credits" rel="noopener noreferrer"&gt;Wikipedia: Debits and Credits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Double-entry_bookkeeping#Accounting_equation_approach" rel="noopener noreferrer"&gt;Wikipedia: Accounting equation approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Accounting_equation" rel="noopener noreferrer"&gt;Wikipedia: Accounting equation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Double-entry_bookkeeping#History" rel="noopener noreferrer"&gt;Wikipedia: Double-entry Bookkeeping history&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Hot Swapping Data Models</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Fri, 13 May 2022 20:03:32 +0000</pubDate>
      <link>https://forem.com/kallmanation/hot-swapping-data-models-56kg</link>
      <guid>https://forem.com/kallmanation/hot-swapping-data-models-56kg</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@jandira_sonnendeck?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Jandira Sonnendeck&lt;/a&gt; on &lt;a href="https://unsplash.com/@jandira_sonnendeck?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patrik&lt;/strong&gt; : "So, we need to track where our employees work. Can we do that?"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : "Like, just what store each person works at? Not too hard. Does anyone work at more than one store? Like a regional manager or something?"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Patrik&lt;/strong&gt; : "No. That's not possible. Each employee works at exactly one store."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : "Alright, easy enough. I'll work something up this week."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hcDfuqFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277720676/TNwVgyDV8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcDfuqFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277720676/TNwVgyDV8.png" alt="Diagram of an employee table in a many-to-one relationship with a stores table" width="880" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;...Later...&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Patrik&lt;/strong&gt; : "Devin! We've been loving the employee tracker you built, thanks. Just one little issue-"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : "Let me guess."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Patrik&lt;/strong&gt; : "We &lt;em&gt;do&lt;/em&gt; need some employees to be marked as working at multiple stores. Can we make that change?"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : &lt;em&gt;sigh&lt;/em&gt; "Yeah, let me schedule a maintenance window to make the structural changes to the database-"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Patrik&lt;/strong&gt; : "No, we can't have any downtime for this app. Everyone needs to run their reports. We need to make the changes without a maintenance window."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : "Uhhhhhh...."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Devin&lt;/strong&gt; : &lt;em&gt;Googling&lt;/em&gt; "How to make database changes with no downtime; Migrate tables without downtime; How to hot swap a data model"&lt;/p&gt;

&lt;p&gt;Do not worry Devin! Changing models without downtime, while tedious, can be done rather easily. First, let us look at where we need to get to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aX4Qeg-U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277862438/_zRMGt8uB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aX4Qeg-U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277862438/_zRMGt8uB.png" alt="Diagram of an employee table in a many-to-many relationship with a stores table via a employeestores join table" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And remember, where we are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hcDfuqFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277720676/TNwVgyDV8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcDfuqFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277720676/TNwVgyDV8.png" alt="Diagram of an employee table in a many-to-one relationship with a stores table" width="880" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Patrik's requirements pair up to be a one-two punch: change the structure of our database, carry the pre-existing data into the new model, and all without any interruption of service. I have used six simple steps to accomplish just that, more than once, where there could be zero interruption of service and there could be zero data loss or corruption. Here is how I do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement new models&lt;/li&gt;
&lt;li&gt;Write to both models&lt;/li&gt;
&lt;li&gt;Backfill old to new&lt;/li&gt;
&lt;li&gt;Read from new models&lt;/li&gt;
&lt;li&gt;Stop writing to old models&lt;/li&gt;
&lt;li&gt;Remove old models&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or IWBRSR for short (alright, alright &lt;a href="https://dev.to/kallmanation/s-t-a-h-p-4j21"&gt;I'll stop&lt;/a&gt;). That's it. That's the whole article. Simple, but effective.&lt;/p&gt;

&lt;p&gt;Working through Devin's conundrum, our six steps would work out like this. First, Devin must add the join table we need for the many-to-many relationship between &lt;code&gt;employees&lt;/code&gt; and &lt;code&gt;stores&lt;/code&gt;. However, Devin does not remove the old foreign key from &lt;code&gt;employees&lt;/code&gt; to &lt;code&gt;stores&lt;/code&gt; (yet).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--be4ncaFB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277751125/eyJC-2AHX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--be4ncaFB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277751125/eyJC-2AHX.png" alt="Diagram of an employees table in a many-to-one relationship with a stores table while also being in a many-to-many relationship via a employeestores join table" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this database structure change made; Devin now begins adjusting the code wherever an insert, update, or delete is made that affects the foreign key from &lt;code&gt;employees&lt;/code&gt; to &lt;code&gt;stores&lt;/code&gt; to also insert, update, or delete a corresponding &lt;code&gt;employeestores&lt;/code&gt; record. (These code changes do not even need to all be in a single release; anything not correctly mirrored between the old and new models will be handled by the next step).&lt;/p&gt;

&lt;p&gt;After those changes, Devin has completed steps 1 and 2. Devin can now prepare a backfill. For each relationship that the old foreign key has that the new join table does not have, Devin will create a row on the join table between &lt;code&gt;employees&lt;/code&gt; and &lt;code&gt;stores&lt;/code&gt;. Since all new activity keeps the relationships that the new join table and the old foreign key have in sync (from the code changes of step 2), once Devin completes this backfill the old model and the new model are perfect mirrors and will stay perfect mirrors of each others' data.&lt;/p&gt;

&lt;p&gt;At this point, it may be a good idea to pause for a step 3.5 where we inspect the database to make sure the new and old models are in fact perfect mirrors of each other (like I just claimed).&lt;/p&gt;

&lt;p&gt;Once satisfied, we can move on to step 4 and the work will be downhill from here. Devin begins making code changes wherever &lt;code&gt;employees&lt;/code&gt; and &lt;code&gt;stores&lt;/code&gt; are queried to use the new join table instead of the old foreign key. (Like step 2, this also does not need to be released at once; since the old and new models mirror one another, reading data from one gives the same results as reading from the other until step 5).&lt;/p&gt;

&lt;p&gt;Step 5, Devin is almost done! At this point, the new model effectively runs the application (being written to and read from). Devin can safely go to those places they changed in step 2 and remove the inserts, updates, and deletes to the old foreign key. (Again, this does not need to be done in a single release).&lt;/p&gt;

&lt;p&gt;To put the cherry on top, Devin goes in and drops the old foreign key column from &lt;code&gt;employees&lt;/code&gt;. Completing step 6 and leaving Patrik with what they wanted, with absolutely no downtime!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aX4Qeg-U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277862438/_zRMGt8uB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aX4Qeg-U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652277862438/_zRMGt8uB.png" alt="Diagram of an employee table in a many-to-many relationship with a stores table via a employeestores join table" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now, this was a somewhat contrived and simple example. When doing this for real many, many complications can arise in each step. But, the steps are exactly the same! They work well because each step can be done in parts and redone, little by little, iteration by iteration, until the step works correctly and the next step can be undertaken. So remember, IWBRSR:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement new models&lt;/li&gt;
&lt;li&gt;Write to both models&lt;/li&gt;
&lt;li&gt;Backfill old to new&lt;/li&gt;
&lt;li&gt;Read from new models&lt;/li&gt;
&lt;li&gt;Stop writing to old models&lt;/li&gt;
&lt;li&gt;Remove old models&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>database</category>
      <category>data</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Attracting &amp; Retaining in a Boring Industry</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Tue, 10 May 2022 12:18:17 +0000</pubDate>
      <link>https://forem.com/kallmanation/attracting-retaining-in-a-boring-industry-4n4n</link>
      <guid>https://forem.com/kallmanation/attracting-retaining-in-a-boring-industry-4n4n</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@siavashghanbari?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Siavash Ghanbari&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/boring?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I work in a boring industry. Not only do I work in insurance (already boring) I work in claims handling. I know, just writing those words some of you already clicked away. For a fun party trick, answer the what do you do question with "I'm a software engineer handling insurance claims" and watch eyes glaze faster than a Krispy Kreme conveyor belt.&lt;/p&gt;

&lt;p&gt;Despite the stigma, I've been here for several years. And plan to be here for more. Why?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because it has not been boring to me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I'll tell you why it has not bored me. From my experiences and observations, these are some things that will attract and retain developers and engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell the Stories of their Work
&lt;/h2&gt;

&lt;p&gt;A boring industry's measurements of performance are usually boring. "Oh how interesting, loss ratio has gone down from 87% to 82%", thought no one. Hearing how average claim times dropped from 5 days to 3 days only grabs your attention so many times.&lt;/p&gt;

&lt;p&gt;We especially want our work to feel like we have an impact on our world. We feel rewarded when we do good, more than just bagging a big paycheck. Stories like Sharonne keeping her job because she didn't miss a shift while her car was repaired are powerful. Kyle was able to make rent because he was paid today, not in 30 days (a typical, but unacceptable, turn-around time in claims). A criminal organization getting busted on their intricately fabricated fraud scheme? That's better than a movie.&lt;/p&gt;

&lt;p&gt;Tell us those stories. Keep us engaged and our restless minds want to stay put.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let them work on hard problems
&lt;/h2&gt;

&lt;p&gt;Nothing may be more soul sapping to the mind of a technical person than having to do the same thing twice. Our brains crave challenge. If we are not challenged at work, we will find a challenge somewhere else.&lt;/p&gt;

&lt;p&gt;Too often boring industries bore us because they lack vision. Decisions are made at only the highest level. And often those decision makers only want to change a few metrics they already measure by a few percentage points. Any engineer or developer faced with uninspired and unqualified prescriptions from above finds a new environment that has inspiration with decisions made by qualified, intelligent people.&lt;/p&gt;

&lt;p&gt;Hire people who are smart enough to recognize problems and build solutions at every level, from small to big. Then give them the power and trust to do what they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let them work on other things
&lt;/h2&gt;

&lt;p&gt;Sometimes, the field truly does not have enough challenges for all your employees all the time. This is true and a fair point. But we can still satisfy the need for challenge.&lt;/p&gt;

&lt;p&gt;One thing I've long loved about working at &lt;a href="https://root.engineering"&gt;Root&lt;/a&gt; are the "Hack Days". Every 3 months, for 3 days, engineers are left to work on whatever they like. Read books or whitepapers or blog posts. Build a minigame hidden in the app. Make a tool we use daily just a little bit better. Play with the Javascript framework of the week. Whatever we feel is best for our personal/professional improvement, do it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Invest in them
&lt;/h2&gt;

&lt;p&gt;Expanding on the last point. Make sure to grow talent, not just use the talent. In the calculus developers and engineers use to decide where to work next, after salary and personal fulfillment, choosing environments that advance our skills and career will take precedence.&lt;/p&gt;

&lt;p&gt;Give us a stipend for books. Recommend books and articles. Let them spend some of their time presenting and discussing topics. All these things not only make us better at our jobs; but entices us to stay to keep growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pay them
&lt;/h2&gt;

&lt;p&gt;An obvious and critical factor. I did not list this earlier because even with higher salary than any competitor, failing in other areas will completely sabotage the benefits of higher pay. A massively overpaid employee may still leave if they become so unhappy with their employment that they cannot enjoy the money they have made. Pay alone does not retain or even attract talent. But doing everything else right without good compensation makes those all hollow and worthless. So do pay well.&lt;/p&gt;

&lt;p&gt;Also, be honest about compensation. Do not make us bargain and bicker and negotiate persistently just to be paid like our peers. We will find out the new hires are being paid more than us. We will leave. How much does hiring anew cost? No, those are just hard costs; we have not counted the brain drain and morale effects yet. How much does hiring a replacement actually cost? Paying us half that and pocketing the difference might just do wonders for keeping us around. Unlike hardware and software, wetware cannot be replaced so easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share Profits
&lt;/h2&gt;

&lt;p&gt;While we're here, compensating the creation of software with a fixed amount of dollars has always been strange to me. In one afternoon, I'll write something that nets millions of dollars per year (every year, year after year, for decades). The next, nothing, because my idea turned out to be garbage (I have a lot of bad ideas).&lt;/p&gt;

&lt;p&gt;And software often provides much more value as a whole than the sum of its parts. So why would we only pay a simple salary to each developer or engineer? How important is that signup page if there's no features afterwards? How valuable is your service if the login is totally insecure?&lt;/p&gt;

&lt;p&gt;Rewarding us all when we all have done well builds the trust and incentives to stay and continue performing well.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>productivity</category>
    </item>
    <item>
      <title>S T A H P</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Fri, 11 Mar 2022 16:23:00 +0000</pubDate>
      <link>https://forem.com/kallmanation/s-t-a-h-p-4j21</link>
      <guid>https://forem.com/kallmanation/s-t-a-h-p-4j21</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@jodaarba?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Jose Aragones&lt;/a&gt; on &lt;a href="https://unsplash.com/@jodaarba?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Just STAHP already!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt; ome&lt;br&gt;
&lt;strong&gt;T&lt;/strong&gt; imes&lt;br&gt;
&lt;strong&gt;A&lt;/strong&gt; cronyms (and Abbreviations)&lt;br&gt;
&lt;strong&gt;H&lt;/strong&gt; urt&lt;br&gt;
&lt;strong&gt;P&lt;/strong&gt; roductivity&lt;/p&gt;
&lt;h2&gt;
  
  
  Sometimes
&lt;/h2&gt;

&lt;p&gt;Before I rant too much, all right, yes. &lt;em&gt;Some&lt;/em&gt; acronyms are &lt;a href="https://en.wiktionary.org/wiki/oll_korrect#English"&gt;OK&lt;/a&gt; and even helpful. Because (hopefully):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Acronyms (should) save effort in communication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But whose effort? Communication requires the effort of two parties; senders and recipients (and for most conversations we obviously switch roles rapidly between sending and receiving texts/calls/etc.).&lt;/p&gt;

&lt;p&gt;"Good" acronyms save effort from both the sender and the receiver. These are often well known acronyms like ASAP or &lt;a href="https://kallmanation.com/lets-re-acronym-kiss"&gt;KISS&lt;/a&gt;. They are quick to say; quick to write; and quickly understood because of how widespread and memorable they are (being memorable and not too specific makes them widely useable).&lt;/p&gt;

&lt;p&gt;All acronyms save a minuscule amount of effort from the sender. But many drastically increase the effort of the receiver (as they mentally translate; or worse go dig through some archaic document of abbreviations; or costliest of all, ask someone the meaning). These are the "bad" ones that I'd like you to STAHP making up. Just remember when weighing the costs and benefits of your acronym, in business, most communication has more recipients than senders. So you should probably STAHP.&lt;/p&gt;
&lt;h2&gt;
  
  
  Acronyms (and Abbreviations) Hurt
&lt;/h2&gt;

&lt;p&gt;Each acronym and abbreviation we use adds one more point of confusion and catching up for new members of our teams. After a while, new people nearly learn a whole new language of words in order to communicate with you. Not only that, but acronyms aren't unambiguous even in the same business. Imagine reading this in your email inbox:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PR repo ea Mon by ISO for REV to be uploaded to GH&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;👁👄👁&lt;/p&gt;

&lt;p&gt;What? Pull Requests repossessed each Month by the International Standards Organization for review will be in Github?&lt;/p&gt;

&lt;p&gt;No, no, no. &lt;em&gt;Obviously&lt;/em&gt; I'm telling you that the public relations report each Monday by the Internal Service Organization for Recruitment, Engagement, and Volunteerism will be uploaded to Greenhouse. Duh.&lt;/p&gt;

&lt;p&gt;This example may be a bit over-contrived. But it also falls painfully close to the reality of corporate communication. I wonder how much time we waste each day explaining and decoding these acronyms instead of spending a fraction of a second to say what we mean (and mean what we say). So just STAHP.&lt;/p&gt;
&lt;h2&gt;
  
  
  Productivity
&lt;/h2&gt;

&lt;p&gt;I will accept some acronym use. But please, STAHP inventing new abbreviations and acronyms for every word you say! It usually wastes more time than it saves. This concludes my rant, thank you for reading (and STAHP'ing 😅).&lt;/p&gt;



&lt;p&gt;Do you want someone to STAHP? Here's a markdown STAHP linked to this article for you to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[STAHP](https://kallmanation.com/stahp)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>productivity</category>
      <category>communication</category>
    </item>
    <item>
      <title>Hashnode + DEV</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Mon, 24 Jan 2022 20:38:18 +0000</pubDate>
      <link>https://forem.com/kallmanation/hashnode-dev-9hh</link>
      <guid>https://forem.com/kallmanation/hashnode-dev-9hh</guid>
      <description>&lt;p&gt;In the midst of taking a hiatus from writing, I've decided to level up my setup to post on a personal blog at my own domain (&lt;a href="https://kallmanation.com"&gt;www.kallmanation.com&lt;/a&gt;). I wanted to accomplish a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Have control of where my content is published&lt;/li&gt;
&lt;li&gt;Have a backup of my articles&lt;/li&gt;
&lt;li&gt;Easily post to both my own domain and still to dev.to&lt;/li&gt;
&lt;li&gt;Keep up my Web Monetization that I had on DEV&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I found that in &lt;a href="https://hashnode.com"&gt;Hashnode&lt;/a&gt;. Hashnode has settings to automatically backup every post I publish on their platform in my own &lt;a href="https://github.com/kallmanation/kallmanation"&gt;github repo&lt;/a&gt; (giving me number 2 and a little 1); while also letting me host my blog on my own domain (number 1). And as a bonus, my posts will also come up in the main &lt;a href="https://hashnode.com"&gt;Hashnode feed&lt;/a&gt;. They even have out-of-the-box support for Web Monitization!&lt;/p&gt;

&lt;p&gt;To cover the cross-posting, DEV (and the forem platform) is fantastic about this. Simply go to the &lt;a href="https://dev.to/settings/extensions"&gt;extension settings&lt;/a&gt; and set the RSS feed to my Hashnode blog's RSS and check the &lt;code&gt;Mark the RSS source as canonical URL by default&lt;/code&gt; option (this will give me the internet juice for any interaction on DEV, while making it nice to DEV users to simply read on their preferred platform).&lt;/p&gt;

&lt;p&gt;Now that I've copied all my historical posts from DEV to Hashnode, I hope to blog a little more (maybe not every week though). This post serves as something of a test run of my cross-posting setup. Follow me on DEV or Hashnode and I'll see you around!&lt;/p&gt;

</description>
      <category>writing</category>
      <category>meta</category>
    </item>
    <item>
      <title>Install PWAs in Vivaldi</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Thu, 02 Sep 2021 13:36:29 +0000</pubDate>
      <link>https://forem.com/kallmanation/install-pwas-in-vivaldi-50h9</link>
      <guid>https://forem.com/kallmanation/install-pwas-in-vivaldi-50h9</guid>
      <description>&lt;p&gt;PWAs are coming to desktop! If you use &lt;a href="https://vivaldi.com" rel="noopener noreferrer"&gt;Vivaldi&lt;/a&gt; (like me), then you too can join the PWA gang.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update: PWAs are now &lt;a href="https://vivaldi.com/blog/vivaldi-gets-more-private-delivers-an-all-new-capture-pwa-support/" rel="noopener noreferrer"&gt;fully supported&lt;/a&gt; and no longer "experimental"; you can skip this first step and go right to the installation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As of today, you'll need to enable the experimental feature allowing installation by visiting &lt;a href="https://dev.tovivaldi://experiments/"&gt;vivaldi://experiments/&lt;/a&gt; and checking the box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1630589211154%2Fp2tEk1MWp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1630589211154%2Fp2tEk1MWp.png" alt="Box labeled "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once that's done, visit the PWA you'd like to install, like &lt;a href="https://meet.google.com" rel="noopener noreferrer"&gt;Google meet&lt;/a&gt;. Right click the tab and in the context menu there should now be an option like "Install Google Meet...". Click it and congratulations! Welcome to the future built of Progressive Web Apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1630589379077%2FkfEl9tMgk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1630589379077%2FkfEl9tMgk.png" alt="Screenshot of context menu revealed from right clicking a PWA capable tab in Vivaldi"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>tutorial</category>
      <category>vivaldi</category>
    </item>
    <item>
      <title>WAO: How do you get promoted?</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Fri, 11 Jun 2021 16:14:50 +0000</pubDate>
      <link>https://forem.com/kallmanation/wao-how-do-you-get-promoted-27h4</link>
      <guid>https://forem.com/kallmanation/wao-how-do-you-get-promoted-27h4</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@michalmatlon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Michal Matlon&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/wrong-answer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrong Answers Only
&lt;/h2&gt;

&lt;p&gt;Whether getting that "Senior" title, going into management, or going above Senior, how do you get recognized and promoted?&lt;/p&gt;




&lt;p&gt;Comment your wrong answer!&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>discuss</category>
      <category>wronganswersonly</category>
    </item>
    <item>
      <title>WAO: How do you become a "celebrity" developer?</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Fri, 04 Jun 2021 18:08:31 +0000</pubDate>
      <link>https://forem.com/kallmanation/wao-how-do-you-become-a-celebrity-developer-1iee</link>
      <guid>https://forem.com/kallmanation/wao-how-do-you-become-a-celebrity-developer-1iee</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@michalmatlon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Michal Matlon&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/wrong-answer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrong Answers Only
&lt;/h2&gt;

&lt;p&gt;How do you become popular? Gather a following? Be a "celebrity" developer with thousands of followers?&lt;/p&gt;




&lt;p&gt;Comment your wrong answer!&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>discuss</category>
      <category>wronganswersonly</category>
    </item>
    <item>
      <title>WAO: How do you make money with your side hustle?</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Thu, 27 May 2021 15:05:53 +0000</pubDate>
      <link>https://forem.com/kallmanation/wao-how-do-you-make-money-with-your-side-hustle-4pmg</link>
      <guid>https://forem.com/kallmanation/wao-how-do-you-make-money-with-your-side-hustle-4pmg</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@michalmatlon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Michal Matlon&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/wrong-answer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Wrong Answers Only
&lt;/h1&gt;

&lt;p&gt;How do you make money with your side hustle? Whether that's writing articles (like here on DEV) or freelancing or making your own game: what's the worst way to make money with it?&lt;/p&gt;




&lt;p&gt;Comment your wrong answer!&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>discuss</category>
      <category>wronganswersonly</category>
    </item>
    <item>
      <title>WAO: How do you test software?</title>
      <dc:creator>Nathan Kallman</dc:creator>
      <pubDate>Tue, 18 May 2021 19:12:08 +0000</pubDate>
      <link>https://forem.com/kallmanation/wao-how-do-you-test-software-l9f</link>
      <guid>https://forem.com/kallmanation/wao-how-do-you-test-software-l9f</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@michalmatlon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Michal Matlon&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/wrong-answer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Wrong Answers Only
&lt;/h1&gt;

&lt;p&gt;How do you test your software?&lt;/p&gt;




&lt;p&gt;Comment your wrong answer!&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>discuss</category>
      <category>wronganswersonly</category>
    </item>
  </channel>
</rss>
