<?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>DEV Community: Dylan Anthony</title>
    <description>The latest articles on DEV Community by Dylan Anthony (@dbanty).</description>
    <link>https://dev.to/dbanty</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%2F156961%2Fa41fd844-4cdf-4675-8ce2-7b112d19718e.jpeg</url>
      <title>DEV Community: Dylan Anthony</title>
      <link>https://dev.to/dbanty</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dbanty"/>
    <language>en</language>
    <item>
      <title>Replacing Planetscale</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Tue, 26 Mar 2024 23:08:35 +0000</pubDate>
      <link>https://dev.to/dbanty/replacing-planetscale-1j73</link>
      <guid>https://dev.to/dbanty/replacing-planetscale-1j73</guid>
      <description>&lt;p&gt;Now that Planetscale is ditching their free tier, I need a new database for the &lt;a href="https://github.com/marketplace/knope-bot" rel="noopener noreferrer"&gt;free GitHub app&lt;/a&gt; that I maintain. There were three reasons I picked Planetscale in the first place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Aaron Francis&lt;/li&gt;
&lt;li&gt;It was free&lt;/li&gt;
&lt;li&gt;The branching workflow for schema changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want more Aaron, consider &lt;a href="https://screencasting.com" rel="noopener noreferrer"&gt;buying his course&lt;/a&gt;. To address the other two reasons, I'm splitting this blog post in two parts: "The database" and "The workflow". That way, if you only care about one or the other, you can read just that piece. Now, on with the show!&lt;/p&gt;

&lt;h2&gt;
  
  
  The database
&lt;/h2&gt;

&lt;p&gt;First, let's talk requirements. The database for this app needs to be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cheap or free. I don't want to spend more than the GitHub sponsors for &lt;a href="https://github.com/knope-dev/knope" rel="noopener noreferrer"&gt;Knope&lt;/a&gt; are bringing in.&lt;/li&gt;
&lt;li&gt;Vitess-compatible. Data migration should be easy, and I don't want to rewrite all the queries.&lt;/li&gt;
&lt;li&gt;Durable &lt;em&gt;enough&lt;/em&gt;. Some basic periodic backups should be fine. I don't need high availability.&lt;/li&gt;
&lt;li&gt;Close to the app. Less latency means less app runtime, which means less cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Hosting providers
&lt;/h3&gt;

&lt;p&gt;I can broadly categorize the places to host a database into three buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database as a service (DBaaS)&lt;/li&gt;
&lt;li&gt;Hyperscaler managed databases&lt;/li&gt;
&lt;li&gt;DIY databases on smaller clouds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Databases as a service were eliminated pretty much immediately. The cheapest one I could find was $15/month. I guess Planetscale was the only one offering a real free tier (their cheapest is now $39/month, by the way).&lt;/p&gt;

&lt;p&gt;If I were to pick a hyperscaler, it'd be AWS since this app &lt;em&gt;was&lt;/em&gt; running on AWS Lambda within its free tier (I've since moved it to the same provider as the database). I also have managed RDS in production before, so the learning curve wouldn't be too steep. However, the free tier for RDS is time-limited, and the cheapest possible price I could find is twice as expensive as the cheapest DBaaS. So that leaves the third option.&lt;/p&gt;

&lt;p&gt;When it comes to the smaller clouds, there's only one choice. My &lt;a href="https://railway.app?referralCode=xsbY2R" rel="noopener noreferrer"&gt;Railway&lt;/a&gt; account is old enough to still get $5/month for free, &lt;em&gt;and&lt;/em&gt; I've accumulated some credits through referrals (with templates and links like the ones in this post). That means, as long as I keep it below $5/month, I can host the databases &lt;em&gt;and&lt;/em&gt; the app for free. If I go over, I have some credits to sustain the app for a while until it gets sponsors 🤞.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Railway is not sponsoring this post. Any credits I get from the referral links will be used to power free resources like the GitHub app in question.&lt;/p&gt;

&lt;p&gt;Also, as of writing, they have the best developer experience of any cloud provider. So I'd recommend them even without the referral links.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Database technology
&lt;/h3&gt;

&lt;p&gt;Deploying my own database on Railway means I can pick whatever database technology I want. The two main options that are compatible with Vitess are MySQL and MariaDB. MySQL has an official template which can be deployed with a couple clicks, so that's what I chose. Yes, that really is why. Developer experience matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up MySQL
&lt;/h3&gt;

&lt;p&gt;After deploying the template, I enabled &lt;a href="https://docs.railway.app/reference/app-sleeping" rel="noopener noreferrer"&gt;app sleeping&lt;/a&gt; to save on credits (and energy usage) when the app is not in use. Next, I copied over the schema from Planetscale to Railway. I used &lt;a href="https://atlasgo.io/getting-started/" rel="noopener noreferrer"&gt;Atlas&lt;/a&gt;, but &lt;code&gt;mysqldump&lt;/code&gt; would work just as well for this part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Juggling databases
&lt;/h3&gt;

&lt;p&gt;There are a bunch of well-documented strategies for cutting over databases, so I recommend you look for one that best suits your needs if you're doing something similar. For me, most of the data is a cache of GitHub data, so I didn't need everything, just the frequently accessed things (to avoid flooding GitHub's API with requests). To achieve this, I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Added a connection to the new database&lt;/li&gt;
&lt;li&gt;Changed all queries to read from the new database first. If data was missing, it read from the old database and wrote the missing data back to the new one&lt;/li&gt;
&lt;li&gt;Let it run for a few days to capture the majority of the data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once I was satisfied that the remaining gaps were small enough, I removed the connection to the old database, and I was done! Well... almost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How did I make sure I didn't forget any queries? The app is written in Rust, which means I can use "compiler-driven development" to make sure that even the most major refactors don't break anything. If you'd like to see a whole post about this strategy, let me know!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Backups
&lt;/h3&gt;

&lt;p&gt;The official template doesn't have automatic backups. I don't need anything too fancy, just a recent snapshot so I can restore the necessary bits if something catastrophic happens at a datacenter. I checked out the existing templates on Railway, and none of them &lt;em&gt;quite&lt;/em&gt; did what I wanted, so I &lt;a href="https://railway.app/template/7GOA4r?referralCode=xsbY2R" rel="noopener noreferrer"&gt;made my own&lt;/a&gt; (as well as a template which &lt;a href="https://railway.app/template/xNTYS8?referralCode=xsbY2R" rel="noopener noreferrer"&gt;bundles MySQL and the backup job&lt;/a&gt; for even fewer clicks!).&lt;/p&gt;

&lt;p&gt;This leverages Railway's built-in &lt;a href="https://docs.railway.app/reference/cron-jobs" rel="noopener noreferrer"&gt;cron feature&lt;/a&gt; to take a snapshot periodically—so the backup process itself is only running (and costing me credits) when necessary. This also gives me a handy "Run now" button to make one-off backups for testing.&lt;/p&gt;

&lt;p&gt;Internally, it uses &lt;a href="https://github.com/mydumper/mydumper" rel="noopener noreferrer"&gt;mydumper&lt;/a&gt;, a faster alternative to &lt;code&gt;mysqldump&lt;/code&gt;, to take the snapshot. Then, it uses &lt;a href="https://rclone.org/" rel="noopener noreferrer"&gt;rclone&lt;/a&gt; to push that snapshot to a &lt;a href="https://www.cloudflare.com/developer-platform/r2/" rel="noopener noreferrer"&gt;Cloudflare R2&lt;/a&gt; bucket. My usage will stay well within the free tier of R2, so this is effectively a free backup for me.&lt;/p&gt;

&lt;p&gt;Remember, no backup is complete until you've tested restoring from it! I did that on a local MySQL instance and it worked great.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost?
&lt;/h3&gt;

&lt;p&gt;This is not going to be indicative of &lt;em&gt;your&lt;/em&gt; costs—my app is very bursty, so the database can spend a lot of time sleeping. Railway is also very bad at estimating costs for apps that sleep. Plus, there's some sort of bug in the per-service breakdown where disk usage isn't covered. But, after running for about 2 weeks, my two sleepy MySQL instances have cost around $0.61. So for a month of usage at the current rate, I'd expect the total cost to be around $1.50. That's plenty of headroom before I hit my $5/month free limit and need to start using credits (though, this isn't the only project I'm running on Railway).&lt;/p&gt;

&lt;p&gt;In case you're curious, the app itself is set to cost around $0.04/month while sleeping at about the same rate as the databases. Rust on the backend can make for some &lt;em&gt;very&lt;/em&gt; economical apps!&lt;/p&gt;

&lt;p&gt;The backup job says it's costing $0.00, so I guess it's quick and infrequent enough to be a rounding error!&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;One of the main reasons I picked Planetscale in the first place was the ability to propose changes from one database schema to another. Iterating directly on the test database while developing, then applying those changes to production is much nicer than the typical migration script approach.&lt;/p&gt;

&lt;p&gt;I chose &lt;a href="https://atlasgo.io/getting-started/" rel="noopener noreferrer"&gt;Atlas&lt;/a&gt; to replicate this. It has a lot of features, but the important parts for me are inspecting a database, storing the schema as HCL (HashiCorp Configuration Language), and applying that schema to another database. Using this tool, my workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make changes directly to a test database while iterating on a new feature.&lt;/li&gt;
&lt;li&gt;In GitHub Actions, automatically inspect the database and keep the HCL up to date.&lt;/li&gt;
&lt;li&gt;When the feature is done, review the HCL schema changes in the pull request in GitHub.&lt;/li&gt;
&lt;li&gt;Once the PR is merged, apply the changes to the production database in CI before the service is updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As an extra layer of validation, the app connects to the database while building to ensure that the schema is up-to-date. If something goes wrong with this process, the build will fail (rather than queries failing at runtime). If your database library offers this feature (like &lt;a href="https://github.com/launchbadge/sqlx" rel="noopener noreferrer"&gt;sqlx&lt;/a&gt; does), I strongly recommend it.&lt;/p&gt;

&lt;p&gt;Here's what the actions look like:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.sqlx/*"&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;update-schema-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4.1.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.repo.full_name }}&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.ref }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ariga/setup-atlas@master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update schema file&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlas schema inspect -u ${{ secrets.TEST_DATABASE_URL }} &amp;gt; .sqlx/schema.hcl&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stefanzweifel/git-auto-commit-action@v5.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When there's a pull request that affects a file in the &lt;code&gt;.sqlx&lt;/code&gt; directory, Atlas inspects the test database and saves its schema to a file. The format is HCL, though that doesn't really matter to me, as long as it's readable. This file is then committed back to the repository.&lt;/p&gt;

&lt;p&gt;This prevents the HCL file from being updated for unassociated pull requests, like automatic dependency updates. That &lt;code&gt;.sqlx&lt;/code&gt; folder gets updated whenever I change queries in the app, so it's a good signal that the schema might need updating. If I need to manually update the schema, say for an index-only change, I can do that by running the same command locally.&lt;/p&gt;

&lt;p&gt;For extra credit, you can also set up a periodic job to check the schema and, if it's changed, open a pull request with the updated HCL. This would work as drift detection.&lt;/p&gt;

&lt;p&gt;When the HCL file changes on the main branch, this next workflow updates the production schema automatically:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.sqlx/schema.hcl"&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;update-prod-database-schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4.1.1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ariga/setup-atlas@master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update schema&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlas schema apply -u ${{ secrets.DATABASE_URL }} -f .sqlx/schema.hcl --auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So my schema changes are tied to my pull requests, I can review the changes as part of the PR, and when I merge to main to deploy to production, the database gets updated too!&lt;/p&gt;

&lt;p&gt;For extra security, you may not want your MySQL database to be reachable from the public internet. You can use Railway's &lt;a href="https://docs.railway.app/reference/private-networking" rel="noopener noreferrer"&gt;private networking&lt;/a&gt; to achieve this with a few changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turn off app sleeping. Private networking doesn't work with sleeping apps. I don't know whether you only need to keep the database awake or the app too, but it's probably both.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://railway.app/template/cd7y1G?referralCode=xsbY2R" rel="noopener noreferrer"&gt;Deploy a your own GitHub Actions runner&lt;/a&gt; in each Railway environment. Private networking does not work cross-environment, so you'll need a runner that has access to each database which Atlas will be talking to. That runner cannot sleep either.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This workflow relies on having one test database per PR, which works for me because I'm only working on one major feature at a time. If you're working on a team, you'll want to check out &lt;a href="https://docs.railway.app/guides/environments#enable-pr-environments" rel="noopener noreferrer"&gt;Railway's PR environments&lt;/a&gt; or use a local workflow for updating the schema.&lt;/p&gt;

&lt;p&gt;Let me know if you'd like a full guide on setting up a database schema workflow for teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;That's it! I've replaced Planetscale by running MySQL on Railway, sending backups to Cloudflare R2, and managing my schema with Atlas in GitHub Actions. I'm actually &lt;em&gt;happier&lt;/em&gt; with this setup than I was with Planetscale. My metrics are showing the slowest queries as 10x faster, even when the database was sleeping! Atlas is also a nicer solution for schema management; auto-deploys were much easier to set up, and I can see the schema diff right next to my code diff.&lt;/p&gt;

&lt;p&gt;If there's any part of this solution you'd like me to dive deeper into, let me know! I'd also love to hear about &lt;em&gt;your&lt;/em&gt; database setup and how it's working for you (particularly migrations).&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>database</category>
      <category>automaton</category>
    </item>
    <item>
      <title>Making my blog more climate-friendly with Astro</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Tue, 05 Mar 2024 01:44:24 +0000</pubDate>
      <link>https://dev.to/dbanty/making-my-blog-more-climate-friendly-with-astro-2dpb</link>
      <guid>https://dev.to/dbanty/making-my-blog-more-climate-friendly-with-astro-2dpb</guid>
      <description>&lt;p&gt;It's been two years since I last &lt;a href="https://dev.to/blog/new-year-new-website"&gt;rewrote my personal website&lt;/a&gt;, so naturally it's time to do it again! The immediate motivation was simple: my website stopped building with an inscrutable error message. Rather than taking the time to learn more about Zola and what could be causing the issue, I figured I would switch to something with a bit more community support: Astro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro?
&lt;/h2&gt;

&lt;p&gt;I recently created a &lt;a href="https://knope.tech" rel="noopener noreferrer"&gt;new documentation website for Knope&lt;/a&gt; using the &lt;em&gt;incredible&lt;/em&gt; &lt;a href="https://starlight.astro.build/getting-started/" rel="noopener noreferrer"&gt;Starlight theme for Astro&lt;/a&gt;. The developer experience, community resources, and performance of the site were all so good that I knew I wanted to build more with Astro, so it was the natural choice for this rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  But... climate-friendly?
&lt;/h2&gt;

&lt;p&gt;Yes, climate-friendly! There's this neat tool called &lt;a href="https://www.websitecarbon.com/" rel="noopener noreferrer"&gt;Website Carbon&lt;/a&gt; that estimates the carbon emissions of a website. Here are the results for my old website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7ymf50yk39h2lnk7v95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7ymf50yk39h2lnk7v95.png" alt="A screenshot of websitecarbon.com. There is a scale from A+ (best) to F (worst), and my website gets a B. This is cleaner than 79% of other websites, with the global average being between E and F. Below the scale, the website states " width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A B is pretty good, better than 79% of other websites, apparently. But... "good" is never good enough for me, especially when it comes to climate change. So, how does the Astro version of my website compare? I used almost identical styles, markup, and content and I hosted it in exactly the same way. Here are the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F126pf40tahgdw06pbw74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F126pf40tahgdw06pbw74.png" alt="Another screenshot of websitecarbon.com, this time my website gets an A+. This is cleaner than 95% of other websites. Below the scale, the website states " width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A 70% reduction in carbon emissions! Now &lt;em&gt;that's&lt;/em&gt; something to be proud of. But... how? What is it about the Astro site that makes it better for the environment? Primarily, it's reduced data transfer. Check it out, here's the network view from my old site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclezx7d6da1ce40stztb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclezx7d6da1ce40stztb.png" alt="A screenshot of the network view in Safari. There are 15 requests, totaling 920.5KB transferred." width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the network view from the Astro site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtks3jq1wt38c65ng97m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtks3jq1wt38c65ng97m.png" alt="A screenshot of the network view in Safari. There are 9 requests, totaling 93.2KB transferred." width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On first load, the Astro site is 90% smaller than the Zola site! There are two reasons for this, first, Astro is lazy loading the images that are "below the fold", so it doesn't download data until it's needed. Not only that, look what happens when we scroll all the way down and load &lt;em&gt;everything&lt;/em&gt; (even more than the Zola site had loaded):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falyy478p9gbfvbyfnltx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falyy478p9gbfvbyfnltx.png" alt="A screenshot of network stats summary in Safari. There are now 19 requests, totaling 246.1KB transferred." width="788" height="58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Astro site is still 73% smaller than the Zola site! If we look a little closer, we can see why: every individual image is smaller. I'll be honest, I don't know what Astro is doing to make this possible, but to my eyes there's no noticeable difference in quality, and that's incredible!&lt;/p&gt;

&lt;h2&gt;
  
  
  What about clean energy?
&lt;/h2&gt;

&lt;p&gt;My website is hosted on a combination of Netlify and Cloudflare, and both have strong commitments to sustainability. However, all the clean energy that is produced will be used, so even though &lt;em&gt;my&lt;/em&gt; energy is coming from a sustainable source, using less of it will still reduce carbon emissions.&lt;/p&gt;

&lt;p&gt;I like to think of it like the solar panels on my house. They produce electricity as long as the sun is up, and on most (non-cloudy) days, they produce more than we use. So what happens to the excess? It flows back into the grid and helps power my neighbors' homes, replacing energy that may have come from fossil fuels otherwise. The energy I consume is not generating carbon emissions, but by consuming less I'm still reducing the overall emissions. It's the same with my website, and yours! So, even if your site is powered by renewable sources, it's still worth trying to reduce the energy consumption (by reducing data transfer).&lt;/p&gt;

&lt;h2&gt;
  
  
  How hard was it?
&lt;/h2&gt;

&lt;p&gt;Like I said, I'm not a frontend developer, so changing up my website was very intimidating. Luckily, Astro makes things relatively easy. The Astro CLI gave me a blog template that worked great as a starting point. Next, I copied over my Markdown files and images. The only changes I had to make were to the frontmatter. For styling, my old site was using Tailwind, and there's an Astro plugin for that (much easier than it was to add Tailwind to Zola). Really, the hardest part was figuring out how to translate the jinja-like templates of the old site into Astro components. To be honest, the toughest part of &lt;em&gt;that&lt;/em&gt; was understanding the templates of the old site, since I found Astro's syntax much easier to work with.&lt;/p&gt;

&lt;p&gt;All-in-all, it took me a couple of afternoons to fully translate my site to Astro, and I'm extremely happy with the result. There are some minor style differences, but making it look identical wasn't the goal, I just wanted to preserve the general design and content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should everyone switch to Astro?
&lt;/h2&gt;

&lt;p&gt;Probably not 😅. But, I think it's worth checking on the carbon impact of the websites you maintain to see if there's a clear path to improve them. Astro is definitely a good choice for new projects, or if you're planning on rewriting anyway. If you &lt;em&gt;do&lt;/em&gt; find a great way to make your site more sustainable, I'd love to hear about it!&lt;/p&gt;

</description>
      <category>writing</category>
      <category>astro</category>
    </item>
    <item>
      <title>GraphQL is for Backend Engineers</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Mon, 05 Feb 2024 22:22:42 +0000</pubDate>
      <link>https://dev.to/apollographql/graphql-is-for-backend-engineers-3ih</link>
      <guid>https://dev.to/apollographql/graphql-is-for-backend-engineers-3ih</guid>
      <description>&lt;p&gt;Most articles explaining the benefits of GraphQL focus on advantages for the frontend: things like preventing overfetching, reducing round trips, and iterating faster. But GraphQL provides just as many advantages for backend developers, which is why I choose it by default for new APIs and why you should consider it, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved communication
&lt;/h2&gt;

&lt;p&gt;The goal of building any API is to &lt;a href="https://www.postman.com/state-of-api/api-first-strategies/#measuring-success-of-public-apis" rel="noopener noreferrer"&gt;enable someone to use it&lt;/a&gt;, which requires excellent communication between API producers and consumers. However, the state of the art for REST APIs doesn’t quite live up to this expectation.&lt;/p&gt;

&lt;p&gt;On the backend, developers either need to manually document the entire API or rely on auto-generation tools that don’t fully meet their needs. Consumers face the same choice, write code by hand or workaround the bugs in their SDK generator (stated, lovingly, as the maintainer of &lt;a href="https://github.com/openapi-generators/openapi-python-client" rel="noopener noreferrer"&gt;an OpenAPI client generator&lt;/a&gt;). On top of this, these solutions result in inconsistent understandings of the API. Reproducing errors becomes time-consuming and frustrating, which feels like a battle instead of a collaboration. What we need is a shared language to describe how the API works—one that doesn’t add unnecessary layers of abstraction or manual work.&lt;/p&gt;

&lt;p&gt;Meet GraphQL, where every team uses the same schema definition language (SDL) to talk about the API. SDL is designed for developers, omitting extraneous details while describing all the essential bits. This serves both as your documentation and a universal language to communicate between teams.&lt;/p&gt;

&lt;p&gt;On the backend, developers can write code in their language of choice and have the SDL created for them. Alternatively, they can write the SDL by hand, and generate the required code. Either way, the documentation is always kept in sync with the implementation.&lt;/p&gt;

&lt;p&gt;Consumers use this same SDL directly, so there’s no need to maintain a separate documentation website. Instead, they can get information, auto-complete names, and see potential mistakes right in their IDEs. The same request they write in their code can be run by itself for easier debugging, which makes sending snippets to the backend team much more straightforward. With clearer communication, there will be fewer open issues and less wasted time tracking down minor bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Less bikeshedding
&lt;/h2&gt;

&lt;p&gt;Bikeshedding is when you spend time discussing decisions that don’t have a significant impact, and it happens quite a bit with RESTful APIs. To name a few, &lt;em&gt;I&lt;/em&gt; have been in bikeshedding meetings about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Which verb to use? &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; could have a lot of overlap. Maybe you *should*use &lt;code&gt;GET&lt;/code&gt;, but the fact that it can’t contain a body is too limiting for the amount of filters you need. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Should this be a query parameter, path parameter, form data, or JSON body? For path parameters, how should we serialize arrays? What about objects?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do we completely normalize endpoints to reduce duplication, even though that causes slower client performance? Or include the kitchen sink even though that causes slower individual endpoint performance? Maybe a compromise with shallow nested objects containing only the most common parameters or a parameter that allows selecting fields, emulating GraphQL without the type safety.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which HTTP status code is the right one for each response?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Should relationships be indicated with IDs or URIs?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hopefully, these discussions will lead to a comprehensive style guide, but the meetings will continue even with those guides. While there will always be &lt;em&gt;some&lt;/em&gt; debate around things like naming, GraphQL eliminates entire categories of potential style issues (including all those above) by being &lt;em&gt;simpler&lt;/em&gt;. You can spend less time on the little details and more time solving more interesting problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce tech debt
&lt;/h2&gt;

&lt;p&gt;Imagine you have a &lt;code&gt;/v1/data&lt;/code&gt; endpoint called by all your frontend teams: web, iOS, and Android. You decide that the date field should return UTC instead of localized time, so how do you make that breaking change? It usually looks something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new &lt;code&gt;/v2/data&lt;/code&gt; endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inform all the client teams that this new endpoint exists and that they should migrate by some date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Track metrics on &lt;code&gt;/v1/data&lt;/code&gt; and send reminders to consumers who haven’t updated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The deprecation date comes and goes, and you’re still receiving calls to the old endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You either pull the plug and start breaking things or, more likely, continue to support the &lt;code&gt;/v1/data&lt;/code&gt; endpoint indefinitely, creating tech debt with no apparent payoff date.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let’s play through the same scenario with GraphQL. First, you add &lt;code&gt;@deprecated(reason: “use utcDate”)&lt;/code&gt; to your old &lt;code&gt;date&lt;/code&gt; field and add the new &lt;code&gt;utcDate&lt;/code&gt; field. API consumers immediately start seeing warnings in their IDEs and potentially CI/CD about this deprecation since most GraphQL tooling supports it.&lt;/p&gt;

&lt;p&gt;As it turns out, the iOS and Android teams weren’t even using the old &lt;code&gt;date&lt;/code&gt; field, so no changes are necessary for them!&lt;/p&gt;

&lt;p&gt;Someone on the web team notices some squiggly lines in their editor and decides to fix them, following the instructions left in the &lt;code&gt;reason&lt;/code&gt; field. Your backend metrics tell you that the date field is no longer in use, and you’re safe to remove it!&lt;/p&gt;

&lt;p&gt;Creating less churn and an easier upgrade path for clients makes them more likely to upgrade. If they upgrade faster, you can remove that stale old code faster, too!&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL is for you too
&lt;/h2&gt;

&lt;p&gt;One of the main features of GraphQL is its developer experience. By creating a shared language between teams, reducing complexity, and providing a smoother upgrade path, GraphQL makes building and maintaining APIs more delightful. That’s why, even if your frontend engineers aren’t pushing for it yet, you should start evaluating GraphQL for your tech stack. If you have any questions or disagree with anything I’ve said above, let me know on our &lt;a href="http://discord.gg/NUaGwceDhX" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;! If you want to be notified of future posts, there’s also a channel for that.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>api</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Secure GraphQL Microservices</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Wed, 02 Aug 2023 18:17:49 +0000</pubDate>
      <link>https://dev.to/apollographql/secure-graphql-microservices-4pc2</link>
      <guid>https://dev.to/apollographql/secure-graphql-microservices-4pc2</guid>
      <description>&lt;p&gt;Federation unlocks superpowers for our queries, enabling us to split up business logic and improve performance with features like &lt;code&gt;@defer&lt;/code&gt;. However, these same powers can be abused if placed in the wrong hands, so it’s essential to limit who has access to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The threats
&lt;/h2&gt;

&lt;p&gt;Many coordination features of a federated graph rely on an important assumption: &lt;strong&gt;clients always query your router—never individual subgraphs&lt;/strong&gt;. If this assumption is violated and clients query your subgraphs directly, you might expose data and capabilities that you &lt;em&gt;shouldn’t&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;@inaccessible&lt;/code&gt; directive enables subgraphs to define schema fields that &lt;em&gt;other&lt;/em&gt; subgraphs might need, but that the router &lt;em&gt;shouldn’t&lt;/em&gt; expose to clients. If clients can query a subgraph directly, those queries &lt;em&gt;can&lt;/em&gt; include &lt;code&gt;@inaccessible&lt;/code&gt; fields. And even if you don’t have any sensitive  &lt;code&gt;@inaccessible&lt;/code&gt; fields in your graph today, you might in the future!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;@requires&lt;/code&gt; directive enables a subgraph to define schema fields that depend on entity fields from &lt;em&gt;other&lt;/em&gt; subgraphs. Subgraphs rely on the router to resolve this dependency, ensuring that trusted data is provided. But if clients have direct access to subgraph entity resolvers, that data can &lt;em&gt;no longer&lt;/em&gt; be trusted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;@override&lt;/code&gt; directive enables a subgraph to take responsibility for a schema field away from &lt;em&gt;another&lt;/em&gt; subgraph. With direct subgraph access, clients can still query the &lt;em&gt;original&lt;/em&gt; subgraph field, which can result in inconsistent and unexpected behavior.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is just a small selection of the available features today, and more will be added over time. The key takeaway is that federation is a microservice architecture that expects a &lt;em&gt;single&lt;/em&gt; entry point through a controlled router. If subgraphs can be accessed directly, it’s only a matter of time before something unexpected happens, and that unexpected event could be a security breach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting your subgraphs
&lt;/h2&gt;

&lt;p&gt;Exposing subgraphs publicly opens the door to several attacks. To mitigate them, we prevent anything but our routers from accessing our subgraphs. First, we can protect our subgraphs at the application level by adding an extra layer of authorization on top of whatever user auth may be in place. We can configure our routers to send a header containing a shared secret to each of our subgraphs:&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;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;subgraphs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Router-Authorization"&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${env.ORDERS_SUBGRAPH_SECRET}"&lt;/span&gt;
    &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Router-Authorization"&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${env.USERS_SUBGRAPH_SECRET}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we use a unique secret per subgraph, following a security best practice called “the principle of least privilege”. If there’s no reason for the &lt;code&gt;orders&lt;/code&gt; subgraph to have direct access to the &lt;code&gt;users&lt;/code&gt; subgraph, then we should not give it the ability to do so (by sending it valid tokens for another subgraph).&lt;/p&gt;

&lt;p&gt;These values come from environment variables, so how you set them will depend on where you’re running your routers. Most secret managers have a way to inject values via an environment variable. For example, in GraphOS, you can &lt;a href="https://www.apollographql.com/docs/graphos/routing/cloud-configuration#managing-secrets" rel="noopener noreferrer"&gt;set secrets via the UI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How this looks on the subgraph side depends on your implementation. Here’s an example using FastAPI in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_router_security&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Awaitable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;router_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROUTER_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;router_secret&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Router-Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;router_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we’re setting up the expected token as an environment variable called &lt;code&gt;ROUTER_SECRET&lt;/code&gt; in our subgraph. This value would be injected via the secret manager for your hosting platform. If the secret is not set (such as in local development), we turn off this enforcement; you may want to add a more sophisticated method of ensuring this is never turned off in production. If the secret &lt;em&gt;is&lt;/em&gt; set, we ensure every request has a matching &lt;code&gt;Router-Authorization&lt;/code&gt; header. Requests without a valid header will get a bare response with a &lt;code&gt;401 Unauthorized&lt;/code&gt; code—we don’t send any additional information that clients don’t need, and unauthenticated requesters don’t even need to know that this is a GraphQL server.&lt;/p&gt;

&lt;p&gt;We have examples for many languages and frameworks in our &lt;a href="https://www.apollographql.com/docs/graphos/graphs/creating-a-subgraph#starting-from-a-template" rel="noopener noreferrer"&gt;subgraph templates&lt;/a&gt;, so check them out for more inspiration!&lt;/p&gt;

&lt;h3&gt;
  
  
  Extra credit: network protections
&lt;/h3&gt;

&lt;p&gt;As an additional step, depending on your hosting environment, you may be able to use network-level protections to prevent any incoming connections to your subgraphs except your routers. This is typically done via firewall rules (such as access control lists). Not all platforms or architectures will have this option, but if you do, we recommend adding it as a “defense in depth”. &lt;/p&gt;

&lt;h2&gt;
  
  
  Ruling out alternatives
&lt;/h2&gt;

&lt;p&gt;There are a few other solutions to this problem that might &lt;em&gt;seem&lt;/em&gt; viable but can cause issues. Let’s go over them!&lt;/p&gt;

&lt;p&gt;First, you might be tempted to apply router authorization checks only for federation-specific subgraph fields (&lt;code&gt;_service&lt;/code&gt; and &lt;code&gt;_entities&lt;/code&gt; ), but there are problems with this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Newer versions of federation might add &lt;em&gt;more&lt;/em&gt; subgraph fields in the future, which would introduce new vulnerabilities that special-case checks don’t handle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This approach doesn’t solve the problem of reaching &lt;code&gt;@inaccessible&lt;/code&gt; or &lt;code&gt;@requries&lt;/code&gt; fields via a top-level &lt;code&gt;Query&lt;/code&gt;field (a standard, non-federated query).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Second, you might be tempted to enforce router authorization per field, maybe even automatically for any field that implements a federated directive. However, federation enables any single subgraph to influence the behavior of the entire supergraph. For example, if &lt;code&gt;@inaccessible&lt;/code&gt; is applied to a field in &lt;em&gt;any&lt;/em&gt; subgraph, it’s expected to be enforced on &lt;em&gt;every&lt;/em&gt; subgraph. This means that per-field, per-subgraph enforcement is fundamentally inconsistent and can lead to unexpected data leakage.&lt;/p&gt;

&lt;p&gt;Finally, you might consider disabling introspection as a solution—if attackers can’t discover the hidden fields, they can’t use them, right? This is called “security by obscurity” and is widely considered not security at all. A sufficiently determined attacker &lt;em&gt;will&lt;/em&gt; eventually guess the names of fields that you don’t want them to find.&lt;/p&gt;

&lt;p&gt;The only consistent approach to preventing undesired access to subgraph capabilities is to disable access to subgraphs &lt;em&gt;entirely&lt;/em&gt; to any client besides the router.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get secure
&lt;/h2&gt;

&lt;p&gt;Now that you know the dangers of exposing subgraphs publicly and the best approach to mitigating the threat, there’s nothing left to do but start implementing! Start securing your subgraphs immediately.&lt;/p&gt;

&lt;p&gt;Have any questions, comments, or concerns about this post? I’d love to hear about it in our &lt;a href="https://discord.com/invite/graphos" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;! That’s also where you’ll be notified of upcoming security-related live streams.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>api</category>
      <category>security</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Even better GitHub Actions in Rust</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Sun, 25 Jun 2023 03:34:13 +0000</pubDate>
      <link>https://dev.to/dbanty/even-better-github-actions-in-rust-2b2c</link>
      <guid>https://dev.to/dbanty/even-better-github-actions-in-rust-2b2c</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dylananthony.com/blog/how-to-write-a-github-action-in-rust/" rel="noopener noreferrer"&gt;recent post&lt;/a&gt;, I introduced a method for creating GitHub Actions (the reusable code that can be run in CI, not to be confused with the name of the CI platform itself) using Rust and Docker. This &lt;em&gt;was&lt;/em&gt; the easiest way I knew to create Rust-based actions... until now.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/204" rel="noopener noreferrer"&gt;the GitHub discussion for that post&lt;/a&gt;, someone brought up a limitation I hadn't realized. Docker-based actions can only pull images from &lt;em&gt;public&lt;/em&gt; registries, meaning you couldn't author a closed-source, private action using this method. Additionally, &lt;a href="https://docs.github.com/en/actions/creating-actions/about-custom-actions#types-of-actions" rel="noopener noreferrer"&gt;Docker-based actions can only be run on Linux-based runners&lt;/a&gt;, limiting utility even further.&lt;/p&gt;

&lt;p&gt;With a lot of inspiration from that conversation and trial and error, I created version 2.0 of my Rust-based GitHub Action template. You can try it out today with &lt;code&gt;cargo generate dbanty/rust-github-action-template&lt;/code&gt;! Before you do that, read on to see what's changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  No more Docker
&lt;/h2&gt;

&lt;p&gt;The only way to surpass the abovementioned limitations was to stop using Docker-based actions. This leaves us with only two other options: &lt;code&gt;javascript&lt;/code&gt; or &lt;code&gt;composite&lt;/code&gt;. We're writing our actions in Rust, so using JavaScript as an intermediary seems silly—let's go straight to composite.&lt;/p&gt;

&lt;p&gt;A composite action in GitHub is basically a reusable workflow with inputs and outputs that can be published on GitHub Marketplace. Generally, this is good for scripting with Bash or PowerShell and not too much else. However, that scripting is more than enough to &lt;em&gt;run&lt;/em&gt; a Rust binary (you do that every time you run &lt;code&gt;cargo test&lt;/code&gt; in CI), we just need to make the correct binary available!&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributing binaries
&lt;/h2&gt;

&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; copy over the Rust source code for our action and build the binary right there in the reusable action—but consumers would have to then (even if just occasionally, with caching) wait for Rust to compile something, which can take a &lt;em&gt;very&lt;/em&gt; long time.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;other&lt;/em&gt; option is to produce pre-built binaries somewhere and &lt;em&gt;download&lt;/em&gt; them from the composite action, then run them—which is precisely what I did! Luckily, I already have several examples of building Rust binaries and storing them in GitHub releases (that's how my release automation tool &lt;a href="https://github.com/knope-dev/knope" rel="noopener noreferrer"&gt;Knope&lt;/a&gt; works), so I just needed to build up scripting logic which:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;downloads the correct version of the binary for the current platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;passes through arguments from the action inputs to the binary&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sets outputs for the composite action correctly&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We get a GitHub Action that does not require Docker at &lt;em&gt;all&lt;/em&gt;, builds and distributes Rust binaries in a much more standard way, works with private repositories, &lt;em&gt;and&lt;/em&gt; works on macOS and Windows runners.&lt;/p&gt;

&lt;h2&gt;
  
  
  A new developer experience
&lt;/h2&gt;

&lt;p&gt;The new setup has a few tradeoffs, but I think they're worth it. First, instead of tracking a constantly-evolving &lt;code&gt;v1&lt;/code&gt; branch, consumers of the action will target a particular GitHub Release where they can download artifacts. That means consumers need to update their workflows to get the latest action logic—but that's easy with a tool like &lt;a href="https://www.mend.io/renovate/" rel="noopener noreferrer"&gt;Renovate&lt;/a&gt; and probably leads to more consistent CI/CD anyway.&lt;/p&gt;

&lt;p&gt;The trickier part is for you, the maintainer. Instead of your consumers getting a new version of the action every time you merge to the default branch, you'll need to create a new GitHub release with all the required artifacts. That process &lt;em&gt;today&lt;/em&gt; looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run the "Release" workflow by clicking a few buttons in the GitHub Actions interface and putting in your desired, updated version (note that this does &lt;em&gt;not&lt;/em&gt; update the version in &lt;code&gt;Cargo.toml&lt;/code&gt;, it just sets the version in GitHub releases).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the integration tests to pass with the new version to ensure nothing broke.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the new release and fill in any release notes. Set it as the latest release and publish it to the marketplace.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think this can get a little bit easier by integrating it with &lt;a href="https://github.com/knope-dev/knope" rel="noopener noreferrer"&gt;Knope&lt;/a&gt;; for example, the release notes can be set, and the new version can be determined automatically (plus, it'll bump &lt;code&gt;Cargo.toml&lt;/code&gt;). There doesn't seem to be a way to publish to the GitHub Marketplace via API, though, so there will still be some manual steps 🤔.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let me know
&lt;/h2&gt;

&lt;p&gt;So what do you think about this new and improved version? Do you prefer the Docker method? Is there still something missing from the Rust-based-actions developer experience that you're waiting on? Let me know!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Rust is not hard! Part 1: GitHub Actions</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Tue, 02 May 2023 23:56:07 +0000</pubDate>
      <link>https://dev.to/dbanty/rust-is-not-hard-part-1-github-actions-2ngh</link>
      <guid>https://dev.to/dbanty/rust-is-not-hard-part-1-github-actions-2ngh</guid>
      <description>&lt;p&gt;The most common description of Rust I hear is something like, "it's great for performance but too hard or cumbersome or annoying for most tasks." I don't think this description could be more wrong. Sure, it can be fast, but that's not the main reason to pick Rust. You should pick Rust because it's &lt;em&gt;easy&lt;/em&gt; to build with. It empowers you to create incredible software while &lt;em&gt;enjoying&lt;/em&gt; the experience.&lt;/p&gt;

&lt;p&gt;Sound too good to be true? I'll be honest; although this is my fervent belief, I wasn't sure it would hold up in many situations. Until now...&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I implemented the same reusable GitHub Action in both TypeScript and Rust&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It took about 3 hours for each implementation, 5 minutes fewer for the TypeScript version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rust was significantly less frustrating in debugging the problems I had, &lt;em&gt;and&lt;/em&gt; I am more confident in the quality of the result&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I created a &lt;a href="https://github.com/dbanty/rust-github-action-template" rel="noopener noreferrer"&gt;template&lt;/a&gt; that should shave around 45 minutes off of the Rust time for future adventurers, meaning Rust should be &lt;em&gt;even faster&lt;/em&gt; than TypeScript to implement moderate-to-complex actions from now on 🎉&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The first experiment
&lt;/h2&gt;

&lt;p&gt;I wanted to write a reusable GitHub Action which &lt;a href="https://github.com/marketplace/actions/check-graphql" rel="noopener noreferrer"&gt;runs some quick checks on GraphQL servers&lt;/a&gt;. I've authored actions before, but they're usually short &lt;a href="https://github.com/openapi-generators/openapitools-generator-action/blob/v1/entrypoint.py" rel="noopener noreferrer"&gt;Python scripts&lt;/a&gt; distributable via &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action" rel="noopener noreferrer"&gt;composite actions&lt;/a&gt;. This new action is too complex for a simple script.&lt;/p&gt;

&lt;p&gt;So, naturally, I decided to check out the &lt;a href="https://docs.github.com/en/actions/creating-actions/about-custom-actions" rel="noopener noreferrer"&gt;other options for actions&lt;/a&gt; and found that the best-supported, most popular methods are JavaScript and TypeScript. So I wrote this action in TypeScript (my preference of the two) and had a terrible time doing it 😬. "There must be a better way!" I cried and immediately started over, this time in my favorite language.&lt;/p&gt;

&lt;p&gt;That's when the idea for this blog post came about—if I were going to implement this same action in TypeScript and Rust, I might as well compare my experience in each. So, I implemented the action twice more, this time while recording my efforts for science! First, I wrote it in Rust, then in TypeScript (again).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Side note: I'm not going to release that raw footage because it's incredibly boring (trust me, I had to watch it and take notes to come to my conclusions), but if anyone would be interested in a sped-up, commentated version where I walk through each implementation and all the trouble I had, let me know!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I intend to do something similar for other types of projects and languages. If there's a particular comparison &lt;em&gt;you'd&lt;/em&gt; like to see, &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/205" rel="noopener noreferrer"&gt;please let me know&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Rules
&lt;/h2&gt;

&lt;p&gt;I wanted the comparison to be as fair as possible, so I set a few rules for myself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When I encounter a problem I've seen before, I must do my best to solve it as if I don't know the solution. This is especially important because I have more Docker + Rust experience than most developers. I have also written this exact thing in TypeScript before (so I have solved most problems before).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any work which is duplicated between &lt;em&gt;both&lt;/em&gt; implementations doesn't count against either. For example, I created a bunch of integration tests that were completely reusable for both implementations—so I cut that out of my analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The code should be shippable—but does not need to be perfect. I must be confident enough in the action to push the "publish" button, but I don't need to go down every rabbit hole to make it optimal (for example, I didn't parallelize any of the checks or set up benchmarking). I &lt;em&gt;do&lt;/em&gt;, however, need to clean up any warnings or errors from the build tooling and linters.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I mostly wrote each implementation in one go—Rust, then TypeScript—because it was more natural. However, I will tell the story by swapping back and forth between the same section for each language. The first task was getting started—setting up a super simple working action to serve as a foundation.&lt;/p&gt;

&lt;p&gt;In Rust, this took a fair amount of effort. I spent 58 minutes (about one-third of the total) setting up the action. First, I had to read the &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action" rel="noopener noreferrer"&gt;GitHub documentation on Docker actions&lt;/a&gt;, then find an article about &lt;a href="https://dev.to/rogertorres/first-steps-with-docker-rust-30oi"&gt;making a Dockerfile for Rust&lt;/a&gt;. Once I had the file, there was some back-and-forth fighting with GitHub Actions—and that was just the beginning. The hardest part was figuring out how to bypass the rebuilding of the image on every run—Rust is notoriously slow to compile, and waiting several minutes for the action to &lt;em&gt;start&lt;/em&gt; was out of the question.&lt;/p&gt;

&lt;p&gt;This section highlights the worst part of Rust today—it is still a very young programming language, so it's missing a lot of resources available to other languages. After this project, I &lt;a href="https://github.com/dbanty/rust-github-action-template" rel="noopener noreferrer"&gt;made a template&lt;/a&gt; and a corresponding &lt;a href="https://dylananthony.com/blog/how-to-write-a-github-action-in-rust/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; so that future developers (probably me) will have a much easier time implementing Rust actions. However, this template wasn't available to me &lt;em&gt;yet&lt;/em&gt;, so it doesn't count for this experiment.&lt;/p&gt;

&lt;p&gt;On the TypeScript side, setup was much easier. There was already a &lt;a href="https://github.com/actions/typescript-action" rel="noopener noreferrer"&gt;template from GitHub&lt;/a&gt; that took care of the basics. Most of the time spent here was updating dependencies and getting my editor to play nicely with it—18 minutes, about 10% of the total.&lt;/p&gt;

&lt;p&gt;This was the &lt;em&gt;only&lt;/em&gt; part of implementing this action that took me &lt;em&gt;less&lt;/em&gt; time in TypeScript than in Rust—but it took 40 minutes less, which is enough to bring the total development time in favor of TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Writing the business logic of the action took the bulk of the time in both implementations—116 minutes for Rust and 145 minutes in TypeScript. Let's walk through each issue I spent time on in this phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;Rust does not have exceptions. This means that every time there &lt;em&gt;could&lt;/em&gt; be an error, you're forced to deal with it. I don't allow panics in my production code, so I wasn't going to &lt;code&gt;.unwrap()&lt;/code&gt; as a shortcut. Instead, I produced meaningful error messages for every possible error condition I encountered and bubbled them up in a type-safe way. This requires more upfront thought and effort than exception-based languages but means you're less likely to show an unfriendly error message (like a stack trace) to an end user.&lt;/p&gt;

&lt;p&gt;In TypeScript, there are exceptions. As with &lt;em&gt;most&lt;/em&gt; exception-based languages, there are no static tools to help you know where or when they could occur. For production code, displaying an exception's stack trace is unacceptable (just like panicking)—I want to give users actionable advice. In TypeScript, you either have to encounter an error organically (hopefully not in production) or rely on documentation. It's also exceedingly rare for the &lt;em&gt;type&lt;/em&gt; of all potential errors to be documented, so usually, I had to run the code through known error conditions and read the output (or set a breakpoint with a debugger) to find out what information was available to me. For example, I want to tell the difference between someone providing a malformed URL and a server being unreachable (so that I can provide users with relevant suggestions). The only way to do that is to know how the errors differ between those two conditions.&lt;/p&gt;

&lt;p&gt;Overall, I think I spent more time handling Rust errors (I certainly had more checks), but I was more &lt;em&gt;frustrated&lt;/em&gt; dealing with TypeScript errors (because what I was looking for was hard to find). I definitely have more confidence that my end users will have a better experience with the Rust version since I may have missed some possible TypeScript exceptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning about GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Even with the setup done, there was much to learn about using GitHub Actions in both environments. The TypeScript template mostly came with examples of functions to call to get inputs, set outputs, etc. For the Rust version, I mostly resorted to reading &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions" rel="noopener noreferrer"&gt;bash examples&lt;/a&gt; and translating those to equivalent Rust code. TypeScript is the clear winner, though the total effort for &lt;em&gt;either&lt;/em&gt; language was quite low. Again, like the initial setup, this is because TypeScript is older and more popular than Rust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outdated idioms
&lt;/h3&gt;

&lt;p&gt;This is the other side of the "Rust is a young language" coin. When following examples and IDE suggestions, there was only a single instance of copied code being out of date—and the code still ran fine; the linter just told me I should do it the newer way (&lt;code&gt;format!("{thing}")&lt;/code&gt; instead of &lt;code&gt;format!("{}", thing)&lt;/code&gt;). There wasn't a single time that an example or suggested code didn't compile and run correctly.&lt;/p&gt;

&lt;p&gt;On the Node side, conflicting idioms are documented everywhere, and it's not usually clear whether they are outdated! For example, &lt;code&gt;axios&lt;/code&gt; is documented using the Promise API and CommonJS &lt;code&gt;require()&lt;/code&gt; imports, but neither worked for my project. My ESLint setup (inherited from GitHub's template) told me that &lt;code&gt;async/await&lt;/code&gt; is preferred, which required rewriting &lt;em&gt;and&lt;/em&gt; made the error-handling documentation for &lt;code&gt;axios&lt;/code&gt; all but useless to me. My setup also wanted ESM-style &lt;code&gt;import&lt;/code&gt; instead of &lt;code&gt;require()&lt;/code&gt;—but switching broke Jest. Several articles and incorrect fixes later, I realized that I just needed to update Jest. These issues were constant in TypeScript, and the error messages and search results were rarely helpful.&lt;/p&gt;

&lt;p&gt;Here we have a clear win for Rust, and I don't expect that to change. The official Rust linter, Clippy, is the best I've seen in any language at suggesting (or automatically fixing) updates to idioms. Rust also has a strong backward-compatibility guarantee and dependency management system that, together, mean your builds won't start failing in the future when new versions of the compiler or your libraries come out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paralyzed by choice
&lt;/h3&gt;

&lt;p&gt;While there is often one idiomatic way to do things in Rust, sometimes the choice is unclear. For example, &lt;code&gt;if&lt;/code&gt; or &lt;code&gt;match&lt;/code&gt; could be equally valid in branching your logic—sometimes, you have to try both to know what feels better for a particular case. Likewise, choosing between functional-style iterators and imperative loops is not always clear until you've started down one path. It can be good to have options, but it's also easy to waste time deciding the &lt;em&gt;best&lt;/em&gt; method.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another example of this is going down the rabbit hole of references and lifetimes. Remember, premature optimization is &lt;em&gt;never&lt;/em&gt; a good idea. &lt;strong&gt;Start by cloning if you have borrowing issues, only try to optimize with references if you're sure you need them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TypeScript is certainly not immune to this, but I found myself spinning for options much less frequently. Usually, there is one &lt;em&gt;preferred&lt;/em&gt; method and several older discouraged methods (which ESLint often catches). I give TypeScript the edge on this issue for less time spent on &lt;a href="https://en.wiktionary.org/wiki/bikeshedding" rel="noopener noreferrer"&gt;bikeshedding&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging remote responses
&lt;/h3&gt;

&lt;p&gt;When your code is talking to a remote data source—you will inevitably have to inspect what's happening at runtime when weird things occur. The two approaches I take are setting a breakpoint with a debugger and logging out the information I need to a console (printing). Using a debugger in Rust is easy (with an IDE like CLion), but the information you get from it is not always useful. For example, if I want to inspect a generic, parsed JSON payload without modifying my code, it's pretty much impossible. Because of this, I often resorted to printing the result, tweaking my code, and repeating until everything worked.&lt;/p&gt;

&lt;p&gt;In TypeScript, the dynamically-typed nature of the code leads to a much clearer debugging experience. Actually, my process for figuring out what a remote server is doing is pretty much identical to figuring out how each exception worked. I suppose it makes sense that the debugging experience would be better for a language where less static information is available. TypeScript wins here.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tooling
&lt;/h3&gt;

&lt;p&gt;In Rust, you get a standard set of tools that all work very well together. The build tool (&lt;code&gt;cargo&lt;/code&gt;) manages the compiler (&lt;code&gt;rustc&lt;/code&gt;), your tests, and your dependencies for you. If you used &lt;code&gt;rustup&lt;/code&gt; (the recommended installer/version manager for Rust), you also get a formatter (&lt;code&gt;rustfmt&lt;/code&gt;) and linter (&lt;code&gt;clippy&lt;/code&gt;) for free, which are guaranteed to work well with the rest of the toolchain. There are many more features (e.g., docs) that I didn't need here, but the picture is generally that everything plays nice and gets out of my way unless it has &lt;em&gt;useful&lt;/em&gt; feedback.&lt;/p&gt;

&lt;p&gt;In TypeScript, everything is separate. The compiler (&lt;code&gt;tsc&lt;/code&gt;), formatter (&lt;code&gt;prettier&lt;/code&gt;), linter (&lt;code&gt;eslint&lt;/code&gt;), testing framework (&lt;code&gt;jest&lt;/code&gt;), and runtime (&lt;code&gt;node&lt;/code&gt;) are all distinct components, usually requiring extra dependencies to integrate them. NPM is generally pretty bad at telling you when you have incompatibilities (e.g., when my version of &lt;code&gt;@types/node&lt;/code&gt; did not match my version of Node). The tools like fighting each other (e.g., when &lt;code&gt;eslint&lt;/code&gt; was mad about an import that &lt;code&gt;tsc&lt;/code&gt; needed for proper typing or when &lt;code&gt;jest&lt;/code&gt; + &lt;code&gt;ts-jest&lt;/code&gt; needed a different, special config from &lt;code&gt;tsc&lt;/code&gt;/&lt;code&gt;ts-node&lt;/code&gt;). You end up with many more config files and a lot more time spent fiddling with the tools than would be needed in Rust—sometimes to no end (I had to disable type-checking in a couple of places because I couldn't make it happy 😔)! This makes for a &lt;em&gt;frustrating&lt;/em&gt; developer experience, especially when compared to the joy of Rust's.&lt;/p&gt;

&lt;p&gt;Without question, Rust has better tooling. In fact, Rust has the &lt;em&gt;best&lt;/em&gt; tooling of any language I've ever used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Every library I've used in Rust has docs on &lt;a href="https://docs.rs" rel="noopener noreferrer"&gt;https://docs.rs&lt;/a&gt; generated with the standard &lt;code&gt;cargo doc&lt;/code&gt; tool. You always know what to expect, examples can be tested with &lt;code&gt;cargo test&lt;/code&gt;, and links to other dependencies are kept up to date. This is also the same format that &lt;a href="https://doc.rust-lang.org/std/" rel="noopener noreferrer"&gt;the standard library is documented with&lt;/a&gt;. Overall, it's easy to learn how to use a new library.&lt;/p&gt;

&lt;p&gt;In TypeScript, every library has its own custom documentation. Often, this is just a README file uploaded to NPM, which is usually insufficient. When you combine this with less descriptive error messages from the compiler (or exceptions from the runtime), it's much harder to learn how to use a library in TypeScript than in Rust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fighting the language
&lt;/h3&gt;

&lt;p&gt;This is a much broader category, but sometimes the code you write doesn't work the way you expected it to. Rust and TypeScript have very different issues—Rust eliminates several categories of bugs that TypeScript is prone to but introduces some other challenges. For this, I'll focus on the one issue that stood out the most in each language when reviewing the footage.&lt;/p&gt;

&lt;p&gt;For Rust, it was one of the things folks complain about the most: the dreaded borrow checker 🙀. When going about my business, an error popped up in my IDE that said something like "cannot borrow partially moved value". The easy solution was to add &lt;code&gt;.clone()&lt;/code&gt;, but whenever the borrow checker indicates that I'm re-using a piece of data, I try consolidating them. As it turns out, I had two &lt;code&gt;if&lt;/code&gt; statements that were easy to combine and made for nicer code. That diversion took about 2 minutes—though it required understanding ownership, a concept that doesn't have a parallel in most languages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning about ownership taught me to see my data in a different light. I have caught &lt;strong&gt;several bugs&lt;/strong&gt; in other languages because I was thinking like the borrow checker. I don't think learning ownership is an obstacle to overcome, but rather a useful tool that every developer should have.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In TypeScript, my biggest hurdle started with a failing unit test: the array of error messages coming back from the main function &lt;em&gt;should&lt;/em&gt; have had a length of 1, but had a length of 0. My first instinct was that there was a bug in the error-checking code, so I set a breakpoint and started debugging. After carefully stepping through, I found that I had used a &lt;code&gt;concat&lt;/code&gt; method instead of a &lt;code&gt;push&lt;/code&gt; with a spread operator. With unit tests passing, I pushed up the changes to find that, in CI, the integration tests were failing! After adding some print debugging and pushing back to CI, I found that I had used &lt;code&gt;concat&lt;/code&gt; in a &lt;em&gt;second&lt;/em&gt; location, causing separate errors. Overall this took 20 minutes of messing around to get an answer for something that &lt;em&gt;should&lt;/em&gt; have been caught statically—and would have in Rust.&lt;/p&gt;

&lt;p&gt;One of the big differences between "fighting the language" in Rust and TypeScript is that Rust puts &lt;em&gt;almost every problem&lt;/em&gt; in front of you immediately in the build and lint steps. Instead of failing tests, you get red squigglies in your editor, pointing you immediately to where the problem is. An example of this is &lt;code&gt;#[must_use]&lt;/code&gt;, which a function like &lt;code&gt;concat&lt;/code&gt; would have in Rust. Basically, if a function returns a value, and you forget to use it (like, say, with &lt;code&gt;errors.concat(new_errors)&lt;/code&gt;), it's a compile error.&lt;/p&gt;

&lt;p&gt;While it can be annoying to see more of your bugs up front, I definitely prefer that to stepping backwards from a distant effect. Rust wins here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring
&lt;/h2&gt;

&lt;p&gt;After getting to the end of each implementation, I decided to take them a step forward by reworking some logic. Basically, I wanted to automate something that previously required a manual user setting.&lt;/p&gt;

&lt;p&gt;First, I rewrote the required code in TypeScript; it took 4 minutes. Then, I did exactly the same thing in Rust—another 4 minutes! So that's a tie... right? Well, not quite. When I changed the Rust code, the compiler pointed out a bug that was easy to fix. After I was done, I returned to the TypeScript code to see if it contained the same bug.&lt;/p&gt;

&lt;p&gt;Sure enough, I was consistent enough to write the same bug into both implementations 🤦. The tests I'd written didn't catch it, but the Rust compiler did. After another 8 minutes, I reorganized the TypeScript code to work properly. Without the Rust compiler, I'm unsure how long it would have taken me to catch the TypeScript bug.&lt;/p&gt;

&lt;p&gt;One of the greatest strengths of Rust being so explicit and strict is that it can catch many bugs that other languages can't. This is often talked about in terms of memory safety (when compared to something like C), but it goes &lt;em&gt;way&lt;/em&gt; beyond this. If you leverage the type system to reflect the expectations you're making as you code, it &lt;em&gt;will&lt;/em&gt; catch bugs that would slip through in other languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Is Rust hard? As with most things, I don't think this is a binary yes/no question. However, I find Rust &lt;em&gt;easier&lt;/em&gt; to work with than TypeScript (at least in this case—and I don't &lt;em&gt;believe&lt;/em&gt; that TypeScript is considered a hard language. Personally, I would rather write production code in Rust than any other language.&lt;/p&gt;

&lt;p&gt;Still not convinced? &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/205" rel="noopener noreferrer"&gt;Let me know&lt;/a&gt; which language and scenario you'd like to see compared next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A list of languages I've written production code in and consider the tooling significantly worse than Rust's, in no particular order: Python, Java, Kotlin, Swift, Go, TypeScript, JavaScript, C.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regarding how much professional experience I have writing in programming languages, Python is the first, followed by TypeScript, then Rust. I have certainly spent more time writing Rust than TypeScript, but I've been paid for more hours (and had more peer reviews) with TypeScript. The order of my &lt;em&gt;confidence&lt;/em&gt; in writing each language is Rust, then Python, then TypeScript.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>rust</category>
      <category>typescript</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>How to write a GitHub Action in Rust</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Tue, 07 Feb 2023 04:26:13 +0000</pubDate>
      <link>https://dev.to/dbanty/how-to-write-a-github-action-in-rust-4jm9</link>
      <guid>https://dev.to/dbanty/how-to-write-a-github-action-in-rust-4jm9</guid>
      <description>&lt;p&gt;Creating reusable GitHub Actions is an easy way to automate away everyday tasks in CI/CD. However, actions are typically implemented in TypeScript or JavaScript, and getting started in another language is much more challenging. My favorite language is Rust, so naturally, I wanted an easier way to oxidize my actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  cargo-generate
&lt;/h2&gt;

&lt;p&gt;Rather than walk through all of the manual steps like I had to, you can use &lt;a href="https://crates.io/crates/cargo-generate" rel="noopener noreferrer"&gt;&lt;code&gt;cargo-generate&lt;/code&gt;&lt;/a&gt; to get started quickly. From the command line, run &lt;code&gt;cargo-binstall cargo-generate&lt;/code&gt; followed by &lt;code&gt;cargo generate dbanty/rust-github-action-template&lt;/code&gt;, then follow the prompts to fill in the boilerplate values.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not familiar with &lt;a href="https://crates.io/crates/cargo-binstall" rel="noopener noreferrer"&gt;&lt;code&gt;cargo-binstall&lt;/code&gt;&lt;/a&gt; ? You can use it in place of &lt;code&gt;cargo install&lt;/code&gt; to install supported binaries rather than compiling them from source! You should make &lt;em&gt;your&lt;/em&gt; next Rust binary cargo binstallable!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, you'll get a fully-functioning GitHub Action implemented in Rust, ready for customization! You can see an example of the output in my &lt;a href="https://github.com/dbanty/sample-rust-action" rel="noopener noreferrer"&gt;Sample Rust Action&lt;/a&gt; repo, which is also a GitHub template (in case you want to skip the &lt;code&gt;cargo-generate&lt;/code&gt; step).&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;There's a "TODO" section in the generated &lt;code&gt;README.md&lt;/code&gt; that gives you a high-level set of next steps—so feel free to dive right in if you're a hands-on learner! For completeness, I'll walk through each of the steps here.&lt;/p&gt;

&lt;p&gt;First, you'll want to update the &lt;code&gt;README&lt;/code&gt; to describe what your action does and how to use it. I find it easier to describe the user experience I &lt;em&gt;want&lt;/em&gt; to create before I try to create it, a sort of "documentation-driven development". As an example, you can check out the docs for my &lt;a href="https://github.com/dbanty/graphql-check-action" rel="noopener noreferrer"&gt;GraphQL Check Action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that you've designed your action, you need to define your inputs and outputs in &lt;code&gt;action.yml&lt;/code&gt;. Each input needs to be defined in two places:&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;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;display,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ghcr.io/&amp;lt;your_username&amp;gt;/&amp;lt;your_repo_name&amp;gt;:v1'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.error }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; section is how you define inputs for the action itself—GitHub will do some validation here, and users might peak at the description to double-check what each input does. Then, in the &lt;code&gt;runs&lt;/code&gt; section, you pass the input to your Rust binary. The &lt;strong&gt;order here matters&lt;/strong&gt;—so it's easiest to add inputs one at a time.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;outputs&lt;/code&gt; section lets you tell users what they can receive when the action fails. I recommend including, at a minimum, an &lt;code&gt;error&lt;/code&gt; output for easier testing:&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;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;occurred'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With inputs and outputs defined in &lt;code&gt;action.yml&lt;/code&gt;, you need to consume the inputs and output the outputs! The generated code comes with an example of each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;//! src/main.rs&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;github_output_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GITHUB_OUTPUT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: {error}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;github_output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error={error}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we can see the list of arguments passed in from the &lt;code&gt;args&lt;/code&gt; section of &lt;code&gt;action.yml&lt;/code&gt; ends up in our &lt;code&gt;args&lt;/code&gt; variable. The first entry in this &lt;code&gt;Vec&lt;/code&gt; is the name of the binary, so &lt;strong&gt;the first argument is at index 1&lt;/strong&gt;. The &lt;code&gt;eprintln!&lt;/code&gt; line is to write a message to standard error—this will appear in the GitHub logs so that users know what happened. The usage of &lt;code&gt;write()&lt;/code&gt; is an example of setting an output—you have to write to a file path which is set to the environment variable &lt;code&gt;GITHUB_OUTPUT&lt;/code&gt; using the format &lt;code&gt;&amp;lt;output_name&amp;gt;=&amp;lt;output_value&amp;gt;&lt;/code&gt;. Then, &lt;code&gt;exit(1)&lt;/code&gt; will make the action fail the workflow (putting that little red x on a status check and preventing PRs from merging).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more ways you can interact with GitHub Actions (like setting warning messages), I recommend reading &lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last step is to &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/changing-the-default-branch" rel="noopener noreferrer"&gt;change your default branch&lt;/a&gt; to be named &lt;code&gt;v1&lt;/code&gt;. This is because the template assumes you will use semantic versioning and that users will always want the latest compatible release (v1). You can use whatever branching and tagging strategy you want, though you'll have to alter more of the generated code.&lt;/p&gt;

&lt;p&gt;That's it! If you can handle inputs and outputs and set actions to failed, you're ready to start implementing basic actions! Just write Rust code like normal—you can even install any dependencies you want!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing branches and pull requests
&lt;/h2&gt;

&lt;p&gt;The generated code comes with an included &lt;code&gt;.github/workflows/integration_tests.yml&lt;/code&gt; file for testing the action. If we take a peak inside, we'll see that it consumes our GitHub action using the &lt;code&gt;uses: ./&lt;/code&gt; syntax:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works just fine on the &lt;code&gt;v1&lt;/code&gt; branch, but if we look back at our action definition, we can see a problem:&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;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ghcr.io/&amp;lt;your_username&amp;gt;/&amp;lt;your_repo_name&amp;gt;:v1'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.error }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action uses the &lt;code&gt;v1&lt;/code&gt; tag of a Docker image built from this repo. That &lt;code&gt;v1&lt;/code&gt; tag corresponds to the &lt;code&gt;v1&lt;/code&gt; Git branch—meaning that if you run these tests on any other branch, you'll still be testing the &lt;code&gt;v1&lt;/code&gt; branch! The easiest way to override this (e.g., for testing a pull request) is to change that to &lt;code&gt;image: 'Dockerfile'&lt;/code&gt; . This will test the code of whatever branch you are on, but &lt;strong&gt;be careful not to commit this change back to&lt;/strong&gt; &lt;code&gt;v1&lt;/code&gt; &lt;strong&gt;or it will cause terrible performance for action consumers.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it all works
&lt;/h2&gt;

&lt;p&gt;Now that you know how to implement and test your actions, you're ready to go! But if you're still curious about how everything works, stick around for a deeper dive.&lt;/p&gt;

&lt;p&gt;First, we use the &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action" rel="noopener noreferrer"&gt;Docker container action&lt;/a&gt; method—one of three ways to create GitHub Actions. This enables us to build whatever kind of binary we want, using whatever dependencies we want, without needing JavaScript or complex, dynamic install scripts. There are some limitations, though. Notably, these actions can &lt;em&gt;only run on runners with a Linux operating system&lt;/em&gt;, making them less flexible or portable than JavaScript actions. Second, some capabilities may not be possible from your actions (like setting environment variables).&lt;/p&gt;

&lt;p&gt;Another limitation of Docker actions is the one I mentioned in the testing section above. You either need to publish a Docker image and pin your action to a specific tag or rebuild the Docker image every time that action runs. Rust can take a long time to compile, especially when waiting for Cargo to download dependencies—so building the image every time makes for a poor experience for the action's consumers. However, pinning to a tag makes it harder to test multiple branches, a tradeoff we have to accept for now.&lt;/p&gt;

&lt;p&gt;So, to get from your Rust code to a consumable action—we have to build a Docker image and publish it to a registry so that the action can pull it before running your code. Let's take a deeper look at each of these steps.&lt;/p&gt;

&lt;p&gt;The template generates a workflow in &lt;code&gt;.github/workflows/docker-publish.yml&lt;/code&gt; that builds a new image with every push to the &lt;code&gt;v1&lt;/code&gt; branch. It's a rather complicated workflow, so we'll take a look at a couple of snippets:&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;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# other steps omitted&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-and-push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name != 'pull_request' }}&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we see that we're publishing to the &lt;code&gt;ghcr.io&lt;/code&gt; registry with an image named the same as our repository—that enables us to push to GitHub Packages with no additional authentication, all of your artifacts live with your repository! We use the &lt;code&gt;cache-from&lt;/code&gt; and &lt;code&gt;cache-to&lt;/code&gt; inputs to enable Docker layer caching—crucial given how slow Rust-based images can be to build. We're also passing the input &lt;code&gt;context: .&lt;/code&gt;, which means it should build from a file called &lt;code&gt;Dockerfile&lt;/code&gt;—let's look at that next!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rust:1.67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="c"&gt;# create a new empty shell project&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;root cargo new &lt;span class="nt"&gt;--bin&lt;/span&gt; sample-rust-action
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /sample-rust-action&lt;/span&gt;

&lt;span class="c"&gt;# copy over your manifests&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./Cargo.lock ./Cargo.lock&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./Cargo.toml ./Cargo.toml&lt;/span&gt;

&lt;span class="c"&gt;# this build step will cache your dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;src/&lt;span class="k"&gt;*&lt;/span&gt;.rs

&lt;span class="c"&gt;# copy your source tree&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./src ./src&lt;/span&gt;

&lt;span class="c"&gt;# build for release&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; ./target/release/deps/sample_rust_action&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# our final base&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/cc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;

&lt;span class="c"&gt;# copy the build artifact from the build stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /sample-rust-action/target/release/sample-rust-action .&lt;/span&gt;

&lt;span class="c"&gt;# set the startup command to run your binary&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/sample-rust-action"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going through this line by line, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Start with the official &lt;code&gt;rust&lt;/code&gt; image for building—some slimmer images probably work, but this gives us maximum flexibility for template users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We create an empty Rust binary with &lt;code&gt;cargo new&lt;/code&gt;, this is a simple way to get Docker layer caching to work. For a more robust solution, you may want to check out &lt;a href="https://github.com/LukeMathWalker/cargo-chef" rel="noopener noreferrer"&gt;cargo-chef&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The next few steps build just enough of our code to get dependencies to cache. Note that modifying &lt;code&gt;Cargo.lock&lt;/code&gt; or &lt;code&gt;Cargo.toml&lt;/code&gt; will bust the cache; this is partially why &lt;code&gt;cargo-chef&lt;/code&gt; may be a better option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The next couple of steps (starting with &lt;code&gt;COPY ./src ./src&lt;/code&gt; and going through &lt;code&gt;RUN cargo build --release&lt;/code&gt;) will build our finished binary&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, we switch over to a smaller base image. You &lt;em&gt;can&lt;/em&gt; make this even smaller by switching to &lt;code&gt;gcr.io/distroless/static&lt;/code&gt; but it makes building harder (you have to use some musl toolchain stuff), and I found that it doesn't make the action any faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We pop our binary over into the fresh &lt;code&gt;cc&lt;/code&gt; image, and set up the entry point (note that &lt;code&gt;CMD&lt;/code&gt; doesn't work here; you have to use &lt;code&gt;ENTRYPOINT&lt;/code&gt;). That's the whole image!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once we've built and published the image, we immediately test it to catch any last-minute problems. Let's look back at the &lt;code&gt;.github/workflows/integration_tests.yml&lt;/code&gt; file from earlier:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test consuming this action&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;v1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Docker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Publish"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;v1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;completed&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;

  &lt;span class="na"&gt;test_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify failure&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.test.outputs.error != ''&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Failed as expected"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unexpected success&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.test.outputs.error == ''&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Succeeded unexpectedly" &amp;amp;&amp;amp; exit &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;workflow_run&lt;/code&gt; section tells this action to run after we publish to Docker—this ensures we're testing the version we &lt;em&gt;just&lt;/em&gt; published and not an earlier variant. Then, the workflow comes with two tests as examples—you'll want to replace these with ones that exercise the actual inputs and outputs. The second job, &lt;code&gt;test_error&lt;/code&gt;, is much more interesting—this is how you can test &lt;em&gt;failure&lt;/em&gt; conditions (and why we set the error output). It's just as important to test expected failures as expected successes, maybe even more important!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My little &lt;code&gt;cargo-generate&lt;/code&gt; template will hopefully make it easier than ever to write Rust-based GitHub actions. If you try it out and have any suggestions or questions, please &lt;a href="https://github.com/dbanty/rust-github-action-template/issues" rel="noopener noreferrer"&gt;open an issue on the repository&lt;/a&gt;. If you want to hear more about the motivation for this template—why I'm writing actions in Rust instead of TypeScript, follow me for that upcoming post!&lt;/p&gt;

</description>
      <category>announcement</category>
      <category>forem</category>
      <category>devto</category>
      <category>offers</category>
    </item>
    <item>
      <title>Stop Writing DRY Code</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Tue, 05 Apr 2022 23:14:24 +0000</pubDate>
      <link>https://dev.to/dbanty/stop-writing-dry-code-51em</link>
      <guid>https://dev.to/dbanty/stop-writing-dry-code-51em</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cover photo created by &lt;a href="https://www.instagram.com/jazlynborkowski/" rel="noopener noreferrer"&gt;Jazlyn Borkowski&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"DRY" is an acronym introduced, seemingly universally, to software engineers early in their education. For example, when I searched "software engineering best practices," 4 of the top 5 results mentioned DRY. It stands for "Don't Repeat Yourself" and is one of the worst things you can teach a fledgling developer.&lt;/p&gt;

&lt;p&gt;If you take nothing else away from this post, take this: DRY should &lt;strong&gt;never&lt;/strong&gt; be a &lt;strong&gt;goal&lt;/strong&gt; when writing code. It is not an indicator of code quality; it is, at best, a tool to be applied in &lt;em&gt;some&lt;/em&gt; circumstances. Code that does not repeat itself is not inherently better than code that does, so "DRY" should never appear as a recommendation in your code reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  MOIST
&lt;/h2&gt;

&lt;p&gt;Code is a bit like cake. A dry cake is brittle and likely crumble when you touch it. Likewise, DRY code—code that has no repetition—will resist extension and alteration. By combining two implementations into a single one, you bind them together, making it so you can't change one without changing both.&lt;/p&gt;

&lt;p&gt;On the other hand, a wet cake will fall apart, not holding its structure. In software, WET usually means something to the effect of "write every time"—it's the opposite of DRY in that you constantly repeat yourself. If your code is WET, you can change one piece without breaking any others. However, two pieces of information that should be the same can easily fall out of sync.&lt;/p&gt;

&lt;p&gt;The best cake, and code, is MOIST—"Maintain One Indisputable Source of Truth." Rather than being a hard and fast rule about writing code, it gives you a reason &lt;em&gt;why&lt;/em&gt; to refactor code. MOIST gives you the best of both worlds by increasing rigidity to prevent bugs and keeping flexibility wherever possible. As with all things, balance is vital.&lt;/p&gt;

&lt;p&gt;Let's look at an example where keeping your code MOIST is essential. We're writing a program that handles semantic versioning for projects. It has two commands: &lt;code&gt;auto&lt;/code&gt; reads your commit history and generates a new version. The &lt;code&gt;manual&lt;/code&gt; command takes a rule name from the user and bumps the version according to that rule. A first attempt might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;major&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BREAKING CHANGE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;major&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.major&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"feat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fix"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;write_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;manual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"major"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.major&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"minor"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"patch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown rule: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;write_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're maintaining two different implementations for applying a semantic rule to a semantic version—two distinct truths about the rules! Unfortunately, this separation could easily lead to disparate behaviors between the two commands, confusing users. So let's maintain only a single source of truth for how to bump that version number!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"patch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BREAKING CHANGE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"major"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"feat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"minor"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"major"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.major&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"minor"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"patch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;write_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;manual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"major"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"minor"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"patch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown rule: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;bump_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, all good, right? Well, not entirely. Our new &lt;code&gt;bump_version&lt;/code&gt; function is the arbiter of applying rules to versions—but now we have three different places those rules are defined! If we want to add a "prerelease" rule, we'd have to remember to change every location separately, which could easily cause a bug! So, again, we'll maintain only a single source of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Minor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Patch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BREAKING CHANGE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Major&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"feat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Minor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Major&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.major&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Minor&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.minor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Patch&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="py"&gt;.patch&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;write_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;manual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"major"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Major&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"minor"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Minor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"patch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;bump_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Patch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown rule: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we go, one source of truth for the rules, how to apply them to a semantic version, how to generate them from commit messages, and how to interpret a user's input.&lt;/p&gt;

&lt;p&gt;But I see more repetition! What if we changed to a single &lt;code&gt;commit.message.contains&lt;/code&gt; statement with a map from the keywords to the rule they represent?&lt;/p&gt;

&lt;p&gt;When considering any refactor, it's crucial to think about the goal. In the case of MOIST, we have to ask ourselves, "what is the truth we're trying to protect?" Is it true that "breaking changes" and "features" should always be determined the same way? No! In fact, this implementation is not consistent with Semantic Versioning yet, and the two branches will eventually diverge further! Coupling these two pieces of information would increase rigidity &lt;em&gt;without&lt;/em&gt; protecting a single source of truth, so we should &lt;strong&gt;not&lt;/strong&gt; combine them.&lt;/p&gt;

&lt;p&gt;What about the user input? Surely we should factor out the string-to-rule map and only have a single function call location! Let's try the same test—what is the single truth we're trying to protect? Is it "every rule should be determined by only a single string?" That feels more like an implementation detail. In fact, if we add a &lt;code&gt;prerelease&lt;/code&gt; rule, we'll need some additional info to select a prefix. This change feels like it would be a reduction in repetition without a clear goal—it would bind the separate rules together, making it harder to change just one without an obvious benefit.&lt;/p&gt;

&lt;p&gt;MOIST can get subjective and be a bit mushy—as can any coding practice, but it tries to draw a line between &lt;em&gt;harmful&lt;/em&gt; repetition and benign code. The key to successfully applying any "best practice" is understanding the true goal and always keeping that in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAINY
&lt;/h2&gt;

&lt;p&gt;Cake aside, there are a couple more worthwhile goals semi-related to DRY. I'll attempt to shoehorn them into more acronyms semi-antonymic to DRY. "Reusable Abstractions, Ideally Not Yours" is another way of saying "don't build when you can buy." It is far more efficient for one person to solve a problem and share that solution than for hundreds of people to solve it independently. This practice is applicable at many scales but achieves another one of the fundamental goals that DRY tries to stand for.&lt;/p&gt;

&lt;p&gt;The best example of this is the open-source community. Rather than re-do work that someone else has done, you can build on top of what exists. Likewise, you can share solutions to problems you've faced so that others don't need to waste future effort—and you multiply the impact of your work. RAINY is like taking "don't repeat yourself" and applying it across our entire community. More like "let's not repeat &lt;em&gt;ourselves&lt;/em&gt;."&lt;/p&gt;

&lt;p&gt;As with everything, there is a balance to be struck. We don't want a MONSOON, where "Maintaining Open source is a Nuisance So Others Often Neglect it." 🤪 Basically, as a consumer, installing dependencies is a pain, and keeping them up to date is even more of a pain (you should be using &lt;a href="https://www.whitesourcesoftware.com/free-developer-tools/renovate/" rel="noopener noreferrer"&gt;Renovate&lt;/a&gt; to mitigate that, though). On the other side, staying on top of issues and pull requests as a maintainer is burdensome and often thankless. This level of effort on both sides can lead to relatively simple bugs never being fixed.&lt;/p&gt;

&lt;p&gt;Balancing this is difficult, and I think a conversation about the good and bad of open source can go far beyond this article—so I'm going to leave it there. However, if you'd like a post titled either "Do You Really Need that Dependency?" or "When to Open Source it", &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FRESH
&lt;/h2&gt;

&lt;p&gt;"Functions Read Easier in Short Hunks." Yes—my acronyms are getting more and more unhinged and also straying further from DRY. I'm not sorry.&lt;/p&gt;

&lt;p&gt;Often, reviewers use DRY as a code word for "make that into a function." Code readability is vital for maintainability, and an easy way to improve readability is to split large functions into several smaller ones. This way, a reader can get a high-level understanding of what the code is doing just by reading the function names in order—and they should read a bit like prose. &lt;/p&gt;

&lt;p&gt;Of course, there's a tradeoff to be made here; a function call is not &lt;em&gt;always&lt;/em&gt; easier to read than the statements that make it up, and calling functions can often have performance penalties. Still, as a goal, refactoring to readability is far more valuable than refactoring simply to reduce duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Test
&lt;/h2&gt;

&lt;p&gt;Here's a set of questions I suggest you ask yourself next time you're wondering if you should be repeating yourself or not:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Are there two separate sources of truth for a single concept? If yes, try to unify them.&lt;/li&gt;
&lt;li&gt;Is this code solving a general problem or something specific to me / my work? If it can be generalized, consider publishing a reusable module (whether open source or in a private/corporate location).&lt;/li&gt;
&lt;li&gt;Can I read through this code and understand what it's doing without stopping to inspect it? For this one, ideally, have someone who didn't write the code answer the question in a code review. If it's hard to parse for humans, consider abstracting away details that aren't needed, like with functions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hopefully, all of my acronymic nonsense has provided you with enough alternatives to DRY that you never use it again. Remember, not repeating yourself is not a valuable outcome of refactoring. Instead, try keeping a goal in mind like improving correctness, benefitting the community, or making the code easier to read. Now go enjoy some cake; you've earned it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Was this post super helpful to you? Tip me on &lt;a href="https://github.com/sponsors/dbanty?frequency=one-time&amp;amp;sponsor=dbanty" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.patreon.com/dbanty" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt;, or &lt;a href="https://ko-fi.com/dbanty" rel="noopener noreferrer"&gt;Ko-Fi&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question or comment about this post? Leave it in the &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/199" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; thread on GitHub!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to be notified of future posts? Watch releases in &lt;a href="https://github.com/dbanty/dylananthony.com" rel="noopener noreferrer"&gt;the GitHub repo&lt;/a&gt; or &lt;a href="https://twitter.com/TBDylan" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have an idea or request for a future blog topic? Drop it in the GitHub discussions under &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;ideas&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>New Year, New Website</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Sun, 02 Jan 2022 05:00:09 +0000</pubDate>
      <link>https://dev.to/dbanty/new-year-new-website-3797</link>
      <guid>https://dev.to/dbanty/new-year-new-website-3797</guid>
      <description>&lt;p&gt;I really like blogging. Sharing my discoveries and thoughts with anyone interested is one of a few ways I give back to the developer community. Having a topic also gives me an objective—and learning with an objective is the only way I can learn anything. So why haven't I posted in over eight months? That's what I hope to answer here, as well as share the ideas I've had for picking back up where I left off.&lt;/p&gt;

&lt;p&gt;When debugging any problem, the first question to ask is "what changed?" The system (that is, the way my blogs get produced) is wildly complex, but I've come up with three components to look at:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Content—has there been a significant change in the topics I want to write about?&lt;/li&gt;
&lt;li&gt;The Writer—have I changed? Do I have less motivation to blog?&lt;/li&gt;
&lt;li&gt;The Process—did something change about how I write, which holds me back?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The answer is, of course, yes to all of the above, so let's dig in one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Content
&lt;/h2&gt;

&lt;p&gt;The old adage says, "write what you know," and I sure have. I had the goal in my day job to develop the best web APIs possible, which meant using the best tooling available. I wanted to write an OpenAPI server in Rust and host it on serverless technologies, so I explored all the options I could find in that space for a while. However, I pretty quickly ran out of options, with none of them entirely fulfilling my needs. &lt;/p&gt;

&lt;p&gt;To make matters worse, although some of the solutions came close, none of them would be ready in time to use for the project I had in mind. As a result, my motivation to write about them quickly decreased. So the content wasn't technically changing, but its relevance to me was.&lt;/p&gt;

&lt;p&gt;However, this wasn't the case for the whole of my eight-month hiatus. I worked on a side project that needed a new web server, so I decided to try hosting it on Microsoft Azure. The experience ended up being pretty terrible, but I never brought myself to finish any of the blog drafts about it. Recently, a new OpenAPI web server framework for Rust has appeared—Poem. It's an incredible new entry that seems to satisfy most of my needs and could be a real contender for the "FastAPI of Rust" crown—but still no blog post.&lt;/p&gt;

&lt;p&gt;What gives? Clearly, even though the content was an issue early on, it's not the only thing that's put a pause on my writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Writer
&lt;/h2&gt;

&lt;p&gt;As I hinted earlier, my priorities have definitely shifted over the year. It started with changing projects at my old job and accelerated when I switched companies entirely. Not working on Rust-based APIs during the day definitely lowered my motivation to write on the topic, but it wasn't the only cause.&lt;/p&gt;

&lt;p&gt;I'm not sure enough to diagnose this, but switching jobs also reduced how much I work on side projects and contribute to open source. It could be that I'm more satisfied in my day-to-day work and don't need to seek out additional accomplishments. On the other hand, it could be that the increased code-writing of the new position leaves me with less energy to do it after hours. Either way, I think the solution here is to write on topics that require less coding in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;Here it is, the moment you've all been waiting for; why did I rewrite my website? I write my blog posts in Markdown, and that hasn't changed. It's a lovely, straightforward format that is supported by Dev.to (one of the places I post) as well as my note-taking app. What has changed is the way I convert that Markdown into a website for your viewing pleasure (and a place to point canonical links).&lt;/p&gt;

&lt;p&gt;I used to use Next.js with many addons so complex that I can't describe them. I can, however, explain how that complexity caused me pain and made me much less likely to post.&lt;/p&gt;

&lt;p&gt;First, it's essential to understand that I cannot leave things out of date. Am I leaving performance on the table and thereby wasting energy every time my site builds or loads? Is my site horribly broken for some users because of a bug? Is there some security vulnerability that could actually impact my readers even though my site is essentially static? These questions constantly plague me as long as dependencies are out of date. I did not have time to read and comprehend the dozens of updates per month that Renovate indicated were available. Beyond that, my JavaScript package manager of choice—Yarn—just didn't work with some dependencies (e.g., the latest TypeScript and Next.js). Switching to &lt;code&gt;npm&lt;/code&gt; would be too painful, so I had to test every dependency update manually, which, again, I simply couldn't justify the time investment for. Plus, an update to one dependency would often break a seemingly unrelated part of my website, and I'd have no idea why or how to fix it.&lt;/p&gt;

&lt;p&gt;Another problem with my website that I couldn't ignore was JavaScript itself. Even though my content is static, my combination of tools could not convert it into plain HTML and CSS. This, again, meant wasted energy every time someone loaded the site, and their browser had to download and run some useless code. These aren't big problems; the performance difference is insignificant on modern machines. Still, it &lt;em&gt;felt awful&lt;/em&gt; to have a bunch of &lt;em&gt;stuff&lt;/em&gt; happening that didn't need to happen.&lt;/p&gt;

&lt;p&gt;What I wanted was a way to convert my Markdown into pure HTML and CSS. I wanted the syntax highlighting for my code blocks to not be done on the fly with JavaScript but instead rendered once at build time and shipped as styled text. And most of all, I wanted &lt;em&gt;way fewer&lt;/em&gt; dependencies, ideally while avoiding the &lt;code&gt;npm&lt;/code&gt; ecosystem altogether. I got all of that with Zola.&lt;/p&gt;

&lt;p&gt;Zola is now the only dependency I need. It takes my Markdown content and converts it into a purely static (HTML and CSS) website. The documentation is excellent, the build process is speedy (4x faster on Vercel than Next.js builds were), and I'll never again have to dread a backlog of updates. It does come with some downsides, like not having Tailwind, but it's well worth the tradeoff for the relief I feel. The whole conversion process only took me a handful of hours, and most of that was deciding on a theme to start with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Many reasons can (and did) cause burnout, and many little changes can help relieve it. Will my combination of changes get me back to posting again regularly? Only time will tell. One thing is for sure though, taking the time to "debug" my own burnout sure did make me feel better about it. I hope to talk to you all again soon.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Was this post super helpful to you? &lt;a href="https://github.com/sponsors/dbanty?frequency=one-time&amp;amp;sponsor=dbanty" rel="noopener noreferrer"&gt;Tip me on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to be notified of future posts? Watch releases in &lt;a href="https://github.com/dbanty/dylananthony.com" rel="noopener noreferrer"&gt;the GitHub repo&lt;/a&gt; or &lt;a href="https://twitter.com/TBDylan" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have an idea or request for a future blog topic? Drop it in the GitHub discussions under &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;ideas&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best Supported Serverless Languages</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Thu, 29 Apr 2021 00:56:33 +0000</pubDate>
      <link>https://dev.to/dbanty/best-supported-serverless-languages-l5</link>
      <guid>https://dev.to/dbanty/best-supported-serverless-languages-l5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cover image created by me using the &lt;a href="https://github.com/remojansen/logo.ts" rel="noopener noreferrer"&gt;Community TypeScript Logo&lt;/a&gt;, the &lt;a href="https://blog.golang.org/go-brand" rel="noopener noreferrer"&gt;Go logo&lt;/a&gt;, the &lt;a href="https://github.com/dotnet/brand" rel="noopener noreferrer"&gt;.NET logo&lt;/a&gt;, and &lt;a href="https://www.rustacean.net" rel="noopener noreferrer"&gt;Ferris the Crab&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I'm a big fan of serverless functions, but my perspective is usually limited to what will run on AWS Lambda. So I took a tally of the supported languages on the serverless platforms that I've heard of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;I didn't go searching for new serverless providers, these are the ones I already knew about.&lt;/li&gt;
&lt;li&gt;These are only the languages prominently listed with tutorials or examples on the provider's website. You'll notice, for example, that Rust on AWS Lambda is not included here even though I've written several blog posts about it!&lt;/li&gt;
&lt;li&gt;This is not an endorsement or review of any languages or providers, merely a count of official support. If you have specific questions you want answered (or a slightly different list ranking related things) &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/71" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Some of these entries have their own specific caveats, they are indicated with a number at the end of the entry. Details on that number are at the bottom of the post.&lt;/li&gt;
&lt;li&gt;I have not used all of these language/provider combos, I'm assuming if they are displayed prominently on the site then they are possible to use effectively.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Big Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The best supported language is JavaScript, followed closely by TypeScript. No shock there.&lt;/li&gt;
&lt;li&gt;Python and Go are tied for third place in availability.&lt;/li&gt;
&lt;li&gt;Cloudflare Workers are &lt;em&gt;by far&lt;/em&gt; the most flexible in way of supported / documented languages with 12—though many of those rely on cross-compiling the language to JavaScript.&lt;/li&gt;
&lt;li&gt;AWS, Azure, and GCP are all tied for second at 8 supported languages. Realistically, these are the most flexible platforms as those 8 languages don't require transpilation (except for TypeScript but... you know...).&lt;/li&gt;
&lt;li&gt;Rust has an explicit tutorial on the Azure website! 🥰 I'm suddenly more likely to use Azure than AWS in my next big project.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The List
&lt;/h2&gt;

&lt;p&gt;Here is the list of all the supported languages with the platforms that support them. Reminder, a number in {} after the language means there's a caveat, check the bottom of the post for it!&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Netlify&lt;/li&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;li&gt; Vercel&lt;/li&gt;
&lt;li&gt; Cloudflare&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  TypeScript
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Netlify&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;li&gt; Vercel&lt;/li&gt;
&lt;li&gt; Cloudflare&lt;/li&gt;
&lt;li&gt; AWS {1}&lt;/li&gt;
&lt;li&gt; GCP {1}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Netlify&lt;/li&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Azure {2}&lt;/li&gt;
&lt;li&gt; Vercel&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;li&gt; Vercel&lt;/li&gt;
&lt;li&gt; Cloudflare {3}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Ruby
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Vercel&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Java
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  C
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  PHP
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; GCP&lt;/li&gt;
&lt;li&gt; Cloudflare {3}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Powershell
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; AWS&lt;/li&gt;
&lt;li&gt; Azure&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Rust
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Azure {2}&lt;/li&gt;
&lt;li&gt;Cloudflare {4}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  C and Cobol
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Cloudflare {4}&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Kotlin, Dart, Scala, Reason / OCaml, Perl, and F
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Cloudflare {3}&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Language-Specific Caveats
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The runtime is for Node.js and there is no TypeScript example, but TypeScript is fairly easy to compile to JavaScript.&lt;/li&gt;
&lt;li&gt;There is a tutorial for using this language, but not an official runtime.&lt;/li&gt;
&lt;li&gt;Supported by compiling to JavaScript, some language features / libraries may not work.&lt;/li&gt;
&lt;li&gt;Supported by compiling to WebAssembly, your mileage may vary.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Was this post super helpful to you? &lt;a href="https://github.com/sponsors/dbanty?frequency=one-time&amp;amp;sponsor=dbanty" rel="noopener noreferrer"&gt;Tip me on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question or comment about this post? Leave it in the &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/71" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; thread on GitHub!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to be notified of future posts? Watch releases in &lt;a href="https://github.com/dbanty/dylananthony.com" rel="noopener noreferrer"&gt;the GitHub repo&lt;/a&gt; or &lt;a href="https://twitter.com/TBDylan" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have an idea or request for a future blog topic? Drop it in the GitHub discussions under &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;ideas&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
    </item>
    <item>
      <title>Running GraphQL on Lambda with Rust</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Wed, 21 Apr 2021 00:53:24 +0000</pubDate>
      <link>https://dev.to/dbanty/running-graphql-on-lambda-with-rust-1lak</link>
      <guid>https://dev.to/dbanty/running-graphql-on-lambda-with-rust-1lak</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cover image created by me using &lt;a href="https://www.rustacean.net" rel="noopener noreferrer"&gt;Ferris the Crab&lt;/a&gt;, &lt;a href="https://github.com/graphql/artwork" rel="noopener noreferrer"&gt;the GraphQL logo&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/architecture/icons/" rel="noopener noreferrer"&gt;the AWS Lambda logo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Taking a break from OpenAPI for a bit, I decided to explore the primary alternative for web API development—GraphQL. I've been pleasantly surprised so far at how easy it's been to write a GraphQL API using Rust, &lt;em&gt;much&lt;/em&gt; easier than doing so with the OpenAPI tools I've tested so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Options
&lt;/h2&gt;

&lt;p&gt;There are two real contenders for GraphQL crates: &lt;a href="https://async-graphql.github.io/async-graphql/en/index.html" rel="noopener noreferrer"&gt;async-graphql&lt;/a&gt; and &lt;a href="https://github.com/graphql-rust/juniper" rel="noopener noreferrer"&gt;Juniper&lt;/a&gt;. Having read briefly through both sets of documentation, &lt;a href="https://async-graphql.github.io/async-graphql/en/index.html" rel="noopener noreferrer"&gt;async-graphql&lt;/a&gt; seemed like the more feature-rich and well-documented option, so that's what I went with. If you'd like me to take a look at Juniper and do an in-depth comparison, please leave a note &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/56" rel="noopener noreferrer"&gt;in the discussion thread&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The good news is that both solutions are fairly feature-complete using stable Rust, and neither is tightly coupled to a web server! You'll know that this is perfect for my use-cases if you've read &lt;a href="https://dylananthony.com/posts/fastapi-rust-2-research" rel="noopener noreferrer"&gt;my previous posts&lt;/a&gt; where I repeatedly jumped through hoops shoe-horning a web server into a serverless environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;As I mentioned, I went with &lt;a href="https://async-graphql.github.io/async-graphql/en/index.html" rel="noopener noreferrer"&gt;async-graphql&lt;/a&gt; as the primary crate to build my experiment with. I used a similar AWS CDK setup as in &lt;a href="https://dylananthony.com/posts/fastapi-rust-6-aws-lambda" rel="noopener noreferrer"&gt;my previous post&lt;/a&gt; since it was the easiest and most robust way to both test Rust lambdas locally and to deploy them to AWS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interested in seeing how to apply this CDK setup to Python applications? Upvote &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/36" rel="noopener noreferrer"&gt;this idea thread&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used &lt;a href="https://sagiegurari.github.io/cargo-make/" rel="noopener noreferrer"&gt;cargo-make&lt;/a&gt; to orchestrate a bunch of tasks (I highly recommend you check it out if you find yourself using &lt;code&gt;Makefile&lt;/code&gt;s with Rust). Postgres via &lt;code&gt;docker-compose&lt;/code&gt; is my go-to database, though you can easily swap in MySQL or MSSQL since I'm using &lt;a href="https://docs.rs/crate/sqlx/0.5.1" rel="noopener noreferrer"&gt;SQLx&lt;/a&gt; to run queries. Throw in a bit of &lt;a href="https://docs.rs/tracing/0.1.25/tracing/" rel="noopener noreferrer"&gt;tracing&lt;/a&gt; following the examples of the fantastic &lt;a href="https://www.zero2prod.com" rel="noopener noreferrer"&gt;Zero to Production in Rust&lt;/a&gt; book, and we've got ourselves a pretty solid development platform for building our API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;You can explore the codebase yourself by looking at &lt;a href="https://github.com/dbanty/rust-lambda-graphql-example" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;, but here's quick rundown. The code consists of two main pieces: the handler and the API.&lt;/p&gt;

&lt;p&gt;The handler is the same sort of thing I've written for &lt;a href="https://dylananthony.com/posts/fastapi-rust-3-trying-actix" rel="noopener noreferrer"&gt;previous posts&lt;/a&gt;—it's the bridge between the &lt;code&gt;lamedh_&lt;/code&gt; crates (for interfacing with AWS Lambda) and &lt;a href="https://async-graphql.github.io/async-graphql/en/index.html" rel="noopener noreferrer"&gt;async-graphql&lt;/a&gt;. All of that is in its own &lt;code&gt;handler.rs&lt;/code&gt; file, so it could be easily split out into a crate in the future. It's a bit messy with all the generic-handling, but if you were to couple it to your specific schema then the code would look a lot cleaner.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would you be interested in a lamedh + async-graphql crate so you don't have to write your own handler? Let me know &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/56" rel="noopener noreferrer"&gt;in the discussion thread&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The API code is fairly simple, for the most part you can just follow &lt;a href="https://async-graphql.github.io/async-graphql/en/index.html" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;. There are a few things that I struggled to find the answers to—and a few things I haven't yet solved—but generally you just start writing structs and resolver functions, and your GraphQL API comes to life.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm considering putting together a full tutorial on how to build a GraphQL API with Rust and host it with serverless technologies. It would cover auth, subscriptions via websockets, authenticating the GraphiQL UI, the works. If that's something you want, drop a note in &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/56" rel="noopener noreferrer"&gt;the discussion thread&lt;/a&gt; and tell me how much, if anything, you'd be willing to pay for such a resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a GraphQL API in Rust using AWS Lambda to host it is &lt;em&gt;way easier&lt;/em&gt; than trying to build the equivalent with OpenAPI. The main problem is the lack of an end-to-end guide covering everything you need to know. The &lt;code&gt;async-graphql&lt;/code&gt; docs are great, but terse (as are the &lt;code&gt;SQLx&lt;/code&gt; docs), so more are needed to make this setup more accessible. There are of course some major differences between GraphQL and OpenAPI (upvote &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/55" rel="noopener noreferrer"&gt;this idea&lt;/a&gt; if you want a full comparison), so you'd have to be sold on GraphQL already to use this solution. All of that said, this is definitely my favorite solution for making APIs with Rust that I've tried so far.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Was this post super helpful to you? &lt;a href="https://github.com/sponsors/dbanty?frequency=one-time&amp;amp;sponsor=dbanty" rel="noopener noreferrer"&gt;Tip me on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question or comment about this post? Leave it in the &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/56" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; thread on GitHub!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to be notified of future posts? Watch releases in &lt;a href="https://github.com/dbanty/dylananthony.com" rel="noopener noreferrer"&gt;the GitHub repo&lt;/a&gt; or &lt;a href="https://twitter.com/TBDylan" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have an idea or request for a future blog topic? Drop it in the GitHub discussions under &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;ideas&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>graphql</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Replacing FastAPI with Rust: Part 6 - AWS Lambda</title>
      <dc:creator>Dylan Anthony</dc:creator>
      <pubDate>Thu, 04 Mar 2021 00:58:32 +0000</pubDate>
      <link>https://dev.to/dbanty/replacing-fastapi-with-rust-part-6-aws-lambda-inm</link>
      <guid>https://dev.to/dbanty/replacing-fastapi-with-rust-part-6-aws-lambda-inm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cover image created by me using &lt;a href="https://www.rustacean.net" rel="noopener noreferrer"&gt;Ferris the Crab&lt;/a&gt;, &lt;a href="https://www.rust-lang.org/policies/media-guide" rel="noopener noreferrer"&gt;the Rust logo&lt;/a&gt;, and &lt;a href="https://github.com/tiangolo/fastapi" rel="noopener noreferrer"&gt;the FastAPI logo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The &lt;a href="https://dylananthony.com/posts/fastapi-rust-5-rocket-0.5" rel="noopener noreferrer"&gt;last blog post&lt;/a&gt; was a bit long, so I figured I'd take a bit of a break and tackle a shorter topic. In this post, we'll take a look at two different methods to deploy our Rocket application to AWS Lambda: the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;SAM CLI&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;If you want to follow any of the instructions below, you'll first need an AWS account to experiment on. I did this on an account where the free tier has already expired and I didn't accumulate any charges so you &lt;em&gt;should&lt;/em&gt; be able to do this for free, but I make no guarantees.&lt;/p&gt;

&lt;p&gt;Once you have your account set up, you'll need to generate some IAM credentials, install the AWS CLI, and configure it to use your credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAM CLI
&lt;/h2&gt;

&lt;p&gt;If you've followed along with previous posts, you'll know that I've been using the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;SAM CLI&lt;/a&gt; in order to test the application locally. This allows me to emulate the AWS environment for testing, which is invaluable, but it's not the only purpose of the tool. It can also be used to build and deploy the application to AWS.&lt;/p&gt;

&lt;p&gt;To start off, you'll need to do a &lt;code&gt;sam build&lt;/code&gt; before each deploy, just like was necessary before doing a &lt;code&gt;sam local start-api&lt;/code&gt; to run locally. If you've run this before with the same settings I have in &lt;a href="https://github.com/dbanty/rust-fastapi-experiments" rel="noopener noreferrer"&gt;the experiments repo&lt;/a&gt;, you'll know that it feels like it takes &lt;em&gt;forever&lt;/em&gt; because you get no feedback on the build process. There might be a way to fix this, but I haven't found it yet.&lt;/p&gt;

&lt;p&gt;After the app is finish building, you'll want to do a &lt;code&gt;sam deploy --profile &amp;lt;aws_profile_name&amp;gt; --guided&lt;/code&gt; (omit the &lt;code&gt;--profile&lt;/code&gt; if you've only got one AWS profile configured) and follow the prompts. This will create a &lt;code&gt;samconfig.toml&lt;/code&gt; file with some additional required information for SAM. In the future, to redeploy, you can omit the &lt;code&gt;--guided&lt;/code&gt; because you already have this file. Now feels like a good time to note that if you've copied the &lt;code&gt;template.yml&lt;/code&gt; from my repo previously, you'll want to update it from the one in &lt;a href="https://github.com/dbanty/rust-fastapi-experiments/tree/rocket-0.5-SAM" rel="noopener noreferrer"&gt;the SAM experiments branch&lt;/a&gt; because it was missing a required attribute previously.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sam deploy&lt;/code&gt; command is going to deploy, it's that easy! Answer any questions it has and wait for it to be done. Now you've got an API Gateway URL you can hit to talk to your API in the cloud! But wait... where's the URL?&lt;/p&gt;

&lt;p&gt;I had to do quite a lot of digging to figure out where to call my freshly hosted API. Eventually I found &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html" rel="noopener noreferrer"&gt;these docs&lt;/a&gt; which informed me the url should be something like &lt;code&gt;https://{restapi_id}.execute-api.{region}.amazonaws.com/{stage_name}/&lt;/code&gt;. The &lt;code&gt;{restapi_id}&lt;/code&gt; is replaced with an ID I found in the API Gateway console, &lt;code&gt;{region}&lt;/code&gt; is what I configured with &lt;code&gt;sam deploy --guided&lt;/code&gt;, and &lt;code&gt;{stage_name}&lt;/code&gt; appears to be &lt;code&gt;Prod&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;With that URL, I was able to test out my API and verify that everything was working. Now it was time to tear down that test infrastructure, so it didn't sit there in my account now that it was unneeded. Unfortunately there doesn't seem to be a &lt;code&gt;sam&lt;/code&gt; command for that, so I had to go into the CloudFormation console and tell it to delete everything for me. There was one slight snag with that as I had to find the S3 bucket it created and empty it myself before it would delete.&lt;/p&gt;

&lt;p&gt;Overall the &lt;code&gt;sam&lt;/code&gt; experience was &lt;em&gt;fairly&lt;/em&gt; painless. I'd say the toughest part was figuring out which URL to call once everything was deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CDK
&lt;/h2&gt;

&lt;p&gt;AWS CDK is a tool that allows you to define your AWS infrastructure in a programming language instead of using CloudFormation syntax (e.g. in &lt;code&gt;template.yml&lt;/code&gt; for &lt;code&gt;sam&lt;/code&gt;). The languages available listed in the docs are TypeScript, JavaScript, Java, Python, and C#. The CLI tool also seems to indicate that F# is an option, so that might be worth looking into if it interests you. I went with TypeScript for this project because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's a language I know.&lt;/li&gt;
&lt;li&gt;It's strongly, statically typed.&lt;/li&gt;
&lt;li&gt;The CDK CLI requires &lt;code&gt;node&lt;/code&gt; anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before I walk through how I got to a working solution (as it was fairly difficult to piece together all the answers), feel free to take a look at &lt;a href="https://github.com/dbanty/rust-fastapi-experiments/tree/rocket-0.5-CDK" rel="noopener noreferrer"&gt;the CDK branch of the experiments repo&lt;/a&gt; to see the result for yourself. The relevant bits are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Makefile&lt;/code&gt; which contains rules for building and deploying via CDK&lt;/li&gt;
&lt;li&gt;The various &lt;code&gt;.json&lt;/code&gt; files which configure the Node/TypeScript things&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;cdk&lt;/code&gt; directory which contains the actual infrastructure code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the high level process I followed to get there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html" rel="noopener noreferrer"&gt;these instructions&lt;/a&gt; to install CDK.&lt;/li&gt;
&lt;li&gt;Create a directory to keep your infrastructure code in and initialize it with cdk. I did &lt;code&gt;mkdir cdk &amp;amp;&amp;amp; cd cdk &amp;amp;&amp;amp; cdk init app --language typescript&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Delete the TypeScript handler code since we won't be needing that.&lt;/li&gt;
&lt;li&gt;Follow &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/serverless_example.html" rel="noopener noreferrer"&gt;these instructions&lt;/a&gt; to add the necessary components for Lambda and API Gateway.&lt;/li&gt;
&lt;li&gt;Take some inspiration from &lt;a href="https://dev.to/aws-builders/building-an-aws-lambda-extension-with-rust-3p81"&gt;this blog post&lt;/a&gt; for setting up Rust builds with CDK.&lt;/li&gt;
&lt;li&gt;Find and use &lt;code&gt;apigateway.LambdaRestApi&lt;/code&gt; in CDK to make the infrastructure setup way simpler.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the CDK code all set up correctly, the steps to deploy are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;cdk bootstrap --profile &amp;lt;aws_profile&amp;gt;&lt;/code&gt; to set up some basic infrastructure (you only have to do this once).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;make deploy&lt;/code&gt; which builds the binary, copies it into a new folder and names it "bootstrap", then runs &lt;code&gt;cdk deploy&lt;/code&gt;. CDK has been configured to upload this folder to Lambda to use as a runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The solution is very nice once you put all the pieces together. I strongly recommend copying most of what I have if you're going with a straight proxy (all requests from API Gateway go to one lambda handler). The clear benefits over SAM are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using a typed language instead of YAML means you get autocompletion and don't have to fumble around as much through documentation to find what you're looking for.&lt;/li&gt;
&lt;li&gt;Using normal &lt;code&gt;cargo build&lt;/code&gt; then copying over the binary means you get normal cargo outputs instead of a blank screen while building.&lt;/li&gt;
&lt;li&gt;CDK comes with a nice &lt;code&gt;cdk destroy&lt;/code&gt; command which will tear down &lt;em&gt;most&lt;/em&gt; of what it created (just not the bootstrap stuff).&lt;/li&gt;
&lt;li&gt;It's easy to break up your code into reusable components. You can even create normal packages with whatever language you selected and share those to other projects, so you don't have to copy/paste infrastructure. I'll &lt;em&gt;definitely&lt;/em&gt; be doing this in the future.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only question left to answer was how to run the function locally. Luckily AWS provides official and easy instructions for running a CDK function using SAM, and it works super easily! In my repo, I can do &lt;code&gt;make local&lt;/code&gt; which basically builds the app, runs &lt;code&gt;cdk synth &amp;gt; template.yaml&lt;/code&gt;, then runs the same &lt;code&gt;sam local start-api&lt;/code&gt; I was already doing.&lt;/p&gt;

&lt;p&gt;There is another option for running locally which you can try, but I had very little success with it. There is a project called LocalStack for emulating AWS services via Docker containers. I've used it in the past for doing integration tests with SQS, however their API Gateway support is pretty atrocious. You have to dig around in their docs for a while to find the long, custom URL you need to call which requires copying and pasting the API ID every time. All the effort didn't seem worth it to me, but if you want to try it out yourself I found &lt;a href="https://github.com/codetalkio/patterns-serverless-rust-minimal" rel="noopener noreferrer"&gt;this GitHub repo&lt;/a&gt; which should get you started in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AWS CDK definitely seems like a great option for deploying functions, as well as managing any other infrastructure you need (like maybe an RDS database?). The only real downside is that you have to add yet another tool on top of SAM and a bunch of dependencies for whatever language you choose to write in. Too bad there's no Rust CDK option... yet.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://ko-fi.com/R6R7223BQ" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fko-fi.com%2Fimg%2Fgithubbutton_sm.svg" alt="ko-fi" width="223" height="30"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question or comment about this post? Leave it in the &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/29" rel="noopener noreferrer"&gt;discussions&lt;/a&gt; thread on GitHub!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to be notified when the next part of this series is released? Watch releases in &lt;a href="https://github.com/dbanty/dylananthony.com" rel="noopener noreferrer"&gt;the GitHub repo&lt;/a&gt; or &lt;a href="https://twitter.com/TBDylan" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have an idea or request for a future blog topic? Drop it in the GitHub discussions under &lt;a href="https://github.com/dbanty/dylananthony.com/discussions/categories/ideas" rel="noopener noreferrer"&gt;ideas&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
