<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Daan Debie</title>
        <link>https://www.daan.fyi/</link>
        <description>This is a feed of all posts on the website of Daan Debie</description>
        <lastBuildDate>Thu, 06 Aug 2026 12:45:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro + feed package</generator>
        <language>en</language>
        <image>
            <title>Daan Debie</title>
            <url>https://www.daan.fyi/images/banner.png</url>
            <link>https://www.daan.fyi/</link>
        </image>
        <copyright>Copyright 2026 - Daan Debie</copyright>
        <item>
            <title><![CDATA[How the State of the World Affects My Mental Health]]></title>
            <link>https://www.daan.fyi/writings/mental-health</link>
            <guid>https://www.daan.fyi/writings/mental-health</guid>
            <pubDate>Sun, 23 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[I sometimes have a hard time coping with the ugliness of people's opinions on the internet and in the world at large.]]></description>
            <content:encoded><![CDATA[<p>Yesterday was the first day of my much-needed and much-deserved (if I may say so myself) 2-week vacation. We’re not
going anywhere. Instead, this will be a staycation in which I allow myself to indulge in all of my hobbies outside
of work, ranging from going on trips with my wife and dog, reading books, playing video games, working on
<a href="https://github.com/DonDebonair/slack-machine">side-projects</a> and generally taking life as it comes. And these
staycations are usually a great opportunity to pay my social debts as well by seeing friends and family I haven’t
seen for way too long.</p>
<p>The first day of a typical staycation for me starts with sleeping in - preferably an outrageous amount - after which
I will slowly get into some of the aforementioned activities. Yesterday started in such a way, but a couple of hours
after I got up, I was sitting on the couch bawling my eyes out, surrounded by my wife and dog who were trying to
console me.</p>
<p>This is what happened.</p>
<h2 id="keeping-up-with-current-events"><a aria-hidden="true" tabindex="-1" href="#keeping-up-with-current-events"><span class="icon icon-link"></span></a>Keeping up with Current Events</h2>
<p>One of the first things I used to do after waking up, is to open the Apollo app to see what’s happening on Reddit.
For better of for worse, through <em>/r/news</em> and <em>/r/worldnews</em>, I tried to keep myself up-to-date on what is
happening in the world. After the <a href="https://en.wikipedia.org/wiki/2023_Reddit_API_controversy">2023 Reddit API</a> I
refuse to use Reddit anymore, so I looked for other outlets to consume news and being curious about local news as
well, I ended up frequenting <a href="https://www.nu.nl/">nu.nl</a>, a Dutch news outlet.</p>
<h2 id="comment-sections-are-the-worst"><a aria-hidden="true" tabindex="-1" href="#comment-sections-are-the-worst"><span class="icon icon-link"></span></a>Comment Sections Are the Worst</h2>
<p>Everybody who’s been on the modern internet for more than 2 seconds knows this: comment sections on websites are the
worst. And social media are basically just giant comment sections without useful content (which is why I don’t have
Facebook/Instagram/Twitter accounts anymore). For some reason, I thought it would be a good idea to create an
account on nu.nl to have friendly discussions with my fellow citizens on the news of the day.</p>
<p><strong>I was dumb</strong></p>
<p>Each day I got confronted more and more by how excruciatingly unempathetic, selfish, unreasonable and unkind people
have become. You used to be able to explain this by virtue of
<a href="https://knowyourmeme.com/memes/greater-internet-fuckwad-theory">The Greater Internet Fuckwad Theory</a>, which states:</p>
<blockquote>
<p>Normal Person + Anonymity + Audience = Total Fuckwad</p>
</blockquote>
<p><em>- John Gabriel (2004)</em></p>
<p>But a bunch of things have changed to make this even worse:</p>
<ul>
<li>Politicians have become more brazen and empowered to spread un-truths, ignore science and generally walk back on
hard-won freedoms and improvements to the fabric of society</li>
<li>People, feeling unhappy about their lot in life and feeling empowered by those politicians, don’t even care about
anonimity anymore. People are openly being dicks wherever you look</li>
</ul>
<aside class="sidenote sidenote-info" data-astro-cid-cl5kivku><div class="sidenote-header" data-astro-cid-cl5kivku> <span class="sidenote-icon" data-astro-cid-cl5kivku><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" aria-hidden="true" focusable="false"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="currentColor" d="M11 7h2v2h-2V7zm0 4h2v6h-2v-6zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg></span> <h2 class="sidenote-title" data-astro-cid-cl5kivku>Dutch comment sections are even worse because of the 'polite form'</h2></div><p>I have a personal pet peeve with Dutch comments on the internet. The Dutch language has 2 different pronouns to address
the person you’re speaking to. "Je" is the common form and "U" is the polite form.</p><p>The polite form is getting less and less use these days. But what I often see happening in Dutch comments on websites,
is that people use "U" as some form of veneer to make their comment seem "normal" while trying to hide the uglyness of
what they’re actually saying.</p><p>It will usually happen in reponse to someone being <em>actually</em> nice and trying to address some injustice, where
the person responding will then "politely" tell them that other people (or science) are not important.</p></aside>
<h2 id="what-triggered-me"><a aria-hidden="true" tabindex="-1" href="#what-triggered-me"><span class="icon icon-link"></span></a>What Triggered Me</h2>
<p>Yesterday, I read 2 news articles that in particular triggered emotional distress with me - and especially the
comments underneath the reporting on nu.nl on those topics:</p>
<ul>
<li><a href="https://www.theguardian.com/world/2023/jul/22/malaysia-festival-halted-matty-healy-1975-criticises-anti-lgbtq-laws">Malaysia bans the 1975 after Matty Healy defies anti-LGBTQ+ laws with stage kiss</a>
(<a href="https://www.nu.nl/muziek/6273484/maleisie-breekt-festival-af-na-zoen-tussen-frontman-en-bassist-the-1975.html">article on nu.nl</a>)</li>
<li><a href="https://edition.cnn.com/2023/07/21/europe/italy-lesbian-couples-birth-certificates-scli-intl/index.html">Italy starts removing lesbian mothers’ names from children’s birth certificates</a>
(<a href="https://www.nu.nl/buitenland/6273432/italie-schrapt-namen-niet-biologische-moeders-met-lhbtiq-relatie-uit-geboorteakten.html">article on nu.nl</a>)</li>
</ul>
<p>I care deeply about LGBTQI+ issues, not in the least because my father is gay, I have a transgender niece and I have
many people in my social circles that identify as LGBTQI+. And what makes me so sad is that - despite years of
fighting for LGBTQI+ rights - I’m noticing a clear movement <em>backwards</em> on these topics. People seem to have this
idea that the fight for equality, equity, diversity and inclusion is over now and they’re using this to argue we should
sweep any expression of "otherness" under the rug.</p>
<blockquote>
<p>I’m not against gay people, but why do you have to kiss in public?</p>
</blockquote>
<p><em>- Paraphrased from a comment under an <a href="https://www.nu.nl/pride/6273006/amsterdamse-pride-dit-jaar-dubbel-gevierd-dit-moet-je-erover-weten.html">article about the Amsterdam Pride event</a></em></p>
<p><strong>If you cannot accept self-expression of other people different than you, you are effectively <em>against</em> those
people being different</strong></p>
<h2 id="the-cult-of-i"><a aria-hidden="true" tabindex="-1" href="#the-cult-of-i"><span class="icon icon-link"></span></a>The Cult of I</h2>
<p>The sentiment of people towards issues I consider to be <em>wrong</em> in the world - and that I naively believed were
<em>generally</em> considered to be wrong - can be summed up by <strong>me, me me</strong>. This sentiment pervades both on personal and
societal level:</p>
<ul>
<li>People increasingly care about only themselves, and want others - including the government - to optimize for
<em>their</em> happiness and any cost to the happiness of others is considered acceptable. The most notable example is
climate change: even if people are willing to concede it’s real, a large group of people seem to be <em>completely</em>
unwilling to accept even the smallest changes to their life-style for the greater good and the future of humanity
(which includes their own children, grand-children etc.)</li>
<li>The previously common attitude in modern society to <em>live and let live</em> has been turned on its head. Instead of
letting people that are "different" live their lives in a way they want, we’re seeing an increasing acceptance of
letting bad actors suppress people that are "different". In a way, <em>live and let live</em> applies only to the
oppressors now. We <em>let</em> them get on with it.</li>
</ul>
<span class="small" data-astro-cid-z4i2txjc>I use quotes with "different" because I happen to think that <em>everybody</em> is different in some way, including
myself</span>
<p>The first point is not only prevalent on places like Twitter, Facebook, Reddit and the comment sections of the
average news outlet. Even LinkedIn - a professional networking website - is rife with people that love to
"challenge" ideas to improve the global optimum just so they can optimize for their own local happiness. I lament the
passing of the days when LinkedIn was about professionals discussing professional things professionally with each
other.</p>
<span class="small" data-astro-cid-z4i2txjc>But I’m happy to contribute to the downfall of professionalism on LinkedIn by posting this opinion piece once
I’m done writing it</span>
<p>It’s the second point that makes my blood truly boil, especially because the acceptance of oppression is done under
the guise of <em>reasonableness</em>. In the comment section on the article of The 1975 being banned from Malaysia, a
common sentiment could be summed up as: <em>you go to their country, you follow their laws</em>. This completely ignores
that there is such a thing as <strong>Universal Human Rights</strong>.</p>
<p>When you accept oppression - even under the guise of "law" - you let society slide downwards on a slippery slope and it
will not end well. I truly don’t think I’m being hyperbolic when I say this kind of acceptance is pretty much
<em>exactly</em> what happened in Europe by the end of the 1930ies.</p>
<p><strong>We should <em>always</em> keep fighting for the rights of our fellow citizens and <em>borders</em> do not matter in that regard</strong></p>
<h2 id="how-does-this-affect-my-mental-health"><a aria-hidden="true" tabindex="-1" href="#how-does-this-affect-my-mental-health"><span class="icon icon-link"></span></a>How Does This Affect My Mental Health</h2>
<p>Society turning "harder" and "colder" by the day, causes depression in me. Reading how unreasonable and intolerant
my fellow citizens are, makes me immensely sad. It also makes me feel <strong>lonely</strong>, because it sometimes feels I’m the
only one who still cares about their fellow citizens that are not friends or family.</p>
<p>To be honest, I had some pretty dark thoughts yesterday.</p>
<blockquote>
<p>If only the whole of humanity would just die, it would be better for everyone, especially the rest of the species
we share our planet with</p>
</blockquote>
<p><em>- Me, after reading one inane comment too many</em></p>
<p>I feel a strong juxtaposition of the happiness induced by my wife, my pets, my friends and <a href="https://source.ag/">my job</a>
and my increasing unhappiness with the world I live in. I’m sure other people have this as well, and part of me
hopes that by writing this blog post, I’ll encounter people saying "Hey, I feel this too!" which would make me feel
less lonely. But it is honestly increasinly hard to deal with.</p>
<h2 id="what-will-i-do-about-it"><a aria-hidden="true" tabindex="-1" href="#what-will-i-do-about-it"><span class="icon icon-link"></span></a>What Will I Do about It?</h2>
<p>I resolve to talk more about these feelings I have with others. Maybe we can form a counter-movement. Maybe we can
rebel against the tide of "not caring".</p>
<p>I considered not reading these comment sections anymore. I’m not sure if I should or shouldn’t, because I also think
that if I can turn only one mind towards positivity and caring about others, I will have "won" a little. And not
speaking up is exactly what I’m trying to rebel against here.</p>
<h2 id="what-should-we-do-about-it"><a aria-hidden="true" tabindex="-1" href="#what-should-we-do-about-it"><span class="icon icon-link"></span></a>What Should We Do about It?</h2>
<p>It is <em>really simple</em>. We (Society) should strive to <a href="https://knowyourmeme.com/memes/wheatons-law"><strong>not be a dick</strong></a>.
You should care about your fellow citizens. We should agree to uphold
<a href="https://www.un.org/en/about-us/universal-declaration-of-human-rights">Universal Human Rights</a>.</p>
<p>Here is one of its most simple tennets and it is to me the <em>core</em> of what each person is entitled to:</p>
<blockquote>
<p>Everyone is entitled to all the rights and freedoms set forth in this Declaration, without distinction of any kind,
such as race, colour, sex, language, religion, political or other opinion, national or social origin, property,
birth or other status. Furthermore, no distinction shall be made on the basis of the political, jurisdictional or
international status of the country or territory to which a person belongs, whether it be independent, trust,
non-self-governing or under any other limitation of sovereignty.</p>
</blockquote>
<p><em>- Article 2 of the Universal Declaration of Human Rights</em></p>
<p>One important thing to note here, is that this only works if freedom of one group is not abused to limit the
freedoms of another group. This means for example that <strong>you cannot limit the rights of LGBTQI+ people because you
want to express your own religion</strong>.</p>
<p>Furthermore, I think that the time of being accepting of <em>all opinions</em> should come to an end. Any opinion that
infringes on universal human rights, should not be tolerated.
<a href="https://en.wikipedia.org/wiki/Paradox_of_tolerance">If we tolerate <em>everything</em>, we tolerate <strong>nothing</strong></a>.</p>
<p>It is time that news outlets and social media - such as nu.nl - take responsibility and limit the ability of people
to express thoughts that lead to oppression. It is for example <strong>not ok</strong> to express your "opinion" that same-sex
couples kissing is "icky" and should not be done in public. And news outlets and social media should not let you
express such opinions in public.</p>
<p>I personally would even go so far to state that scientifically proven facts should not be allowed to be contradicted
without proper evidence to the contrary. Everyone should be able to challenge the status quo, but there is the
<a href="https://en.wikipedia.org/wiki/Scientific_method">scientific method</a> you can follow to do this in a rigorous and
fair way.</p>
<h2 id="ps-im-back"><a aria-hidden="true" tabindex="-1" href="#ps-im-back"><span class="icon icon-link"></span></a>PS: I’m Back?</h2>
<p>When I started the new version of my blog, I promised that I would write more. That hasn’t really materialized and I
don’t know if it will. I felt so strongly about the topic of this blog post, that I wanted to write down my thoughts.
Let’s see if that will happen more often.</p>
<p>Peace!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS IAM Demystified]]></title>
            <link>https://www.daan.fyi/writings/iam</link>
            <guid>https://www.daan.fyi/writings/iam</guid>
            <pubDate>Mon, 21 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[AWS Identity and Access Management (IAM) provides a way to configure who can do what in AWS Accounts. It's powerful but also hard to understand. This post explains the basics.]]></description>
            <content:encoded><![CDATA[<p>All access management in AWS is done through <a href="https://aws.amazon.com/iam/"><strong>AWS Identity and Access Management</strong></a>
(or <em>IAM</em> in short). IAM provides a way to configure <em>who</em> can do <em>what</em> in an AWS account.
The "who" in this case can be humans, machines/software or other AWS services.</p>
<p>IAM is quite complex and there are several concepts you need to understand in order to know how to
give people, software and machines the right access to the right resources in your AWS Accounts.
This can become complicated quite fast if you’re managing multiple AWS Accounts with many different
resources and services, as explained in the <a href="https://www.daan.fyi/aws-multi-account">previous post in this series</a>.</p>
<p>In this blog post I’m going to attempt to explain the terminology in a clear and concise way. Over
the past months, we worked hard at <a href="https://source.ag/">Source.ag</a> to create a good setup for our
AWS-hosted infrastructure. I feel that this gave me a solid understanding of how IAM works
(among other things). This blog post will help me personally as a kind of <em>cheat sheet</em> for later
recall and hopefully be of use to others to whom these concepts are still new.</p>
<h2 id="accounts-and-resources"><a aria-hidden="true" tabindex="-1" href="#accounts-and-resources"><span class="icon icon-link"></span></a>Accounts and Resources</h2>
<p>A <strong>Resource</strong> in AWS is basically any piece of infrastructure or any AWS service you can pay for
and use. This also represents the things you want to control access to. Some examples of this:
<a href="https://aws.amazon.com/ec2/"><strong>EC2</strong></a> servers and <a href="https://aws.amazon.com/s3/"><strong>S3</strong></a> buckets.</p>
<p>For all these resources, you can control who or what can access them and to what extent.</p>
<p>An <strong>Account</strong> in AWS is a container for resources. So it’s <strong><em>not</em> a representation of a human</strong>
using AWS services. In that sense, it’s quite different from the typical definition of an account
in online services like Twitter or Facebook, where accounts are digital extensions of human beings.</p>
<p>An AWS Account is used to <strong>create, manage and pay for resources</strong>. It can be similar to an
<em>environment</em> or <em>project</em>, with servers, databases, S3 buckets etc.</p>
<p>Even though an account does not represent a user, it still has — confusingly — a unique email
address attached to it and a password to log in. This login represents the
<a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html"><em>AWS account root user</em></a> of
an AWS account.</p>
<aside class="sidenote sidenote-danger" data-astro-cid-cl5kivku><div class="sidenote-header" data-astro-cid-cl5kivku> <span class="sidenote-icon" data-astro-cid-cl5kivku><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" aria-hidden="true" focusable="false"><path fill="none" d="M0 0h24v24H0z"/><path fill="currentColor" d="M12 12.9l-2.13 2.09c-.56.56-.87 1.29-.87 2.07C9 18.68 10.35 20 12 20s3-1.32 3-2.94c0-.78-.31-1.52-.87-2.07L12 12.9z"/><path fill="currentColor" d="M16 6l-.44.55C14.38 8.02 12 7.19 12 5.3V2S4 6 4 13c0 2.92 1.56 5.47 3.89 6.86-.56-.79-.89-1.76-.89-2.8 0-1.32.52-2.56 1.47-3.5L12 10.1l3.53 3.47c.95.93 1.47 2.17 1.47 3.5 0 1.02-.31 1.96-.85 2.75 1.89-1.15 3.29-3.06 3.71-5.3.66-3.55-1.07-6.9-3.86-8.52z"/></svg></span> <h2 class="sidenote-title" data-astro-cid-cl5kivku>AWS Account root users should not be regularly used!</h2></div><p>You could use the AWS Account root user to do <em>anything</em> you want in an AWS Account. It is a bad
habit to do so. This <strong>root</strong> user is basically the keys to the kingdom and as such, should be
locked away securely and not be used for everyday tasks.</p><p>Instead, you should create separate IAM and/or AWS SSO users for normal tasks, even for
administrator tasks.</p></aside>
<h2 id="identities-in-aws"><a aria-hidden="true" tabindex="-1" href="#identities-in-aws"><span class="icon icon-link"></span></a>Identities in AWS</h2>
<p>So if <em>Accounts</em> in AWS do not represent human users, how is <strong>identity</strong> established so that
access can be granted to resources in AWS Accounts?</p>
<p>This is done through <strong>Users, Roles and Groups</strong>. All three are a form of <strong>Identity</strong> in AWS.
An Identity is something that can be granted access to an AWS service or resource.</p>
<h3 id="iam-users"><a aria-hidden="true" tabindex="-1" href="#iam-users"><span class="icon icon-link"></span></a>IAM Users</h3>
<p>An <strong>IAM User</strong> represents a human that can log into AWS and/or leverage the AWS API to use
AWS services and access resources on AWS. An IAM User has 2 sets of credentials:</p>
<ul>
<li><strong>Username/Password</strong>: used to log into the web-based <a href="https://aws.amazon.com/console/"><em>AWS Management Console</em></a></li>
<li><strong>Access Key/Access Key Secret</strong>: used to access AWS programmatically through the API and/or command line</li>
</ul>
<p>Two key things to understand about IAM users:</p>
<ul>
<li>a regular IAM User does <strong>not</strong> have an email address attached to it</li>
<li>IAM Users are created <em>within a single AWS Account</em> and (usually) only have access to resources
within that account</li>
</ul>
<p>As we’ll see later, there are special kinds of users you can create that <em>do have an email address</em>
and can access resources in multiple AWS Accounts. These are created through AWS SSO.</p>
<p>IAM Users can have permissions (policies, see below) that dictate what they can and cannot do on
AWS. These permissions are used to allow/deny access to AWS services and resources.</p>
<p>Users usually represent humans but can also represent software and machines <em>outside of AWS</em> that
you want to give programmatic access to certain AWS services and resources through the AWS API.
If you want to grant software running <em>inside</em> AWS access to other AWS services and resources, you
can use <strong>Roles</strong>.</p>
<h3 id="iam-roles"><a aria-hidden="true" tabindex="-1" href="#iam-roles"><span class="icon icon-link"></span></a>IAM Roles</h3>
<p>A <strong>Role</strong> in IAM is similar to a User in that it is an Identity with permissions that determine
what it can and cannot do within AWS. The difference with a User is that a Role is not tied to a
specific human or machine. Instead, a role can be <strong>assumed</strong> by users or AWS services to
temporarily gain access to certain AWS services and/or resources.</p>
<p>Roles do not have permanent login/API credentials. Instead, a role provides temporary credentials
when the role is assumed.</p>
<h3 id="iam-groups"><a aria-hidden="true" tabindex="-1" href="#iam-groups"><span class="icon icon-link"></span></a>IAM Groups</h3>
<p>In IAM, <strong>Groups</strong> are used to group Users together so the same permissions (<em>policies</em>, see below)
can be applied to multiple Users at once.</p>
<h2 id="granting-and-denying-access-policies"><a aria-hidden="true" tabindex="-1" href="#granting-and-denying-access-policies"><span class="icon icon-link"></span></a>Granting and Denying Access: Policies</h2>
<p>Now that we have established that we have <em>Resources</em><span class="asterisk" title="Infrastructure you can rent from AWS" data-astro-cid-q77q622o><strong data-astro-cid-q77q622o>*</strong></span>
and <em>Identities</em><span class="asterisk" title="Users, Roles and Groups" data-astro-cid-q77q622o><strong data-astro-cid-q77q622o>*</strong></span> that can potentially access those
Resources, how exactly do we determine <em>who</em> can access <em>what</em>?</p>
<p>This is done through <strong>Policies</strong>. In IAM, Policies are basically <em>permissions</em> on <em>resources</em></p>
<p>A policy defines access (or denial) to an AWS service and/or resource. There are 2 types of policies:</p>
<ul>
<li><strong>Identity-based policy</strong>: this policy is attached to a certain <em>Identity</em> to allow/deny this identity access to one or more Resources</li>
<li><strong>Resource-based policy</strong>: this policy is attached to a <em>Resource</em> to define access to that Resource</li>
</ul>
<p>A policy contains the following elements:</p>
<ul>
<li><strong>Effect</strong>: should we <em>allow</em> or <em>deny</em> something?</li>
<li><strong>Resource</strong>: what do we want to control access to?</li>
<li><strong>Action</strong>: what action is allowed/denied?</li>
<li><strong>Principal</strong>: who should be allowed/denied access?</li>
</ul>
<p>The <em>Principal</em> is only specified in the case of Resource-based policies. That makes sense if you
think about it, because in the case of Identity-based policies, the Principal is the Identity itself
the policy is attached to!</p>
<h3 id="example-identity-based-policy"><a aria-hidden="true" tabindex="-1" href="#example-identity-based-policy"><span class="icon icon-link"></span></a>Example: Identity-Based Policy</h3>
<p>Let’s say that we want to give IAM User <em>kiara</em> access to read all files in the <code>confidential-data</code>
S3 bucket. What would the Effect, Resource and Action look like for our policy? The policy would
look something like this:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">{</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">  "</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">Effect</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> "</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">Allow</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">  "</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">Action</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> [</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    "</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">s3:List*</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    "</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">s3:Get*</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">  ],</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">  "</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">Resource</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> [</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    "</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">arn:aws:s3:::confidential-data</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    "</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">arn:aws:s3:::confidential-data/*</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">"</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">  ]</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span></span></code></pre>
<p>This policy would then be attached to the IAM User <em>kiara</em> to grant her access.</p>
<h3 id="its-a-matter-of-principal"><a aria-hidden="true" tabindex="-1" href="#its-a-matter-of-principal"><span class="icon icon-link"></span></a>It’s a Matter of Principal</h3>
<p>As mentioned before, for Identity-based policies, you don’t need to specify a Principal. But what
exactly <em>is</em> a Principal? Is it the same as an <em>Identity</em>? It turns out it’s more than that:</p>
<p>A Principal is an AWS identity <strong>or Service</strong>. To understand why services can be used as Principal,
we have to understand where Principals are specified, which is in 2 places:</p>
<ul>
<li>In a Resource-based policy, the principal determines <em>who</em> can access the resource the policy is attached to</li>
<li>In a Role, the principal determines who (what Identity) can assume the role</li>
</ul>
<p>When specifying a Principal in a Role, we can say that instead of allowing a regular (human) User
to assume the Role, we can allow an AWS Service (e.g. AWS Lambda) to assume the role for us. An
example of how this can be useful: we could allow a function run on AWS Lambda to send a message on
an SNS topic for us whenever something important happens during its execution. To do this, we would
create a special Role for this Lambda function with a Policy attached that would allow this Role
to publish messages on an SNS topic. Then we would allow AWS Lambda to assume this Role.</p>
<h3 id="resource-based-policies"><a aria-hidden="true" tabindex="-1" href="#resource-based-policies"><span class="icon icon-link"></span></a>Resource-Based Policies</h3>
<p>What is perhaps counter-intuitive, is that <em>Identity-based policies</em> are more common than
<em>Resource-based policies</em> for defining access to resources. The common way of doing things, is to
attach policies to Identities to specify what <em>Resources</em> they have access to and in what ways.</p>
<p>Part of the reason is that Resource-based policies are not supported by all services and
resource-types in AWS, whereas you can use any Resource identifier in an Identity-based polity.</p>
<p><strong>So in what situations are Resource-based policies being used?</strong></p>
<p>Resource-based policies are often used when you want to grant an Identity <strong>outside your AWS
account</strong> access to a Resource in your AWS account. To understand how this works, let’s assume the
following scenario:</p>
<ul>
<li>There are 2 AWS Accounts: Account A (managed by Company A) and Account B (managed by Company B)</li>
<li>Account A has a special Resource: S3 bucket <code>secret-stuff</code></li>
<li>Company A wants to grant Company B access to the <code>secret-stuff</code> bucket</li>
<li>Company B decides that they want to use User X to access the <code>secret-stuff</code> bucket that Company A
as granted them access to</li>
</ul>
<p>To make this happen you have to do 2 things:</p>
<ol>
<li><strong>Allow access from AWS Account B to a specific Resource</strong> (the <code>secret-stuff</code> bucket): on the
aforementioned S3 bucket, we would create a <em>Resource-based policy</em> that has <em>AWS Account B</em> as
principal</li>
<li><strong>Allow access from a specific Identity in Account B to a specific Resource in Account A</strong>: on
the <em>IAM User X</em> that we want to grant access to, we would create an <em>Identity-based policy</em> to
grant access specifically to the <code>secret-stuff</code> bucket, which happens to live in AWS Account A.</li>
</ol>
<p>What is interesting here, is that Company A would have no say in exactly what <em>Identity</em> is used
by Company B — in AWS Account B — to access their resource. The only thing they would control, is
the fact that AWS Account B can access their resource in AWS Account A. It is Account B where it is
determined exactly what Identity gets to access the <code>secret-stuff</code> bucket.</p>
<h2 id="iam-with-multiple-aws-accounts-organizations"><a aria-hidden="true" tabindex="-1" href="#iam-with-multiple-aws-accounts-organizations"><span class="icon icon-link"></span></a>IAM with Multiple AWS Accounts: Organizations</h2>
<p>As explained in the <a href="https://www.daan.fyi/aws-multi-account">previous blog post</a>, you can leverage AWS Organizations to
group multiple AWS Accounts together to consolidate billing but also access control.</p>
<p>How does IAM work in the context of AWS Organizations? This happens through 2 additional concepts:</p>
<ul>
<li>AWS SSO Users & Groups</li>
<li>Permission sets</li>
</ul>
<h3 id="aws-sso"><a aria-hidden="true" tabindex="-1" href="#aws-sso"><span class="icon icon-link"></span></a>AWS SSO</h3>
<p>AWS Single Sign-on lets you define Users and Groups in a central place — usually in the
<em>management account</em> of your AWS Organization — and control access to resources in all AWS Accounts
in an Organization for those Users and Groups.</p>
<p><strong>SSO Users</strong> have a few fundamental differences compared to regular IAM users:</p>
<ul>
<li>SSO Users have an email address to identify them</li>
<li>SSO Users usually have access to resources in <em>multiple</em> AWS Accounts</li>
<li>SSO Users don’t have (Identity-based) policies attached directly to them</li>
</ul>
<p><strong>SSO Groups</strong> are similar to IAM Groups in that they’re used to group together Users so that the
same policies can be applied to all users within a group. A key difference here is that — just
like with SSO Users — policies are not attached to SSO Groups directly.</p>
<p>If you cannot attach policies to SSO Users & Groups directly, how can you allow those Users & Groups
access to resources in the AWS Accounts in your Organization? This is done through
<strong>Permission Sets</strong>.</p>
<h3 id="permission-sets"><a aria-hidden="true" tabindex="-1" href="#permission-sets"><span class="icon icon-link"></span></a>Permission Sets</h3>
<p>Permission Sets are basically the AWS SSO alternative to regular IAM Policies. They are set up in a
very similar way — with Effects, Actions and Resources. The big difference is that Permission Sets
are not applied to an SSO User/Group alone, but they are applied in combination with an AWS Account.</p>
<p><strong>A Permission Set is applied to an SSO User/Group for a <em>specific AWS Account</em></strong></p>
<p>This means that when you apply a Permission Set to an SSO User/Group, you choose an AWS Account so
that the policies defined in the Permission Set are applied to the SSO User/Group <em>for the selected
AWS Account</em>.</p>
<p>Example: let’s say you have created a Permission Set that allows read-only access to all resources.
Now you can grant <code>kiara@examplecorp.com</code> read access to all resources in Account A in your
Organization by applying the aforementioned Permission Set to <code>kiara@examplecorp.com</code> <em>specifically</em>
for Account A.</p>
<p>Permission Sets encourage reuse of similar policies across AWS Accounts and SSO Users.</p>
<h3 id="how-aws-applies-access-control-in-sso"><a aria-hidden="true" tabindex="-1" href="#how-aws-applies-access-control-in-sso"><span class="icon icon-link"></span></a>How AWS Applies Access Control in SSO</h3>
<p>If you apply a Permission Set to an SSO User for a specific AWS Account, how does that work
internally? In the end, AWS will still use IAM to do the actual access management. For each
Permission Set you apply to a certain AWS Account — for 1 or more SSO Users/Groups — AWS will
create a dedicated Role in that AWS Account. Let’s say that in the example above where we want to
grant <em>read-only</em> access, we called the Permission Set <code>ReadOnlyPermissions</code>. When we apply this
Permission Set to Account A for <code>kiara@examplecorp.com</code>, AWS will do 3 things:</p>
<ul>
<li>It will create a Role in Account A based on the <code>ReadOnlyPermissions</code> Permission Set</li>
<li>It will attach policies to that Role according to the policies defined in the <code>ReadOnlyPermissions</code>
Permission Set</li>
<li>It will add <code>kiara@examplecorp.com</code> as Principal to the created Role so she can assume that Role
in Account A</li>
</ul>
<p>Now when <code>kiara@examplecorp.com</code> logs into AWS using SSO, she will be presented with a choice to
assume the <code>ReadOnlyPermissions</code> Role in Account A — next to all the other Roles she can assume
in the same or other AWS Accounts in the Organization, all based on the Permission Sets applied
to her for specific Accounts.</p>
<h2 id="conclusion"><a aria-hidden="true" tabindex="-1" href="#conclusion"><span class="icon icon-link"></span></a>Conclusion</h2>
<p>When I started writing this post, I hoped it would be concise. I guess I failed a bit on that
account, which just goes to show how complex AWS IAM really is. I still hope that this gives you an
overview that is easier to digest than reading through pages of AWS documentation.</p>
<hr/>
<p><em>Thanks to <a href="https://twitter.com/djvdorp">Dan</a> for proofreading this yet again! ❤️</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setting up an AWS Multi-Account Strategy for Fun nor Profit]]></title>
            <link>https://www.daan.fyi/writings/aws-multi-account</link>
            <guid>https://www.daan.fyi/writings/aws-multi-account</guid>
            <pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Using multiple AWS accounts is not fun, nor is it profitable, but it can help you set up your infrastructure in a more secure way. It also helps with keeping your sanity as your company/team grows in size.]]></description>
            <content:encoded><![CDATA[<p>Don’t you just love the freedom of working at an early stage startup, with a relentless focus on
product and a blatant disregard for any non-functional distractions? I do.</p>
<p>When I started working at <a href="https://source.ag">Source</a> about 9 months ago, it was just me — the
first employee — and the 2 founders. The only code written, were a couple of demos for investor
pitches which we threw away quickly. I was in the luxurious position to be able to start building
our product <em>from scratch</em>. I deployed <em>everything</em> I built into <strong>a single AWS root account</strong>.</p>
<p>Fast-forward to today, and we’re with more than 30 people — most of whom are software engineers
and data scientists writing and deploying code. In December, we finished migrating all of our
infrastructure to a <strong>multi-account setup</strong> in AWS. In this blog post I want to explain why we
did this and what our multi-account setup looks like.</p>
<img src="https://www.daan.fyi/_astro/butwhy.DkAFdwp1_2dgW2l.webp" alt="Buy why?!" loading="lazy" decoding="async" width="729" height="393">
<h2 id="when-the-cracks-start-showing"><a aria-hidden="true" tabindex="-1" href="#when-the-cracks-start-showing"><span class="icon icon-link"></span></a>When the Cracks Start Showing</h2>
<p>As mentioned, when we started everything was deployed into a single AWS account.
When the moment came that we wanted to have separate development and production environments, I
created 2 VPCs in the same account and deployed an identical setup in both, each consisting of:</p>
<ul>
<li>An ECS cluster for our backend services</li>
<li>A PostgreSQL database on RDS for persistence</li>
<li>Some S3 buckets for storing unstructured data</li>
<li>Some Lambdas for data processing.</li>
</ul>
<p>It looked something like this:</p>
<img src="https://www.daan.fyi/_astro/aws-single-account.BuBX5H1g_12wAnV.webp" alt="All AWS resources in 1 account" loading="lazy" decoding="async" width="2000" height="1069">
<p>I deployed our applications and services (except for our frontend, which is deployed on Vercel) in
two identical VPCs — still living in the same AWS account — and called it a day.</p>
<hr/>
<p>When we started building our team and the first software engineers and data scientists joined, I
created IAM users for everyone and solved access control by defining groups with specific IAM
policies (permissions) attached to them and adding the IAM users to those groups. The more people
joined the team, the more diverse the access requirements became, for example:</p>
<ul>
<li>A data scientist does not need access to the same things as a software engineer</li>
<li>A junior person does not need the same <em>level</em> of access as a senior person</li>
</ul>
<p>And there are many more subtle differences in the kind of access to infrastructure people need and
should have.</p>
<h3 id="single-account-problems-in-a-nutshell"><a aria-hidden="true" tabindex="-1" href="#single-account-problems-in-a-nutshell"><span class="icon icon-link"></span></a>Single-Account Problems in a Nutshell</h3>
<p>In the end, our single-account setup made it difficult to build a secure and resilient
infrastructure:</p>
<ul>
<li><strong>It is less secure</strong>. If someone gains access to this one account, they have
access to <em>everything</em>. It’s hard to separate resources for different environments
(e.g. production, development) and treat these resources differently security wise.</li>
<li><strong>It is less safe and resilient</strong>. If something goes wrong with one environment (e.g. development),
it’s easy to affect services in other environments (e.g. production) when they’re in the same
account. They get caught in the <em>blast radius</em></li>
<li><strong>It’s hard to maintain</strong>. All AWS resources live in 1 account so when different users need
different levels of access, this needs to be controlled on a per-resource level, which is hard to
configure and keep track of</li>
<li><strong>It’s hard to control spend in this way</strong>. Once certain access is granted to create new resources,
there is no limit to how many resources can be created and what services can be used</li>
</ul>
<p>Many of these problems <em>can</em> actually be solved — even in a single-account setup — by "properly"
tagging all your AWS resources and creating elaborate IAM policies for users and resources based on
those tags. But such a tagging strategy will quickly become really complex and will cost a lot of
time to maintain. Needless to say that it’s quite error-prone as well.</p>
<p>Aside from the security and safety implications, our setup had some other problems:</p>
<ul>
<li>All IAM Users were created and managed through CDK/CloudFormation, because we love <em>Infrastructure
as Code</em>. This is problematic:
<ul>
<li>It makes it possible for anyone with access to CloudFormation to see all Access Keys + Secrets</li>
<li>It’s not easy to scale, because we now need to manually deploy a CDK stack when new users need
to be added</li>
<li>It’s hard to broadly define policies for different access patterns in this way</li>
</ul>
</li>
<li>Users have to use a separate set of credentials for accessing AWS instead of using an existing
login (i.e. their Google Workspace account)</li>
</ul>
<h2 id="there-is-a-better-way"><a aria-hidden="true" tabindex="-1" href="#there-is-a-better-way"><span class="icon icon-link"></span></a>There Is a Better Way</h2>
<p>When I was researching a way forward, I found <a href="https://aws.amazon.com/blogs/mt/governance-risk-and-compliance-when-establishing-your-cloud-presence/">many</a>
<a href="https://www.parkmycloud.com/blog/aws-multi-account-strategy/">blog</a> <a href="https://medium.com/@mailyashacharya/aws-account-strategy-for-every-business-22aad27b7fa1">posts</a>
<a href="https://medium.com/cloudpegboard/aws-multi-account-management-has-come-of-age-510ddf33f9d3">extolling the virtues</a>
of putting your infrastructure into multiple AWS accounts. But before getting a better
understanding of how this would work, I had some reservations about using multiple AWS accounts:</p>
<ul>
<li>Do I need to create multiple IAM users for each person, so they can log in to multiple accounts?</li>
<li>How do we control costs? Do I need to keep track of different bills for each account?</li>
<li>How can I share resources, such as reusable Docker images or shared data in an S3 bucket?</li>
</ul>
<p>AWS offers a nice solution to these problems (and others) in the form of <a href="https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/core-concepts.html#aws-organizations"><strong>AWS Organizations</strong></a></p>
<p>According to AWS:</p>
<blockquote>
<p>An organization is an entity that you create to consolidate a collection of accounts so that you
can administer them as a single unit.</p>
</blockquote>
<img src="https://www.daan.fyi/_astro/whatdoesthatevenmean.BnDLAMU-_ZVgX4C.webp" alt="What does that even mean?!" loading="lazy" decoding="async" width="300" height="300">
<p>AWS Organizations lets you organize a bunch of related AWS accounts in a hierarchical, tree-like
structure. It will let you manage all those accounts from one central <em>management account</em>. With
<em>Consolidated Billing</em>, all bills from all member accounts will be aggregated to the management
account where you can pay them as one.</p>
<p>Arguably the most powerful feature of AWS Organizations is that you can group the member accounts
together in <em>Organizational Units</em> (OUs) and apply common security policies to those. This lets you
define permissions and control access in broad strokes — and from a central place — while keeping
the natural separation of concerns that comes from using separate AWS accounts to organize your
infrastructure.</p>
<h3 id="easy-user-management-using-aws-sso"><a aria-hidden="true" tabindex="-1" href="#easy-user-management-using-aws-sso"><span class="icon icon-link"></span></a>Easy User Management Using AWS SSO</h3>
<p>When it comes to user management, AWS Organizations offers the convenience of letting users log in
with <em>one</em> user account (not to be confused with AWS account), using
<a href="https://aws.amazon.com/single-sign-on/"><em>AWS Single Sign-on</em></a>. AWS SSO lets you create users in
the <em>root account</em> (also known as <em>management account</em>) of your organization and
give them login credentials within AWS itself or link them to an external <em>identity provider</em>
like Google or Active Directory. You can then give these users access to specific AWS accounts within
your AWS Organization. Users can be assigned specific permissions within these accounts.
<span class="small" data-astro-cid-z4i2txjc>(I will explain more on how this works in a later blog post)</span></p>
<p>This has several advantages compared to using regular IAM users:</p>
<ul>
<li>Users only need <em>one</em> set of credentials to log into multiple AWS Accounts</li>
<li>Everyone can log in using credentials they already use on a daily basis (Google Workspace
credentials in our case)</li>
<li>There is no more need to create and manage IAM users through CDK</li>
<li>There is no credential leakage through CloudFormation</li>
<li>We can potentially automate user creation by programmatically syncing AWS users and Google
Workspace users</li>
</ul>
<h2 id="what-does-a-good-aws-organization-setup-look-like"><a aria-hidden="true" tabindex="-1" href="#what-does-a-good-aws-organization-setup-look-like"><span class="icon icon-link"></span></a>What Does a Good AWS Organization Setup Look like?</h2>
<p>Well, I don’t know. But I do know what works for us <span class="small" data-astro-cid-z4i2txjc>so far</span></p>
<p>When deciding what AWS accounts to create and how to group them, I had the following requirements:</p>
<ul>
<li>There must be a clear separation between Development, Staging and Production resources/infrastructure</li>
<li>It should be easy to control access to resources with different security and "stability"
properties without having to manually define lots of policies</li>
<li>We want to give <em>sandbox</em> accounts to individual engineers and data scientists to allow and
encourage experimentation while limiting spend</li>
<li>Sensitive resources and data should be placed out of reach from easy access when not needed for
normal day-to-day activities</li>
<li>We want to follow the <em>Principle of least privilege</em>: give only the minimum set of permissions to
allow access to exactly the resources a person or service needs to perform their job and not more.</li>
<li>We want as little overhead and bureaucracy as possible within the constraints of the previous
requirements</li>
</ul>
<p>This was aside from solving the aforementioned problems we had with our single-account setup.</p>
<p>This led me to create the following accounts, organized by Organizational Unit:</p>
<img src="https://www.daan.fyi/_astro/aws-org-design.BYj1_wf7_dhodK.webp" alt="AWS Organization design" loading="lazy" decoding="async" width="2000" height="1304">
<p>Some notes and highlights:</p>
<ul>
<li><strong>Workloads</strong>: this contains the "meat" of the infrastructure used for the day-to-day running of
our products. Each of the workload accounts represents a different <em>environment</em>, like Production,
Staging and Development. They all have a similar setup and contain basic infrastructure like:
<ul>
<li>a VPC network with private and public subnets</li>
<li>a couple of ECS clusters for running services in (Docker) containers</li>
<li>some PostgreSQL databases on RDS</li>
<li>many lambdas for data processing, machine learning etc.</li>
</ul>
</li>
<li><strong>Shared</strong>: as the name implies, this OU houses accounts with infrastructure that is shared
between workloads/services on other accounts. Some highlights:
<ul>
<li><em>Access Management</em>: this contains IAM users for external machines and services that need to
access AWS infrastructure on our behalf. Given the sensitive and "dangerous" access some of these
machine users get, the Access Management account is tightly locked down. Example of an IAM user in
this account, is the <em>Github Actions</em> user that is used to deploy code to our workload accounts.
This user assumes roles within the workload accounts for deployments.</li>
<li><em>Sensitive Data</em>: this account holds sensitive data that is not needed for day-to-day operations,
but is occasionally used for training ML models and building new features</li>
<li><em>Infra</em>: contains shared infrastructure, such as our central Docker registry. Docker images
pushed to this registry are automatically replicated to our workload accounts for easy access</li>
</ul>
</li>
<li><strong>Sandbox</strong>: our employees occasionally need to experiment with AWS services so they get a
sandbox account to do so, without messing up our operational workloads. Budget restrictions are
placed on these accounts</li>
</ul>
<h2 id="conclusion"><a aria-hidden="true" tabindex="-1" href="#conclusion"><span class="icon icon-link"></span></a>Conclusion</h2>
<p>Using AWS Organizations we have now set up our infrastructure at Source in such a way that it is
more secure and also more scalable and easier to maintain from a security perspective.
Our Software Engineers and Data Scientists have easier access to the infrastructure, services and
resources they need — using only <em>one</em> set of credentials — without exposing them to
sensitive data and/or critical infrastructure.</p>
<p>In future blog posts I will elaborate on the steps taken to set all of this up and share some
practical guides on how to use a multi-account setup on a day-to-day basis.</p>
<hr/>
<p><em>Thanks to <a href="https://twitter.com/djvdorp">Dan</a> for proofreading this ❤️</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Customzing Pyenv Behavior with Hooks]]></title>
            <link>https://www.daan.fyi/writings/pyenv-hooks</link>
            <guid>https://www.daan.fyi/writings/pyenv-hooks</guid>
            <pubDate>Sun, 28 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[You can customize the behavior of pyenv or its plugins - like pyenv virtualenv - by using hooks. These are simple (bash) scripts that are run at specific points when pyenv commands are run.]]></description>
            <content:encoded><![CDATA[<p>I use <a href="https://github.com/pyenv/pyenv">pyenv</a> to manage different Python versions on my laptop. It
also comes with an <a href="https://github.com/pyenv/pyenv-virtualenv">official plugin</a> that lets you manage
<em>virtualenvs</em> through pyenv, which I find very convenient. Basically, virtualenvs are treated as
just different Python versions by pyenv.</p>
<p>One thing that bothered me is that, whenever I create a new virtualenv and use <code>pip</code> in it, I am
inevitably greeted by a message telling me <code>pip</code> is out-of-date:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>WARNING: You are using pip version 21.2.3; however, version 21.3.1 is available.</span></span>
<span class="line"><span>You should consider upgrading via the '~/.pyenv/versions/3.10.0/envs/test/bin/python3.10 -m pip install --upgrade pip' command.</span></span></code></pre>
<p>So I end up always having to upgrade pip after creating a new virtualenv. Wouldn’t it be nice if
this could be automated?</p>
<p>Turns out, we actually <em>can</em> by leveraging <a href="https://github.com/pyenv/pyenv/wiki/Authoring-plugins#pyenv-hooks"><em>pyenv hooks</em></a>.</p>
<h2 id="pyenv-hooks"><a aria-hidden="true" tabindex="-1" href="#pyenv-hooks"><span class="icon icon-link"></span></a>Pyenv Hooks</h2>
<p>pyenv hooks are scripts that are executed by pyenv whenever certain commands are run. These can be
regular pyenv commands like <code>pyenv install</code> or <code>pyenv rehash</code> for example. But what is not
apparent from the pyenv documentation, is that you can also create hooks for plugins, like
<code>pyenv virtualenv</code>.</p>
<p>You can create a hook by creating a script at the following location:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#999999;--shiki-dark:#D6DEEB">$</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">PYENV_ROOT/pyenv.d/</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"><</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">hook-name</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">></span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">/</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"><</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">whatever</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">></span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">.bash</span></span></code></pre>
<p><code>hook-name</code> here can be any of: <code>exec</code>, <code>rehash</code>, <code>which</code>, <code>install</code> — which are all regular pyenv
commands — but it can also be a plugin command, like <code>virtualenv</code>. The filename of the script doesn’t
matter, and neither does the extension. I use <code>.bash</code> here to make it explicit that this is a bash
script, but pyenv hooks can be written in any language.</p>
<p>To create a hook that upgrades <code>pip</code> and some other default packages, you can create a new script
as follows:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">mkdir</span><span style="--shiki-light:#990055;--shiki-dark:#82AAFF"> -p</span><span style="--shiki-light:#999999;--shiki-dark:#D6DEEB"> $</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">PYENV_ROOT</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">/pyenv.d/virtualenv/</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#D6DEEB">$</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">EDITOR </span><span style="--shiki-light:#999999;--shiki-dark:#D6DEEB">$</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">PYENV_ROOT/pyenv.d/virtualenv/after.bash</span></span></code></pre>
<p>Where <code>$EDITOR</code> is your favorite editor (like <code>vim</code>, <strong>RIGHT?!</strong>)</p>
<p>Then add the following contents:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">after_virtualenv</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> '</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">PYENV_VERSION="$VIRTUALENV_NAME" pyenv-exec pip install --upgrade pip setuptools wheel</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span></span></code></pre>
<p><code>after_virtualenv</code> is the command that tells pyenv <em>when</em> to execute the following command. In this
case its defined by the <code>pyenv virtualenv</code> plugin. First we set the pyenv version to the name
of the virtualenv we just created. This is set by pyenv virtualenv as <code>$VIRTUALENV_NAME</code>. Then we
install/upgrade <code>pip</code> itself and <code>setuptools</code> and <code>wheel</code>.</p>
<p>That is all there is to it! Now any time you create a new virtualenv using <code>pyenv virtualenv</code>, the
aforementioned packages will be automatically upgraded after the virtualenv was created.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Static Duck Typing in Python with Protocols]]></title>
            <link>https://www.daan.fyi/writings/python-protocols</link>
            <guid>https://www.daan.fyi/writings/python-protocols</guid>
            <pubDate>Fri, 19 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Duck typing is considered to be one of Python's strengths. If you want to have the benefits of duck typing but also want your types statically checked, Protocols offer an excellent solution.]]></description>
            <content:encoded><![CDATA[<p>At <a href="https://source.ag/">Source</a>, we write most of our code in Python. It’s a language that both our
Software Engineers and Data Scientists are equally at home in. It’s easy to be productive in Python,
in part due to its dynamic nature. Not having to think too much about the types of your variables
and functions, can make it easier to experiment, especially if you’re not entirely clear yet
on how you’re going to solve a particular problem.</p>
<p>When moving our code to production however, we want to have more guarantees about the behaviour of
our code. Writing (unit) tests is one way to get those guarantees, but we also make heavy use of
<a href="https://www.python.org/dev/peps/pep-0484/">type hints</a> to give us more confidence in our code.
Type hints can also provide a productivity boost, because not only humans can reason better about
type hinted code, your editor can as well!</p>
<p>Sometimes though, using type hints everywhere can feel like you’re losing out on a lot of the magic
and speed that a dynamic type system brings you. One particular trait of dynamic typing that is
pretty idiomatic in Python, is <strong>duck typing</strong>.</p>
<h2 id="duck-typing"><a aria-hidden="true" tabindex="-1" href="#duck-typing"><span class="icon icon-link"></span></a>Duck Typing</h2>
<p><a href="https://en.wikipedia.org/wiki/Duck_typing">Duck typing</a> is a philosophy in programming where you
care more about the behaviour and properties of an object than its stated type to determine if that
object is useful in a certain situation. Duck typing is inspired by the <em>duck test</em>:</p>
<blockquote>
<p>If it walks like a duck and it quacks like a duck, then it must be a duck</p>
</blockquote>
<p>In practice this means that when you write a function that receives a certain input, you care only
about the behaviour and/or attributes of that input, not the explicit <em>type</em> of that input.</p>
<p>One interesting question that arises is: if you don’t want to be strict about the type of the
parameters a function receives, are there still any <em>static type guarantees</em> to be had?</p>
<p>And the other way around is interesting as well: if you have a function with statically typed
inputs, can you <em>loosen up</em> those parameters to make the function more universally useful, the way
duck typing does?</p>
<p>As it turns out, Python provides a neat way to have our cake and eat it too!</p>
<h2 id="protocols-to-the-rescue"><a aria-hidden="true" tabindex="-1" href="#protocols-to-the-rescue"><span class="icon icon-link"></span></a>Protocols to the Rescue</h2>
<p>When reviewing some code recently, I came across a function that looked roughly like this:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">def</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> calculate_windowed_avg</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        measurements</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Union</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">List</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">TemperatureMeasurement</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">],</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> List</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">HumidityMeasurement</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">]],</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        window_size</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> timedelta</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        field_name</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> str</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    )</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> -></span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Dict</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">datetime</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> float</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">]:</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    window_upper_bound </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> measurements</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#990055;--shiki-dark:#F78C6C">0</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">].</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">timestamp </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">+</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> window_size</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    current_window </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> []</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    window_averages </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> OrderedDict</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">()</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">    for</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> m </span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">in</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> measurements</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span></span>
<span class="line"><span style="--shiki-light:#708090;--shiki-dark:#809393">        # various calculations happen here</span></span>
<span class="line"><span style="--shiki-light:#708090;--shiki-dark:#809393">        # based on the timestamp of each measurement</span></span>
<span class="line"><span style="--shiki-light:#990055;--shiki-dark:#82AAFF">        ...</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">    return</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> window_averages</span></span></code></pre>
<p>The goal of this function is to calculate the average of a certain field (identified by
<code>field_name</code>) in a rolling window. At the time of writing this function, we were using it for
<code>TemperatureMeasurement</code> and <code>HumidityMeasurement</code>, but it is very likely we’ll want to use it for
different types of measurements in the future.</p>
<p>If we look closely at how the function uses the input, it turns out that the only thing we want to
be guaranteed of, is that the items we pass into the function have a <code>timestamp</code> field. So instead
of specifying each different type that has adheres to this contract, we’d like to tell the type
checker that we only care about having a <code>timestamp</code> field to work with.</p>
<p><code>Protocol</code> from the <code>typing</code> module lets us do that. Just like with duck typing, <strong>Protocols</strong> let
you specify the behaviour or attributes you expect, without caring about the type. Here is what
that looks like:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">from</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> typing </span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">import</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Protocol</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> List</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Dict</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">from</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime </span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">import</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime</span></span>
<span class="line"></span>
<span class="line highlighted"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">class</span><span style="--shiki-light:#DD4A68;--shiki-dark:#FFCB8B"> MeasurementLike</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">Protocol</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">):</span></span>
<span class="line highlighted"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    timestamp</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">def</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> calculate_windowed_avg</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span></span>
<span class="line highlighted"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        measurements</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> List</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">MeasurementLike</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">],</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        window_size</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> timedelta</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">        field_name</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> str</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    )</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> -></span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Dict</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">datetime</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> float</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">]:</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    window_upper_bound </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> measurements</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">[</span><span style="--shiki-light:#990055;--shiki-dark:#F78C6C">0</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">].</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">timestamp </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">+</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> window_size</span></span>
<span class="line"><span style="--shiki-light:#990055;--shiki-dark:#82AAFF">    ...</span></span></code></pre>
<p>Now the type checker doesn’t know <em>exactly</em> what the type is of whatever is provided as
<code>measurements</code> but it <em>does</em> know what those items have a <code>timestamp</code> field because they adhere to
the <code>MeasurementLike</code> Protocol.</p>
<p>In a sense, a Protocol acts like one side of an <em>Interface</em> as we know it from Java or Typescript.
Instead of having to specify the behaviour and properties both on a <em>type</em> and on the functions
that use it, we only have to specify it on a function, without caring about the types of the objects
that are provided to the function.</p>
<h2 id="protocol-and-generics"><a aria-hidden="true" tabindex="-1" href="#protocol-and-generics"><span class="icon icon-link"></span></a>Protocol and Generics</h2>
<p>You can also use Protocols together with <code>TypeVar</code> for even more generic functions that are still
type checked to some extend. One use-case that comes to mind, is when you don’t care about the
input type to a function, as long as it follows a protocol, but you also want to guarantee that
the <em>output</em> of the function is of the same type as the <em>input</em>, no matter what the exact type is.</p>
<p>This works as follows:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">from</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> typing </span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">import</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> Protocol</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> TypeVar</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">from</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime </span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">import</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">class</span><span style="--shiki-light:#DD4A68;--shiki-dark:#FFCB8B"> MeasurementLike</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">Protocol</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">):</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    timestamp</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> datetime</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">M </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> TypeVar</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">M</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> bound</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">MeasurementLike</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">def</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> measurement_as_timezone</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">measurement</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> M</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> tz</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> tzinfo</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> -></span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> M</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">    measurement</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">timestamp </span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> measurement</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">timestamp</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">astimezone</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">tz</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">    return</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> measurement</span></span></code></pre>
<p>Here we create a function that takes any object that has a <code>timestamp</code> field and guarantees that
the output will be of the same type as the input.</p>
<h2 id="conclusion"><a aria-hidden="true" tabindex="-1" href="#conclusion"><span class="icon icon-link"></span></a>Conclusion</h2>
<p>Protocols in Python provide a nice way to use duck typing while still having some static type
guarantees. You can define contracts for your functions without caring too much about the actual
types of your inputs.</p>
<hr/>
<p><em>Update on 2021–11–23: There was a wrong type annotation in this article, as <a href="https://news.ycombinator.com/item?id=29315363">pointed out by
<strong>ragebol</strong> on Hacker News</a>, which is now fixed</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Add an RSS Feed to a NextJS Blog]]></title>
            <link>https://www.daan.fyi/writings/rss</link>
            <guid>https://www.daan.fyi/writings/rss</guid>
            <pubDate>Tue, 05 Oct 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[RSS is an important feature to give your content maximum exposure while also retaining control over your content. This post shows how to add syndication feeds to a statically generated NextJS site]]></description>
            <content:encoded><![CDATA[<p>Today, I released a new version of my website with only one new feature:
<a href="https://en.wikipedia.org/wiki/Web_syndication">syndication</a> through RSS, Atom and JSON Feed. Even
though <a href="https://techcrunch.com/2018/04/07/rss-is-undead/">by some accounts</a>, RSS
<a href="https://mjtsai.com/blog/2019/12/26/apple-news-no-longer-supports-rss/">seems to be dead</a>,
I strongly believe RSS is an important feature in the fight to keep ownership over your own content
while also increasing exposure. The way I approach publishing and sharing my content, is called
<a href="https://indieweb.org/POSSE">POSSE</a>:</p>
<blockquote>
<p><strong>Publish (on your) Own Site, Syndicate Elsewhere</strong>.</p>
</blockquote>
<p>RSS is an important part of that strategy.</p>
<p>Luckily, I’m not the only one who values RSS. <a href="https://news.ycombinator.com/item?id=20813021">There</a>
<a href="https://news.ycombinator.com/item?id=23212812">are</a> <a href="https://news.ycombinator.com/item?id=26014344">others</a></p>
<p>In this article I want to share how I implemented syndication feeds in my
<a href="https://nextjs.org/">NextJS-powered</a> website.</p>
<h2 id="options-and-requirements"><a aria-hidden="true" tabindex="-1" href="#options-and-requirements"><span class="icon icon-link"></span></a>Options and Requirements</h2>
<p>I did some Googling to see how other NextJS users were generating RSS feeds, and there turned out
to be many people that had solved this particular problem <a href="https://www.google.com/search?q=rss+feed+nextjs">and wrote about it</a>
I found a couple of different approaches to generating and rendering RSS feeds in NextJS:</p>
<p><strong>Generating feeds</strong>:</p>
<ul>
<li>Hand-build the required XML using templated strings 🤢</li>
<li>Use a library to do it for you. The most popular library for this in the JS ecosystem seems to be
<a href="https://www.npmjs.com/package/feed"><em>feed</em></a> 🤩</li>
</ul>
<p><strong>Rendering feeds</strong>:</p>
<ul>
<li>Use <a href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering">Server-side rendering</a>
<ul>
<li>Advantages:
<ul>
<li>Create dynamic feeds (more on that later) that can be different for each visitor</li>
<li>Using NextJS pages to represent feeds feels natural in the NextJS way of doing things</li>
</ul>
</li>
<li>Disadvantages:
<ul>
<li>You can’t generate the website statically anymore (at least parts of it remain dynamic),
which reduces performance
As a side note: from a development perspective, server-side rendered pages and statically generated
pages in NextJS are so similar that the difference doesn’t matter</li>
</ul>
</li>
</ul>
</li>
<li>Use <a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation">Static generation</a>
<ul>
<li>Advantages:
<ul>
<li>Because the feeds are generated at build time, the site remains snappy</li>
</ul>
</li>
<li>Disadvantages
<ul>
<li>You cannot set the <code>Content-Type</code> header for statically generated pages, so you can’t serve
those pages as <code>application/rss+xml</code>. I’m not how big of a problem this is and what black
magic <a href="https://vercel.com/">Vercel</a> applies when serving my NextJS site</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="picking-the-requirements"><a aria-hidden="true" tabindex="-1" href="#picking-the-requirements"><span class="icon icon-link"></span></a>Picking the Requirements</h3>
<p>After looking at the options, I decided on the following requirements for my feeds:</p>
<ul>
<li>I want to go for statically generated feeds, to keep my site fast and the implementation simple</li>
<li>Support for <a href="https://en.wikipedia.org/wiki/RSS">RSS</a>,
<a href="https://en.wikipedia.org/wiki/Atom_(Web_standard)">Atom</a> and <a href="https://www.jsonfeed.org/">JSON Feed</a></li>
<li>I want to include the <strong>complete blog post contents</strong> in the feeds. This is an important one
because I personally really hate it when my RSS reader only shows me a summary of a post and
I have to go the the website to read all of it.
Caveat though: my website is built using MDX, so I might include components in the future
that are not easily convertible to static HTML without Javascript enabled. In that case,
readers will have to click through to my site.</li>
</ul>
<h2 id="implementation"><a aria-hidden="true" tabindex="-1" href="#implementation"><span class="icon icon-link"></span></a>Implementation</h2>
<p>As per my requirements, I wanted to generate the feeds at build time. But as mentioned before,
NextJS doesn’t support setting the <code>Content-Type</code> header for statically generated pages.
The alternative that many people use, is to have a separate script generate your feeds and
writing them to the public folder where all other static assets such as images are stored as well.
That way, the feeds would be served as static assets instead of statically generated pages — which,
from the browsers perspective doesn’t make a difference!</p>
<p>I found a <a href="https://ashleemboyer.com/how-i-added-an-rss-feed-to-my-nextjs-site">good explanation by Ashlee Boyer</a>
of this technique.</p>
<p>My plan:</p>
<ol>
<li>Write script to generate feeds, using the <em>feed</em> library from NPM</li>
<li>Run this script as a <code>postbuild</code> step so it would always be invoked after building the site
using <code>npm run build</code> (this happens not only locally, but also when Vercel deploys my site)</li>
</ol>
<h3 id="problem-1-running-typescript-modules-is-hard"><a aria-hidden="true" tabindex="-1" href="#problem-1-running-typescript-modules-is-hard"><span class="icon icon-link"></span></a>Problem 1: Running TypeScript Modules Is Hard</h3>
<p>I immediately hit a snag with (1), because I couldn’t manage to use ES6/Typescript modules in a
script run outside of my normal website code.</p>
<p>I’m using Typescript, and apparently <code>ts-node</code>, the tool to run Typescript files, doesn’t support
modules. Writing the script in Javascript wasn’t really an option for me because I wanted to reuse
a lot of logic that I already wrote for reading and parsing MDX files in the website itself.</p>
<h4 id="solution"><a aria-hidden="true" tabindex="-1" href="#solution"><span class="icon icon-link"></span></a>Solution</h4>
<p>I decided to follow the route that Ashlee Boyer suggests in her blog post and sneak in the
function to generate my feeds as a "stowaway" in the <code>getStaticProps</code> function of my index
page. This works beautifully!</p>
<div class="code-title-wrapper"><div class="rehype-code-title">pages/index.tsx</div><pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">export</span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4"> const</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> getStaticProps</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">:</span><span style="--shiki-light:#DD4A68;--shiki-dark:#FFCB8B"> GetStaticProps</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> =</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> ()</span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4"> =></span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> {</span></span>
<span class="line"><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">    generateMainFeeds</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">();</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">    const</span><span style="--shiki-light:#990055;--shiki-dark:#82AAFF"> lastPosts</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> =</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> getAllPostsFrontMatter</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">blog</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#990055;--shiki-dark:#F78C6C"> 3</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">);</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">    return</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> {</span></span>
<span class="line"><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">        props</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> {</span></span>
<span class="line"><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">            lastPosts</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">        },</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    };</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">};</span></span></code></pre></div>
<h3 id="problem-2-including-the-full-content-in-the-feeds"><a aria-hidden="true" tabindex="-1" href="#problem-2-including-the-full-content-in-the-feeds"><span class="icon icon-link"></span></a>Problem 2: Including the Full Content in the Feeds</h3>
<p>The code of my website already supported translating MDX files into valid JSX to be rendered
by React. But how to generate valid HTML from that content and include it in the feeds?</p>
<h4 id="solution-1"><a aria-hidden="true" tabindex="-1" href="#solution-1"><span class="icon icon-link"></span></a>Solution</h4>
<p>I couldn’t find many examples of this, but did find out about
<a href="https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup"><code>ReactDOMServer.renderToStaticMarkup</code></a>.
This function will take a bunch of React components and render them into HTML. This is what is
used by many React server-side rendering solutions <em>(maybe also by NextJS?)</em> and works perfectly
here as well.</p>
<p>One caveat: if your content contains internal links — which are often relative links — then you
have to be mindful that those relative links are meaningless in the context of an RSS feed.
The way I solved this is by doing some regex-based replacements on the generated HTML.</p>
<p>The complete content generation part looks like this:</p>
<div class="code-title-wrapper"><div class="rehype-code-title">lib/feeds.tsx</div><pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">const</span><span style="--shiki-light:#990055;--shiki-dark:#82AAFF"> url</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> =</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> `${</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">baseUrl</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">/blog/</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">${</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">post</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">frontMatter</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">slug</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}`</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">;</span></span>
<span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">const</span><span style="--shiki-light:#990055;--shiki-dark:#82AAFF"> htmlContent</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> =</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> ReactDOMServer</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">renderToStaticMarkup</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span></span>
<span class="line"><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">    <</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">ChakraProvider resetCSS theme</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">{</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">theme</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">></span></span>
<span class="line"><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">        <</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">MDXRemote </span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">{</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">...</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">post</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">.</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">mdxSource</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC"> components</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">=</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">{</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">MDXComponents</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> /></span></span>
<span class="line"><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">    </</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">ChakraProvider</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4">></span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    .</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">replace</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#EE9900;--shiki-dark:#EE9900">href="</span><span style="--shiki-light:#669900;--shiki-dark:#82AAFF">\/</span><span style="--shiki-light:#EE9900;--shiki-dark:#EE9900">#</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">g</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> `</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">href="</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">${</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">url</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">#</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">`</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    .</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">replace</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#EE9900;--shiki-dark:#EE9900">href="</span><span style="--shiki-light:#669900;--shiki-dark:#82AAFF">\/</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">g</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> `</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">href="</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">${</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">baseUrl</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">/</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">`</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">    .</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF">replace</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#EE9900;--shiki-dark:#EE9900">src="</span><span style="--shiki-light:#669900;--shiki-dark:#82AAFF">\/</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">/</span><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">g</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> `</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">src="</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">${</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">baseUrl</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">/</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">`</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">;</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">)</span></span></code></pre></div>
<h3 id="problem-3-getting-rid-of-style-information"><a aria-hidden="true" tabindex="-1" href="#problem-3-getting-rid-of-style-information"><span class="icon icon-link"></span></a>Problem 3: Getting Rid of Style Information</h3>
<p>My site uses <a href="https://chakra-ui.com/">Chakra UI</a> for theming, which uses
<a href="https://emotion.sh/">Emotion</a> — a CSS-in-JS library — under the hood. Emotion will happily
render tons of <code> </code> tags when statically generating HTML from your React components. For most
use cases where you render React on the server (statically or not), this is desirable. In the case
of RSS/Atom feeds, this is pretty useless.</p>
<h4 id="solution-2"><a aria-hidden="true" tabindex="-1" href="#solution-2"><span class="icon icon-link"></span></a>Solution</h4>
<p>The solution here is to strip all the <code> </code> and <code><style></code> tags from the generated HTML.
Rather than summoning
<a href="https://stackoverflow.com/a/1732454/872397"><em>The One whose Name cannot be expressed in the Basic Multilingual Plane</em></a>
by trying to use regex here, I found <a href="https://www.npmjs.com/package/string-strip-html">this library</a>
to help me with this task:</p>
<div class="code-title-wrapper"><div class="rehype-code-title">lib/feeds.tsx</div><pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="typescript"><code><span class="line"><span style="--shiki-light:#0077AA;--shiki-dark:#FFA7C4">const</span><span style="--shiki-light:#990055;--shiki-dark:#82AAFF"> cleanHtmlContent</span><span style="--shiki-light:#9A6E3A;--shiki-dark:#FFA7C4"> =</span><span style="--shiki-light:#DD4A68;--shiki-dark:#82AAFF"> stripHtml</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">(</span><span style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC">htmlContent</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> {</span></span>
<span class="line"><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">    onlyStripTags</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> [</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">script</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> '</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">style</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">],</span></span>
<span class="line"><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">    stripTogetherWithTheirContents</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">:</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> [</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">script</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">,</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA"> '</span><span style="--shiki-light:#669900;--shiki-dark:#00A3C4">style</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">'</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">],</span></span>
<span class="line"><span style="--shiki-light:#999999;--shiki-dark:#C792EA">}).</span><span style="--shiki-light:#990055;--shiki-dark:#80CBC4">result</span><span style="--shiki-light:#999999;--shiki-dark:#C792EA">;</span></span></code></pre></div>
<h2 id="the-end-result"><a aria-hidden="true" tabindex="-1" href="#the-end-result"><span class="icon icon-link"></span></a>The End Result</h2>
<p>I now have serve <a href="https://www.daan.fyi/feeds/feed.xml">RSS</a>, <a href="https://www.daan.fyi/feeds/atom.xml">Atom</a> and a <a href="https://www.daan.fyi/feeds/feed.json">JSON Feed</a>
for your reading pleasure. Most of the relevant code <a href="https://github.com/DandyDev/daan.fyi/blob/main/lib/feeds.tsx">can be found here</a></p>
<h2 id="future-plans"><a aria-hidden="true" tabindex="-1" href="#future-plans"><span class="icon icon-link"></span></a>Future Plans</h2>
<p>At some point I want to diversify my writing output by not only writing about tech. And
even within the topic of tech there are many sub-topics I could write about, not all of which
are equally interesting to every reader (all 5 of them, including my mom 👩‍👦).
I’m planning to introduce <a href="https://github.com/DandyDev/daan.fyi/issues/3">tags</a> to allow
filtering content once I have enough of it.</p>
<p>Once I have tags, I would like to start supporting dynamic feeds so readers can subscribe only
to the stuff they actually want to read. I imagine building an endpoint like this:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>/feeds/by-tags.xml?tags=tag1,tag2</span></span></code></pre>
<p>I’m curious if others are doing this!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Use Tools That Suit You and the Problem]]></title>
            <link>https://www.daan.fyi/writings/tools</link>
            <guid>https://www.daan.fyi/writings/tools</guid>
            <pubDate>Fri, 10 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Don't let anyone tell you what tools to use. Make sure that your builds are tool-agnostic]]></description>
            <content:encoded><![CDATA[<p>A couple of weeks ago someone shared
<a href="https://alexander-hansen.dev/blog/benefits-of-not-using-an-ide">an article</a> on Hacker News that
discussed the drawbacks of using an IDE for programming. Needless to say, when you touch
people’s tools, you can be sure to
<a href="https://news.ycombinator.com/item?id=28257116">spark a hefty discussion</a>.</p>
<span class="small" data-astro-cid-z4i2txjc><p>To be fair, the author did clarify that they’re not against IDE’s <em>in general</em> and they actually
use one on a daily basis.</p></span>
<p>When reading the discussion, one of the first things I wondered is if there are places on the
internet where carpenters or plumbers furiously criticize each other’s choice of tools.
But <em>even that</em> has <a href="https://news.ycombinator.com/item?id=28257382">a place on Hacker News</a></p>
<p>In this post I want to explore the fundamental problems with this type of discussion and how you
can embrace freedom of choice.</p>
<h2 id="its-all-about-context"><a aria-hidden="true" tabindex="-1" href="#its-all-about-context"><span class="icon icon-link"></span></a>It’s All about Context</h2>
<p>When discussing someone else’s choice of tools — in this case the choice of editor or IDE — it’s
good to be mindful of what we <em>don’t know</em> about this person and their reasons for choosing certain
tools. Aside from personal preference and/or familiarity, there can be lots of extra factors to
consider:</p>
<ul>
<li>We don’t know what kind of problems they’re working on</li>
<li>We don’t know the context in which they work (e.g. security requirements laid down by the company
they work for, available budget)</li>
<li>We don’t know their level of experience</li>
</ul>
<p>I think it’s good form to voice an opinion only when we can see the whole picture. I also
think that people should be encouraged to <strong>choose the right tool for the job</strong>. And to put it
simply: <strong>the right tool for the job is the one that fits the problem and makes you the most
productive</strong>.</p>
<h2 id="its-also-about-definition"><a aria-hidden="true" tabindex="-1" href="#its-also-about-definition"><span class="icon icon-link"></span></a>It’s Also about Definition</h2>
<p>Another issue with this kind of discussion is that everyone has a different idea of what really
constitutes an "IDE". We’re inclined to frame the choice of editor as a binary choice — "pure text
editor" vs "IDE", almost like a false dichotomy — while it really is a spectrum. On this spectrum
you’ll not only find different editors, but even different configurations of the same editor with
varying degrees of "smartness" applied through plugins.</p>
<p>So when someone says <em>"why use an IDE when you can do everything an IDE does in VIM if you install
the following 20 plugins"</em>, is this really so different from an actual IDE (that was branded as
such)?</p>
<h2 id="philosophies-in-tool-choices"><a aria-hidden="true" tabindex="-1" href="#philosophies-in-tool-choices"><span class="icon icon-link"></span></a>Philosophies in Tool Choices</h2>
<p>When looking at how people setup their development environments, I basically see 2 different
approaches:</p>
<ul>
<li>The <em>"additive"</em> approach: this basically follows the UNIX philosophy of composing many different
tools that each do one thing well. It often involves a terminal-based editor like VIM, optionally
with some plugins and supported by external tools like grep, sed and awk that can help with
refactoring code. You basically stack tools until you have an "integrated" development environment</li>
<li>The <em>"subtractive"</em> approach: you take one "big" editor as the basis of your stack. This is often
an editor that is branded as an IDE (e.g. Eclipse, Jetbrains editors). Then you take away/replace
("subtract") the parts that work better through dedicated tools (according to personal preference
of course)</li>
</ul>
<p>To me, both approaches have their merits and uses!</p>
<h2 id="how-to-create-freedom-of-choice"><a aria-hidden="true" tabindex="-1" href="#how-to-create-freedom-of-choice"><span class="icon icon-link"></span></a>How to Create Freedom of Choice?</h2>
<p>I think the most important question around editors is not</p>
<blockquote>
<p><em>How to choose the right editor?</em></p>
</blockquote>
<p>but rather</p>
<blockquote>
<p><em>How to enable each member of my team to choose the editor that makes them <strong>most productive</strong>,
while enabling <strong>effective collaboration</strong>?</em></p>
</blockquote>
<p>Here’s my recipe to do that:</p>
<p>My basic starting point in teams: <em>everyone can use the editor/IDE of their choice</em>. To enable
effective collaboration, the following rules must be followed:</p>
<ul>
<li>Editor configuration is never checked into version control, unless it’s generic and
<a href="https://editorconfig.org/">applicable across editors</a>. Examples of generic editor configuration
are: line endings, tabs vs spaces, max line length etc. There is some overlap with linters here</li>
<li>Enforce common code style through external tools (i.e. linters, code formatters)</li>
<li>Everyone should be able to build and run a project, regardless of the choice of editor,
preferably through the terminal. This means that we’ll always prefer external tools for this.
Many languages provide tooling for this:
<ul>
<li>Java → Maven/Gradle</li>
<li>JS/TS → NPM</li>
<li>Go → Go</li>
<li>Rust → Cargo</li>
<li>…</li>
</ul>
</li>
<li>Additionally, building and running code can be made easier and better reproducible by using
Makefiles and/or Docker (combined with Docker Compose)</li>
</ul>
<p>I have to admit that in some ways I also limit choice within my teams: building and running
projects often involves shell scripts which are portable between Linux and MacOS, but not to
Windows. This can be solved by using WSL on Windows.</p>
<h2 id="this-is-my-setup"><a aria-hidden="true" tabindex="-1" href="#this-is-my-setup"><span class="icon icon-link"></span></a>This Is My Setup</h2>
<p>My personal setup involves quite a lot of tools.</p>
<p>For any "serious" development work (i.e. long stretches of coding and big projects) I take the
aforementioned "subtractive" approach. My basis is one of the Jetbrains tools, depending on the
language(s) I’m working with. I find the Jetbrains tools make <em>me</em> really productive and their
<a href="https://www.jetbrains.com/all/"><em>All products pack</em></a> offers great value for money, especially with
their huge loyalty discounts after 1 or more years.</p>
<p>I still use the terminal and external tools for many things in this case. I put quite some time in
improving and maintaining <a href="https://github.com/DandyDev/dotfiles">my dotfiles</a> as well.</p>
<ul>
<li>I prefer to use the terminal for: building, linting, running tests, using git</li>
<li>I prefer to use the IDE for: writing and editing code, refactoring, searching for code,
find/replace through regex</li>
</ul>
<p>For quick and short edits and viewing files where speed is preferred and conveniences like syntax
highlighting and intellisense are not important, I use Sublime Text or VIM. Example of this:
editing my dotfiles.</p>
<p>Sometimes, but rarely, I use VSCodium for editing or viewing files which are not part of a project
and which I want to reformat/reindent. This doesn’t work well for me in Sublime Text. For example:
viewing/editing minified JSON files.</p>
<h2 id="conclusion"><a aria-hidden="true" tabindex="-1" href="#conclusion"><span class="icon icon-link"></span></a>Conclusion</h2>
<p>To wrap this post up, I just want to reiterate: use whatever tools make you productive and help you
solve the problems you’re trying to solve and please let others do the same.</p>
<p>And here’s some additional closing advice: should you ever grow unhappy with the tools you’re
using, just write a blog post extolling the virtues of your current tools. You can be sure people
on the internet will tell you how much better their tools are, thereby providing you with potential
alternatives to your current setup 😇</p>
<hr/>
<p><em>Thanks to <a href="https://twitter.com/djvdorp">Dan</a> for proofreading this</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Pitfalls of Deploying a NextJS Frontend on AWS Amplify]]></title>
            <link>https://www.daan.fyi/writings/amplify</link>
            <guid>https://www.daan.fyi/writings/amplify</guid>
            <pubDate>Mon, 23 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Half-baked products lead to much frustration]]></description>
            <content:encoded><![CDATA[<p>Have you ever gone above and beyond to give a product a fair chance, only giving up way past your
own sanity and patience? I did. The product was <a href="https://aws.amazon.com/amplify/">AWS Amplify</a></p>
<h2 id="lets-build-something-with-nextjs"><a aria-hidden="true" tabindex="-1" href="#lets-build-something-with-nextjs"><span class="icon icon-link"></span></a>Let’s Build Something with NextJS</h2>
<p>When I started developing the primary (web) UI for the product we’re building at
<a href="https:/source.ag">Source</a>, I quickly decided to use the <a href="https://nextjs.org/">NextJS</a> framework as
the basis for the frontend. One thing I really like about NextJS is that it does <a href="https://nextjs.org/docs/basic-features/pages">filesystem-based
routing</a>. Even though I like to diss on PHP as much
as the next person, this particular feature of NextJS reminds me a bit of PHP <em>in a good way</em>.</p>
<p>Another very compelling feature of NextJS, is the ability to have different rendering strategies
for each different page. You can decide to use:</p>
<ul>
<li><a href="https://nextjs.org/docs/basic-features/pages#static-generation-recommended">Static generation</a>
for pages and content that rarely change. These pages get pre-rendered at build time and can be
aggressively cached on a CDN.
These pages can of-course still be highly dynamic by using client-side data fetching methods</li>
<li><a href="https://nextjs.org/docs/basic-features/pages#server-side-rendering">Server-side rendering</a> for
pages that need to return different content on each request and/or for each different user</li>
<li><a href="https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration">Incremental static regeneration</a>,
which is a variation on Static generation that will cache pages for a period you specify and then
regenerate the pages server-side when they’re considered stale</li>
</ul>
<h2 id="deployment"><a aria-hidden="true" tabindex="-1" href="#deployment"><span class="icon icon-link"></span></a>Deployment</h2>
<p>This particular feature requires a specific hosting setup that supports the different <em>flavours</em>
of rendering. You basically want:</p>
<ul>
<li>a good CDN to cache and serve your static pages</li>
<li>a server-side solution to serve the server-side rendered pages and facilitate incremental static
regeneration. This should also take care of
<a href="https://nextjs.org/docs/api-routes/introduction">API routes</a>, should you use them</li>
</ul>
<p>The NextJS documentation <a href="https://nextjs.org/docs/deployment">mentions a couple of options</a> for
deployment:</p>
<ol>
<li>Use <a href="https://vercel.com/">Vercel</a>. Vercel is the company behind NextJS. They offer a seamless
experience where you can use any rendering method, and Vercel will deploy each part of your app
in the right way. One neat thing they do is that they’ll convert your server-side rendered pages
and API routes into serverless functions that they’ll deploy to edge locations all over the globe.</li>
<li>Setup a NodeJS server somewhere for the server-side stuff and use a CDN manually to serve the
static content.</li>
</ol>
<p>At Source however, we’ve made a conscious decision to use AWS for all of our infrastructure needs. We’re
too early in the lifecycle of both our product and company to consider a hybrid cloud setup and
AWS offers all the features that we need for prices that are reasonable <em>for now</em>. Through one of
our investors we also got a boat-load of AWS credits to get started, which helps.</p>
<h2 id="amplify"><a aria-hidden="true" tabindex="-1" href="#amplify"><span class="icon icon-link"></span></a>Amplify</h2>
<p>I didn’t want to manually set up a hybrid deployment with NodeJS and CDN - which I could probably do
using Cloudfront and ECS - so I looked for alternatives. AWS Amplify claims to be the</p>
<blockquote>
<p>Fastest, easiest way to build mobile and web apps that scale</p>
</blockquote>
<p>And they <a href="https://docs.amplify.aws/guides/hosting/nextjs/q/platform/js/">claim to support NextJS</a>
as well as <a href="https://aws.amazon.com/blogs/mobile/host-a-next-js-ssr-app-with-real-time-data-on-aws-amplify/">server-side rendering</a>
by virtue of <a href="https://aws.amazon.com/lambda/edge/">Lambda@Edge</a></p>
<p>Sounds great! So how does that all work?</p>
<h2 id="amplify---the-setup"><a aria-hidden="true" tabindex="-1" href="#amplify---the-setup"><span class="icon icon-link"></span></a>Amplify - The Setup</h2>
<p>The idea behind Amplify is that it lets you focus on building your app without having to worry
about the infrastructure. This means that it will not only let you build and deploy your frontend
easily, it will also let you hook-up backend components like
<a href="https://docs.amplify.aws/lib/auth/getting-started/q/platform/js/">authentication</a> through
<a href="https://aws.amazon.com/cognito/">Cognito</a>,
a <a href="https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js/">GraphQL API</a> using
<a href="https://aws.amazon.com/appsync/">AppSync</a>
and a <a href="https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js/">DataStore</a>.</p>
<p>The way Amplify manages all of this, is through the idea of "backends". A backend contains one or
more of the aforementioned components. You can setup different backend environments in Amplify and
couple these to different (git) branches of your frontend.</p>
<h2 id="amplify---the-developer-experience"><a aria-hidden="true" tabindex="-1" href="#amplify---the-developer-experience"><span class="icon icon-link"></span></a>Amplify - The Developer Experience</h2>
<p>We are bringing our own database and API and wanted to use a pre-existing Cognito user pool.
Amplify lets you import an existing Cognito user pool instead of creating one for you, and this
worked fine… until I tried to create a second "backend environment" with a different Cognito
user pool.</p>
<p>I want to allow different users on our development environment than our production
environment. These environments correspond to the <code>main</code> branch in Git (production) and any other
branches in Git (development).</p>
<h4 id="how-do-i-tie-an-amplify-backend-environment-to-a-frontend-branch"><a aria-hidden="true" tabindex="-1" href="#how-do-i-tie-an-amplify-backend-environment-to-a-frontend-branch"><span class="icon icon-link"></span></a>How Do I Tie an Amplify Backend Environment to a Frontend Branch?</h4>
<p>Interaction with Amplify is done through the web console or the
<a href="https://docs.amplify.aws/cli/">Amplify CLI</a>. According to the documentation, we can create a new
Amplify backend environment like this:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>amplify env add</span></span></code></pre>
<p>But nowhere is it made clear how this is linked to our frontend branch. The Amplify CLI tries to
mimic the <code>git</code> CLI by having <code>amplify push</code> and <code>amplify pull</code>. So I assume we need to <code>push</code> our
new environment to the cloud. Does this command depend on the git branch I’m on? I don’t know.
I could just as easily overwrite my existing backend environment when using this command.</p>
<p>Eventually I was able to create a <code>main</code> environment and a <code>dev</code> environment, but no matter how hard
I tried, I could not get those environments to use different Cognito user pools.</p>
<h4 id="how-do-new-team-members-setup-an-existing-project"><a aria-hidden="true" tabindex="-1" href="#how-do-new-team-members-setup-an-existing-project"><span class="icon icon-link"></span></a>How Do New Team Members Setup an Existing Project?</h4>
<p>The Amplify CLI will create some configuration files, some of which you need to commit to git, and
some of them you need to ignore. These files should make the build reproducible. It is unclear
however, what a new developer needs to do to be able to run an existing Amplify project.
The documentation mentions multiple Amplify CLI commands to setup a project:</p>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>amplify pull</span></span></code></pre>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>amplify init</span></span></code></pre>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>amplify configure project</span></span></code></pre>
<pre class="astro-code astro-code-themes prism-default-light night-owl-pink" style="--shiki-light:#1A202C;--shiki-dark:#F7FAFC;--shiki-light-bg:#F7FAFC;--shiki-dark-bg:#1A202C;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>amplify env checkout <my-env></span></span></code></pre>
<p><em>To be run in arbitrary order?</em></p>
<p><a href="https://stackoverflow.com/questions/60365208/how-to-import-existing-aws-amplify-back-end-into-an-empty-aws-amplify-project-lo">Three</a>
<a href="https://stackoverflow.com/questions/60951924/how-do-i-continue-working-with-amplify-on-a-new-machine">similar</a>
<a href="https://stackoverflow.com/questions/61104135/how-to-run-an-existing-aws-amplify-project">questions</a>
on StackOverflow give 3 different answers on this topic.</p>
<p>When I was on-boarding new team-members, we basically tried some commands in different order until
we were able to run the project locally for that engineer.</p>
<h4 id="how-do-i-figure-out-why-my-build-failed"><a aria-hidden="true" tabindex="-1" href="#how-do-i-figure-out-why-my-build-failed"><span class="icon icon-link"></span></a>How Do I Figure Out Why My Build Failed?</h4>
<p>In short: <a href="https://github.com/aws-amplify/amplify-console/issues/2065">you don’t</a>. Or you enlist
the help of AWS Support (which you will have to pay for). The Amplify web console doesn’t share
detailed build logs, so you’ll have to fix your problems by trial and error.</p>
<h2 id="amplify---the-bugs"><a aria-hidden="true" tabindex="-1" href="#amplify---the-bugs"><span class="icon icon-link"></span></a>Amplify - The Bugs</h2>
<p>AWS Amplify seems to have quite some bugs. For example: when you use Gitlab, you
cannot put the source code of your project
<a href="https://github.com/aws-amplify/amplify-console/issues/1941">in a sub-group</a>. This will silently
fail your build, with no clear error message in sight.</p>
<p>The aforementioned problem of using multiple Cognito user pools for different
environments of one Amplify frontend, I also consider a bug.</p>
<p>Not having access to the <em>actual</em> build logs, I also consider a bug.</p>
<h2 id="amplify---no-help-included"><a aria-hidden="true" tabindex="-1" href="#amplify---no-help-included"><span class="icon icon-link"></span></a>Amplify - No Help Included</h2>
<p>The most painful issue I have with AWS Amplify - and AWS in general - is the complete lack of
support. You are paying a lot of money for their services, but that does not come with any form of
support, unless you pay extra. And when you make use of that paid support, it goes something like
this:</p>
<ol>
<li>Contact AWS Business support through their website and try to explain your problem in a plain
text field that you cannot resize or use formatting in</li>
<li>Wait far longer than the promised 4 hours</li>
<li>Get a response from someone telling you they’ll forward it to "the team" (why not have someone
respond who can actually help you with your problem?)</li>
<li>Get a canned response with a solution akin to "turn it off and on again"</li>
<li>Tell them you have an <em>actual problem</em></li>
<li>Get forwarded to the next person who you have to clue in again on what your problem is</li>
<li>Go to 2 and repeat</li>
</ol>
<p>This means that in practice, AWS Amplify is <em>not usable</em> unless you are willing to pay extra for
support and have a lot of patience.</p>
<p>As a side note, I find it really frustrating that with big corporations like Amazon in general, you
quickly feel helpless and ignored when you have problems. Stories about Google customer support (or
lack thereof) are rampant on Hacker News, and I think Amazon is no different. You’d think that with
so much revenue they should be able to set up actual proper customer support 🙄</p>
<h2 id="the-pattern-with-aws"><a aria-hidden="true" tabindex="-1" href="#the-pattern-with-aws"><span class="icon icon-link"></span></a>The Pattern with AWS</h2>
<p>I think that these problems with Amplify are endemic of a larger problem with AWS: <strong>their PaaS and
SaaS solutions suck</strong>. Aside from pricing (which is quite high compared to competitors like
Digital Ocean), I think AWs’ infrastructure-as-a-service offerings are fine. If you want to have a
bunch of servers (EC2), a database here and there (RDS) and some serverless functions (Lambda), AWS
will serve you just fine.</p>
<p>But if you want something on top of that, like authentication (Cognito), or a PaaS solution like
Amplify, be prepared for a sub-par offering compared to the competition. I think Amazon should
focus on strengthening their core offerings instead of offering everything and the kitchen sink.</p>
<h2 id="the-solution"><a aria-hidden="true" tabindex="-1" href="#the-solution"><span class="icon icon-link"></span></a>The Solution</h2>
<p>In the end, I dropped Amplify and went with Vercel. $20/user/month is a small price to pay for
actually being able to work on the product instead of fighting AWS services and support.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Joy of Starting Something New]]></title>
            <link>https://www.daan.fyi/writings/new</link>
            <guid>https://www.daan.fyi/writings/new</guid>
            <pubDate>Mon, 16 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The start of a new blog/job/...]]></description>
            <content:encoded><![CDATA[<p>I don’t really know how to say this, but: I’m going to try and blog again! After building this site,
I didn’t even know how to begin <em>actually writing</em> again. Looking for inspiration, I studied some
famous tech people’s websites but came up empty trying to find examples of "first blog posts" there.
Most people like to just dig right in with some on-topic tech content it seems…</p>
<p>But <strong>HERE IT IS!</strong></p>
<p><em>My first blog post on my new website.</em></p>
<h2 id="previously-on"><a aria-hidden="true" tabindex="-1" href="#previously-on"><span class="icon icon-link"></span></a>Previously On…</h2>
<p>It’s been nigh on 4 years since I last wrote something on my old crib
<a href="https://dandydev.net">dandydev.net</a> (by the time you’re reading this, I’ll have most probably
redirected everything from there to here). I don’t really know why I stopped blogging in the first
place, but it probably had something to do with life/work/… getting in the way (or me letting it
get in the way anyways).</p>
<p>So what has been going on?</p>
<p>After working for KLM Royal Dutch Airlines for 4.5 years I got a dream position at
<a href="https://source.ag">Source.ag</a>, a new startup co-founded by a friend, focusing on Agriculture & AI.
As VP of Engineering I help build both the tech and the team and I’m loving it to bits!</p>
<p>Keeping with the theme of this post, it’s really exciting to start something so brand new. I’m
actually employee #1 which gives me ample opportunity to have a major impact. There are no
pre-established rules, processes, dynamics. For the first couple of months, it was just the 2
co-founders and me figuring out what to build, how to build it and then actually building it while
at the same time trying to grow the company. And now, 5 months after I started, we’re 12 lifeforms strong
and growing!</p>
<p>And after my previous job veered more and more into organizational politics instead of tech, it
feels great to be building an actual product again. It’s both amazing and terrifying to code most
of an MVP all by yourself in 5 months 😱</p>
<p><em>If you’re interested in bringing positive change to the food system,
<a href="https://source.ag/careers/">you can join us</a></em></p>
<h2 id="the-excitement-of-new-things"><a aria-hidden="true" tabindex="-1" href="#the-excitement-of-new-things"><span class="icon icon-link"></span></a>The Excitement of New Things</h2>
<p>Interlude: why is it that I find starting new things so exciting? I often wonder about this.</p>
<ul>
<li>I’m probably one of the few people not actually terrified of starting a new job</li>
<li>There are few things that give me more energy than starting a new side-project</li>
<li>Even as a gamer, I often spend more time <a href="https://steamcommunity.com/id/kajel/games/">trying out</a>
<a href="https://www.gog.com/u/Curois/games">new games</a> than actually finishing them
(don’t worry, I also have a Humble Bundle backlog and a PSN profile full of games that you don’t
know about yet 🙄)</li>
</ul>
<p>I think it comes down to wanting to <em>learn</em> new things as much as possible and I guess I just love
exploring the unknown.</p>
<p>But I’m gonna be honest: the challenge is to stick with things. For each side-project I finished,
there are many left in the dust. They were never time badly spent, because each time I learnt
something new. But I think there is value in finishing things as well.</p>
<p>I hope that a different approach with this blog will make it easier for me to stick with it.</p>
<h2 id="doing-things-differently"><a aria-hidden="true" tabindex="-1" href="#doing-things-differently"><span class="icon icon-link"></span></a>Doing Things Differently</h2>
<p>I want to sound off this rambling first blog post by sharing how things that are (going to be)
different with this website:</p>
<ul>
<li>I ditched my old domain dandydev.net for some place simpler and more elegant: <strong>daan.fyi</strong>.
I paid a pretty penny to get a domain of only my first name and an extension that didn’t look
too ridiculous and I love it. It feels a bit more mature as well</li>
<li>Always looking to learn new things, I chose to forego a traditional static site generator for a
more hand-built experience. This site was built on <a href="https://nextjs.org/">NextJS</a>, React and
Typescript. And I actually designed and built the theme myself using
<a href="https://chakra-ui.com/">ChakraUI</a>. I did get a lot of inspiration from other people’s websites
(see below)</li>
<li>I’m writing my posts using <a href="https://mdxjs.com/">MDX</a> instead of regular markdown. For this
particular post there is not actually any difference, but MDX will allow me to create more
tailored and interactive experiences in the future, should I need/want it</li>
<li>In terms of content, I plan to have a different approach than before:
<ul>
<li>I’ll try to write shorter posts and consequently (hopefully) write more often</li>
<li>I’m allowing myself to write about other stuff than tech. This blog is meant to <em>express</em>
myself more than to <em>advertise</em> myself. This means I’ll probably write about non-tech topics
like movies & tv-shows I’ve seen, games I played and maybe even reflect on politics once in
a while 😬 Once this kind of content arrives, I’ll make sure it’s easy to see and skip it 😉</li>
</ul>
</li>
</ul>
<h2 id="acknowledgements-of-inspiration"><a aria-hidden="true" tabindex="-1" href="#acknowledgements-of-inspiration"><span class="icon icon-link"></span></a>Acknowledgements of Inspiration</h2>
<p>When building this website, there were a few websites that inspired me and/or helped me with actual
solutions to coding problems (yay for open source!):</p>
<ul>
<li><a href="https://alyssax.com/">Alyssa X</a></li>
<li><a href="https://leerob.io/">Lee Robinson</a></li>
<li><a href="https://www.joshwcomeau.com/">Josh W. Comeau</a></li>
<li><a href="https://marcel.is/">Marcel Krcah</a></li>
</ul>
<hr/>
<p>See you next time!</p>]]></content:encoded>
        </item>
    </channel>
</rss>