<?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/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>The Aging Developer</title>
        <link>https://agingdeveloper.com/</link>
        <description>for growing old in the software development community</description>
        <lastBuildDate>Mon, 02 Mar 2026 14:29:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>The Aging Developer</title>
            <url>https://agingdeveloper.com/.netlify/images?url=_astro%2Fwizard.B8B3AQ03.jpg&amp;w=1280&amp;h=1707&amp;dpl=69a59eb298f6bd0007e61b7b</url>
            <link>https://agingdeveloper.com/</link>
        </image>
        <copyright>2026-03-02T14:29:58.740Z</copyright>
        <category>Technology</category>
        <atom:link href="https://agingdeveloper.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Staying Healthy in the New Year]]></title>
            <link>https://agingdeveloper.com/article/2026-01-30-healthier-new-year</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2026-01-30-healthier-new-year</guid>
            <pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Balancing a demanding work schedule with wellness goals is tough, especially when working across time zones. Here is how I am approaching health in 2026 without sacrificing productivity.]]></description>
            <content:encoded><![CDATA[<p>As a software engineer, staying healthy can be a real challenge. Long hours in front of a screen, the demands of deep focus, and the mental load of context-switching do not exactly lend themselves to great habits. Add in the complexity of working across time zones with a shortened lunch break, and it's easy to see why health often gets pushed aside.</p>
<p>I had planned to share this earlier in the month, but ended up writing and posting <a href="https://agingdeveloper.com/article/2026-01-11-breaking-bias">Using AI and Awareness to Break the Bias Loop</a> instead. This draft sat quietly for a bit. Then I had a conversation with Rachel that reminded me that these kinds of reflections are still worth sharing, even if they come a little later than planned.</p>
<p>So here it is: not a resolution, but a handful of small shifts I'm hoping to make in 2026 to feel a little more clear, focused, and grounded in both work and life.</p>
<h2>The Morning Reset</h2>
<p>One shift I'm hoping to make this year is treating my morning dog walk as more than just a routine. It's already part of my day, but I want it to become a real mental reset—something that helps me start work with clarity instead of stress. Too often, I go straight from bed to laptop, especially when early meetings or messages are waiting. But I'm realizing there's value in building a buffer—something calm, consistent, and intentional.</p>
<p>What I want is to use that walk as a way to center myself. No podcasts, no inboxes, just a chance to breathe and think about how I want the day to unfold. It's a small window of time, but I think it could help me shift from reactive mode into something more grounded. I haven't figured it out yet, but that's the direction I'm moving toward.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fgabrielle-marie-kozak-mnKE-N3cPAE-unsplash.8C709Uo6.jpg&amp;w=1920&amp;h=1440&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Morning Walk" /></p>
<h2>Standing Focus</h2>
<p>I've had a standing desk for a while, but I've only used it in bursts. This year, I want to use it more consistently. I think small physical changes might help me stay more present in long stretches of work.</p>
<p>I'm planning to pair it with the Pomodoro Technique: 25-minute sprints with 5-minute breaks. During those breaks, I want to stand, stretch, maybe switch my desk height, or just move a little. I'm hoping that rhythm can bring some energy and intentionality back into my workdays. Especially on the ones that feel like a wall of meetings.</p>
<p><strong>I'm currently looking for a good Pomodoro timer app</strong> — if you've got one you like, <a href="https://bsky.app/profile/richwklein.bsky.social">let me know on BlueSky</a>!</p>
<h2>Mental Buffering</h2>
<p>One habit I'd like to build is giving myself more mental space between tasks. Right now, I tend to jump from code to meetings to emails without a pause, and it leaves my brain feeling scattered. I'm learning that even a few minutes of transition time can make a difference.</p>
<p>What I want to try is using small buffers (a short walk, a quick personal task, or knocking out an easy five-minute win) before switching gears. These tiny resets could help me bring more focus and calm into the next thing, instead of dragging the last task's residue with me. It's a work in progress, but it feels like a shift worth making.</p>
<h2>Eating for Energy</h2>
<p>With time zone overlap and short lunch windows, meals during the workday tend to get minimized or rushed. I want to change that. I'm trying to be more intentional about what I eat. I want to prioritize protein to stay steady through the afternoon.</p>
<p>I'm not following a strict plan, just trying to be smarter about prep. I use my <a href="https://www.myrecipes.com/">myrecipes.com</a> account to collect high-protein recipes that are quick to prep or batch friendly. That site makes it easy to save and organize the meals I actually want to eat. I'll occasionally scroll through on a slow weekend day and add a few more so I'm not defaulting to the same meals over and over. Having solid options ready makes it easier to get back to work feeling focused, not foggy.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fmyrecipe-collections.Bx82rg8j.jpg&amp;w=1920&amp;h=1090&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Recipe Collections" /></p>
<h2>Cutting Back, Thinking Clearer</h2>
<p>I've been reflecting on how alcohol fits into my day-to-day. While I'm not cutting it out completely, I do want to be more mindful. Even a single drink can mess with sleep, and lately, I've realized how much that impacts my focus, mood, and energy the next day.</p>
<p>So I'm hoping to experiment with cutting back and seeing how it feels over time. I'm also trying to get to bed a little earlier, with the goal of making mornings easier to show up for. This isn't about rigid rules; it's more about building a better foundation and giving myself the chance to feel clearer and more focused when I need it most.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fkate-stone-matheson-uy5t-CJuIK4-unsplash.BCRDHsfb.jpg&amp;w=1920&amp;h=1295&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Sleeping Cat" /></p>
<h2>Final Thoughts on a Healthier 2026</h2>
<p>I'm not aiming to do all of this perfectly. The real goal is to build habits that help me feel more present, more clear headed, and more grounded; even on the busiest days. Some of these shifts might stick, others might evolve, but they're all pointed in the direction I want to grow.</p>
<p><strong>What small changes are you making this year to stay healthy while working in tech?</strong> I'd love to hear your strategies and tips. Feel free to share them with me on <a href="https://bsky.app/profile/richwklein.bsky.social">BlueSky</a>!</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/effydesk-aDZ-UZSvhE4-unsplash.BZwSeAlz.jpg"/>
        </item>
        <item>
            <title><![CDATA[Before You Repost: Using AI and Awareness to Break the Bias Loop]]></title>
            <link>https://agingdeveloper.com/article/2026-01-11-breaking-bias</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2026-01-11-breaking-bias</guid>
            <pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Viral posts shape more than opinions. They shape our reality. This post explores how AI tools can help us examine sentiment, bias, and funding before we hit share, and how resharing impacts both our algorithms and our worldview.]]></description>
            <content:encoded><![CDATA[<p>I've been deeply disheartened by the politics and polarization in this country — not just because of the headlines themselves and what they represent, but because of how quickly we rush to share them. We often hit "reshare" before we ask:</p>
<p><em>Is this true?</em><br />
<em>Who benefits from this narrative?</em><br />
<em>Is this feeding my confirmation bias?</em></p>
<p>Before we repost, we should slow down just enough to ask better questions. AI can help us do that.</p>
<h2>The Power of a Post</h2>
<p>This TikTok really hit me in the gut: <a href="https://www.tiktok.com/@obriengoespotatoes/video/7593014440849624351">TikTok by @obriengoespotatoes</a>. It's raw, emotional, and speaks to a very real fear many of us feel. I've been thinking about it a lot lately. It has made me want to reflect on my own feelings about safety, community, and change.</p>
<p>Emotional content <em>sticks</em>. It bypasses our rational brain and appeals straight to our identity. Even though I empathize with the underlying feeling in the video, I also recognize it has a <strong>bias</strong>: in tone, in framing, and in what it leaves out. That doesn't make it invalid. It makes it powerful <em>and</em> potentially dangerous if consumed without critical thought.</p>
<p>Same goes for this <a href="https://www.facebook.com/share/p/17k3dwYyaC/">Facebook post</a>, which has been reshared by some people I know. It reinforces a perspective that's already popular with my friends; but how much of it is fact, and how much of it is narrative?</p>
<h2>Engagement Amplifies</h2>
<p>It's not just resharing that boosts a post's reach. <strong>commenting, reacting, quote-posting, or even clicking</strong> all feed the algorithm data. Every interaction; whether you agree, disagree, or are simply outraged; teaches the platform that <em>this content is engaging</em>.</p>
<p>That means:</p>
<ul>
<li>Commenting "This is wrong!" still counts as a signal to promote the post.</li>
<li>Clicking to "read the comments" helps the post get pushed further.</li>
<li>Arguing under a post you hate still boosts its visibility to people who might <em>not</em> disagree with it.</li>
</ul>
<p>Social media algorithms don't care what you think. It only needs to know that you're thinking about it and staying engaged. The outrage economy thrives on this.</p>
<p>Sometimes, <strong>not engaging at all</strong> is the wiser path; not out of avoidance, but as an act of intentional non-amplification. I've personally learned this the hard way. I was defriended by a family member because of how I was baiting them on some of the content they reshared. I disagreed with the post. I was outraged, and instead of stepping away, I leaned into that outrage. I wanted a reaction. And I got one.</p>
<p>That moment forced me to confront something uncomfortable: engaging from anger doesn't just feed the algorithm, it damages relationships. It didn't change his mind. It didn't make the situation better. It only added more heat; and more visibility; to something that was already divisive.</p>
<p>If you're truly trying to reduce the spread of misinformation or inflammatory content, the most powerful move might be no engagement at all. Or better yet, engaging with something <em>constructive</em> instead. Starve the drama. Feed the dialogue.</p>
<h2>What You Share, Shapes What You See</h2>
<p>Algorithms are reflection machines. When you interact with content that aligns with your worldview, platforms learn: <em>Ah, more of that please.</em> And then, that's what you see. Again and again.</p>
<p>This is how <strong>confirmation bias</strong> becomes an echo chamber. Over time, it's not that people don't want to "do their own research"; it's that they don't even see the <em>other side</em> anymore. The algorithm has trained them not to.</p>
<p>I've seen this happen firsthand in my own social feeds. The longer I interact with something on TikTok; the more often similar content shows up. The more I engage with a post that aligns with my beliefs; the more my feed fills with that perspective.</p>
<hr />
<h2>Start With AI Before You Repost</h2>
<p>I've started thinking of AI as a pause button. It is something that creates just enough space between reaction and response. Here's an easy process to check yourself before you hit share:</p>
<h3>1. Do a Sentiment Analysis</h3>
<p>I've found that noticing the emotional tone of a post is often the fastest way to see how it's trying to influence me. Before deciding what to believe or how to respond, identify the emotional signals at play.</p>
<p>Use a tool like ChatGPT (or any AI with natural language understanding) to ask:</p>
<ul>
<li>What is the emotional tone of this post?</li>
<li>What emotional response is it trying to evoke?</li>
</ul>
<p>Paste the post into ChatGPT and prompt it with:
<em>“Can you do a sentiment analysis of this text?”</em><br />
<em>“What emotional language is used, and what feelings might it provoke?”</em></p>
<h3>2. Ask About Bias (Including Your Own)</h3>
<p>Bias isn't always obvious and it's not always bad. Recognizing bias helps you see the assumptions shaping the message (and your reaction to it). Before accepting or rejecting a post, ask: <em>Whose perspective is being centered and whose is missing?</em></p>
<p>Use a tool like ChatGPT to ask:</p>
<ul>
<li>Does this post show signs of political, cultural, or ideological bias?</li>
<li>What assumptions does the author make?</li>
<li>What perspectives are not represented?</li>
</ul>
<p>Paste the post into ChatGPT and prompt it with:<br />
<em>“Can you identify any biases in this text?”</em><br />
<em>“What worldview or position is this post coming from?”</em><br />
<em>“Who might disagree with this, and why?”</em></p>
<h3>3. Follow the Money</h3>
<p>Understanding who funds a message, or who stands to gain from it, can reveal motivations that aren't obvious at first glance. Ask yourself: <em>Is this post truly grassroots, or is there a financial or political engine behind it?</em></p>
<p>Use a tool like ChatGPT to ask:</p>
<ul>
<li>Who owns or funds the outlet or creator behind this post?</li>
<li>Are there financial, political, or ideological incentives influencing the message?</li>
<li>Is the content sponsored, monetized, or aligned with a larger agenda?</li>
</ul>
<p>Paste the post or its source into ChatGPT and prompt it with:<br />
<em>“Who funds or owns this media outlet or creator?”</em><br />
<em>“Does this platform have known political or commercial affiliations?”</em><br />
<em>“Is there any conflict of interest that could shape this message?”</em></p>
<h3>4. Ask Who It Helps or Harms</h3>
<p>Every post, even if unintentionally, serves a purpose. It supports a narrative, shapes perception, or influences behavior. Ask yourself: <em>Who benefits if I believe this? Who could be hurt by it being shared or taken out of context?</em></p>
<p>Use a tool like ChatGPT to ask:</p>
<ul>
<li>Who is portrayed positively or negatively in this post?</li>
<li>What action or belief is the post encouraging?</li>
<li>Could this harm individuals or groups — especially if the information is misleading or incomplete?</li>
</ul>
<p>Paste the post into ChatGPT and prompt it with:<br />
<em>“Who is this post positioning as the 'hero' or 'villain'?”</em><br />
<em>“What behavior or emotion is this message trying to drive?”</em><br />
<em>“Could this content reinforce harmful stereotypes or misinformation?”</em></p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fchat-sidebar.BYFmF29U.png&amp;w=3164&amp;h=1890&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Chat Sidebar" /></p>
<h2>Putting This Into Practice</h2>
<p>The screenshot above, showing damaged solar panels, came from a recent repost in a Facebook group I'm part of. My first instinct was to jump into the comments.</p>
<p>Instead, this is where theory becomes habit. I spent just a few minutes asking a couple of questions from section four.</p>
<ul>
<li>Who funds or owns this media outlet?</li>
<li>Is there any conflict of interest?</li>
</ul>
<p>I don't do this every time. We have a motto in our house: <em>Practice over Perfection</em>. The more often I ask these questions, the more natural that pause becomes.</p>
<p>What follows is a simple framework for evaluating content before reacting or resharing.</p>
<p>You can write this out by hand, journal-style. Or you can paste the post into ChatGPT and work through each section there.</p>
<p>If you do this often, consider creating a simple <strong>custom GPT</strong> or saving a reusable prompt template.</p>
<h3>Post Analysis Template</h3>
<p><strong>Platform:</strong><br />
Where did you see it? (e.g., Facebook, TikTok, X, Instagram)</p>
<p><strong>Outlet or Creator:</strong><br />
Who posted or published this content?</p>
<p><strong>Emotional Sentiment:</strong><br />
What emotional tone is being used? Is it angry, fearful, hopeful, mocking, etc.?</p>
<p><strong>Observed Bias (if any):</strong><br />
Is there a clear political, cultural, or ideological slant? What perspectives are not being represented?</p>
<p><strong>Funding or Incentives Behind the Source:</strong><br />
Is this person or outlet supported by ad clicks, political donors, or a specific community? Who benefits from this message?</p>
<p><strong>What This Encourages the Viewer to Feel or Do:</strong><br />
Is the post trying to stir outrage? Urge action? Reinforce a belief?</p>
<p><strong>What Context Might Be Missing:</strong><br />
Does the post simplify complex issues? Leave out important facts? Has the story been fact-checked elsewhere?</p>
<h2>Browser Extensions That Can Help You Do This</h2>
<p>While no tool does it all, here are a few browser extensions that can support this kind of thinking:</p>
<h3>Chrome Extensions</h3>
<ul>
<li><strong><a href="https://chromewebstore.google.com/detail/media-bias-fact-check/ganicjnkcddicfioohdaegodjodcbkkh?hl=en&amp;utm_source=chatgpt.com">Media Bias / Fact Check</a></strong></li>
<li><strong><a href="https://chromewebstore.google.com/detail/bias-finder/jojjlkfeofgcjeanbpghcapjcccbakop?utm_source=chatgpt.com">Bias Finder</a></strong></li>
</ul>
<h3>Firefox Add-Ons</h3>
<ul>
<li><strong><a href="https://addons.mozilla.org/en-US/firefox/addon/truthly/?utm_source=chatgpt.com">Truthly</a></strong></li>
<li><strong><a href="https://addons.mozilla.org/en-US/firefox/addon/lnk-media-bias-analyzer/?utm_source=chatgpt.com">LNK Media Bias Analyzer</a></strong></li>
</ul>
<blockquote>
<p><em>Tip:</em> I have not personally vetted any of these extensions. Always read reviews before installing any tool, especially those claiming to "detect fake news." No tool is perfect, and some may have their own biases or limitations.</p>
</blockquote>
<h2>We Need Better Reflexes, Not Just Better Opinions</h2>
<p>The problem isn't that people care too much. It's that we often don't stop long enough to care wisely. Sharing emotional content without context is like handing out torches in a dry forest. It feels like action. But it can cause real harm.</p>
<p>I want us to be better than that. I want us to use the tools we now have - AI, transparency tools, even just questions - to check ourselves, and each other.</p>
<p>Not because we want to be "neutral." But because we want to be honest.
So the next time something hits your feed and your first reaction is "YES, THIS!!" — pause.</p>
<ul>
<li>Breathe.</li>
<li>Paste it into a tool like ChatGPT.</li>
<li>Ask some questions.</li>
<li>Sit with the answers.</li>
</ul>
<p>Then decide if you want to be part of amplifying it.</p>
<p>And if something hits your feed and your first reaction is "Absolutely not!" or "This is infuriating". Pause there too. That knee-jerk urge to quote-tweet it with outrage, to drop a sarcastic comment, or to drag it into your stories? That's still engagement. Still amplification. Still fuel for the feed.
Ask yourself:</p>
<ul>
<li>Is this worth engaging with?</li>
<li>Will it change minds, or just deepen divisions?</li>
<li>Am I reacting to disinformation; and if so, do I really want to help spread it, even in protest?</li>
</ul>
<p>We can't control everything about this country or this moment, but we <em>can</em> control how we show up for the conversation — and what we choose to elevate.</p>
<p>The algorithm isn't neutral. But our awareness can be powerful.</p>
<h2>A Final Reminder: Be Human First</h2>
<p>While AI can help us analyze content, it can't replace our empathy.</p>
<p>Before you repost, take a moment to imagine yourself as the subject of the video, article, or post. Or imagine it is about someone you love; a friend, a sibling, a parent. What would it feel like to read that headline or see that comment? Would it still seem fair? Would it still feel true?</p>
<p>Whether it's a post that confirms something you already believe, or one that makes the 'other side' look awful, we are all human. And that means we owe each other kindness, compassion, and respect; especially when we disagree. Leading with empathy is not weakness. It is strength. It's not perfect and it's slower than outrage. But it is a way forward that doesn't leave us worse off.</p>
<h2>Before You Move On...</h2>
<p>What is one post you reshared recently that you wish you had looked into more deeply? What did it teach you?</p>
<p>What we share trains the world, and ourselves, on what matters.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/akshar-dave-qlGpYENt-ig-unsplash.7U-57XJD.jpg"/>
        </item>
        <item>
            <title><![CDATA[Why Emojis Matter in Pull Requests]]></title>
            <link>https://agingdeveloper.com/article/2025-12-28-pr-emojis</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2025-12-28-pr-emojis</guid>
            <pubDate>Sun, 28 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[As engineering teams move async by default, small signals matter more. This post explores why emojis have become an important tool in pull request communication—and how to use them intentionally.]]></description>
            <content:encoded><![CDATA[<p>Most engineering teams don't fail because they lack information. They fail because the context lives in someone's head.</p>
<p>As work has shifted toward remote and async-first collaboration, written communication has become the primary interface between humans. Pull requests, issue comments, and Slack threads are more than just artifacts. <em>They are the work.</em> Written text is a lossy medium. It strips away tone, intent, urgency, and emotional nuance. Signals humans rely on to collaborate effectively.</p>
<p>This isn't a new observation. What <em>is</em> new is the scale at which we experience it.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fremote-team.BsDuf4tS.jpg&amp;w=1920&amp;h=1003&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Teams working remotely" /></p>
<h3>Async by Default, Ambiguity by Accident</h3>
<p>Async communication breaks in boring, predictable ways. Without real-time feedback loops, small ambiguities in tone and intent balloon into larger misunderstandings:</p>
<ul>
<li>Feedback that reads harsher than intended</li>
<li>Unclear intent (“Is this a suggestion or a blocker?”)</li>
<li>Increased cognitive load for readers trying to infer subtext</li>
</ul>
<p>At staff+ levels, like Staff Engineer or Principal Engineer roles where influence extends beyond a single team, these issues compound fast because your comments travel further than you do. You're often reviewing work across teams, time zones, and domains. You don't always have the luxury of synchronous clarification. Every extra ounce of ambiguity slows decision-making and chips away at trust.</p>
<p>This is where emojis began to matter, not as decoration but as <strong>contextual signals</strong>.</p>
<h3>Emojis as Social and Emotional Metadata</h3>
<p>I've come to think of emojis as a kind of shorthand for tone—the stuff you'd normally get from a raised eyebrow or a pause. Linguists and communication researchers increasingly describe emojis as a form of <em>paralinguistic</em> information, the digital equivalent of facial expressions, gestures, and tone of voice. In other words, they don't replace words; they help shape how those words are interpreted.</p>
<p>That framing matches my own experience, and research backs it up:</p>
<ul>
<li>Studies of developer communication on GitHub show that emojis are most often used to express sentiment or soften critique—especially in comments where tone matters most. [^1]</li>
<li>More broadly, research has found that emojis act as emotional signposts, helping readers more accurately interpret a writer's attitude. [^2]</li>
<li>In longer-running communities, emoji usage also correlates with higher engagement and lower dropout rates. [^3]</li>
</ul>
<p>They function a bit like log levels for conversation: small signals that help the reader calibrate how much weight or urgency to assign to what follows.</p>
<p>They quietly answer questions like:</p>
<ul>
<li>How should I read this?</li>
<li>How strongly does the author feel about this point?</li>
<li>Is this encouragement, concern, or caution?</li>
</ul>
<p>All without adding another paragraph.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Femoji-sheet.DwSMvsxv.jpg&amp;w=600&amp;h=356&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Sheet of Emojis" /></p>
<h2>Why This Matters for Engineering Leaders</h2>
<p>For staff+ engineers, communication isn't just about correctness. It's about keeping <strong>work moving and people aligned</strong>.</p>
<p>In async environments:</p>
<ul>
<li>Small misunderstandings can stall progress for hours or days</li>
<li>Review feedback that feels sharp—even unintentionally—can discourage contribution</li>
<li>A lack of visible acknowledgment can make collaborators feel unseen</li>
</ul>
<p>I've spent months trying to push a plan forward after a request for discussion that never quite landed. In an async environment, silence is rarely neutral. Used intentionally, emojis can help close these gaps by restoring some of the <strong>human context</strong> that text-only communication strips away.</p>
<p>Emojis didn't become important because teams became more casual. They became important because teams became more distributed, more async, and more dependent on written communication to carry both technical and social meaning.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Ftime-spent.Cy5xjxbJ.jpg&amp;w=800&amp;h=500&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Time Spent on Communication" /></p>
<h2>Emojis in Pull Request Comments</h2>
<p>Pull request comments sit at an uncomfortable intersection. They are technical, evaluative, and often time-constrained, but they are also deeply human. This is where people give feedback, challenge decisions, acknowledge effort, and negotiate trade-offs—all without the benefit of tone, timing, or immediate clarification.</p>
<p>At scale, PR comments become one of the highest-risk surfaces for miscommunication in an async workflow.</p>
<h2>The Hidden Cost of Ambiguity in Code Review</h2>
<p>Most teams understand the mechanics of good code review: be clear, be respectful, focus on the code, not the person. What's harder is managing how feedback is <strong>received</strong> when it's read hours later, out of context, and often alongside other competing priorities.</p>
<p>A short comment like:</p>
<blockquote>
<p>“This logic is unnecessary.”</p>
</blockquote>
<p>may be intended as a neutral observation. Without additional signals, it can easily read as dismissive or overly sharp. Engineers—especially those earlier in their careers or working outside their primary domain—tend to fill in missing context pessimistically.</p>
<p>This is where emojis become useful—not as decoration, but as <strong>intent markers</strong>.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fmiscommunication-at-work.CZu1IvRh.jpg&amp;w=1920&amp;h=721&amp;dpl=69a59eb298f6bd0007e61b7b" alt="miscommunication at Work" /></p>
<h2>Emojis as Intent and Tone Signals</h2>
<p>Research into developer communication shows that emojis are most frequently used to convey sentiment, soften critique, or reinforce intent—particularly in comments where tone matters most. [^1]</p>
<p>Rather than replacing written feedback, they help shape how that feedback is received.</p>
<p>In practice, this shows up in small but meaningful ways:</p>
<ul>
<li>💡 signals a suggestion, not a blocker</li>
<li>👍 acknowledges understanding or agreement without adding noise</li>
<li>👀 indicates active review, reducing uncertainty during longer feedback cycles</li>
</ul>
<p>These cues help answer an unspoken question every reader has: <em>How should I read this comment?</em></p>
<p>Studies outside software engineering support this effect. Research on emoji interpretation shows that emojis function as emotional signposts, helping readers more accurately infer a writer's attitude and reducing the likelihood of negative misinterpretation. [^2] In technical discussions, where precision matters and emotional context is sparse, that additional signal carries disproportionate value.</p>
<h2>The Risk of Unshared Meaning</h2>
<p>Emojis are not self-documenting. The same emoji can mean different things to different people, teams, or cultures. A 👀 may read as “I'm reviewing” to one person and “I'm skeptical” to another. A 🔥 might signal enthusiasm, urgency, or risk, depending on context.</p>
<p>Without shared expectations, emojis can introduce a new layer of ambiguity rather than removing one.</p>
<p>At higher levels, this matters even more. Your comments carry more perceived weight, and small signals can be over-interpreted.</p>
<h2>Why Shared Conventions Matter</h2>
<p>The value of emojis in pull request comments doesn't come from the symbols themselves. It comes from <strong>shared understanding</strong>.</p>
<p>When teams align on what certain emojis mean, they become a lightweight vocabulary for intent, tone, and status. Research suggests that consistent emoji usage correlates with higher engagement and lower dropout rates in remote communities—benefits that depend on predictability and shared norms. [^3]</p>
<p>Used this way, emojis function like any other engineering convention: they reduce cognitive load by making intent explicit.</p>
<h2>Making the Convention Part of the Workflow</h2>
<p>On my team, we adopted an existing, community-maintained reference: the <strong>Code Review Emoji Guide</strong>.</p>
<p>https://github.com/erikthedeveloper/code-review-emoji-guide</p>
<p>What makes this guide useful isn't the emojis themselves, but the clarity of intent behind them. A 🔧 signals a required change. A ❓ marks a clarifying question. A ⛏ identifies a non-blocking nit.</p>
<p>Rather than relying on tribal knowledge, we linked the guide directly from our pull request template:</p>
<pre><code>## Code Review Guidance

We use a shared emoji legend to clarify intent in review comments.
Please refer to the Code Review Emoji Guide:
https://github.com/erikthedeveloper/code-review-emoji-guide

Common signals:

- :wrench: — Change requested
- :question: — Clarifying question
- :pick: — Minor nitpick
- :seedling: — Suggestion for future consideration
- :smiley: — Positive feedback or acknowledgment
</code></pre>
<p>Embedding this guidance directly into the PR flow made emoji usage intentional rather than performative. Over time, it became part of the team's review culture.</p>
<h2>Small Signals, Durable Teams</h2>
<p>As engineering teams continue to operate asynchronously, the quality of our written communication matters as much as the quality of our code. Pull requests are no longer just a review mechanism; they are a primary place where trust, shared understanding, and technical judgment are built.</p>
<p>Emojis, used intentionally, help carry context that plain text often loses. They are not a substitute for clear writing or thoughtful feedback. They are small signals that shape how feedback is received and acted upon.</p>
<p>The emojis I reach for most often reflect that mindset. The 🌱 seedling, 🏕️ camping, and 📌 pushpin let me suggest cleanup, refactoring, or future improvements without turning every observation into a required change.
That distinction matters. It keeps reviews focused. It respects momentum. And it creates space for improvement without blocking delivery.</p>
<p>At this level, it's not really about emojis. It's about building systems where feedback moves without friction, preserves clarity, and helps teams move forward with confidence. Emojis just happen to be a lightweight, human tool that supports those goals when used with care.</p>
<p>Small conventions, applied consistently, can materially improve how teams collaborate. Emojis are one such convention. Used well, they help make async work not just tolerable, but sustainable.</p>
<hr />
<p>[^1]:
Cao, J., Lu, J., Guo, Z., Li, S., &amp; Chen, X.<br />
<em>A First Look at Emoji Usage on GitHub: An Empirical Study.</em><br />
https://arxiv.org/pdf/1812.04863.pdf</p>
<p>[^2]:
Miller, H., Thebault-Spieker, J., Chang, S., Johnson, I., Terveen, L., &amp; Hecht, B.<br />
<em>“Blissfully Happy” or “Ready to Fight”: Varying Interpretations of Emoji.</em><br />
https://dl.acm.org/doi/10.1145/2858036.2858380</p>
<p>[^3]:
Riordan, M. A.<br />
<em>Emojis as Tools for Emotion Work in Computer-Mediated Communication.</em><br />
Summary: <em>Emojis help remote workers stay engaged and reduce dropout.</em><br />
https://www.eurekalert.org/news-releases/940515</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/marc-newberry-xKnUnPEUiWA-unsplash.BTWy9iDX.jpg"/>
        </item>
        <item>
            <title><![CDATA[Smarter Prompts, Better Answers]]></title>
            <link>https://agingdeveloper.com/article/2025-07-29-prompt-tips</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2025-07-29-prompt-tips</guid>
            <pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Clear prompts get better answers. Discover practical tips and proven strategies for crafting prompts that help AI chat tools understand exactly what you need.]]></description>
            <content:encoded><![CDATA[<p>I'm not an expert in AI or prompt engineering, but I do use AI tools regularly in both my personal and professional life. Along the way, I've learned a few things worth sharing. These simple strategies can help you get better results when using tools like ChatGPT.</p>
<p>The examples in this article come from a real planning session with <a href="https://chatgpt.com/">ChatGPT</a> to find a family vacation spot for the fall.</p>
<hr />
<h2>Use Clear and Specific Language</h2>
<p>Avoid vague or ambiguous terms in your prompts. The clearer and more specific you are, the better the AI can understand what you're asking for. Instead of saying "Tell me about vacation spots," try to be more specific: "List five vacation destinations."</p>
<h2>Set the Tone or Style You Want</h2>
<p>If you're looking for responses in a particular tone (professional, friendly, casual, or funny) just say so. The AI will adapt its wording to match.</p>
<pre><code>Write your response in a casual tone, like you're talking to a friend.
</code></pre>
<h2>Employ the CARE Method</h2>
<p>I wanted to have a little fun with this article, so I had ChatGPT created a CARE Bear to help illustrate the CARE method. The CARE method is a mnemonic to help you remember how to structure your prompts. It stands for: Context, Ask, Rules, and Examples.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fcare-prompt-bear.DX9kMNWC.png&amp;w=512&amp;h=768&amp;dpl=69a59eb298f6bd0007e61b7b" alt="CARE Bear" /></p>
<p>The bear is here to remind you to be clear, ask specific questions, set rules, and provide examples when crafting your prompts.</p>
<h3>Context</h3>
<p><em>Give background information that sets the scene</em>. This gives the AI a better understanding of what you're looking for. Include giving the AI a specific job title or role to play. So, instead of just asking "Where can my family go on vacation?", you could set up the context by saying:</p>
<pre><code>You are a travel agent specializing in vacation destinations in the United States.
</code></pre>
<h3>Ask</h3>
<p><em>Clearly state what you want the AI to do or answer</em>. Be direct and specific in your request. For example, instead of saying "Tell me about vacation spots," you could ask:</p>
<pre><code>What are five vacation destinations for a family trip? include details about the drive time, weather, and activities.
</code></pre>
<h3>Rules</h3>
<p><em>Add any requirements or limitations</em>. This helps narrow down the options and ensures the AI's response aligns with your needs. For my vacation planning prompt, I added:</p>
<pre><code>The destination should be around a 10-hour drive from Iowa. A stop in Rolla, MO should not extend the drive time beyond an additional hour. The weather should be warmer than Iowa in late November. The destination should be family-friendly and have activities for a 15-year-old and two young adults.
</code></pre>
<h3>Examples</h3>
<p><em>Include sample inputs or outputs to illustrate what you're looking for</em>. These help the AI tailor its answers to your needs.</p>
<pre><code>An example of a suitable destination is Austin, Texas. It has warm weather in late November, is family-friendly, and has activities for teenagers and young adults.
</code></pre>
<h2>Iterate and Refine</h2>
<p>If the AI doesn't give you exactly what you need, don't worry. Rephrase your prompt, add more detail, or ask follow-up questions to guide the conversation.</p>
<pre><code>Can you provide more details about the activities available?
</code></pre>
<h2>Ask the AI to Improve Your Prompt</h2>
<p>You can even ask the AI for help crafting better prompts! Try asking:</p>
<ul>
<li>"How can I rephrase this prompt to get better results?"</li>
<li>"What additional information should I include to make this clearer?"</li>
</ul>
<p>This is a great way to learn what the AI needs in order to give more helpful answers.</p>
<h2>Reuse Prompts That Work</h2>
<p>If you find a prompt that gives you great results, save it! You can reuse good prompts for similar tasks or adapt them slightly for new situations. Think of them as templates for future conversations.</p>
<p><strong>Bonus Tip</strong>: Start a running document of your best prompts for common tasks such as writing emails, planning events, or summarizing documents.</p>
<hr />
<h2>Conclusion</h2>
<p>You don't need to be an AI expert to get great results. By using clear language, setting the tone, and applying the CARE method, you'll get more accurate and useful responses from AI tools like ChatGPT. Treat it like a conversation by asking follow-up questions and clarify your needs. When you find prompts that work well, save them and reuse them as templates.</p>
<p>The key is to be thoughtful and specific. And don't be afraid to experiment.</p>
<p><strong>Happy prompting!</strong></p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/berke-citak-adrO5seSbBE-unsplash.DGWx7Xtu.jpg"/>
        </item>
        <item>
            <title><![CDATA[Keeping Your Git Working Directories Clean]]></title>
            <link>https://agingdeveloper.com/article/2025-02-01-git-cleanup</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2025-02-01-git-cleanup</guid>
            <pubDate>Sat, 01 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Tired of messy Git working directories? The git-cleanup script can help you keep your directories organized with ease.]]></description>
            <content:encoded><![CDATA[<h2>The Power and Messiness of Git</h2>
<p>Git is the backbone of modern software development, providing a powerful way to track, manage, and collaborate on code. Whether you're working solo on a passion project, contributing to open-source, or juggling multiple enterprise repositories, Git keeps everything versioned and organized. Like many developers, I use Git both at work and for personal projects. This site is developed using Git and hosted on <a href="https://github.com/richwklein/agingdeveloper">github.com</a>.</p>
<p>But let's be honest—Git repositories can get messy. Old stashes, merged-but-forgotten branches, and lingering references accumulate, making it harder to navigate and manage your work efficiently.</p>
<p>To tackle this problem, I built <a href="http://github.com/richwklein/git-cleanup">git-cleanup</a>, a lightweight <em>bash</em> script designed to keep your repositories neat and clutter-free. It automates the removal of outdated references, so you can focus on writing great code instead of cleaning up after yourself.</p>
<hr />
<h2>Why Git Repositories Get Messy</h2>
<p>Over time, repositories accumulate digital debris that can slow you down. Here's what typically happens:</p>
<ul>
<li><strong>Forgotten Stashes</strong>: Stashed changes often get left behind, creating unnecessary clutter.</li>
<li><strong>Merged but Undeleted Branches</strong>: Once a branch is merged, it's easy to forget about it.</li>
<li><strong>Detached References</strong>: Temporary commit references linger longer than they should.</li>
<li><strong>Multiple Repositories</strong>: If you manage multiple projects, keeping everything tidy can be overwhelming.</li>
</ul>
<p>If you're like me, all your repositories probably live in a single <code>projects</code> directory, and manually cleaning them up is not how you want to spend your time.</p>
<hr />
<h2>Automating the Cleanup Process</h2>
<p>The <code>git-cleanup</code> script automates this tedious process by removing unnecessary references in your Git repositories. It will:</p>
<ul>
<li>Fetch updates from remote repositories and prunes any remote-tracking references that no longer exist on the remote.</li>
<li>Remove local branches that have been deleted on the remote.</li>
<li>Remove local branches that have been merged into the main branch.</li>
<li>Prune orphaned objects from the local repository.</li>
<li>Check for old stashes and notifies if any are found.</li>
<li>Optionally removes any untracked branches.</li>
</ul>
<h2>How to Use It</h2>
<p>You can run the script with the following options:</p>
<pre><code>Usage: ./git_cleanup.sh [-d directory] [-u]
</code></pre>
<ul>
<li>-d directory: Specify the directory to clean up. Defaults to the current directory (.).</li>
<li>-u: Removes untracked branches.</li>
</ul>
<p>If the specified directory contains a <em>.git</em> file, the script will clean up just that directory. This is useful when working in a specific project and you notice things have gotten unwieldy. Otherwise, the script will recurse into child directories to find and clean up any Git repositories.</p>
<hr />
<h2>Making it easier with an Alias</h2>
<p>Since I run this cleanup regularly, I made it even simpler with a shell alias. Adding this to your <code>.bashrc</code> or <code>.zshrc</code> file saves even more time:</p>
<pre><code>alias cleanup="sh $PROJECTS/git-cleanup/git_cleanup.sh -d $PROJECTS"
</code></pre>
<p>The <code>$PROJECTS</code> is an environment variable that I have set to the directory containing all my projects. This alias will run the <em>git_cleanup.sh</em> script from its checked-out location against the directory specified by <code>$PROJECTS</code>.</p>
<p>Now, cleaning all repositories is just one command away:</p>
<pre><code>&gt; cleanup
</code></pre>
<hr />
<h2>Why I’m Sharing This</h2>
<p>I know many developers have their own Git cleanup scripts, but I wanted to share mine in a structured, reusable format. *git-cleanup** is open-source, free to use, and available for anyone to enhance.</p>
<p>If you find it useful, give it a try and let me know how it works for you. You can also contribute improvements or suggestions on the <a href="http://github.com/richwklein/git-cleanup">git-cleanup repository</a>.</p>
<p>Keeping your Git environment clean doesn't have to be a hassle. Automate it once, and spend more time writing code instead of managing clutter.</p>
<p>Happy coding!</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/jennifer-burk-wP9yLk_VKI8-unsplash.CT5fMRRN.jpg"/>
        </item>
        <item>
            <title><![CDATA[From Chalkboard to Screen]]></title>
            <link>https://agingdeveloper.com/article/2024-12-11-quote-board</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2024-12-11-quote-board</guid>
            <pubDate>Wed, 11 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I introduce an exciting new feature on my website, inspired by the tradition of jotting down quotes on our kitchen chalkboard.]]></description>
            <content:encoded><![CDATA[<p>When I had my original website, it felt like a way to share pieces of my life that meant the most. Back then, I often posted quotes that resonated with me, ideas I wanted to hold onto or share with others. But when Rachel hung the chalkboard in our kitchen, it quickly became my favorite canvas. I'd carefully choose a quote each week that captured our mood, our milestones, or simply a lesson worth remembering.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fkitchen-chalkboard.vyipzCsj.png&amp;w=1280&amp;h=960&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Kitchen Chalkboard" /></p>
<p>I wanted to bring that same spirit to this site. Sharing those chalkboard quotes online felt like a way to extend our kitchen beyond its walls, letting others connect with the moments and meaning that shaped our family. Sometimes, I imagine visitors reading those quotes and trying to guess what's going on in our lives — what event, challenge, or joy might have inspired the words that week.</p>
<p>The virtual quote board is officially live. You'll find it just below the lead article, featuring the most recent quote pulled straight from our kitchen chalkboard. Clicking on it will take you to a page with past quotes—a growing collection of the words we've shared here. These don't go all the way back to when I first started jotting them down on the board, but they do go back to when I first decided to share them online. It's a small but meaningful step in capturing the moments that shape us.</p>
<p><a href="https://agingdeveloper.com/#quote-card-lead"><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fvirtual-quote-board.CGo94Vfi.png&amp;w=2542&amp;h=344&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Quote Board" /></a></p>
<p>So how did the Virtual Quote Board come to life?</p>
<h2>Upgrading to the Latest Astro Framework</h2>
<p>This site is built using the Astro framework, which initially had a somewhat rigid content layer for defining data. Originally, I would have needed a separate file for each quote—something I wanted to avoid. Fortunately, Astro 5.0 introduced updated content APIs that allowed me to consolidate all the data into a single file, making the process far more efficient. I've mentioned before that <a href="https://agingdeveloper.com/article/2022-02-26-major-harmful">major version updates can be risky</a>, and the upgrade from Astro 4.x to 5.x was no exception. It required modifying 49 files along the way, but the end result was worth the effort.</p>
<h2>Defining the Quote Content Collection</h2>
<p>The Astro <code>defineCollection</code> method is used to define a content collection. It requires a <code>loader</code> function to load the data and a <code>schema</code> function to define the structure of the data within the collection. For this, the <code>Zod</code> library is used to define and validate the schema.</p>
<pre><code>const quote = defineCollection({
  loader: file('src/content/data/quote.json'),
  schema: () =&gt;
    z.object({
      text: z.string().min(1, 'Quote text cannot be empty'),
      author: z.string().min(1, 'Author name cannot be empty').default('Anonymous'),
      chalked: z
        .string()
        .date()
        .transform((val) =&gt; new Date(val)),
    }),
})
</code></pre>
<p>The schema above represents the structure I created for the quotes. The data is stored in an array within a single JSON file, where each entry contains a quote. Each quote consists of the text, the author's name, and the date it was written on the chalkboard. If the author's name is left out, it defaults to "Anonymous," ensuring that the entry is still valid without requiring additional edits.</p>
<p>I chose the property name "chalked" because it felt most representative of the context. Initially, I considered "published," but that seemed ambiguous—it could imply the date the original author published the quote or the date it was published on this site. "Chalked" captures the essence of when the quote first appeared on our kitchen chalkboard.</p>
<p>I considered adding a source citation property, but much of the time, I'm unfamiliar with the origins of the quote.</p>
<h2>Designing the Quote Card Components</h2>
<p>When displaying quotes, I wanted to use the most semantically correct HTML tags to ensure both accessibility and proper structure. I based my approach on the CSS-Tricks <a href="https://css-tricks.com/quoting-in-html-quotations-citations-and-blockquotes/#aa-hey-what-about-the-figure-element">article</a> about quoting in HTML. Following this guidance, I structured the quote text inside a <code>&lt;blockquote&gt;</code> element nested within a <code>&lt;figure&gt;</code>. The author is included in a <code>&lt;figcaption&gt;</code> for proper attribution, and the "chalked" date is displayed using a <code>&lt;time&gt;</code> element for semantic clarity.</p>
<p>Here's an example of the markup:</p>
<pre><code>&lt;figure&gt;
  &lt;blockquote class:list={['text-3xl tracking-wide']}&gt;
    {quote.data.text}
  &lt;/blockquote&gt;
  &lt;figcaption&gt;&amp;mdash;&amp;nbsp;{quote.data.author}&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;time datetime={quote.data.chalked.toISOString()} class:list={['mt-4 block']}&gt;
  {formatDate(quote.data.chalked)}
&lt;/time&gt;
</code></pre>
<p>This structure ensures the content is both visually appealing and easy to interpret for assistive technologies. By combining semantic HTML with thoughtful styling, the quotes are presented in a way that feels intentional and polished.</p>
<p>I settled on two variations for displaying quotes, both using the same semantic markup as shown above. On the historic quotes page, the design takes a simple approach with a card outline, utilizing the standard fonts and colors consistent with the rest of the site. For the <strong>lead</strong> variation displayed on the home page, I wanted it to evoke the feel of an actual chalkboard and link to the historic page. To achieve this, I incorporated a chalkboard background image, a thick yellow border, and a font styled to resemble chalk.</p>
<pre><code>&lt;div
  id="quote-card-lead"
  class:list={[
    'bg-black bg-[url("../content/image/chalkboard.jpg")] bg-cover bg-top-left',
    'font-chalk text-base text-slate-300',
    'rounded-md border-4 border-yellow-600',
    'flex h-full min-h-40 flex-col justify-between p-4',
    className,
  ]}
&gt;
&lt;/div&gt;
</code></pre>
<p>The <code>font-chalk</code> class specifies a custom font family defined in my Tailwind configuration:</p>
<pre><code>chalk: ['Walter Turncoat', ...defaultTheme.fontFamily.sans],
</code></pre>
<p>The font, <strong>Walter Turncoat</strong>, was downloaded and installed from <a href="https://fontsource.org/fonts/walter-turncoat">fontsource.org</a>. It is a playful, chalk-like style that adds the perfect touch to bringing the chalkboard theme to life.</p>
<h2>Integrating Quote Components Across the Site</h2>
<p>I integrated the quote components into two key pages: the home page and the historic quotes page. The home page highlights the most recent quote, while the historic page lists all quotes in descending order based on their chalked dates. To manage this functionality, I created two utility methods to retrieve and sort the quote data.</p>
<pre><code>export const getQuotes = async (limit?: number, exclude?: string): QuotesResponse =&gt; {
  const quotes = await getCollection('quote')
  return quotes
    .filter((quote: CollectionEntry&lt;'quote'&gt;) =&gt; quote.id != exclude)
    .sort(
      (a: CollectionEntry&lt;'quote'&gt;, b: CollectionEntry&lt;'quote'&gt;) =&gt;
        b.data.chalked.valueOf() - a.data.chalked.valueOf()
    )
    .slice(0, limit)
}

export const getLatestQuote = async (): Promise&lt;CollectionEntry&lt;'quote'&gt;&gt; =&gt; {
  const [latest] = await getQuotes(1)
  return latest
}
</code></pre>
<ul>
<li>
<p><strong><code>getQuotes</code> Method</strong>: This retrieves all quote entries from the collection and sorts them by their chalked dates in descending order. It can accept an optional <code>limit</code> parameter to specify the maximum number of quotes to return and an <code>exclude</code> parameter to filter out a specific quote by ID. The method returns an array of quotes based on these parameters.</p>
</li>
<li>
<p><strong><code>getLatestQuote</code> Method</strong>: This leverages the <code>getQuotes</code> method with a limit of one, returning the most recent quote as a single entry.</p>
</li>
</ul>
<p>With those two methods defined, wiring up the components becomes straightforward. Below is an example of how the lead quote component is integrated into the home page:</p>
<pre><code>---
/**
 * The home page with a lead article and most recent.
 */
import ArticleCardLead from '@components/ArticleCardLead.astro'
import ArticleGrid from '@components/ArticleGrid.astro'
import BackLink from '@components/BackLink.astro'
import QuoteCardLead from '@components/QuoteCardLead.astro'
import Layout from '@layouts/Layout.astro'
import { getArticles } from '@utils/article'
import { getLatestQuote } from '@utils/quote'
import { getDefaultSite } from '@utils/site'

const articles = await getArticles(9)
const site = await getDefaultSite()
const data = site.data
const lead = articles.shift()
const latest = await getLatestQuote()
---

&lt;Layout site={site}&gt;
  {lead ? &lt;ArticleCardLead article={lead} /&gt; : null}
  &lt;QuoteCardLead quote={latest} class:list={['mt-4']} /&gt;
  &lt;ArticleGrid articles={articles} foldCount={4} class:list={['mt-4']} /&gt;
  &lt;BackLink name="articles" to={'/article/archive-1'} class:list={['mt-4']} /&gt;
&lt;/Layout&gt;
</code></pre>
<h2>Bringing Quotes to Life Online</h2>
<p>Developing this feature was both fun and meaningful, offering a little glimpse into the moments that shape life in our house. By sharing these quotes online, I hope to capture some of the spirit behind our family's kitchen chalkboard while making the content easy to explore.</p>
<p>If you're curious about the technical details, the site's code is open source and available online. You can check out the <a href="https://github.com/richwklein/agingdeveloper/pull/715">pull request</a> for this feature, along with this article, at the project's repository: <a href="https://github.com/richwklein/agingdeveloper">agingdeveloper on GitHub</a>.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/joyce-hankins-Th4AD-YDEjI-unsplash.CbkYnQgC.jpg"/>
        </item>
        <item>
            <title><![CDATA[How to Add Privacy-Focused Analytics to Your Site with Umami]]></title>
            <link>https://agingdeveloper.com/article/2024-11-03-umami</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2024-11-03-umami</guid>
            <pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to set up Umami Analytics for precise, privacy-first tracking, enabling you to make data-driven decisions while respecting user confidentiality.]]></description>
            <content:encoded><![CDATA[<p>I’ve recently been exploring different analytics platforms, and during my research, I came across <em>Umami</em>—an open-source, privacy-focused analytics tool that serves as a powerful alternative to Google Analytics. By default it does not collect any personal information and all data is anonymized.</p>
<p>I had the chance to create a proof-of-concept using <em>Umami</em>, and I thought it would be valuable to share my experience. To demonstrate the usage of <em>Umami</em>, I'm going to replace this sites current analytics implementation with one based on <em>Umami</em>.</p>
<h2>Server Setup</h2>
<p>The first step is setting up a server to collect analytics. Umami offers a <a href="https://umami.is/pricing">cloud version</a> with various pricing options, including a free tier for hobby sites. Alternatively, you can host Umami on your own server; all you need is a database and server. There are several guides available for <a href="https://umami.is/docs/guides/hosting">self-hosting</a>. Since this is just a demonstration, I'll be running the database and Node.js server locally using Docker.</p>
<h3>Step 1: Clone the Umami Repository</h3>
<p>To get started, pull the latest source code from the Umami repository using Git:</p>
<pre><code>git clone https://github.com/umami-software/umami.git
</code></pre>
<h3>Step 2: Start the Docker Containers</h3>
<p>Next, use Docker Compose to start the Docker containers with PostgreSQL support:</p>
<pre><code>docker compose up -d
</code></pre>
<h3>Step 3: Access the Umami Dashboard</h3>
<p>Once the Docker containers are up and running, navigate to the local address where the server is running, <code>http://localhost:3000</code>. Log in with the default credentials:</p>
<ul>
<li>Username: admin</li>
<li>Password: umami</li>
</ul>
<h3>Step 4: Add a Website to Track</h3>
<p>After logging in, create a website by navigating to <em>Settings &gt; Website</em> and clicking the "Add Website" button. For this example, I'm using <strong>The Aging Developer</strong> as the site name and <code>localhost</code> as the domain, since this is a local demonstration. Be sure to enter your actual domain if you're setting this up for a live site.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fadd-website-screenshot.z684e3v8.png&amp;w=2376&amp;h=1762&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Add Website" /></p>
<h3>Step 5: Retrieve the Tracking Code</h3>
<p>Once the website is saved, click the "Edit" button next to it, then go to the <em>Tracking Code</em> tab. This tab contains a script that needs to be inserted into the site’s HTML to start tracking visits. I'll modify this script in the next section to work with the <em>Astro</em> framework this site is built on.</p>
<pre><code>&lt;script
  defer
  src="http://localhost:3000/script.js"
  data-website-id="46903d23-08ec-4519-9bb2-d6594f1110fa"
&gt;&lt;/script&gt;
</code></pre>
<h2>Start Tracking</h2>
<p>In <em>Astro</em> components, scripts are hoisted and loaded as JS modules by default. To ensure that our tracking script runs inline and does not get hoisted, we need to modify it accordingly. This script will be placed in the <code>&lt;head&gt;</code> of the page, which is located in the <code>Layout</code> component. In a production environment, I recommend moving the "website-id" into an environment variable and using that in the data-website-id property.</p>
<pre><code>&lt;script
  is:inline
  async
  src="http://localhost:3000/script.js"
  data-website-id="46903d23-08ec-4519-9bb2-d6594f1110fa"
&gt;&lt;/script&gt;
</code></pre>
<p>With that script in place, <em>Umami</em> will automatically start capturing page views. After starting the Astro local development server with <code>npx astro dev</code> and navigating to this article in progress, I received two page view events.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Finitial-event-screenshot.BM5cSAXh.png&amp;w=2684&amp;h=1356&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Initial Events" /></p>
<h2>Custom Events</h2>
<p>In addition to basic page views, I also want to track clicks on outbound links and instances when a user utilizes the action button to scroll back to the top of the page, using two different techniques.</p>
<h3>Scroll Top</h3>
<p>The <code>ScrollTop</code> component is a button wrapped in a custom HTML element. This button already has a click listener attached to it. To implement custom tracking, I simply added a call to the tracking function within the listener.</p>
<pre><code>const button = this.querySelector('button')
button?.addEventListener('click', () =&gt; {
  window.umami.track('scroll-top')
  window.clearTimeout(timer)
  window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
})
</code></pre>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fscroll-top-event-screenshot.BfCPFEmf.png&amp;w=2378&amp;h=202&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Scroll Top Event" /></p>
<h3>Outbound Links</h3>
<p>Another powerful way <em>Umami</em> allows you to track events is by using a data property that specifies the event name. I created an Astro component called <code>LinkExternal</code>, which serves as a wrapper for all links to external sites. This component includes some default properties already set.</p>
<pre><code>&lt;a
  data-link="external"
  data-umami-event="external-link"
  target="_blank"
  rel="noopener noreferrer"
  href={to}
  class:list={[className]}
  title={title}
&gt;
  &lt;slot /&gt;
&lt;/a&gt;
</code></pre>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fexternal-link-event-screenshot.D7HLL9U2.png&amp;w=2394&amp;h=202&amp;dpl=69a59eb298f6bd0007e61b7b" alt="External Link Event" /></p>
<p>If I wanted to track additional dimensions for this event, such as the URL being navigated to, I could use a click handler in conjunction with the tracking function.</p>
<h2>Conclusion</h2>
<p>As you can see, it is relatively quick and straightforward to start collecting metrics for your site while utilizing an open-source library that helps you stay privacy-focused.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/choong-deng-xiang--WXQm_NTK0U-unsplash.1PL8b2ZQ.jpg"/>
        </item>
        <item>
            <title><![CDATA[Houston, We Have Liftoff]]></title>
            <link>https://agingdeveloper.com/article/2024-08-16-astro-launch</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2024-08-16-astro-launch</guid>
            <pubDate>Fri, 16 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The seemingly annual process of reworking this site in another framework.]]></description>
            <content:encoded><![CDATA[<p>A few years ago, I started eyeing the framework <a href="https://astro.build/">Astro</a>, and it immediately caught my attention. When I refreshed the site last year by updating to a new version of <a href="https://www.gatsbyjs.com/">Gatsby</a>, I considered making the switch to Astro at that time. However, I decided against it, as it would require learning a new toolset, and I believed that Gatsby would offer an easier upgrade path and simpler long-term maintenance to keep the site running smoothly.</p>
<h2>Gatsby Concerns</h2>
<p>I believe it's important to carefully manage any changes that could disrupt existing systems. However, I've become increasingly concerned about the future of the Gatsby framework. According to their <a href="https://www.gatsbyjs.com/docs/reference/release-notes/gatsby-version-support/">long term support policy</a>, each version of Gatsby is only supported for one year. The latest major version was released on November 8th, 2022, and since then, there hasn't been any significant feature development. In fact, there's an ongoing discussion within the Gatsby community questioning whether the project has been discontinued. Jared Scott, a member of the community, provided a detailed <a href="https://github.com/gatsbyjs/gatsby/issues/38696#issuecomment-2103823965">analysis</a> of recent commits, showing that no substantial updates have been made.</p>
<p>Additionally, Gatsby was <a href="https://www.netlify.com/press/netlify-acquires-gatsby-inc-to-accelerate-adoption-of-composable-web-architectures/">acquired</a> by Netlify in February 2023. Shortly after, Netlify experienced a <a href="https://www.netlify.com/blog/ceo-announcement-to-the-netlify-team/">round of layoffs</a> in July 2023, which adds to the uncertainty surrounding Gatsby's future.</p>
<h2>Astro Exploration</h2>
<p>Given my concerns about Gatsby, I began exploring Astro as an alternative about three weeks ago. Since then, I've made significant changes behind the scenes: transitioning from JavaScript to TypeScript, replacing React components with Astro components, and switching from Material UI to Tailwind for styling. I'll dive deeper into these changes throughout this article. Today, I'm excited to celebrate the launch of The Aging Developer, now completely rewritten using the Astro framework.</p>
<h2>Typescript</h2>
<p>TypeScript is a version of JavaScript that includes extra features to help developers write more reliable code. One of its key benefits is adding "types" to variables, which means the code can check for certain errors before it even runs. I’ve attempted to switch my site to TypeScript a few times in the past, but it required a lot of setup, and I could never get it quite right. What’s great about Astro is that it comes with built-in TypeScript support, making the transition much smoother. The documentation provided clear instructions, and I was able to get everything set up without much hassle. Previously, with Gatsby, I used a package called <code>prop-types</code> to define the types for my components. Now, with TypeScript, I can easily do this using an interface, which is both simpler and more powerful.</p>
<pre><code>interface Props {
  image: ImageMetadata
  imageAlt?: string
  site: CollectionEntry&lt;'site'&gt;
}
</code></pre>
<p>Astro includes a handy <a href="https://docs.astro.build/en/guides/typescript/#type-checking">check</a> function that automatically scans your code for errors, such as type mismatches, ensuring everything works smoothly before building the site.</p>
<h2>Components</h2>
<p>Astro components are made up of two main parts. The first part, known as the "frontmatter," contains the component’s script, which runs during the website's build process. The second part is the HTML template, which uses a mix of standard HTML and JSX-like syntax to structure the content. If you need to add interactivity on the client side (like responding to user clicks), you can do this with script tags within the component. Since I only need a small amount of client-side interaction, I opted to use <strong>web components</strong> for those tasks. For example, I created a component that displays a floating action button (FAB) that lets users quickly scroll back to the top of the page.</p>
<pre><code>&lt;scroll-top class:list={['fixed bottom-8 right-8']}&gt;
  &lt;button
    class:list={[
      'rounded-full bg-secondary-main p-2 text-secondary-contrast opacity-0 shadow-xl',
      'hover:bg-secondary-dark focus:bg-secondary-dark'
    ]}
  &gt;
    &lt;Icon title="Scroll to Top" name="mdi:keyboard-arrow-up" class:list={['h-6 w-6 ']} /&gt;
  &lt;/button&gt;
&lt;/scroll-top&gt;

&lt;script&gt;
  // Define the behaviour for our new type of HTML element.
  class ScrollTop extends HTMLElement {
    constructor() {
      super()

      let timer: number
      const button = this.querySelector('button')
      button?.addEventListener('click', () =&gt; {
        window.clearTimeout(timer)
        window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
      })

      const showButton = () =&gt; {
        window.clearTimeout(timer)
        timer = window.setTimeout(() =&gt; {
          if (document.body.scrollTop &gt; 100 || document.documentElement.scrollTop &gt; 100) {
            button?.classList.remove('fade-out')
            button?.classList.add('fade-in')
          } else {
            button?.classList.remove('fade-in')
            button?.classList.add('fade-out')
          }
        }, 2.5)
      }

      window.onscroll = () =&gt; {
        showButton()
      }
    }
  }

  // Tell the browser to use our ScrollTop class for &lt;scroll-top&gt; elements.
  customElements.define('scroll-top', ScrollTop)
&lt;/script&gt;
</code></pre>
<p>Astro is compatible with various component libraries, including React, Vue, Svelte, and more. This site initially used <a href="https://react.dev/">React</a> components along with the <a href="https://mui.com/material-ui/">Material UI component library</a>.</p>
<p>However, as I transitioned to Astro components, I decided to replace the React components entirely. Since Material UI is specifically designed for React, I needed a new approach to styling. I opted for <a href="https://tailwindcss.com/">Tailwind CSS</a>, a utility-first CSS framework. Tailwind doesn’t provide pre-made components like Material UI, but it is great in building custom designs. I used <a href="https://tailwindflex.com/">TailwindFlex</a>, as a valuable guide for creating the components for the site. The ScrollTop component above demonstrates how Tailwind CSS can be used to achieve the desired look.</p>
<pre><code>  &lt;button
    class:list={[
      'rounded-full bg-secondary-main p-2 text-secondary-contrast opacity-0 shadow-xl',
      'hover:bg-secondary-dark focus:bg-secondary-dark'
    ]}
  &gt;
</code></pre>
<h2>Content</h2>
<p>While Gatsby uses <a href="https://graphql.org/">GraphQL</a> to provide data for generating pages, Astro takes a different approach. In Astro, content collections are defined in a configuration file, with <a href="https://zod.dev/">Zod</a> used to define the schema for each collection.</p>
<pre><code>const site = defineCollection({
  type: 'data',
  schema: ({ image }) =&gt;
    z.object({
      title: z.string(),
      tagline: z.string(),
      category: z.string(),
      repository: z.string(),
      avatar: image(),
      icon: image(),
      background: z.string(),
      theme: z.string(),
      displayLimit: z.number(),
    }),
})
</code></pre>
<p>JSON files in the src/content/site directory are then converted into entries in the collection, which can be retrieved by the filename as their ID:</p>
<pre><code>const site = await getEntry('site', 'agingdeveloper')
</code></pre>
<p>Alternatively, a collection can be iterated over as a whole:</p>
<pre><code>export const getArticles = async (limit?: number) =&gt; {
  const articles = await getCollection('article')
  return articles
    .sort((a, b) =&gt; b.data.published.valueOf() - a.data.published.valueOf())
    .slice(0, limit)
}
</code></pre>
<h2>Issues</h2>
<p>I've encountered a few challenges and limitations. Manipulating image sizes has been difficult when working with endpoint APIs compared to standard pages. Additionally, the site has lost the blurred image placeholders that were easily implemented in Gatsby. Since Astro uses <a href="https://sharp.pixelplumbing.com/">Sharp</a> as its default image service, I believe these issues can be resolved, but they do require some additional effort.</p>
<p>Astro components currently fall short in terms of documentation support, though this is on the roadmap for future updates. Another challenge has been the need to rely on an experimental feature to write unit tests for my components, which isn’t ideal for stability or long-term maintenance.</p>
<h2>Impressions</h2>
<p>Overall, I have a positive impression of the framework. The speed at which the development server runs and builds is impressive. I can build the entire site with image optimizations from a cold start in under 30 seconds—a significant improvement over the previous setup, where just starting the dev server took longer. Astro delivers the static HTML and CSS generation I had been hoping for, making it a strong choice for content focused projects.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/spacex-6SbFGnQTE8s-unsplash.BqRRdYzT.jpg"/>
        </item>
        <item>
            <title><![CDATA[GoPro Timeline Fix]]></title>
            <link>https://agingdeveloper.com/article/2024-02-11-gopro-dates</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2024-02-11-gopro-dates</guid>
            <pubDate>Sun, 11 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[How I fix created date information in GoPro videos to correctly locate the media in my library timelines.]]></description>
            <content:encoded><![CDATA[<p>When we started to travel more extensively as a family I wanted to get a GoPro. I thought that it would be worthwhile for capturing moments we normally couldn't get with a regular camera or phone. I ended up purchasing the <a href="https://www.amazon.com/GoPro-HERO6-Black-Waterproof-Digital/dp/B074X5WPC5">Hero 6 Black</a> which was the newest model at the time. I've used it on several of our vacations and we've got a trip coming in the fall where I'll likely use it again. It is especially handy while in the water. I've captured some really great moments like the one below.</p>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/IRFGZpP04MQ?si=lbEt5v6q0TmvDBIw"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<h2>Date Issues</h2>
<p>I've had an annoying issue with my GoPro though. Any time a camera isn't used for an extended period of time the date and time can get off. If I haven't used my GoPro in a while and then use it before either manually setting the time or syncing the camera with my phone then the date embedded in the video are off. The date on the camera and the recorded video is something like <em>January 1, 2016 at 12:00 PM</em>.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fdate-warning.BWBu4B4o.png&amp;w=1180&amp;h=1117&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Date Warning" title="GoPro Quick date warning" /></p>
<p>This happens with lots of different cameras not just this brand. A lot of people probably just live with it or aren't bothered by it. When I go back to look at vacations we've taken, those videos are not listed with the others making them hard to find.</p>
<p>When I first realized I had some files with this problem I started searching for a solution. At that time editing the created date in the <a href="https://gopro.com/en/us/shop/quik-app-video-photo-editor">GoPro Quick</a> application changed it only in that one application. Changing the file created timestamp in the file system worked for some libraries, but not all of them. Our media library has been spread out over various devices and cloud services over the years and each library has a little bit different algorithm for figuring out timelines. So I needed to figure out a way to fix the created date everywhere we store media.</p>
<h2>EXIF Data</h2>
<p>All modern devices that take photos or video embed metadata in the those files. That metadata is in a format called EXIF which stands for "Exchangeable image file format". The metadata that is embedded usually includes things like: the camera model, lens, resolution, geolocation, and created timestamp.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fexif-data.MxLN1Spz.png&amp;w=556&amp;h=1008&amp;dpl=69a59eb298f6bd0007e61b7b" alt="EXIF Data" title="EXIF data display in Mac Photos" /></p>
<p>Changing those embedded dates is the only way I've found that fixes where these videos show up in timelines across all our media libraries.</p>
<h2>The Solution</h2>
<p>There is a great command line application <a href="https://exiftool.org/">exiftool</a> that can be use to view and update the EXIF data in various media file types. For example:</p>
<pre><code>exiftool -d "%Y-%m-%d %H:%M:%S" -CreateDate -s -S GH014479.MP4

</code></pre>
<p>Outputs the create date tag using the passed in datetime format "2022-02-19 09:39:21" for the passed in file. It is super powerful, but not very practical for manipulating batches of files.</p>
<p>I wrote a python script that you can find in the <a href="https://github.com/richwklein/exif-dates">exif-dates</a> repo that wraps <code>exiftool</code> specifically for updating batches of GoPro videos and photos all at once.</p>
<p>The script scans the passed in directory finding all <em>mp4</em>, <em>lrv</em>, <em>jpg</em>, and <em>thm</em> files. It extracts the CreateDate from the EXIF metadata of the found files and pulls the timestamp from the date. The time delta that is passed in gets added to the extracted time. Finally, multiple metadata dates get set to the new date with the updated timestamp.</p>
<p>The dates that get set are: CreateDate, TrackCreateDate, TrackModifyDate, MediaCreateDate, and MediaModifyDate.</p>
<p>So for example if you wanted to set all the files in a directory to January 10, 2024 and shift all the times by 4 hours you call the script like:</p>
<pre><code>python exif_dates.py "/Users/richard/copy" --date="2024-01-10" --delta=240
</code></pre>
<p>The output of the script lists the file being changed along with the old timestamp and the new timestamp.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fscript-output.B0xDSckJ.png&amp;w=1036&amp;h=570&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Script Output" title="exif-date python script output" /></p>
<p>The <code>exif-dates</code> script has a <code>dry</code> argument that you can be used to see what will be changed without actually making the change.</p>
<p>To be safe I put all the files I want to update in a separate directory making a backup of them. Then I call the script from the command line with the <code>dry</code> argument to see what the new dates will be. Once I'm satisfied with the output I remove the dry argument and let it run for real. After all the files are updated I move them back to their original locations.</p>
<p>Valid arguments to the script are:</p>
<ul>
<li>directory: Full path to the directory to process.</li>
<li>date: New date in the YYYY-MM-DD format.</li>
<li>delta: Time delta in minutes.</li>
<li>dry: Execute as a dry run with no updates.</li>
</ul>
<h2>Conclusion</h2>
<p>I wrote the <strong>exif-dates</strong> script to scratch my own itch and fix a problem that I was having. It has come in handy a few different times when I've figured out I had videos in the wrong spot. It is a little bit technical, but the script is publicly available if you think it might help you fix your own photos and videos.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/jakob-owens-PJziurStmac-unsplash.CZP9cTFQ.jpg"/>
        </item>
        <item>
            <title><![CDATA[Switching Code Blocks to prism-react-renderer]]></title>
            <link>https://agingdeveloper.com/article/2024-01-13-mdx-codeblock</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2024-01-13-mdx-codeblock</guid>
            <pubDate>Sat, 13 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The process to switch the site from gatsby-remark-prismjs to prism-react-renderer]]></description>
            <content:encoded><![CDATA[<p>When I was <a href="https://agingdeveloper.com/article/2023-12-31-gatsby-rebuild">rebuilding</a> this site, I spent a fair amount of time in the documentation for each plugin that is being used. While reading through the <a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-mdx/?=mdx#gatsby-plugin-mdx">gatsby-plugin-mdx</a> page, I ran across a <em>gatsby-remark</em> section. That section suggests switching to use the <strong>prism-react-renderer</strong> for code blocks.</p>
<blockquote>
<p>In some cases, like gatsby-remark-prismjs, it makes more sense to use a library like prism-react-renderer to render codeblocks using a React component.</p>
</blockquote>
<p>The Aging Developer was previously using the <strong>gatsby-remark-prismjs</strong> plugin so I created a Github issue to replace it. I tackled that <a href="https://github.com/richwklein/agingdeveloper/issues/572">issue</a> this week. There is a wonderful <a href="https://prince.dev/prism-react-renderer">article</a> that I used as a guide while I did it. That article got me most of the way there, but there were a couple of gotchas that had to be figured while I was doing it. I'm sure others will run into the same issues, so I thought it worthwhile for me to write about it.</p>
<p>To write a custom component that uses the <strong>prism-react-renderer</strong> package, I first installed the <strong>prism-react-renderer</strong> package.</p>
<pre><code>npm install prism-react-render
</code></pre>
<h2>Associating the Component with an MDX Shortcode</h2>
<p>The <code>MDXProvider</code> that is used in this site's <a href="https://github.com/richwklein/agingdeveloper/blob/main/src/templates/article.jsx">article template</a> needs to know that a custom component should be used when rendering code blocks. This is done by associating the component with an mdx <a href="https://mdxjs.com/table-of-components">shortcode</a>.</p>
<pre><code>import MDXCode from '../components/MDXCode'
import MDXLink from '../components/MDXLink'

const components = {
  pre: MDXCode,
  a: MDXLink,
}

;&lt;Grid item md={9} sm={12} xs={12}&gt;
  &lt;MDXProvider components={components}&gt;{children}&lt;/MDXProvider&gt;
&lt;/Grid&gt;
</code></pre>
<p>Here is where this site's setup is a little different from the original article I was using. That article replaces the <code>&lt;pre&gt;</code> tag with a <code>&lt;div&gt;</code> then uses the custom component for <code>&lt;code&gt;</code> tags. What I found is that setup caused the new component to be used for inline code blocks as well. I possibly could have made that work by looking at the type of the children prop being passed in and handling a string differently than a react component. Instead I followed this <a href="https://github.com/hashicorp/next-mdx-remote/issues/244#issuecomment-1061832370">hashicorp bug comment</a> and matched the component with the <code>&lt;pre&gt;</code> tag instead.</p>
<p>To handle inline code blocks I added some basic css to style it.</p>
<pre><code>code {
  border-radius: 0.3em;
  padding: 0.15em 0.25em;
  /* colors taken from the vsDark them */
  color: rgb(156, 220, 254);
  background-color: rgb(30, 30, 30);
}
</code></pre>
<h2>Writing the Code Block Component</h2>
<p>The <code>MDXProvider</code> will use the <code>MDXCode</code> component when rendering any <code>&lt;pre&gt;</code> tags. So we have to define that component.</p>
<pre><code>export const MDXCode = ({ children }) =&gt; {
  if (children.type != 'code') {
    return &lt;pre&gt;{children}&lt;/pre&gt;
  }

  const {
    props: { className, children: code },
  } = children
  const language = className?.replace(/language-/, '').trim() || ''

  return (
    &lt;Highlight theme={themes.vsDark} code={code.trim()} language={language}&gt;
      {({ className, style, tokens, getLineProps, getTokenProps }) =&gt; (
        &lt;pre className={className} style={{ ...style }}&gt;
          {tokens.map((line, index) =&gt; {
            const lineProps = getLineProps({ line, key: index })
            return (
              &lt;div key={index} {...lineProps}&gt;
                {line.map((token, key) =&gt; (
                  &lt;span key={key} {...getTokenProps({ token, key })} /&gt;
                ))}
              &lt;/div&gt;
            )
          })}
        &lt;/pre&gt;
      )}
    &lt;/Highlight&gt;
  )
}

export default MDXCode
</code></pre>
<p>The html <code>&lt;pre&gt;</code> tag defines preformatted text. The MDX outputs that around our code block. There is still a slim chance that tag may be used for something other than code. The first thing our new component does is check that the child react element is of a "type" <strong>code</strong>. If not the component just wraps the children in a <code>&lt;pre&gt;</code> so that it would have behaved similar to if the site wasn't using a custom component at all.</p>
<pre><code>if (children.type != 'code') {
  return &lt;pre&gt;{children}&lt;/pre&gt;
}
</code></pre>
<p>Next the component gets the className and the children of the children (which is the actual code). The component then pulls the language from the className. These are then used by the <code>&lt;Highlight&gt;</code> component that <strong>prism-react-renderer</strong> provides.</p>
<pre><code>const {
  props: { className, children: code },
} = children
const language = className?.replace(/language-/, '').trim() || ''
</code></pre>
<p>The rest of the component is pretty much the same as all the examples for using the <code>&lt;Highlight&gt;</code> component including those on the article I was following along with. I am using the <em>vsDark</em> theme and added a little css to add some padding and a border radius.</p>
<pre><code>pre[class*='language-'] {
  border-radius: 0.3em;
  padding: 0.2em 0.6em;
}
</code></pre>
<p>The other thing that the component does is trim the code string so as to remove any trailing new lines or spaces. A trailing new line was another issue that I was running into.</p>
<p>I added a snapshot test to make sure the component was rendering like I wanted and a test that made this component wraps a non-code children in a <code>&lt;pre&gt;</code> and return it. With those tests the component was complete and was being used for the MDX.</p>
<h2>Removing gatsby-plugin-prismjs</h2>
<p>The prismjs package had been installed directly so that one of the built-in themes could be used. I remove the reference to that theme from the <em>PageLayout</em> component. Uninstalled the old plugin and remove the reference to it from the <em>gatsby-config.mjs</em></p>
<pre><code>-          {
-            resolve: "gatsby-remark-prismjs",
-            options: {
-              classPrefix: "language-",
-              showLineNumbers: false,
-              noInlineHighlight: false,
-            },
-          },
</code></pre>
<h2>Done</h2>
<p>That is it. The <strong>gatsby-remark-prismjs</strong> package has been removed and the site is now using the <strong>prism-react-renderer</strong> component instead. The code inside that component is pretty verbose, but it does give my a lot of flexible to how I want to render it. In the future I can add things like line numbers and code highlighting pretty easily if I want.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/ferenc-almasi-L8KQIPCODV8-unsplash.DqvCWMM_.jpg"/>
        </item>
        <item>
            <title><![CDATA[Rebuilding with Gatsby]]></title>
            <link>https://agingdeveloper.com/article/2023-12-31-gatsby-rebuild</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2023-12-31-gatsby-rebuild</guid>
            <pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[The journey to bring this site back to life through a rebuild on the latest major releases.]]></description>
            <content:encoded><![CDATA[<p>I've previously wrote that <a href="https://agingdeveloper.com/article/2022-02-26-major-harmful">major versions should be considered harmful</a>. I haven't done anything with this site in a long time partially because of that. Another reason is I wanted something where I could run a small amount of code on the front-end, but I wanted the majority of the site to be served statically. I do not mean pre-generating large amounts of javascript that then hydrates and renders the page client side. I wanted to generate actual html that is returned by the server and only have small amount of JavaScript for interactions. That has lead me to look at various other static site generators like <strong><a href="https://astro.build/">astro</a></strong> and <strong><a href="https://www.11ty.dev/">11ty</a></strong>. Astro's island architecture looked promising, but it was yet another framework and language to learn.</p>
<p><strong><a href="https://www.gatsbyjs.com/">Gatsby</a></strong> continues to be a set of technologies that are interesting to me and it was announced there is a new feature called <em>Partial Hydration</em>. Since partial hydration seemed like it may fulfill what I was looking for, I rebuilt the site with the most recent version of Gatsby.</p>
<p>This article chronicles my experience in going from the existing <code>Gatsby 2.*</code> based site to a <code>Gatsby 5.*</code> site. I used the original code as guidance, but it is a few years old so I have mainly rebuilt from scratch.</p>
<h2>Timeline</h2>
<p>I started this endeavor on October 7th, 2023. My hope was to work on this on the weekends when I had time available. Silly me thought I could relaunch fairly quickly but, I have had to scramble to get it out the door prior to the new year.</p>
<h2>Partial Hydration</h2>
<p>Hydration or re-hydration is the process of using client-side JavaScript to add application state and interactivity to server-rendered HTML. Gatsby apps were always fully hydrated on the client. Gatsby 5 added support for partial hydration. Gatsby uses <a href="https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md">React server components</a> as the backbone of it's partial hydration implementation. It marks all components as server components starting from the top level pages. Unless specified with the “use client” directive. Gatsby generates RSC files for each page. Instead of fetching page component JavaScript files in the browser now, page-data-rsc.json files are requested. See <a href="https://www.gatsbyjs.com/docs/conceptual/partial-hydration/">this article</a> for the details.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Ffull-partial-hydration.8na_Z-7F.png&amp;w=2484&amp;h=1074&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Partial Hydration" /></p>
<p>After doing some more reading the partial hydration feature there were some caveats that I had to evaluate. It required using an experimental version of <em>react</em> and <em>react-dom</em>. It may not work well with some other new features that improve performance like <a href="https://www.gatsbyjs.com/docs/reference/release-notes/v5.0/#slice-api">Slices</a>. It also didn't really seem like what I was looking. I continued the rebuild anyway and hoped there were no show stoppers.</p>
<h2>Testing</h2>
<p>The first time creating this site, I didn't bother creating unit tests. It was a personal project being built in my spare time. It didn't feel worthwhile to create them. This time around, I've started adding unit tests for at least the components. Each component has a <a href="https://jestjs.io/docs/snapshot-testing">snapshot test</a> and additional tests for branching logic. These will give me some assurance that dependency version bumps have automated testing on them.</p>
<p>Part of the hesitancy with creating tests before was around all the scaffolding needed to stand up basic unit tests. That is still a complaint. I needed to install 7 dev packages, 4 configuration files, and two mock files just to get some very basic testing going. Along with the <a href="https://www.gatsbyjs.com/docs/how-to/testing/unit-testing/">Gatsby documentation</a> The following articles were a huge help in setting these up.</p>
<ul>
<li><a href="https://danielabaron.me/blog/gatsby-unit-testing/">Get started with Gatsby and Unit Testing</a></li>
<li><a href="https://www.freecodecamp.org/news/testing-react-hooks/">How to Test React Components: the Complete Guide</a></li>
</ul>
<h2>Stumbling Blocks</h2>
<p>I hit a few stumbling blocks along the way which made / makes development more difficult than it needed to be. I couldn't get the site to build locally with those experimental react components so the rebuild was done using the long term releases instead which meant <em>no partial hydration</em>.</p>
<pre><code>Module not found: Error: Can't resolve '@mdx-js/react'
</code></pre>
<h3>Material UI Upgrade</h3>
<p>Doing this upgrade I went up a major version of <a href="https://mui.com/material-ui/getting-started/">Material UI</a>. Between the versions the import paths all changed. The paths went from "@material-ui" to "@mui/material/". Another big change to deal with was going from JSS to <a href="https://emotion.sh/docs/introduction">Emotion</a> for the default styling. This meant a whole new syntax when wanting to override the theme.</p>
<p>I had to switch from doing something like:</p>
<pre><code>const useStyles = makeStyles((theme) =&gt; ({
  cardContent: {
    margin: 0,
    paddingTop: theme.spacing(1),
  },
});

const classes = useStyles();
&lt;CardContent className={classes.cardContent}&gt;
</code></pre>
<p>to the <a href="https://mui.com/material-ui/customization/theme-components/#the-sx-syntax-experimental">sx syntax</a>:</p>
<pre><code>&lt;CardContent component="p"
  sx={{
    m: 0,
    pt: 1,
    "&amp;:last-child": {
      pb: 1.5,
    },
  }}&gt;
</code></pre>
<h3>Common JS vs ES Modules</h3>
<p>I started out with the react components for this build using the ES modules syntax for imports and exports. I also started to switch over Gatsby files to that format as well. The eslint configuration is setup using the module syntax. I then started to run into some import problems with the slugify library in my unit tests. This lead me down a rabbit hole where I learned Jest only has experimental support for ES Modules and Gatsby also only has partial support. If I specify "module" as the type in my <em>package.json</em> then the <code>gatsby develop</code> command starts failing trying to load a file from cache without an extension on it.</p>
<p>I ended up sticking with using the ES Module syntax where possible. I have the type set in package.json to commonjs, to act as the default syntax, but I used the ES Module syntax in <code>.mjs</code> files for Gatsby and in the <code>.jsx</code> files for React.</p>
<h3>NPM Segfault</h3>
<p>While working, I usually run <code>gatsby develop</code> from the command line to view updates to the site in real time. When I need to stop the process, I use <em>Ctrl+C</em> to send a <strong>SIGINT</strong>. Whenever I try to run either the build or develop commands after that I get a segfault exception. At first I would quit the terminal and restart it, which took a long time. Eventually, I figured out I could run <code>gatsby clean</code> in between and it would avoid the failure. Because of this, I now run the below command every time.</p>
<pre><code>npm run clean &amp;&amp; npm run develop
</code></pre>
<h2>The Results</h2>
<p>There were a few stumbling blocks that slowed development, but I feel like the code is to the stage where I can replace the current deploy. I've made a fair amount of improvements to the site and code health along the way. I've also removed some features. Some of those trims are likely to be permanent where others are only going to be temporary.</p>
<h3>Improvements</h3>
<p>The front page of the site has been tweaked to have a new hero image component on the top, and then a grid of horizontal article cards below it. This replaces the vertical cards with badging on the new articles. The badge was some of the client-side interactions that I thought would be used with partial hydration. Instead this new layout freshens up the site a little bit.</p>
<p>The article pages have the author byline and time to read moved around with some space added for a related articles feature that I'm planning in the future. The hero / featured image on the article has been wrapped in a <code>&lt;figure&gt;</code> tag with credits for the image now in a <code>&lt;figcaption&gt;</code>.</p>
<p>I have added JSDoc strings to all the react components. Those can be generated via <code>npm run docs</code>. I am going to continue to make improvements to that documentation and look to see if I can use something like a github action to automatically generate new documentation when merging pull requests into master.</p>
<p>Almost all the components now have <a href="https://www.npmjs.com/package/prop-types">prop-type</a> validation. There are a few tests that break the validation, but the deployed site has all console errors taken care of. I had to comment out some lint rules and not provide prop-types for the <code>Head</code> hook that Gatsby provides as that caused problems during the build.</p>
<p>I previously had support for <a href="https://agingdeveloper.com/article/2020-08-22-guest-authors">guest authors</a>. When I was first looking to rebuild I thought that might be a feature that hit the cutting room floor. The mappings in the <em>gatsby-config</em> file was not very robust and honestly no one else is going to publish to this thing. However, getting author support re-added was pretty easy, and I like the way I did it. Instead of an <strong>author</strong> subdirectory in the data folder, I have a single <strong>author.yaml</strong> file now. The file contains a list of author nodes. I then added some schema customization to the <em>gatsby-node</em> file that maps the author in the mdx frontmatter to the AuthorYaml node via a "slug" property in the yaml.</p>
<pre><code>export const createSchemaCustomization = ({ actions, schema }) =&gt; {
  const { createTypes } = actions
  const typeDefs = [
    'type Mdx implements Node { frontmatter: Frontmatter }',
    schema.buildObjectType({
      name: 'Frontmatter',
      fields: {
        author: {
          type: 'AuthorYaml',
          resolve: (source, args, context, info) =&gt; {
            return context.nodeModel.findOne({
              type: 'AuthorYaml',
              query: {
                filter: { slug: { eq: source.author } },
              },
            })
          },
        },
      },
    }),
  ]
  createTypes(typeDefs)
}
</code></pre>
<p>Before the rebuild all links in the mdx content files were rendered as regular anchors tags. I was able to replace those anchors with a new <code>MDXLink</code> component. This component tests the href. If the href is a relative url then the internal Gatsby link component is used. If the url starts out with the same host as the site then the href is converted to a relative link. Otherwise, an external link is used. This is done by using the components property of the <code>MDXProvider</code>.</p>
<pre><code>const components = {
  a: MDXLink,
};
...
&lt;MDXProvider components={components}&gt;
  {children}
&lt;/MDXProvider&gt;
</code></pre>
<h3>Trims</h3>
<p>These are things that did not make the rebuild. Some of them are coming back shortly, but others are likely gone for good.</p>
<ul>
<li><strong>RSS Support</strong>: this will be re-added shortly via <a href="https://github.com/richwklein/agingdeveloper/issues/580">issue #580</a>.</li>
<li><strong>Comments</strong>: I have an <a href="https://github.com/richwklein/agingdeveloper/issues/193">issue</a> to re-evaluate this, but they are probably gone for good.</li>
<li><strong>Top Level Navigation</strong>: These links are not how people interact with the site. They are being replaced with a searchbox instead.</li>
</ul>
<h2>Take Aways</h2>
<p>All-in-all I think the site is in pretty good shape now and should be easier to maintain. I have some immediate improvements that I want to make. I also have a few article ideas floating around (including the distributed authorization series I previously promised). Doing this rework has also rekindled some of the enjoyment I use to have doing this kind of development. That joy has been lacking a little bit lately so this rebuild was worth it just for that.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/cande-cop-PhP4GQywOLE-unsplash.Z6oLScp0.jpg"/>
        </item>
        <item>
            <title><![CDATA[Authorization in Distributed Systems]]></title>
            <link>https://agingdeveloper.com/article/2022-06-18-distributed-authorization</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2022-06-18-distributed-authorization</guid>
            <pubDate>Sat, 18 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Ideas on how authorization can be done in distributed systems.]]></description>
            <content:encoded><![CDATA[<p>Authorization, the granting or denying of access to a resource, is something that almost all software has to do. Yet, developers tend not to give it a lot of attention. It is a difficult problem to solve in large distributed systems. In distributed or microservice environments you are more likely to run into communication and coordination issues dealing with multiple teams, repositories, and release schedules. Those individual services are also much more sensitive to having dependencies on other services and the performance implications that may have.</p>
<p>I have spent large portions of my career on authorization related development and it is something that I can put a spotlight on here. This article explores some of the basics of authorization, what it means, and a few ways to do authorization in microservice environments. Subsequent articles will follow-up with some more concrete examples using different libraries and frameworks.</p>
<h2>Authentication is not Authorization</h2>
<p><strong>Authorization</strong>, abbreviated as Authz, is the act of granting or denying access to a resource. This is often confused with <strong>Authentication</strong> (Authn) which is the process of confirming the identity of a user or service. These two "Auths" are often intertwined and get easily confused with one another. Frequently, the first step in authorizing a request is making sure that it originates from someone who has been authenticated. However, just being authenticated is often not enough to prove that you are authorized. The Authn and Authz systems can be highly coupled or can be completely separate from one another.</p>
<p>This article will not really go into the mechanics of authentication. Authentication is not as implementation dependent as authorization and there are lots of off the shelf solutions for authentication that can be integrated fairly easily.</p>
<h2>Common Access Control Models</h2>
<p>An access control model is the method used in authorization systems to determine who or what should be granted or denied access to a resource. When talking about the "who or what", I will refer to them as a <em>principal</em>. Regardless of the system that is being used for authorization one of these five "Access Control" models will likely be used. I have the most experience with discretionary and role based access control and those will be used as examples in the follow-up articles.</p>
<h3>Mandatory Access Control</h3>
<p>&lt;abbr title="Mandatory Access Control"&gt;MAC&lt;/abbr&gt; is a static access control method. Resources are
classified using labels or levels. Users are also classified with the same labels. A user can access
a resource if they have the same or greater label. When you hear someone has been granted "Top
Secret" clearance that is an example of a MAC implementation.</p>
<h3>Discretionary Access Control</h3>
<p>When using the &lt;abbr title="Discretinary Access Control"&gt;DAC&lt;/abbr&gt; method the <strong>owner</strong> of the resource decides who can access it. An &lt;abbr title="Access Control List"&gt;ACL&lt;/abbr&gt; controls who has access to the resource and the data owner sets the permissions. The permissions identify what actions the principal can perform on the resource. The file system is a classic example of a DAC. The permissions in that case are "read", "write", and "execute".</p>
<h3>Role Based Access Control</h3>
<p>When using &lt;abbr title="Role Based Access Control"&gt;RBAC&lt;/abbr&gt; access is determined based on the role a principal is given. The role may be a job position, group membership, or security level. The role is granted or denied access to the resource.</p>
<h3>Rule-Based Access Control</h3>
<p>&lt;abbr title="Rule based access control"&gt;RB-AC&lt;/abbr&gt; is based on rules to deny or allow access to
resources. This is typically seen most often in thing like network devices where ACLs determine
which IPs or port numbers are allowed through, and this is done using rules.</p>
<h3>Attribute-Based Access Control (ABAC) Model</h3>
<p>&lt;abbr title="Attribute Based Access Control"&gt;ABAC&lt;/abbr&gt; allows or denies access based on
attributes. These can be the attributes of a particular person, of a resource, or of an environment.
Attributes may be the Subject (height of a person in an amusement park), Resource (software that
only runs on a particular operating system or website), or Environmental (time of day or length of
activity time passed).</p>
<h2>The Parts of an Authorization System</h2>
<p>I've spent a lot of time thinking about and working with authorization systems, but it wasn't until the last few years that I internalized the parts involved in authorization. Regardless of the system being used or if you have a single or multiple services, these parts make up an authorization system. When deciding on an authorization system these parts should be kept in mind.</p>
<p>&lt;dl&gt;
&lt;dt&gt;Enforcement Point&lt;/dt&gt;
&lt;dd&gt;
The &lt;em&gt;Enforcement Point&lt;/em&gt; is the spot in your software where you are going to enforce an
authorization decision. In a client server application there would likely be two enforcement
points. In the client when deciding to show or hide elements based on an authorization decision.
For example disabling a create button if a user doesn't have permission to create an object. The
other point would be on the server, usually at the control or service layer. You must always
double check authorization at the service layer as someone could simply craft the request
without actually using the UI elements.
&lt;/dd&gt;
&lt;dt&gt;Decision Point&lt;/dt&gt;
&lt;dd&gt;
The &lt;em&gt;Decision Point&lt;/em&gt; is the spot where the decision is made to grant access to a
resource. This may be the same place as enforcement or it could be somewhere completely
different. The decision point is responsible for gathering all the information required to make
an authorization decision, making that decision, and returning the results to the enforcement
point so that the decision can be enforced.
&lt;/dd&gt;
&lt;dt&gt;Information Point&lt;/dt&gt;
&lt;dd&gt;
The &lt;em&gt;Information Point&lt;/em&gt; is where all the information that is needed to make an
authorization decision are gathered. It can be co-located with other points in the authorization
process or it can be separate. In a MAC control system the information needed would be the
access level of the resource being accessed and the level of the principal doing the action.
&lt;/dd&gt;
&lt;dt&gt;Administration Point&lt;/dt&gt;
&lt;dd&gt;
The &lt;em&gt;Administration Point&lt;/em&gt; is the place where the policies for an access control method
are authored and/or managed. This may also be the point where the information to make a decision
is managed.
&lt;/dd&gt;
&lt;/dl&gt;</p>
<p>These terms were popularized by the &lt;abbr title="eXtensible Access Control Markup Language"&gt;XACML&lt;/abbr&gt; standard that was first ratified in 2003 and last updated in 2017. Below is a diagram from <a href="https://www.axiomatics.com/100-pure-xacml/">axiomatic</a> showing off these parts.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fxacml-axiomatic-diagram.Co4OIX6x.png&amp;w=1024&amp;h=797&amp;dpl=69a59eb298f6bd0007e61b7b" alt="XACML Diagram" /></p>
<p>If any of the above authorization points is not co-located it is important that the communication between them is secured. It is also critical that if the communication fails between the points that the enforcement fails in the way that is most important to your application. In a lot of applications this would mean to <em>deny</em> access by default.</p>
<h2>Authorization System Patterns</h2>
<p>When doing authorization in microservices there are a few approaches that emerge. It's worth spending a little bit of time on each approach since they have various pros and cons. It also allows us to examine where each part of the authorization system is implemented.</p>
<h3>Local Authorization</h3>
<p>In the local authorization pattern the individual resource services are responsible for developing their own authentication and authorization of a principal. The resource service is responsible for making all it's authorization decisions.</p>
<h4>Pros:</h4>
<ul>
<li>Each service can implement the authorization mechanisms best suited for that service.</li>
<li>There is no communication overhead or dependency on other services.</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>It's hard to understand why a principal has access to one resource and not another.</li>
<li>Each team needs to know and understand the access controls for the system.</li>
<li>Changing the access control system globally becomes extremely difficult.</li>
</ul>
<h3>Centralized Authorization</h3>
<p>In the centralized authorization pattern then the resource services hands off all responsibility for authentication and authorization to a centralized service. The access control decision is made at the centralized service and returned.</p>
<h4>Pros</h4>
<ul>
<li>There is less duplicate code.</li>
<li>It's easier to determine why a principal was given access to a resource.</li>
<li>It's easier to make global changes to those mechanisms.</li>
</ul>
<h4>Cons</h4>
<ul>
<li>There is an extra network request involved in accessing a resource.</li>
<li>Resource services are dependent on another service.</li>
</ul>
<h3>Local Decision / Centralized Policy</h3>
<p>This is a hybrid approach that combines the strengths of the previous two patterns. In this pattern the decision to allow access to a resource is made at the resource service, yet the access control mechanisms are defined and maintained at a central service.</p>
<h4>Pros</h4>
<ul>
<li>There is no network request when making decisions.</li>
<li>There is less duplicate code.</li>
<li>It's easier to determine why a principal was given access to a resource.</li>
<li>It's easier to make global changes to those mechanisms.</li>
</ul>
<h4>Cons</h4>
<ul>
<li>All information for authorization needs to be present at the resource service.</li>
</ul>
<p><strong>Local Decision / Centralized Policy</strong> systems have been a growing trend in recent years. These types of authorization systems have a meta-model that is used to describe what access control mechanisms are in use by your resource services (policies). Information can then be applied against the meta-model to make an authorization decision.</p>
<p>I believe these policy systems will be the most common pattern for doing authorization in microservices moving forward.</p>
<h2>Follow Up</h2>
<p>In my next article in this series I am going to explore what it looks like to do authorization using some policy based authorization systems: <a href="https://casbin.org/">Casbin</a> and <a href="https://www.openpolicyagent.org/">Open Policy Agent</a>.</p>
<p>I'll demonstrate what it looks like to model some of the various forms of access control and how you might gather the information that needs to be supplied to the authz system so that it can produce an access control decision.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/parsoa-khorsand-Dd6n63H9szw-unsplash.CYwk1Ukh.jpg"/>
        </item>
        <item>
            <title><![CDATA[Disappointed in Disney World]]></title>
            <link>https://agingdeveloper.com/article/2022-05-23-themepark-sadness</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2022-05-23-themepark-sadness</guid>
            <pubDate>Mon, 23 May 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[We extended our vacation in February so we could take the boys to Disney World and were disappointed in the experience.]]></description>
            <content:encoded><![CDATA[<p>We had a cruise planned for our family vacation in February. Since we were flying to Florida anyway to get on the cruise ship, we decide to extend our vacation for a few days and go to Disney World as well. This family of 5 has been to Universal Studios a few different times, which we've loved. Rachel and I had been to Disney World prior to having kids, but we've never been to any of the Florida Disney parks as a whole family. Since we extended the trip for two days we decided that we would go to the <a href="https://disneyworld.disney.go.com/destinations/magic-kingdom/">Magic Kingdom</a> and <a href="https://disneyworld.disney.go.com/destinations/hollywood-studios/">Hollywood Studios</a> theme parks. As with any vacation we've taken, the chances are that I would do again. However, we were disappointed in the overall experience at Disney World and wanted to share with others. Our second day at Hollywood Studios was much better than Magic Kingdom, but it wasn't without its problems.</p>
<h2>Cost</h2>
<p>As a family of five we knew that this experience was not going to be cheap. The cost of the tickets to get into the park vary based on the day. Our cost was roughly $140 per person per day. That meant the upfront cost to walk through the gates was around $1,400. Since this was an expected cost, it really wasn't one of our main issues. The systemic additional expenses the park charges for virtually <strong>everything</strong> was an issue. It is a reoccurring theme you will see running throughout the rest of this article.</p>
<h2>Lightning Lanes</h2>
<p>There are two basic types of queues / lanes available for attractions at the parks: "Standby" and "Lightning Lanes". Standby lanes are your traditional queues where you just wait in line for the ride. The Lightning Lanes bypass large sections of the Standby lane, but you must first get a pass and wait in a virtual queue until it is your time for the attraction. The Disney app shows the lane information for each ride.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Finapp-queues-display.Cpegqc-r.jpg&amp;w=583&amp;h=685&amp;dpl=69a59eb298f6bd0007e61b7b" alt="In-App Queues" /></p>
<p>The "Lightning Lanes" can save you a lot of time, but you can't just access them. There are two types of passes available to gain access to these queues, <a href="https://disneyworld.disney.go.com/genie/?int_cmp=INS-intWDWtoWDW-Genie">Genie+</a> is the replacement for the old Fast Pass system. The Genie+ service is $15 per person per day and gives you Lightning Lane access to a selection of attractions per park (excluding all the tent pole ones that you really want access to). A major caveat and drawback of the service is you can only have one Genie+ pass active at a time. That means that if it is 9am and a select a pass that is for 1pm then you can not make another selection until after 1 o'clock. The time slots for these passes fill up fast as well.</p>
<p>The only way to gain lightning lane access to the crowd favorite rides like "Star Wars Rise of the Resistance" or "Seven Dwarfs Mine Train" is to pay for individual passes.</p>
<p>We were going to be at the parks the weekend prior to presidents day weekend. The forecasted crowds were not suppose to be <em>that bad</em>, 5 and 6 on a scale of 1 to 10. Since we were only going to be at the parks for two days, we knew that we would have to minimize our wait times if we were going to be able to ride the most iconic rides. We opted into purchasing Genie+ for everybody ($150). The initial plan was to not buy any of the individual passes.</p>
<p>The lightning lanes require scanning your admission ticket to make sure it is your turn to enter the attraction. Our tickets were emailed to us, which we could then access through the Disney app on the phone. If we were to use the app to scan the tickets at each of the Lightning Lanes I would have had to swipe through 5 different tickets each time and we would not have been able to split up. We opted into purchasing magic bands, roughly $30 each, for everyone instead. These bands could be used at the gate and at each attraction for the Lightning Lanes.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fmagic-bands.CHiM5DnP.png&amp;w=1232&amp;h=1643&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Magic Bands" /></p>
<p>I don't really feel like Genie+ was worth it for us. We were only able to use it twice each day. The second day we had hoped to book a pass through Genie+ for Tower of Terror, but by 10 o'clock in the morning the time for the ride was already late at night. Although, we hadn't planned on purchasing individual Lightning Lanes, the crowd levels were much high than anticipated so we did end up buying them for three rides (another $285).</p>
<h2>Temporarily Closed</h2>
<p>Splash mountain closed in January and it was suppose to open back up on the day that we were going to be at Magic Kingdom. It did eventually open that day; one hour before park close. That was a foreshadowing of the problems with attraction availability. We decided to take the Monorail in when we first got to the park. The train we decided to take was sitting on the track when we got up to it, but they wouldn't let us on right away. Eventually the operators did let us on just to ask us to get back off again. Apparently there was a problem with that particular train and they had to remove it from the rail and bring in a new one. So we started the day with about a 30 minute delay just getting into the park. Space mountain is an iconic ride that we had wanted to do as a family. It is <em>Temporarily Closed</em> right now as I write this article. It was maybe open for a couple of hours that day. We even paid for a <a href="https://agingdeveloper.com/article/2022-05-23-themepark-sadness#lightning-lanes">Lightning Lane</a> to ride it, but when our time came around the ride was down again and it wasn't going open back up before park close. At one point while we were at Magic Kingdom the following rides were all <strong>temporarily closed</strong> at the same time.</p>
<ul>
<li>Splash Mountain</li>
<li>Space Mountain</li>
<li>Big Thunder Mountain</li>
<li>Pirates of the Caribbean</li>
<li>Jungle Cruise</li>
</ul>
<p>According to <a href="https://touringplans.com/">Touring Plans</a> a full 10% of the attractions were unavailable that day.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Ftouring-plan.D3z-DQ7m.png&amp;w=505&amp;h=308&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Touring Plan" /></p>
<p>The attraction availability issues pushed crowds to the other areas of the park making it super crowded. Hollywood Studios was not immune to these issues either. Rise of the Resistance did not open in the morning when it was suppose to and it shut down for around three hours in the middle of the day. When that ride did reopen so many people had Lightning Lane passes, including us, that it made the standby line irrelevant. Similar to <a href="https://www.disneyfanatic.com/lightning-lane-left-space-mountain-standby-line-broken-tb1/?fbclid=IwAR0r0dv10KqBxBCZBP6XTrtJpqESliVPrXP1Qjc5Huk03XkBVnv30RytqSo">this Disney Fanatic article</a>.</p>
<h2>Crowds</h2>
<p>The forecasted crowd level for the day we were to be at Magic Kingdom was a <em>5</em>. Attendance was measured at a <em>9</em> based on actual attendance and attraction closures. This made it very hard to get around and made the lines for everything longer than usual (2 hours was pretty common). The attraction operators on more than one occasion ask people to scoot closer together and "we would get on faster". I don't think they know how ride capacity actually works …</p>
<h2>Online Food Ordering</h2>
<p>Disney really advertises and pushes for you to use the app to order the food online. We did this for the majority of our food, but we had some gift cards to use so we didn't do it for all of them. It was a disappointment that we couldn't load the gift card into the app and use those for the payment and it was sometimes a hassle to go to an actual cashier. The whole mobile food ordering is just another queueing system in disguise. After entering your order, the app asks you to press a button when you are close to the venue. Your order isn't actually submitted until that point. The app will then give you a time to come back when your order will be ready. It also notifies you of the line to get into when that time comes. Your order isn't actually ready when you are notified. When you get to the window / person you were directed to is when your order is actually put together.</p>
<p>Aloha Isle is the only place in Magic Kingdom where you can get a Dole whip. It is also a spot in the park with terrible mobile and wifi reception. When trying to order the Dole whips we spent over an hour from order to pick up time.</p>
<h2>Return</h2>
<p>If you look online, you will find a growing list of people unhappy with the way Disney is handling their parks. I think overall our family has enjoyed Universal Studios much more. So I think we would be more inclined to return there instead of Disney. I would go back to Disney again, but I would first like to see them make some changes first. The Genie+, individual Lightning Lane purchases, and mobile food ordering all feel like broken experiences that need to improve.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/brian-mcgowan-uPQiTOzYoo0-unsplash.DtXZq2et.jpg"/>
        </item>
        <item>
            <title><![CDATA[Major Versions Considered Harmful?]]></title>
            <link>https://agingdeveloper.com/article/2022-02-26-major-harmful</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2022-02-26-major-harmful</guid>
            <pubDate>Sat, 26 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Semantic versioning and why you should be careful when deciding to do a major release. A major version upgrade may be considered harmful for your project.]]></description>
            <content:encoded><![CDATA[<p>A large portion of software today follows some form of <a href="https://semver.org/">SemVer</a> or <em>Semantic Versioning</em> when it comes to how that software is versioned. When software follows <em>Semantic Versioning</em> then the string used to identify the software's version has meaning. The version is made up of at least three parts separated by periods (<code>&lt;MAJOR&gt;</code>.<code>&lt;MINOR&gt;</code>.<code>&lt;PATCH&gt;</code>). There are specific rules about when each part of the version string should be updated.</p>
<ol>
<li><strong>MAJOR</strong> version when you make incompatible API changes,</li>
<li><strong>MINOR</strong> version when you add functionality in a backwards compatible manner, and</li>
<li><strong>PATCH</strong> version when you make backwards compatible bug fixes.</li>
</ol>
<p>These rules allow people to reason about the kinds of changes that occurred between one release and the next one. A <strong>MAJOR</strong> version change implies some sort of breaking change. Breaking changes are disruptive and should be considered "harmful".</p>
<h2>Harmful Major Release</h2>
<p><a href="https://www.gatsbyjs.com/">Gatsby</a> is the underlying framework used to power this site. It and all it's dependencies follow this <em>SemVer</em> pattern. There has been a fair amount of time, as you can tell, that has elapsed since the last time the site has been updated. <em>Gatsby</em> had put out a major release going from <strong>2.x</strong> to <strong>3.x</strong> (It is above <strong>4.0.0</strong> now). These releases have had backward incompatible changes.</p>
<p>I've spent some time trying to move up to a more recent major version. The changes are disruptive enough that it has taken me more time and energy than I was willing or able to spend to make the upgrade. I am still planning on keeping this site going and up to date, but at this point I think the easiest path is to going to be to rebuild from scratch. I haven't decided yet if I'll stick with Gatsby or try something else at this point.</p>
<p>This situation is not unique. I've seen it countless times in my software development career. A major release of a dependency causes such problems for it's consumers that the consumers either have to spend a large amount of time forward fixing or they may find it easier to just replace the dependency with something else.</p>
<h2>Mitigation Techniques</h2>
<p>A major release does not have to be painful. There are some strategies by both the dependency producer and consumer that can help mitigate these kinds of problems.</p>
<h3>Dependency Producer</h3>
<p>As the producer of APIs or libraries that others rely on, you have an obligation to make updating that dependency as easy as possible.</p>
<ul>
<li>
<p>When making a breaking change then take a long time to consider the reason why. Any breaking change causes significant disruption to downstream developers and should be avoided when possible. There are some legitimate reasons why you must have a breaking change, for example mitigating a security issue.</p>
</li>
<li>
<p>Avoid "Big Bang" releases. A “Big Bang” release is when many different features, bug fixes, improvements, or any type of changes go out in the same release. These types of releases almost always cause problems. Consider limiting the number of breaking changes allowed for any single major release.</p>
</li>
<li>
<p>Limit how often you cut a major release. You do not want to include too many breaking change in any one major release. You also do not want to have major releases too often. Every major release requires some level of effort on your consumers. The more burden you put on them to keep up to date the less likely they are to continue using your software.</p>
</li>
<li>
<p>A companion strategy may be to provide a "Long Term Support" (<em>LTS</em>) release. These are major releases that will continue to get security and maintenance for a long period of time. The LTS versions are considered to be the most stable releases which undergoes extensive testing and mostly includes years of improvements along the way. This gives your consumers confidence to use your software without having to worry about keeping up with every new feature release you put out there.</p>
</li>
<li>
<p>Gradually replace or remove APIs. Introduce a new API to be used as a replacement in one release, marking the old API as deprecated, then remove the deprecated API in a future later release. Deprecation should include meaningful warnings about the usage of the deprecated API, what replaces it, and when you need to stop using it by. This gives consumers some time to make the switch over without introducing an instant failure.</p>
</li>
<li>
<p>Provide clear and easy to find documentation (outside of the runtime) around a breaking change being made. You should include details like: Why is the change being made, instructions on how to update usage, and timelines as to when usage must be updated by.</p>
</li>
<li>
<p>Try not to introduce breaking changes accidentally. I've seen in both major and minor versions where a change was introduced that was breaking, but not marked as such. When and what type of exception being thrown is part of an API's contract and is a common cause of these kinds of accidental breakage.</p>
</li>
</ul>
<h3>Dependency Consumer</h3>
<p>As a consumer of a library, framework, or API; you have less control in mitigating breaking changes. There are still a few things that can be done to lessen these issues.</p>
<ul>
<li>Know your direct and transitive dependencies. Every project and package ecosystem is a little bit different. It is best to at least have some basic knowledge of what your dependencies are. As well as what the release schedule tends to look like for those dependencies. Every package manager has some way to view your dependency tree. <em>npm</em> has the <em>list</em> command where the depth argument lets you specify how deep down the dependency tree to go.</li>
</ul>
<pre><code>npm list --depth=4
</code></pre>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fdependency-tree.DWTyFHkY.png&amp;w=1048&amp;h=560&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Dependency Tree Example" /></p>
<ul>
<li>
<p>Automate dependency updates. There are automatic tools to help keep your dependencies up to date. One example of this is <a href="https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates">Dependabot</a> on github. You can specify on a per repo basis what package manager is being used, number of open pull requests allowed, the update frequency, and more. This will automatically create pull requests when a new version of a dependency is release. It does not help resolve any breaking changes that may be in the release.</p>
</li>
<li>
<p>If your dependency provides an <em>LTS</em> release and you are mildly averse to having to keep up with someone elses release schedule then pin to that <em>LTS</em> release. This will allow you to have a much more controlled updated schedule. You usually only see these supported for Operating Systems and programming languages, but those are dependencies too.</p>
</li>
<li>
<p>Read the documentation. Regardless of how your projects dependencies are kept up to date there will be times where you will need to fix breaking changes. If the dependencies is a major one then they may provide an upgrade guide similar to this <a href="https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v2-to-v3/">Gatsby v2 to v3</a> guide. When a migration guide isn't provided then you will likely have to read through a changelog or individual commits.</p>
</li>
</ul>
<h2>Real World</h2>
<p>Your major version strategy may be great, but there are always complications in the real world. Here are just a few examples:</p>
<p>python 3.x was largely incompatible with 2.x, especially in the handling of strings. Python 3 was first released in 2008 and the 2.x line of Python was deprecated with 2.7 being the <em>LTS</em> release in 2010. December of 2020 was the end of life for the Python 2.x release line. That is 10 years of long term support and companies are still spending millions of man hours making there codebases 3.x compatible. One could argue 10 years is too long even for an <em>LTS</em> release.</p>
<p>The popular Java logging library <em>log4j</em> had a remote code execution vulnerability discovered in December of last year. The vulnerability related to the handling of LDAP and JNDI urls. One of the maintainers <a href="https://twitter.com/yazicivo/status/1469349956880408583?s=21">tweeted</a></p>
<blockquote>
<p>Log4j maintainers have been working sleeplessly on mitigation measures; fixes, docs, CVE, replies to inquiries, etc. Yet nothing is stopping people to bash us, for work we aren't paid for, for a feature we all dislike yet needed to keep due to backward compatibility concerns.</p>
</blockquote>
<p>The key takeaway from that tweet being the second sentence. It is not worth keeping a bad feature or API just for the sake of "backward compatibility".</p>
<h2>Conclusion</h2>
<p>Major releases are always some form of disruption and can be considered harmful. Everyone should seriously weigh the need for a new major release against the effort required by the downstream developers.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/morgane-perraud-VUoMzpSFMrY-unsplash.DziYOZTO.jpg"/>
        </item>
        <item>
            <title><![CDATA[RSS is Dead, Long Live RSS]]></title>
            <link>https://agingdeveloper.com/article/2021-05-17-rss-dead-long-live-rss</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2021-05-17-rss-dead-long-live-rss</guid>
            <pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Many have written RSS or Atom off as a dead technology. However, it is alive and kicking. We explore why and how I added RSS support to this site.]]></description>
            <content:encoded><![CDATA[<h2>What is RSS?</h2>
<p>RSS, RDF Site Summary, Really Simple Syndication, Rich Site Syndication, etc... is a computer readable file format
that websites publish to notify systems that there has been an update to the site. A link element is placed in the
head of the site to allow the <a href="https://www.rssboard.org/rss-autodiscovery">auto-discovery</a> of these files.
The "feeds" could then be used for various purposes one of which is that they were often aggregated in a news
aggregator/reader.</p>
<p>RSS was at it's height in popularity during the early Web 2.0 days prior to walled gardens such as Facebook taking over.
During this time the Chrome &amp; Firefox browsers built in support for these feeds. Google, Digg, and Mac had very popular
feed readers. It was all the rage.</p>
<h2>The Death spiral</h2>
<p>With the rise of non-rss based feeds in Facebook and Twitter, as well as the increase of advertisements on sites, companies
no longer wanted you to experience their content outside of their site. Usage of these tools declined. This produced what
looked like a death spiral for the format. Firefox dropped its RSS button in 2011, Apple dropped RSS out of OS X Mountain
Lion, and Google pulled its Reader in 2013.</p>
<p>The <a href="https://www.rssboard.org/">RSS Advisory Board</a> latest news is from 2014.</p>
<h2>Continued use</h2>
<p>People are <a href="https://somedudesays.com/2020/04/rss-in-2020/">still using RSS</a>, and publishing of the format has actually
seen an upward trend in the last couple of years. Some people have become increasingly disenfranchised by the way social
platforms use their algorithms to decide which news should show up in your feed. These people are
<a href="https://atthis.link/blog/2021/rss.html">turning back</a> to <a href="http://techrights.org/2021/02/06/rss-feeds-www/">RSS and feed readers</a>
as a way to control their own content consumption.</p>
<p>I have personally never stopped using an news reader. I use <a href="https://feedly.com/">Feedly</a> for the local news as well as to stay up
to date on specific categories of content that I'm interested in.</p>
<p>Because of this, I thought it was important to include feed support on this site.</p>
<h2>Adding support</h2>
<p>There are several <a href="https://www.gatsbyjs.com/plugins/?=feed">plugins</a> to add feed support to gatsby based sites. I
tried to use one of these, but there were a few drawbacks. I use <a href="https://mdxjs.com/">MDX</a> which is markdown with JSX
and not all the plugins support it. Several of them also require using <em>graphql</em> in the <strong>gatsby-config</strong> file to
customize what is shown in the feed. That in and of itself isn't too bad, but I'm trying to keep code out of configuration files.
So instead of using one of the pre-built plugins, I rolled my own support.</p>
<p>The first thing I did was add the <a href="https://www.npmjs.com/package/feed">feed</a> library as a dependency. It does all the
heavy lifting for creating RSS 2.0, JSON Feed 1.0, and Atom 1.0 formats.</p>
<pre><code>npm install --save feed
</code></pre>
<p>Code in the <strong>gatsby-node.js</strong> file is run as part of the process of building the site. At the end of the production build
process the <code>onPostBuild</code> method is called. This is the callback I used to create the feed files.
You can see the full implementation <a href="https://github.com/richwklein/agingdeveloper/blob/1381c628fff2e674e5a84345bd21bb8617cb0b17/gatsby-node.js#L142-L308">here</a>.</p>
<p>The posts are sorted in the graphql by date then title.</p>
<pre><code>      allMdx(
        sort: { order: DESC, fields: [frontmatter___date, frontmatter___title] }
      )
  `);
</code></pre>
<p>The <strong>feed</strong> library does most of the heavy lifting here. I just pass the properties the mdx nodes in the graph into it.</p>
<pre><code>articles.map(({ node }) =&gt; {
  const { title, slug, description, date } = node.frontmatter
  const author = node.frontmatter.author
  const image = node.frontmatter.image.childImageSharp.fixed
  const asDate = moment(date).toDate()

  return feed.addItem({
    title: title,
    id: slug,
    link: `${metadata.siteUrl}/article/${slug}`,
    description: description,
    published: asDate,
    published: asDate,
    content: node.html,
    author: {
      name: author.name,
      email: author.email,
      link: `${metadata.siteUrl}/author/${author.id}`,
    },
    image: {
      url: `${metadata.siteUrl}${image.src}`,
    },
  })
})
</code></pre>
<p>I created a <a href="https://github.com/richwklein/agingdeveloper/blob/1381c628fff2e674e5a84345bd21bb8617cb0b17/src/components/FeedLinks.js">FeedLinks</a>
component that is used for the auto-discovery links that go in the head of the site.</p>
<pre><code>const FeedLinks = ({ siteName, siteUrl }) =&gt; {
  return (
    &lt;Helmet&gt;
      {feedData.map(({ postFix, type, path }) =&gt; {
        return (
          &lt;link
            rel="alternate"
            type={type}
            title={`${siteName} ${postFix}`}
            href={`${siteUrl}${path}`}
            key={path}
          /&gt;
        )
      })}
    &lt;/Helmet&gt;
  )
}
</code></pre>
<p>To ease discovery I also placed an RSS icon in the bottom bar on the site which links to the rss feed file.</p>
<pre><code>&lt;Grid item sm={12} md={4}&gt;
  &lt;IconButton component={ExternalLink} title={`${title} RSS`} to={'/rss.xml'}&gt;
    &lt;RssFeed color="secondary" /&gt;
  &lt;/IconButton&gt;
&lt;/Grid&gt;
</code></pre>
<p>So now you can use your favorite news aggregator/reader to catch up on <a href="https://agingdeveloper.com/">agingdeveloper</a>
whenever I have new content.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/david-clode-wOpm2alT6qU-unsplash.DuBsdbpW.jpg"/>
        </item>
        <item>
            <title><![CDATA[Setting up Git Commit Signing]]></title>
            <link>https://agingdeveloper.com/article/2021-02-05-github-signing</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2021-02-05-github-signing</guid>
            <pubDate>Fri, 05 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The hows and whys of git commit signing]]></description>
            <content:encoded><![CDATA[<p>I recently had to completely reinstall the OS and all software on my personal computer. I had to go back and set up my development tools including SSH and
GPG keys. I do this just infrequent enough that I had to review the documentation on how to do it. So I figured it would be worth while to put together this article to help others as well.</p>
<h2>Why Sign Git Commits</h2>
<p>When you make a commit in Git the author information is sent along in the form of a name and email address. This information by itself is not verified in anyway. It is just a way to see who you are collaborating with. It is easy to impersonate someone by simply providing the same name and email address since that author information is not vetted in any way. The impersonation could happen in any repo regardless of your knowledge. In the worst case scenario someone could do this in one of the repositories that you regularly work in which would be extremely difficult to detect.</p>
<p>Git commit signing is a way to put a signature on a commit to show that the commit is actually coming from you.</p>
<h2>How Commit Signing Works</h2>
<p>Commit signing uses a public/private key pair to verify where the commit comes from. When making the commit you sign it with your private key. Others can then use your public key to verify the signature on the commit.</p>
<h2>Getting it Setup</h2>
<p>These instructions will be for macOS, but it should be pretty similar to set this up on other Operating Systems.</p>
<ol>
<li>
<p>Git uses GPG for signing. I use <a href="https://gpgtools.org/">GPGTools</a> for this, but you can choose your own <a href="https://www.gnupg.org/download/">command line tool</a>.</p>
</li>
<li>
<p>Generate a GPG Pair from the command line.</p>
</li>
</ol>
<pre><code>% gpg --full-generate-key
</code></pre>
<ul>
<li><strong>RSA and RSA</strong> is the default key pair. You can just select that one.</li>
<li>Select the key size (<em>4096</em> is a minimum for github).</li>
<li>Select how long the key should be valid for. The default is to never expire.
*. Provide your Name, Email Address, and a Comment. The email address must be the same as your github account.</li>
<li>Supply a passphrase. You could leave this blank, but I would supply it. You can always store it in the <em>GPG Keychain</em> so that you will not have to supply it on every commit.</li>
</ul>
<ol>
<li>List out your keys and select the key id of the one you just generated. It will be the string after the / on the sec line.</li>
</ol>
<pre><code>% gpg --list-secret-keys --keyid-format LONG

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/Users/richard/.gnupg/pubring.kbx
---------------------------------
sec   rsa4096/0QB348777C85D1CC 2021-02-05 [SC]
      453372F6E3A6E764B2EC8EFB0BB938777C85D1CC
uid                 [ultimate] Rich Klein (Git Signing) &lt;richwklein@gmail.com&gt;
ssb   rsa4096/EC054AC0385BCD63 2021-02-05 [E]
</code></pre>
<ol>
<li>Configure git with your signing key</li>
</ol>
<pre><code>% git config --global user.signingkey 0QB348777C85D1CC
</code></pre>
<p>Now you should be able to sign individual commits by supplying the <code>-S</code> to your git commit command. I prefer to have all my commits signed, so I configure git to always do this.</p>
<pre><code>% git config --global commit.gpgsign true
% git config --global tag.gpgSign true
</code></pre>
<p>After making a commit you can verify it was signed using the log command.</p>
<pre><code>% git log --show-signature -1
</code></pre>
<ol>
<li>Export your public key</li>
</ol>
<p>Signing your commits is only part of the process. Others can not verify the commit until they have your public key. So next you'll need to export it.</p>
<pre><code>% gpg --armor --export 0QB348777C85D1CC
</code></pre>
<p>This will print out the <strong>ASCII</strong> version of your key. If you are using github then this is what you upload to your user profile so that they can verify your signatures.</p>
<ol>
<li>Upload your public key.</li>
</ol>
<ul>
<li>Go to the github <a href="https://github.com/settings/keys">keys</a> page.</li>
<li>Click on the <em>New GPG key</em> button.</li>
<li>Paste your key from the previous step in the box.</li>
<li>Click on the <em>Add GPG key</em> button.</li>
<li>Fill in your password.</li>
</ul>
<p>Your commits are now signed and you can see the verified badge on commits in github.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fverified-commit.CwJGDZFE.png&amp;w=1766&amp;h=594&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Verified Commit" /></p>
<h2>Official Documentation</h2>
<p>I used the following documentation to set up my signing and create this article.</p>
<ul>
<li><a href="https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work">Git Tools - Signing Your Work</a></li>
<li><a href="https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key">Github - Generating a new GPG key</a></li>
</ul>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/romain-dancre-doplSDELX7E-unsplash.eNqq6Q1R.jpg"/>
        </item>
        <item>
            <title><![CDATA[Sing Gently - A Virtual Choir]]></title>
            <link>https://agingdeveloper.com/article/2020-12-23-virtual-choir</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-12-23-virtual-choir</guid>
            <pubDate>Wed, 23 Dec 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Using technology to help people come together artistically]]></description>
            <content:encoded><![CDATA[<p>I was recently listening to a <a href="https://www.20k.org/">Twenty Thousand Hertz</a> podcast. This podcast is a great exploration of various topics that all relate to sound. It is worth subscribing to.</p>
<p>The episode I was listening to was <a href="https://www.20k.org/episodes/singgently">Sing Gently</a>. It is an updated interview with composer <a href="https://ericwhitacre.com/">Eric Whitacre</a>. I was so moved by the episode that I wanted to write about it to urge others to listen to what it is about.</p>
<p>Eric Whitacre is a pioneer of "Virtual Choirs". He creates a composer track that people sing along with. People then submit recording of themselves back to him over social media. He compiles those recordings into these beautiful choir recording.</p>
<p>Prior to 2020 he had created five of these virtual choirs. Starting at about the 28 minute mark in the podcast they start discussing the Covid-19 pandemic, how singing is said to help spread the disease, and what that meant for choirs to get together.</p>
<p>This lead him to creating the sixth virtual choir, "Sing Gently". The idea behind the song originated from the current climate where the composer saw a great potential for people to be angry with each other and distance themselves from each other, not just physical distance. The idea in the song is to be gentle with each other, to be compassionate to each other, to show empathy, and to do that together.</p>
<p>The piece starts out: May we sing together always. May our voices be soft. 17,572 singers from 129 countries came together to perform "Sing Gently". A song with a message we should all take to heart this year. Please take a few minutes and listen.</p>
<h2>Sing Gently</h2>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/InULYfJHKI0"
frameborder="0"
title="Sing Gently Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<p>If you enjoyed that, I've included the previous five virtual choir recordings
so that you could listen to them.</p>
<h3>Deep Field: The Impossible Magnitude of our Universe</h3>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/yDiD8F9ItX0"
frameborder="0"
title="Deep Field Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<h3>Fly to Paradise</h3>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/Y8oDnUga0JU"
frameborder="0"
title="Fly to Paradise Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<h3>Water Night</h3>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/V3rRaL-Czxw"
frameborder="0"
title="Water Night Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<h3>Sleep</h3>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/6WhWDCw3Mng"
frameborder="0"
title="Sleep Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
<h3>Lux Aurumque</h3>
<p>&lt;iframe
width="560"
height="315"
src="https://www.youtube.com/embed/D7o7BrlbaDs"
frameborder="0"
title="Lux Aurumque Virtual Choir Song"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen</p>
<blockquote>
<p>&lt;/iframe&gt;</p>
</blockquote>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/david-beale-rU4kvQKjG2o-unsplash.B8Q9oWL0.jpg"/>
        </item>
        <item>
            <title><![CDATA[Samsung is pushing us to be an all Apple family]]></title>
            <link>https://agingdeveloper.com/article/2020-10-03-samsung-ads</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-10-03-samsung-ads</guid>
            <pubDate>Sat, 03 Oct 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Why we are going to make the move to only Apple devices]]></description>
            <content:encoded><![CDATA[<p>We are a fairly vendor agnostic family. We own devices from Amazon, Google, Apple, Samsung and others.</p>
<p>I have a Samsung S8+ phone and a Samsung Gear Fit watch. About a year ago I started to look for a replacement for my Gear Fit because I wanted something a little bit newer with some more features. I was originally sticking with Android Watch OS and Samsung Tizen based devices for my initial search. However, Samsung's advertising practices pushed me over to Apple products.</p>
<p>I've own Samsung phones and tablets for close to 10 years. I've always felt that they were well built. The design and functionality of the products were great. It has only been in the last few years that the advertisements have
become so pervasive throughout the user experience that it is now a problem.</p>
<p>Advertisements show up as push notifications whenever they want you to buy one of their new products.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fnote-10-ads-by-push-notification.NF5D5cmT.png&amp;w=273&amp;h=300&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Notification Ad" /></p>
<p>All of the default apps now devote some real estate to advertising. A full third of the above the fold screen is devoted to ads in the Health app compared to no advertising in Google's Fit app.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fhealth-fit-side-by-side.COQ4XhwU.png&amp;w=1232&amp;h=1267&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Default Apps" /></p>
<p>There is no way to turn these ads off. There are options that looks like it might allow you to. They don't though. You can only opt out of ad customization.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fcustom-ad-opt-out.B-GHH-0L.png&amp;w=1440&amp;h=426&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Custom Opt Out" /></p>
<p>I would expect free or low cost goods or services to be subsidized through advertising. That includes devices. These ads are on a phone that I paid over $1,200 for. This is not to offset the cost of the device in any way shape or
form.</p>
<p>These ads have finally pushed me over the edge and forced my to re-evaluate who we buy all our devices from.</p>
<h2>Why Apple?</h2>
<p>Apple is not without flaws. I think some of their app store practices are down right monopolistic or at the very least anti-competitive. So why switch to Apple products instead of sticking with something Android based.</p>
<p>I was spurred on to make this decision largely because I wanted to replace my aging Samsung Gear Fit. Looking at the fitness trackers and watches on the market today, the only serious replacement is the <a href="https://www.apple.com/watch/">Apple Watch</a>.</p>
<p>Apple charges a premium price for their products and services; in exchange they take a harder stance on <a href="https://www.apple.com/privacy/">privacy</a>. Evidenced by their recent changes to limit how other companies can access your data. This gives me some level of comfort that they will not be selling my data or placing intrusive ads anytime soon.</p>
<h2>How does Amazon get a pass?</h2>
<p>We have some Amazon devices (mainly tablets and Echos) that we will not be getting rid of any time soon. Amazon advertises on the devices that they make. Why take a stand against Samsung yet give Amazon a pass?</p>
<ul>
<li>The Amazon <a href="https://smile.amazon.com/dp/B07FKR6KXF?ref=MarsFS_TAB_F7">Fire 7</a> tablets starts out at $50 and regularly is on sale for less.</li>
<li>It states clearly on the product page that the price point is with ads.</li>
<li>The ads are just on the lock screen and not very intrusive.</li>
<li>If you do not want the ads, you can pay an extra $15 to opt out of their "Special Offers".</li>
</ul>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Famazon-opt-out.-vorsNiN.png&amp;w=1232&amp;h=442&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Special Offer Opt Out" /></p>
<h2>Making a change</h2>
<p>Others are taking <a href="https://www.androidpolice.com/2020/07/04/ads-are-taking-over-samsungs-galaxy-smartphones-and-im-fed-up/">notice</a> of the way Samsung is treating it's customers and are not happy about it either. Their <a href="https://www.flatpanelshd.com/news.php?subaction=showfull&amp;id=1583755244">TV</a>
customers are being treated in much the same way.</p>
<p>They even <a href="https://www.samsung.com/us/business/samsungads/">boast</a> about their advertising platform on a site used to attract advertisers.</p>
<p>Privacy has always been important; especially so in this digital age. It is becoming increasing clear that some companies have no qualms about invading that privacy just to get a few eyeballs on their ads and try to make some sales.</p>
<p>I'm taking a stand against this with my wallet by taking my business somewhere else. Losing a single families business will probably not break Samsung, but if more people do the same maybe they will take notice and it will drive some change.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/david-svihovec-5pE-lRcua5A-unsplash.CfuCCdSO.jpg"/>
        </item>
        <item>
            <title><![CDATA[Why The Aging Developer?]]></title>
            <link>https://agingdeveloper.com/article/2020-09-13-why-aging-developer</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-09-13-why-aging-developer</guid>
            <pubDate>Sun, 13 Sep 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Explains why I chose the site 'The Aging Developer']]></description>
            <content:encoded><![CDATA[<p>Now that the site is getting more feature complete and I have written a few articles, I wanted to explain in more depth why I chose "The Aging Developer" and what it means to me.</p>
<p>I came to a career in software, specifically product R&amp;D, in a round-about way. I did not go to school for software engineering or computer programming. I did not intern for a software company. I had some basic computer training in
middle &amp; high school. My intro engineering class used Fortran, and I took the odd RPG or Microsoft access class through continuing education. Development was not my original focus, but I had always had an interest in it.</p>
<p>Shortly after leaving college, I didn't really have any kind of career lined up. I had been working physical temp jobs and landed steady work at <a href="https://www.tones.com/about">Tone's Spices</a> in inventory control. After a few years there, I was able to use my skills somewhat by hacking MS Office with VBA.</p>
<p>Through continuing education and self learning I was able to move into an <em>IT</em> position. This is when I started to get interested and involved in the open source community, more specifically <a href="https://www.mozilla.org/">Mozilla</a>.
I was participating regularly in that community, including writing extensions. It was that extension writing that got me noticed by the Netscape division of AOL. I was hired there to develop <a href="https://en.wikipedia.org/wiki/Netscape_Navigator_9">Netscape Navigator 9</a>. It was a big leap of faith that I would be able to do it. And that is when
my career in software development really took off.</p>
<p>Once I had landed in R&amp;D for a while, it became obvious to me that I was older than my colleagues. First at <a href="https://en.wikipedia.org/wiki/AOL">AOL</a> then subsequently at <a href="https://en.wikipedia.org/wiki/Workiva">Workiva</a>. It was not by a lot, but by enough for me to notice. I am often the senior in the room by around ten years.
My assumption is that my career start was a contributing factor in this.</p>
<p>The software industry skews much younger. The median age of <a href="https://www.payscale.com/data-packages/top-tech-companies-compared/tech-salaries">Google and Amazon employees is 30</a>. A 2018 Stack Overflow survey of 100,000 programmers around the world found that <a href="https://insights.stackoverflow.com/survey/2018#developer-profile-age">three-quarters of them were under 35</a>.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fstackoverflow-age.Egs9v-Te.png&amp;w=1232&amp;h=725&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Stack Overflow" /></p>
<p>Meanwhile, the median age of <a href="https://www.bls.gov/emp/tables/median-age-labor-force.htm">American workers is 42</a>,
and I just turned 44...</p>
<p>The age difference makes you wonder what happens to those older developers. From my experience there is a constant push towards management. Until recent history a lot of software companies did not have career paths for developers that did not want to move into management. This has been changing for larger software companies like Microsoft, Apple, Google, and others. Workiva is one of these companies that now has career tracks that stays technical.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fcareer-path.BwXafuMW.png&amp;w=1232&amp;h=693&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Technical Career Path" /></p>
<p>I was one of those engineers that had ended up on a management path without really giving it a lot of thought. Some internal restructuring and a new manager gave me the space I needed to reevaluate what I wanted for my career. That led me back to a technical focused path. I did not mind managing people and I really liked the relationships, especially being able to be a mentor to the people that reported to me. I just always enjoyed the code more.</p>
<p>Being largely self taught, already older than a lot of my colleagues, and in a career that does not have many older people does produce a fair bit of anxiety.</p>
<ul>
<li>Is this something I can do until I retire?</li>
<li>Can I stay technically proficient?</li>
<li>Can I keep a relevant skill set?</li>
<li>Does it make sense for the company to replace me with someone younger?</li>
<li>...</li>
</ul>
<p>Some of the doubts that I have can be chaulked up to imposter syndrome. <a href="https://en.wikipedia.org/wiki/Impostor_syndrome">Impostor syndrome</a> is doubt in your accomplishments or talents and a fear of being exposed as a "fraud".
Despite all evidence to the contrary. But it could be legitimate fears.</p>
<p>I started this site as a way to keep updating my skills. The tech stack that I picked was one I'm not familiar with so that it allows me to broaden my knowledge. This helps ensure that my skill set does not get outdated.</p>
<p>I chose a public site to be visible. I wanted to do some work that others outside of large enterprises would see again. It allows me to engage with the general public and network with other.</p>
<p>It is open source so that if others feel like they could contribute to the discussion; they are free to do so with little hindrance. It can also be a platform where you, as a developer, can be seen and heard. Although, I don't
have a large audience yet ;)</p>
<p>The Aging Developer can be a place for others going through the same sorts of worries and feeling to realize they aren't alone. That others share the same fears and hopefully the act of sharing can diminish those fears.</p>
<p>That is why "The Aging Developer".</p>
<hr />
<p>Reference:</p>
<ul>
<li><a href="https://onezero.medium.com/ctrl-alt-delete-the-planned-obsolescence-of-old-coders-9c5f440ee68">Ctrl-Alt-Delete: The Planned Obsolescence of Old Coders</a></li>
<li><a href="https://news.ycombinator.com/item?id=7372997">Hacker News: What happens to older developers?</a></li>
<li><a href="https://www.techrepublic.com/article/no-place-for-the-old-is-software-development-a-young-persons-game/">No place for the old? Is software development a young person's game?</a></li>
<li><a href="https://www.infoworld.com/article/2617093/it-careers-where-do-all-the-old-programmers-go.html">Where do all the old programmers go?</a></li>
<li><a href="https://www.forbes.com/sites/quora/2012/09/17/as-a-software-developer-how-can-i-ensure-i-remain-employable-after-age-50/#6ab73da57264">As A Software Developer, How Can I Ensure I Remain Employable After Age 50?</a></li>
<li><a href="https://simpleprogrammer.com/software-development-career-paths/">Software Development Career Paths</a></li>
<li><a href="https://www.holloway.com/s/trh-job-titles-levels-fundamentals-for-software-engineering">Job Titles &amp; Levels: What Every Software Engineer Needs to Know</a></li>
</ul>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/kevin-ku-aiyBwbrWWlo-unsplash.Z1SFu8Tl.jpg"/>
        </item>
        <item>
            <title><![CDATA[Support for Guest Authors]]></title>
            <link>https://agingdeveloper.com/article/2020-08-22-guest-authors</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-08-22-guest-authors</guid>
            <pubDate>Sat, 22 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[How I implemented authors and how you can become a guest author]]></description>
            <content:encoded><![CDATA[<p>I've previously <a href="https://agingdeveloper.com/article/2020-07-21-intro">mentioned</a> that this site was created for me to interact with the public again as well as to learn some new technical skills. That doesn't mean that "The Aging Developer" has to be a one man show. I do not have to be the only person to contribute to it. Both the code and the content of the site are both hosted in a public <a href="https://github.com/richwklein/agingdeveloper">github</a> repo.</p>
<p>If you've previously been on the site you may have noticed that there was stubbed in navigation for an "Authors" page. This weekend I've finally been able to implemented support for multiple authors.</p>
<p>This article will explain how you can contribute content if you want and also how I went about building support for multiple authors.</p>
<h2>Contributing Content</h2>
<p>If you feel like you have some content that relates to the theme of the site you can create and submit a pull request for me to review and accept or reject.</p>
<p>If this is your first time contributing there are two things you'll need to supply. The article itself, including any images, and an author file.</p>
<p>The article goes in the <strong><em>/content/article</em></strong> directory. There is not a required layout for that directory, but I've been creating a subfolder with the slug of the article as the name. The folder contains both the article as well as any images in the article. This makes it easier to get the links between working correctly. The articles are written in mdx (Markdown + JSX) and should include a section of front matter at the top of the file. The code block below shows the front matter for this article.</p>
<pre><code>---
title: Support for Guest Authors
author: richwklein
image: aaron-burden-y02jEX_B0O0-unsplash.jpg
tags:
  - gatsbyjs
  - material-ui
  - public
  - react
  - code
  - author
  - support
  - yaml
category: site
published: '2020-08-22'
---
</code></pre>
<p>The value of the author key in the front matter gets mapped to the id of an author node in Gatsby's graph during the build process. The author nodes are provided by creating an yaml file in the <strong><em>/content/author</em></strong> directory. That file contains information about the author including: email address, name, a short biography, avatar image, and social media links. Below is a sample of my <a href="https://github.com/richwklein/agingdeveloper/blob/master/content/author/richwklein.yaml">file</a>.</p>
<pre><code>id: richwklein
name: Richard Klein
---
social:
  twitter: https://twitter.com/richwklein
  facebook: https://www.facebook.com/richwklein
  linkedIn: https://www.linkedin.com/in/richwklein/
  github: https://github.com/richwklein
  instagram: https://www.instagram.com/richwklein/
</code></pre>
<h2>How it was implemented</h2>
<p>Now on to how I implemented support for authors on the site. <a href="https://www.gatsbyjs.com/">Gatsby</a> has a few tutorials around how to implement this, but I found some of the tutorials on doing the linking between article and author nodes overly difficult to implement. Hopefully, this example can help others who might be trying to accomplish the same thing without the struggles I originally had.</p>
<p>In my <strong><em>gatsby-config.js</em></strong> file I have my file source set up to point at the <strong><em>/content</em></strong> directory. This allows just a single source plugin to supply both the article and author data. If you want to serve author information out of a different directory than the article information, you can set up a copy of the file source plugin with a path set to a different directory.</p>
<pre><code>plugins: [
  {
    resolve: "gatsby-source-filesystem",
    options: {
    path: `${__dirname}/content`,
  },
]
</code></pre>
<p>There are a few file formats you could choose to supply the author information from. I've seen examples including json and markdown. I decided that I wanted to supply author data via YAML.</p>
<p>To accomplish this I first had to install and configure a transformer. A transformer is a type of Gatsby plugin that takes raw content from a source plugin and transforms it into something more useful. I used the <code>gatsby-transformer-yaml</code>
plugin to transform the author file content into a node in the graph with the appropriate attributes from the YAML data.</p>
<pre><code>npm install --save gatsby-transformer-yaml
</code></pre>
<pre><code>plugins: [
  "gatsby-transformer-yaml",
  {
    resolve: "gatsby-source-filesystem",
    options: {
    path: `${__dirname}/content`,
  },
]
</code></pre>
<p>After creating my YAML file, <strong><em>/content/author/richwklein.yaml</em></strong>, and starting a local development server, the content was available by looking at the graphql ide in the browser.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fauthor_graphql.1uS2qQjN.png&amp;w=1232&amp;h=887&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Author GraphQL" /></p>
<p>Once I had the author information in the graph, I needed to provide a way to link that information with the articles that the author wrote. I did this by adding an "author" key to the front matter of the article files. The key contains the id of the author to link back to. I then added a "mapping" configuration to the <strong><em>gatsby-config.js</em></strong> file.</p>
<pre><code>mapping: {
  "Mdx.frontmatter.author": "AuthorYaml",
},
</code></pre>
<p>The full information of the author could then be queried as part of the front matter of the individual articles.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Farticle_graphql.CMVpt6yH.png&amp;w=1232&amp;h=617&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Article GraphQL" /></p>
<p>It was pretty straight forward to create a byline section on the article pages as well as the various author related pages once the configuration was done.</p>
<p>The byline contains the author's avatar, a link to the author's page, along with the date the article was published and how long it should take you to read the article.</p>
<p>The <a href="https://agingdeveloper.com/author">Authors</a> page contains a grid of all the authors that have contributed to the site with an avatar button to each author's individual page.</p>
<p>The author's individual page has their name, bio, social links, and articles they have written. the page is built from a template via <strong><em>gatsby-node.js</em></strong>. This is done by including the author in the graphql query that runs as part of the build. It outputs a page for each node in the author section of the graph.</p>
<pre><code>exports.createPages = async ({ actions, graphql, reporter }) =&gt; {
  const { createPage } = actions

  const result = await graphql(`
    ...
    authors: allAuthorYaml(sort: { fields: name, order: DESC }) {
      edges {
        node {
          id
        }
      }
    }
`)

  createAuthorPages(createPage, result.data.authors.edges)
}

const createAuthorPages = (createPage, authors) =&gt; {
  const template = path.resolve('src/templates/author.js')
  const prefix = '/author'

  authors.map(({ node }, index) =&gt; {
    const currentPath = node.id

    return createPage({
      path: `${prefix}/${currentPath}`,
      component: template,
      context: {
        currentPath: currentPath,
      },
    })
  })
}
</code></pre>
<p>Once I figured out how to get the mappings working between the article front matter and the author's information, it was not difficult to implement the rest.</p>
<p>It is still probably the biggest change I've made to the site since launching it. So if you find any issues please submit a <a href="https://github.com/richwklein/agingdeveloper/issues">github issue</a>.</p>
<p>Also, if you like what I'm doing and would like me to continue try getting a hold of me using one of the social links on my <a href="https://agingdeveloper.com/author/richwklein">author page</a>.</p>
<p><strong>Remember if you would like to contribute feel free to open a pull request.</strong></p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/aaron-burden-y02jEX_B0O0-unsplash.DRtgq7L_.jpg"/>
        </item>
        <item>
            <title><![CDATA[Default HTTP Config Considered Dangerous]]></title>
            <link>https://agingdeveloper.com/article/2020-08-14-default-http-config</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-08-14-default-http-config</guid>
            <pubDate>Fri, 14 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Gotchas using http clients with default configuration]]></description>
            <content:encoded><![CDATA[<p>An alternatively headline might be: <em>know how your service communicates with it's dependencies</em>. Ideally, a microservice could work in isolation without having to communicate with any other service or dependency. This is not likely the case and HTTP is the most common way that I've seen these services communicating. I've witnessed several problems that have been caused by default configurations being used by these clients. In this article I will explore a couple of these issues and how you can remediate them.</p>
<h4>Java DNS Caching</h4>
<p>When the JVM resolves hostnames to ip addresses it caches those lookups. Depending on the configuration this might not be refreshed until the JVM is restarted. This can especially be an issue if you are using AWS services where the IP Address may
change for a service. I've seen this occurs with aurora failovers and other scenarios. You can prevent this by setting <code>networkaddress.cache.ttl</code> in the <code>$JAVA_HOME/jre/lib/security/java.security</code> file to some low number of seconds. Amazon recommends no more than 60 seconds.</p>
<pre><code>networkaddress.cache.ttl = 5
</code></pre>
<h4>Maximum Connections Per Route</h4>
<p>Creating a tcp connection between services is a time consuming process and consumes lots of resources. This is especially true when TLS is involved. Because of this popular http clients try and reuse the same connection if they are going to the same route. The <a href="https://square.github.io/okhttp/">OkHttp</a> library keeps only a single connection. The Apache <a href="https://hc.apache.org/httpcomponents-client-ga/index.html">HttpClient</a> defaults to two. The Go <a href="https://golang.org/pkg/net/http/">http package</a> also defaults to two.</p>
<p>This is fine when you are communicating over HTTP/2 because that protocol allows for multiple concurrent requests over a single connection.</p>
<p>Http/1.1 however, can only have a single request over the connection at a time. This can create a Head-of-line <strong>(HOL)</strong> blocking issue if you have to make multiple concurrent requests to the same dependency. The result of which may cause your service's requests to back up and cause a cascading failure. The most basic way to mitigated this is by increasing the maximum connections per route.</p>
<pre><code>HttpClientBuilder builder = HttpClients.custom()
builder.setMaxConnPerRoute(20)
return builder.build()
</code></pre>
<h4>Request Timeout</h4>
<p>A lot of clients by default use the timeout set by the OS. This is true for clients in both Java and Go. This is another possible way to cause cascading failures. The best advice is to configure your client with something other than the default. The example below does this globally for the client, but you can also set these on a per request basis.</p>
<pre><code>RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(5000)
        .setSocketTimeout(5000)
        .build();
SocketConfig socketConfig = SocketConfig.custom()
        .setSoTimeout(5000)
        .build();
HttpClientBuilder builder = HttpClients.custom()
        .setMaxConnPerRoute(20)
        .setDefaultRequestConfig(requestConfig)
        .setDefaultSocketConfig(socketConfig);

return builder.build()
</code></pre>
<h4>Other Best Practices</h4>
<p>By following the practices above, you can help keep your service healthy. A few other best practices that I'm not going to go into, but can keep your service resilient include: using circuit breakers to prevent cascading failures, using retry designs for those temporary intermittent failures, and using thread pools or executor services for bulk heading and fault isolation.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/israel-palacio-ImcUkZ72oUs-unsplash.DMKahs8Z.jpg"/>
        </item>
        <item>
            <title><![CDATA[Setting up a Custom Domain With Netlify]]></title>
            <link>https://agingdeveloper.com/article/2020-08-08-custom-domain</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-08-08-custom-domain</guid>
            <pubDate>Sat, 08 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A How-to on using a custom domain with Netlify.]]></description>
            <content:encoded><![CDATA[<p><a href="https://www.netlify.com/">Netlify</a> is a great service. It gets my workflow and I can't heap enough praise on it. It provides a subdomain for your site to be hosted on by default. This is great way to get started. At some point though your going to want to move your site off to it's own domain. Making that change is not particularly hard, and Netlify has <a href="https://docs.netlify.com/domains-https/custom-domains/configure-external-dns/#configure-a-subdomain">great documentation</a>, but for those of us that have not worked much with DNS it can be intimidating. So this article will walk through an example of how
to set this up.</p>
<p>In this example I'm going to assume you already have a domain registered. I went and registered <a href="https://agingdeveloper.net/">agingdeveloper.net</a> as part of this exercise just to remind myself how to do it. My <a href="https://agingdeveloper.com/article/2020-07-26-false-start">False Starts Article</a> explained how I had previously set up hosting and registered the domain agingdeveloper.com with
<a href="https://www.bluehost.com/">BlueHost</a>. That is the registrar I'm going to be using here. You may have to do something different with other registrars. The screenshots are from setting up a second domain, They will look different then when setting up your first domain.</p>
<p>In Netlify go to your site's Settings › Domain management. Select <em>Add custom domain</em> at the bottom of the <strong>Custom domains</strong> panel, enter your domain name, and select <em>Verify</em>.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fnetlify_verify.C7ksWZ67.png&amp;w=1232&amp;h=553&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Netlify Verify" /></p>
<p>Confirm you are the owner of the domain then select <em>Yes, add domain</em>. We are going to choose to delegate the domain Netlify. Make a copy of Netlify's nameservers. These are listed directly below the custom domain panel.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fnetlify_nameservers.I4_pXBOQ.png&amp;w=1232&amp;h=698&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Netlify Nameservers" /></p>
<p>On your Bluehost's domain page, select the domain that you want to delegate. Then select the <em>name servers</em> tab. Choose the option to <em>Use Custom Nameservers</em> and input the previously copied list.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fbluehost_nameservers.wHAMT3EC.png&amp;w=1164&amp;h=550&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Bluehost Nameservers" /></p>
<p>Select <em>save nameserver settings</em>. It may take just a minute for the setting to take effect, but that is all you have to do. Your site should now show up at the new domain name. Pretty easy, right!!!</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/taylor-vick-M5tzZtFCOfs-unsplash.m3P9mqPl.jpg"/>
        </item>
        <item>
            <title><![CDATA[Workflow, Tech Stack, and False Starts]]></title>
            <link>https://agingdeveloper.com/article/2020-07-26-false-start</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-07-26-false-start</guid>
            <pubDate>Sun, 26 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A breakdown of how I got started and the stack that I'm using]]></description>
            <content:encoded><![CDATA[<p>I only had two hard requirements when I started this site up. At work we have an internal development blog that uses a static site generator. The articles are markdown files that you check into the repository and the CI/CD system builds the site and deploys it. That workflow is very familiar to developers, and was an appealing workflow for here. I also wanted to use some newer frameworks, specifically front-end heavy ones, that I was not real familiar with so that I could learn as I was building.</p>
<p>The first thing I did was went out and found the <em>agingdeveloper.com</em> domain and registered it at <a href="https://www.bluehost.com/">bluehost</a> along with a hosting plan. This was the service provider I had previously used. That became my first <strong>false start</strong>. I should have waited to figure out hosting until after I had the framework and deploy pipeline figured out.</p>
<p>I next set up a <a href="https://github.com/richwklein/agingdeveloper">repo</a> and went about trying to figure out which frameworks I wanted to use. I've used <a href="https://docs.getpelican.com/en/stable/index.html">Pelican</a> before, but that wasn't what I was looking for since it is written in Python and not really a new framework for me. I enlisted the help of <a href="https://www.staticgen.com/">StaticGen</a>, a great resource that compares various static site generators. It helped me narrow my choices. My second <strong>false start</strong> came when I started out with <a href="https://www.11ty.dev/">Eleventy</a>. I actually had a branch building with it. In the end the templating felt too similar to what I had used in the past.</p>
<p>I finally landed on <strong><a href="http://gatsbyjs.org/">Gatsby</a></strong> a Javascript framework that uses <a href="https://reactjs.org/">React</a> for templating and is powered by <a href="https://graphql.org/">GraphQL</a>. I wanted to learn more about both React and GraphQL so it was a perfect fit for me to use.</p>
<p>I had my hosting and my framework now I just had to find my deploy pipeline. This is where that first <strong>false start</strong> comes into play. The only way I could deploy my site to bluehost was via FTP. I tried to get this working with
<a href="https://travis-ci.org/">Travis CI</a> for a while, but I never really got it working and it just didn't feel right. I then went out looking for either a new CI/CD system or a new host. I landed with <a href="https://www.netlify.com/">Netlify</a> which is built for a "git based developer workflow" (exactly what I wanted) and includes CI/CD, DNS &amp; Domain management, and hosting.</p>
<p>I first started putting things together by coding all my own React components from scratch. That was my last <strong>false start</strong>. I realized it was silly to be reinventing the wheel when there are lots of component libraries out there.
Just because I was using a component library did not mean I wouldn't be learn React as I went along. I settled on using <a href="https://material-ui.com/">material-ui</a> as my component library.</p>
<p>There are some other interesting bits that make this site tick and are worth mentioning. I'm using <a href="https://letsencrypt.org/">Let's Encrypt</a> to get a <em>free</em> SSL cert. Almost of the images I'm using on here are from <a href="https://unsplash.com/">Unsplash</a> which provides <em>free</em> to use photos for anyone.</p>
<p>In my <a href="https://agingdeveloper.com/article/2020/07/21/intro">previous post</a> I mentioned that it was about a year ago that I started to want to re-engage in the online community. It didn't take a year to go from the idea germinating until it was something that
was visible to share. I am a passionate developer, but I went for long stretches without putting any effort toward this as you can tell by the code frequency graph.</p>
<p><img src="https://agingdeveloper.com/.netlify/images?url=_astro%2Fagingdeveloper-codfrequency-july-2020.DhsD9pqC.png&amp;w=1232&amp;h=742&amp;dpl=69a59eb298f6bd0007e61b7b" alt="Code Frequency" /></p>
<p>It took me a few false starts and some long stretches of down time, but I've landed on a workflow and a tech stack that I think will suite this "Aging Developer".</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/braden-collum-9HI8UJMSdZA-unsplash.CAjJQIti.jpg"/>
        </item>
        <item>
            <title><![CDATA[Introduction and Return]]></title>
            <link>https://agingdeveloper.com/article/2020-07-21-intro</link>
            <guid isPermaLink="false">https://agingdeveloper.com/article/2020-07-21-intro</guid>
            <pubDate>Tue, 21 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Why I created this site and started to re-engage]]></description>
            <content:encoded><![CDATA[<p>Throughout my 20s I was an active participant online. I followed and worked on open source projects in the late 90s and early 2000s. When <a href="https://en.wikipedia.org/wiki/Web_2.0">Web 2.0</a> (also know as the Social Web) started to take shape I was involved. My Space, Digg, Twitter, Facebook were all sites where I participated early on and fairly regularly along with publishing my own blog. I was in constant contact with a thriving online community. This was especially true when I was working at AOL on the <a href="https://en.wikipedia.org/wiki/Netscape_Navigator_9">Netscape</a> browser and <a href="https://web.archive.org/web/*/http://propeller.com">Propeller</a> social news site.</p>
<p>As the online landscape shifted and my career and personal life evolved, I found myself particpating online less and less frequently. My work became more enterprise focused and less available to the general public. I've got an active family life with a wonderful wife and 3 boys that keeps us busy. I just didn't have the time, desire, or energy to spend a lot of time engaging in the general public.</p>
<p>About a year ago I started to feel nostalgic for the level of engagement I had during those earlier years. I hadn't had any real active involvement with the general public in years. My work is not something that most people would see and I was rusty on the skill sets that had given me my initial career path. It had been years since I had done any real front-end development. I not only wanted to go back to participating more, but I also wanted to do work that was seen again by a larger audience.</p>
<p>At the same time I also started to realize that I have a unique perspective. A lot of software development careers shift to a management focus as you ascend the career track. I was a manager for a while, but I took a step back and decided
that I wanted to stay focused on development and engineering. My real passion was building software and not managing people so I changed career paths and went back to development. This means I'm now a mid forty year old developer in an
industry that is well known for <a href="https://www.google.com/search?q=ageism+software+engineering&amp;oq=ageism+software+engineering">skewing towards youth</a>. This was an avenue I could explore and share my own personal experiences.</p>
<p>This site will not be soley dedicated to this "Aging Developer" perspective, but it will color everything that I do with it. <a href="https://agingdeveloper.com/">The Aging Developer</a> is a vehicle for me to re-engage and actively participate more. It also provides a way for me to go back and learn the latest trends in front-end development.</p>
<p><strong>Fair warning</strong>: The site is an active development and a learning location. It will be broken and have missing features for large portions of the time.</p>
]]></content:encoded>
            <author>richwklein@gmail.com (Richard Klein)</author>
            <enclosure length="0" type="image/jpeg" url="https://agingdeveloper.com/_astro/fons-heijnsbroek-v4c2gKPjPMs-unsplash.D_VForbW.jpg"/>
        </item>
    </channel>
</rss>