<?xml version="1.0" encoding="UTF-8"?><rss 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" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Recursive Function]]></title><description><![CDATA[A bi-weekly tech newsletter on the art of computer whispering]]></description><link>https://recursivefunction.blog</link><image><url>https://substackcdn.com/image/fetch/$s_!QePC!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34ff481a-0c57-4e7f-a384-3ebb8d7105f0_512x512.png</url><title>Recursive Function</title><link>https://recursivefunction.blog</link></image><generator>Substack</generator><lastBuildDate>Tue, 21 Apr 2026 10:23:15 GMT</lastBuildDate><atom:link href="https://recursivefunction.blog/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Amitosh Swain Mahapatra]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[recursivefunction@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[recursivefunction@substack.com]]></itunes:email><itunes:name><![CDATA[Amitosh Swain Mahapatra]]></itunes:name></itunes:owner><itunes:author><![CDATA[Amitosh Swain Mahapatra]]></itunes:author><googleplay:owner><![CDATA[recursivefunction@substack.com]]></googleplay:owner><googleplay:email><![CDATA[recursivefunction@substack.com]]></googleplay:email><googleplay:author><![CDATA[Amitosh Swain Mahapatra]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Coding for Humans and Machines Alike]]></title><description><![CDATA[Or Why Clean Code Matters More Than Ever]]></description><link>https://recursivefunction.blog/p/coding-for-humans-and-machines-alike</link><guid isPermaLink="false">https://recursivefunction.blog/p/coding-for-humans-and-machines-alike</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Sun, 07 Sep 2025 18:40:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Kc7Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software development has constantly evolved alongside tools that reduce friction. From compilers to IDEs to version control, every generation of developers has gained leverage. The last few years have brought what I believe is the most significant leap yet &#8212; large language models (LLMs) and coding agents.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kc7Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Kc7Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae05bbab-6b09-437f-ac41-4a02ee7cb373_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tools like Copilot, Cursor, and numerous other &#8220;agents&#8221; like Claude Code &amp; co. can generate boilerplate in seconds, spin up a REST API, scaffold with a single prompt, or even summarise a 5,000-line pull request. I have personally used this to port logic from a language I don&#8217;t want to deal with &#8212; no hate to C++, but sometimes, while working with embedded devices, micropython is such a time saver. Regardless, with the power of LLMs, I can condense various miscellaneous work into minutes, which would require hours of my focus time otherwise.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I have seen the evolution of these tools over the last 2 years, and I can confidently say that they are here to stay. They are not perfect, but they are getting better every day. I have been daily-driving Copilot and Claude Code since they were released and I can claim that I&#8217;m somewhat of a power-user myself. Some day I&#8217;ll write a blog post about my experience with these tools, but for now, I want to focus on a different aspect of this evolution.</p><blockquote><p>The effectiveness of AI coding assistance depends not only on the model&#8217;s intelligence, but also on the quality of the codebase it interacts with.</p></blockquote><p>Pretty straightforward, right? But let me explain why I think this is a big deal, and also try to address some of the FUD going around these tools on Twitter every day.</p><h2>The Rise of Coding Automation</h2><p>Early &#8220;AI pair programmers&#8221; were mostly autocomplete on steroids. They could spit out snippets, but context was limited. Today&#8217;s LLM-based tools ingest entire projects, understand inter-dependencies, and adapt to your coding patterns. This means I can completely offload repetitive work like DTO generation, CRUD endpoints, and unit test scaffolding to the machines. I spend less time grinding through boilerplate and more time focusing on domain logic and architecture.</p><p>But this doesn&#8217;t mean developers are &#8220;done for.&#8221; It means our jobs have shifted from writing every line to more designing, curating, and maintaining code that humans and machines can reason about.</p><h2>Cutting Through the Noise</h2><h3>&#8220;AI will replace all developers.&#8221;</h3><p><em>Just describe features in English and boom&#8212;software ships itself.</em> </p><p><em>This is fantasy</em>. Software is more than code; it&#8217;s architecture, systems thinking, tradeoffs, and a touch of empathy for the future users.</p><p>As someone writing software for a living, I wish it were that easy. I would love to run a bunch of Claudes and mint money selling software to the world.</p><p>I write code that makes <em>LLMs go brrr</em>, so trust me when I say that they are utterly useless for some of the things I do. I routinely turn-off copilot and force-kill Claude for about 20-30% of my work, when it starts to generate garbage on parts of code that work on novel parts &#8212; for example, OpenAI&#8217;s responses API. It was just released a night back and Claude had no clue. It generated wonderfully consistent garbage, but garbage nonetheless. It just hasn&#8217;t seen the API yet, and no amount of prompts and web searches could fix that.</p><h3>&#8220;LLMs are useless.&#8221;</h3><p>Because they sometimes hallucinate or generate brittle code, critics just dismiss them outright.</p><p>That ignores the productivity gains already proven in industry settings. I have bootstrapped complete features while on in-flight WiFi with Claude. The truth is in between. LLMs are tools, not magic wands. Neither they make you Hermione to open door locks with &#8220;Alohomora&#8221;. Their usefulness scales with the structure and readability of the code they touch.</p><h2>The Sweet Spot for Humans and LLMs</h2><p>Uncle Bob didn&#8217;t write &#8220;Clean Code&#8221; with LLMs in mind, but it might as well have been. (Let&#8217;s ignore politics and Clean Code dogmatists for now. It&#8217;s a nice book, read it if you haven&#8217;t)</p><p>Consider a few principles:</p><ul><li><p><strong>Small, single-responsibility functions</strong> make it easier for humans to test, and easier for models to &#8220;understand&#8221; scope.</p></li><li><p>Use <strong>meaningful variable and class names</strong> that act as semantic breadcrumbs for LLM reasoning.</p></li><li><p>Have a <strong>consistent structure</strong>. Predictable patterns make it simpler for models to complete code correctly.</p></li></ul><p>Now flip the scenario, Get a file from a poorly written legacy monster with 30,000 LOC files, cryptic variable names, and spaghetti logic. Working on such code is brutal for humans and <em>impossible</em> for even the smartest LLM to navigate effectively within context windows.</p><p><em>Good code isn&#8217;t just a human courtesy anymore.</em></p><h2>A Peek Under the Hood &#8212; How LLM Tools Parse Your Code</h2><p>LLMs don&#8217;t know words; they know tokens. Each token is a piece of the input text, and the model processes these tokens to understand context and <em>predict</em> responses. With enough training data, LLMs have learned to recognise patterns and make educated guesses about what comes next. So models, in essence, take X tokens in and generate Y tokens out. The more context (X) you can provide, the better the output (Y) tends to be. However, each model has its own context window limitations. Today&#8217;s models have an advertised limit of around few million tokens, but in practice, only a fraction of that is usable for coding tasks. It&#8217;s much better than GPT-3.5&#8217;s 32K limit.</p><p>But our massive projects won&#8217;t fit in a single context window. Even if they did, you don&#8217;t want to do that, because computing each token costs time and money. It&#8217;s not just one call to an API &#8212; agents run in a loop, making multiple calls to refine outputs, and all their memory (including what they read and what they wrote) is going to be part of the context window and you will run out of it pretty quickly.</p><p>So, coding agents have to be smart about what parts of your codebase to load into the model at any given time. Much of the heavy work is done by LLMs, which, like any competent developer, would try to guess the structure of the code they&#8217;re working with.</p><p>Next time you kick off &#8220;Agent mode&#8221;, look carefully at the tool calls the Cursor makes. You will find a lot of searches across files with patterns matching file names, classes and functions. If I gave you a codebase and no documentation, you would do the same thing. There are optimisations like vector search and embeddings, but fundamentally, it&#8217;s the same process.</p><p>If your project has massive, tangled files, the assistant has to make hard tradeoffs about what to load. That means it may drop critical logic, or worse, misinterpret and hallucinate stuff. But if your code is modular, well-named, and neatly segmented, the assistant can pull just the right piece and you get faster, cheaper, and more accurate results.</p><p>Think of it as coding with an additional teammate: one who doesn&#8217;t complain, but has strict memory limits.</p><h2>Coding for Humans and Machines</h2><p>The lesson is simple: clean code has never mattered more. I&#8217;m not saying to follow &#8220;Clean Code&#8221; dogmatically, but strive for clarity, modularity, and good naming. Readable, modular, and well-structured code isn&#8217;t just about making your future self (or a junior dev) grateful. It&#8217;s also about enabling the growing ecosystem of AI coding tools to work effectively. The better your codebase, The better these tools perform, the more they boost your productivity and reduce friction.</p><p>So, in 2025 and beyond, don&#8217;t just code for humans. Code for humans and machines. That&#8217;s how you&#8217;ll get the best of both worlds.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to Build Your Own Coding Agent]]></title><description><![CDATA[Spoiler: It's easier than you think!]]></description><link>https://recursivefunction.blog/p/how-to-build-your-own-coding-agent</link><guid isPermaLink="false">https://recursivefunction.blog/p/how-to-build-your-own-coding-agent</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Mon, 21 Apr 2025 03:31:01 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>November 2022.</strong> ChatGPT arrived quietly, not with a crash but like a letter slipping under a locked door.</p><p>People clapped. We thought it was just a clever tool and didn&#8217;t know how it would change everything.</p><p>Soon, something strange began to happen. The machines weren&#8217;t just helping. They were thinking, writing, and fixing mistakes.</p><p>Picking up where humans left off.</p><p>Then came the Cursor. Windsurf. Devin. Names that seemed harmless, like pets or weekend hobbies. But these were not toys. They read code like old detectives read letters. They poked into corners, solved problems, and sometimes rewrote the story.</p><p>It sounds like science fiction: a machine that chats, edits files, runs commands and corrects itself and keeps going even after it stumbles.</p><p>But there is no secret. No magic.</p><p>It is just a loop. A model. And lots of words and some clever API calls.</p><p>Now, we are going to build one. From scratch. Piece by piece.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="6000" height="4000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4000,&quot;width&quot;:6000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;black flat screen computer monitor&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="black flat screen computer monitor" title="black flat screen computer monitor" srcset="https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1595623654300-b27329804025?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8Y29kZSUyMHRlcm1pbmFsfGVufDB8fHx8MTc0NTE3Mjg4NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Godfrey Nyangechi</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2><strong>Let&#8217;s chat!</strong></h2><p>A coding agent is a chat interface connected to a Large Language Model (LLM) like OpenAI&#8217;s GPT models or (my preferred) Anthropic&#8217;s Claude Sonnet models. You need an API key from OpenAI, Anthropic or Gemini, a system prompt to define the agent&#8217;s <em>persona</em>, ask the user for input in a loop, send the conversation (messages) to the OpenAI chat completions API endpoint, get a result, and print it.</p><pre><code>import json
import requests


def load_config(config_path):
    with open(config_path, 'r') as file:
        config = json.load(file)
    return {
        "endpoint": config["endpoint"],
        "api_key": config["api_key"],
        "model": config["model"]
    }


def chat_completion(endpoint, api_key, model, messages):
    request = {"model": model, "messages": messages}
    headers = {"Authorization": f"Bearer {api_key}"}
    response = requests.post(
        f"{endpoint}/chat/completions",
        json=request,
        headers=headers
    )
    response.raise_for_status()
    return response.json()


def main():
    config = load_config('config.json')

    system_prompt = """You are a helpful chat assistant."""

    messages = [{"role": "system", "content": system_prompt}]

    print("Chat with the assistant. Type 'exit' to quit.")

    while True:
        user_input = input("&gt; ")
        if user_input.lower() == "exit":
            break

        messages.append({"role": "user", "content": user_input})

        response = chat_completion(
            endpoint=config["endpoint"],
            api_key=config["api_key"],
            model=config["model"],
            messages=messages
        )

        choice = response["choices"][0]
        message = choice["message"]
        messages.append(message)

        print(message["content"])


if __name__ == "__main__":
    main()</code></pre><p>Just 60 lines, and you have ChatGPT Pro! It is missing things like chat persistence, image inputs, and all other <em>UI niceties, </em>but you get the idea. The core is simple.</p><blockquote><p>Note: I&#8217;m not using any LLM SDK here, as they are plain HTTP calls underneath. If you use them, you can make this code even smaller.</p></blockquote><h2><strong>AI, follow my instructions!</strong></h2><p>The GPT 3 model, more or less a glorified autocomplete, was the precursor to ChatGPT (GPT 3.5). ChatGPT was a fine-tuned GPT 3 model that was good at following instructions. If you told ChatGPT to rewrite something in the style of King James&#8217; Bible, it complied.</p><p>Soon, we figured we could also instruct the models to generate and understand &#8220;structured&#8221; outputs like JSON. You probably have done things like asking ChatGPT to make tables from JSON.</p><p>A LLM with tools is an &#8220;agent&#8221;. It now has <em>agency</em> to work on things we tell it to do. You can prompt the models to generate a structured response, parse them, call your custom code, pass the prompt again to the agent, and get your formatted response. Modify the system prompt in the chat code we created before and see how it works.</p><pre><code>You are a helpful home assistant, answer the users queries based on your knowledge and provided the tools about home automation

Always format your response with tags.
DO NOT include any other content before or after the tags
- Use the speak tag to return a user-facing message
- Use the call tag to output a json which we will use to call a tool to do something
- You SHOULD respond with multiple call tags if needed

Example:

&lt;speak&gt;content to speak&lt;/speak&gt;
&lt;call&gt;{ "name": "function_name", "args": { "arg1": "value", "arg2": "value" } }&lt;/call&gt;


You have access to following tools:
1. temperature(): number in degree celsius
2. humidity(): number from 0 to 1 to denote relative humidity
3. list_devices(): { deviceId: string, name: string}, name and deviceId of all configured devices
4. get_device_properties(deviceId: string): Record&lt;string, any&gt; of device properties
5. set_device_properties(deviceId: string, properties: Record&lt;string, any&gt;): boolean, set device properties

When you request to run a tool, you will receive JSON formatted tool output in the next message
as { "output": "tool output" } or { "error": "tool error" }

Read the tool output and emit a helpful error message</code></pre><p>If you ask a question, the model will respond with a structured response now. Now respond with a JSON value and see the model format the values appropriately in natural language. If you ask a general query, the model also answers with its intelligence.</p><pre><code><strong>$ python chat.py</strong>
Chat with the assistant. Type 'exit' to quit.
&gt; what is the temperature and humidity
&lt;speak&gt;Let me check the current temperature and humidity for you.&lt;/speak&gt;
&lt;call&gt;{ "name": "temperature", "args": {} }&lt;/call&gt;
&gt; { "output": 32 }
&lt;speak&gt;The current temperature is 32 degrees Celsius. Now, I will check the humidity.&lt;/speak&gt;
&lt;call&gt;{ "name": "humidity", "args": {} }&lt;/call&gt;
&gt; { "output": 0.78 }
&lt;speak&gt;The current humidity level is approximately 78%.&lt;/speak&gt;                                                                                     &gt; C compilers are written in C! how was the first compiler created then?
&lt;speak&gt;The first compiler was created as a separate program written in a different programming language or directly in machine code. For example, the earliest compiler for a high-level language like Fortran was initially written in assembly language or machine-specific code before being rewritten in the target language itself. This process is known as "bootstrapping," where a simpler or existing compiler is used to create a more advanced one. Over time, this foundational work enabled the development of self-hosting compilers written in the language they
compile.&lt;/speak&gt;</code></pre><h2><strong>Building our first tool</strong></h2><p>Parsing model responses was <em>so 2022</em>. It&#8217;s 2025 now, and model providers have done a ton on RLHF to ensure models use tools effectively and also provide a way to specify which tools your models call in the chat completions API itself.</p><p>Let&#8217;s start by defining our first tool <code>read</code>, which reads a file.</p><pre><code>def read(path: str):
    with open(path, 'r') as f:
        return f.read()</code></pre><p>Then, modify our <code>chat_completion</code> code to accept tools and pass it to the API.</p><pre><code>def chat_completion(endpoint, api_key, model, messages, tools):
    request = {"model": model, "messages": messages, "tools": tools}
    headers = {"Authorization": f"Bearer {api_key}"}
    response = requests.post(
        f"{endpoint}/chat/completions",
        json=request,
        headers=headers
    )
    response.raise_for_status()
    return response.json()</code></pre><p>The OpenAI API spec wants us to use JSON schema to describe the tool params, so let&#8217;s define our tools and add a description of our read tool.</p><p>When the model wants to call a tool, it will specify it in its <code>finish_reason</code>; let&#8217;s handle that and call our tool. Since we need to pass the result of our tool immediately call back to the LLM and generate further responses, let&#8217;s add a nested loop to process tool calls and yield the prompt back to the user only when the model completely processes your request.</p><pre><code># ... rest of the code, same as before
def main():
    config = load_config('config.json')

    system_prompt = """You are a helpful chat assistant."""

    messages = [{"role": "system", "content": system_prompt}]

    # Our tools go here...
    tools = [{
        "type": "function",
        "function": {
            "name": "read",
            "description": "Read a text file",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "File path"
                    }
                },
                "required": ["path"]
            }
        }
    }]

    tool_functions = {
        "read": read
    }

    print("Chat with the assistant. Type 'exit' to quit.")

    while True:
        user_input = input("&gt; ")
        if user_input.lower() == "exit":
            break

        messages.append({"role": "user", "content": user_input})

        # Our inner tool-calling loop goes here
        processing = True
        while processing:
            response = chat_completion(
                endpoint=config["endpoint"],
                api_key=config["api_key"],
                model=config["model"],
                tools=tools,
                messages=messages
            )

            choice = response["choices"][0]
            message = choice["message"]
            messages.append(message)

            print(message["content"])

            # Handle tool calls
            if choice["finish_reason"] == "tool_calls":
                tool_calls = message["tool_calls"]
                for tool_call in tool_calls:
                    tool_call_id = tool_call["id"]
                    tool_call_function = tool_call["function"]
                    tool_name = tool_call_function["name"]
                    tool_args = tool_call_function["arguments"]

                    tool_args_dict = json.loads(tool_args)
                    # Tell we are executing the function
                    print(f"{tool_name}({str(tool_args_dict)})")
                    # Execute the tool function
                    result = tool_functions[tool_name](**tool_args_dict)

                    messages.append({
                        "role": "tool",
                        "content": result,
                        "tool_call_id": tool_call_id
                    })


            # If the LLM ran out of output tokens
            elif choice["finish_reason"] == "length":
                messages.append({
                    "role": "user",
                    "content": "Please continue."
                })

            # We are done doing our task, yield back to the user
            elif choice["finish_reason"] == "stop":
                processing = False
</code></pre><p>That&#8217;s our first tool. It doesn&#8217;t do much, but now the LLM can read files and answer based on that. Let&#8217;s try it out.</p><pre><code><strong>$ python chat.py</strong>
Chat with the assistant. Type 'exit' to quit.
&gt; what does chat.py do?
read({'path': 'main.py'})
The chat.py script is designed to operate as a helpful assistant. It interacts with users via prompts and can execute various
tools to facilitate development workflows</code></pre><h2><strong>Bringing it all together</strong></h2><p>Think about the elementary set of tools you, as a programmer, need to write code: list all other files, read the files, write your changes and run commands to test your code. Let&#8217;s add a <code>ls</code> and a <code>write</code> tool to explore the codebase and write any changes you ask the agent to make.</p><pre><code># Add tool implementions

def ls(path: str):
    if not os.path.exists(path):
        return "Path does not exist."

    if os.path.isfile(path):
        return f"File: {path}"

    items = os.listdir(path)
    items_str = "\n".join(items)
    return f"Directory: {path}\n{items_str}"

def write(path: str, content: str):
    with open(path, 'w') as file:
        file.write(content)
    return "File written successfully."

# Register our tools

def main():
    # ... rest of the code
    tools = [
        {
            "type": "function",
            "function": {
                "name": "ls",
                "description": "List files and directories.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Directory path"
                        }
                    },
                    "required": ["path"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "read",
                "description": "Read a text file",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "File patb"
                        }
                    },
                    "required": ["path"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "write",
                "description": "Write content to a file.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "File path"
                        },
                        "content": {
                            "type": "string",
                            "description": "The content to write to the file."
                        }
                    },
                    "required": ["path", "content"]
                }
            }
        }
    ]

    tool_functions = {
        "ls": ls,
        "read": read,
        "write": write
    }
    # ... rest of the code</code></pre><p>While building any agent, think of what you, as a human, would use to complete your tasks. You can start with a basic set of tools and supply more specialized ones to make your agent more efficient.</p><p>For example, let&#8217;s use the agent we just created to introduce a new <code>search_replace</code> tool to make more minor file edits instead of rewriting everything.</p><pre><code><strong>$ python chat.py</strong>
What would you like to do?
&gt; Add a new search_replace tool to main.py
ls({'path': './'})
read({'path': 'chat.py'})
To add a new search_replace tool to `chat.py`, we will:

1. Add a Python function called `search_replace(path, search_text, replace_text)` that reads a file, replaces all instances of `search_text` with `replace_text`, and writes back the changes.
2. Add the `search_replace` tool specification to the `tools` list.
3. Add the mapping in `tool_functions`.

Would you like these lines added for you and the file updated, or do you want to copy and paste them manually?
&gt; Go ahead
read({'path': 'chat.py'})
write({'path': 'chat.py', 'content': 'import json... (trimmed)'})
The `search_replace` tool has been added to `simplified_main.py`.
You can now use this tool to find and replace text in any file via your
assistant interface. If you need to test its usage or want an example command, just let me know!</code></pre><p>If you notice, our agent generates a lot of markdown content, asks for confirmation, etc. By tweaking the system prompt, we can change our agent&#8217;s <em>personality</em> and make it behave as we want.</p><p>Try the following system prompt. You&#8217;ll see your agent perform a lot smarter.</p><pre><code>You are a helpful assistant that assist users with software development tasks.
- Always analyze the user's codebase before providing a solution.
- Use the tools provided to you.
- If you are unsure about the user's intent, ask clarifying questions.
- Keep your responses concise and to the point, avoiding unnecessary verbosity.
Respond in plain text, and do not include any code blocks or formatting.</code></pre><p>And this is <em>prompt engineering 101</em>, no secret sauce. Experiment till you convince your model to behave as you want.</p><p>Here is the <a href="https://gist.github.com/recrsn/e422a48af6abc498232a2785e4f4c06e">complete 250 lines of our basic agent</a>.</p><p>Play around with our simple agent, and you will find that it&#8217;s pretty smart already. It can make edits, write documentation, and review code. If you add specialized tools, like a bash tool to execute commands or a web search tool to look for information on the web, you can make it even more capable.</p><h1><strong>Then what makes Cursor, Windsurf or &lt;insert AI coding tool here&gt; any special?</strong></h1><p><em>Nothing.</em></p><p>This architecture for building agents isn&#8217;t just for toy projects. All the cutting-edge copilots work this way: orchestrating LLMs and practical function calls. The trick is to start simple, add chat and tools, feed a lot of tokens to the models and then the richer interactions. I started writing this article earlier, but OpenAI&#8217;s Codex CLI release this week <a href="https://github.com/openai/codex/blob/main/codex-cli/src/utils/agent/agent-loop.ts">just confirmed the theory</a>.</p><p>Some of these agents are integrated into editors and IDEs as plugins, which gives them a nice UI and a varying level of tool integration.</p><p>This scrappy script does the job, but you can make it much more efficient. You can extend this by adding version control tools or semantic code searches. I have been implementing a more extensive version of this agent with a language server and debugging capabilities.</p><h2><strong>The real magic: </strong><em><strong>context engineering</strong></em></h2><p>If you&#8217;ve read this far, you&#8217;re either in awe or disbelief, asking, &#8220;<em>No, there must be something, right?&#8221;</em></p><p>As we just saw, <em>there is no secret recipe for a coding agent</em>, and as the models have gotten smaller, there is no moat in the system prompts as well. However, these agents have one serious disadvantage compared to humans: memory, or in LLM terms, the context length.</p><p>Each time you say something or run a tool and provide its output, we keep appending the messages and feeding them to the next API call. This sequence of messages is the model&#8217;s context to generate the next set of output tokens. The original ChatGPT model had a token length of 16,384 tokens, translating to roughly 12,000 words. Later models have improved the token length significantly, with Google&#8217;s Gemini topping the charts with 2 million tokens, and the newest OpenAI models have a 1 million context size.</p><p>But even for a small codebase, you will quickly run out of tokens if you iterate enough. Also, with a large token length, the models tend to lose alignment with your instructions and increase the hallucination rate. My personal experience with Claude, which has 200K token sizes, starts to degrade its performance at around 70K tokens.</p><p>As we are paying tokens in + tokens out in every API call, a large context length will not only increase latency, as the model has to read through the entire conversation to generate its output but also the costs, which grow quadratically as the context grows.</p><p>So, a lot of creative engineering is involved in managing the model context as the agent performs its tasks. Some agents do summarization once they are done with a task, while some mix vector searches to mutate the context.</p><p>With a large enough context window and lots of tokens (and $$$), we won&#8217;t need this either. But for now, this is where the real creative magic is.</p><p><em>Exciting times are coming.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Theory of Broken Windows]]></title><description><![CDATA[A stitch in time saves nine, but why did the one become nine?]]></description><link>https://recursivefunction.blog/p/the-theory-of-broken-windows</link><guid isPermaLink="false">https://recursivefunction.blog/p/the-theory-of-broken-windows</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Tue, 03 Sep 2024 06:30:37 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p>Today, I'm going to step aside from the usual technical jargon and talk about a concept that's relevant in the world of software development and our daily lives.</p><p><em>It's called the theory of broken windows.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="2698" height="2193" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2193,&quot;width&quot;:2698,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;brown concrete building&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="brown concrete building" title="brown concrete building" srcset="https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1560203461-3fd0351af77b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1fHxicm9rZW4lMjB3aW5kb3dzfGVufDB8fHx8MTcyNDc1MTg3Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Mih&#225;ly K&#246;les</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>What is the theory of broken windows?</h2><p>The theory of broken windows is a criminological theory that suggests that visible signs of crime, anti-social behaviour, and civil disorder create an urban environment that encourages further crime and disorder, including serious crimes.</p><p>The theory was introduced in 1982 by social scientists James Q. Wilson and George L. Kelling. They argued that maintaining and monitoring urban environments to prevent small crimes such as vandalism, public drinking, and fare evasion helps create an atmosphere of order and lawfulness, thereby preventing more serious crimes.</p><p>Their theory states that if a window in a building is broken and left unrepaired, people walking by will conclude that no one cares and is in charge. As a result, they will be more likely to break windows and commit other acts of vandalism.</p><h2>In everyday life</h2><p>Scholars have applied the broken windows theory to various aspects of life beyond criminology. For example, they have used it to explain how small problems can escalate into larger ones if addressed. </p><p>A common example is if there is litter on the street, people are more likely to litter. If you leave graffiti on a wall, more graffiti will follow. If you walk around your neighbourhood, you will notice how this principle plays out in real life.</p><p>It also applies to individual behaviour. Think about a messy room. If you leave a piece of clothing on the floor, you are likely to leave more clothes on the floor, and soon enough, your room will be a mess. The same goes for an unkempt garden, a cluttered desk, or a neglected car.</p><h2>In software development</h2><p>In software development, you can apply the theory of broken windows can be to code quality. If a codebase has a lot of inconsistencies, bad practices, and outdated code, developers are more likely to continue writing poor code. They might not feel the need to refactor or improve the codebase because it already looks messy and disorganised.</p><p>On the other hand, if a codebase is well-maintained, clean, and follows best practices, developers are more likely to adhere to those standards. They will be more inclined to write clean code, refactor when necessary, and maintain the overall quality of the codebase. It creates a positive feedback loop that reinforces good practices.</p><p>I've seen this theory play out in various software projects throughout my career. Teams prioritising code quality, consistency, and maintainability tend to produce better software in the long run. They are more productive, have fewer bugs, and can effectively respond to changes. On the other hand, teams that neglect these aspects often struggle with technical debt, bugs, and slow development cycles. Since they are used to this laxity, it becomes challenging to convince them to adhere to standard best-practices.</p><h2>What does it tell us?</h2><p>The theory of broken windows is a powerful concept that highlights the importance of maintaining order and addressing small problems before they escalate into larger ones. Whether in crime prevention, everyday life, or software development, the principle remains the same: small things matter. By paying attention to the details and keeping things in order, we can create an environment that fosters positive outcomes and discourages negative ones.</p><p>So the next time you see a broken window, whether a literal or metaphorical one, remember the importance of fixing it before more windows get broken. It saves you a lot of trouble in the long run.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Taming Logs]]></title><description><![CDATA[Learn how to make application logs work for you, not against you...]]></description><link>https://recursivefunction.blog/p/taming-logs</link><guid isPermaLink="false">https://recursivefunction.blog/p/taming-logs</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Mon, 19 Aug 2024 18:32:51 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Logs are essential. They are a very convenient way to know what is happening in your application. But logs can be overwhelming. They can be too verbose, too cryptic, or too noisy. They can be hard to read, hard to search, hard to understand, hard to manage, and ultimately, hard to trust.</p><p>Unless you tame them!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="6720" height="4480" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4480,&quot;width&quot;:6720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;laptop compute displaying command prompt&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="laptop compute displaying command prompt" title="laptop compute displaying command prompt" srcset="https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1532522750741-628fde798c73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw4fHxzY3JlZW4lMjB3aXRoJTIwbG9nc3xlbnwwfHx8fDE3MjQwOTA5OTN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Desola Lanre-Ologun</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Step 1: Store Logs in a Centralized Location</h2><p>Logs are only helpful if you can access them easily when you need them. If logs are scattered across multiple servers, formats, and locations, they are hard to access, search, analyze and reason about.</p><p>The first step in taming logs is to store them in a centralized location, which makes them accessible from a single location. You can use a log management service that comes with your cloud provider, such as <a href="https://aws.amazon.com/cloudwatch/">AWS CloudWatch Logs</a>, <a href="https://cloud.google.com/logging/">Google Cloud Logging</a>, or <a href="https://azure.microsoft.com/en-us/services/monitor/">Azure Monitor</a>. You can also use a third-party log management service, such as <a href="https://www.datadoghq.com/">Datadog</a>, <a href="https://www.loggly.com/">Loggly</a>, <a href="https://papertrail.com/">Papertrail</a>, <a href="https://www.splunk.com/">Splunk</a>, <a href="https://www.sumologic.com/">Sumo Logic</a> or <a href="https://logz.io/">Logz.io</a>.</p><p>You can also use an open-source log management solution, such as <a href="https://www.fluentd.org/">Fluentd</a>, <a href="https://www.elastic.co/what-is/elk-stack">ELK stack</a>, <a href="https://www.graylog.org/">GrayLog</a>, <a href="https://grafana.com/loki">Grafana + Loki</a>, or <a href="https://clickhouse.com/">ClickHouse</a>.</p><p>Such services will require installing and configuring an agent to ship logs to your log server. Some agents will also ship system logs, allowing you to observe other parts of your servers, such as system services.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Step 2: Format Logs in a Standardized Way</h2><p>If logs are in different formats, with varying fields and structure, they are hard for humans and machines to parse. Free-form log text only allows you to search using a full-text index, which is both expensive and limited in terms of expressiveness.</p><p>By formatting logs in a standardized way, you can define a standard parsing schema that you can use to extract essential fields from logs, such as timestamp, log level, message, and metadata. Logs with metadata make them easier to search and filter. Log agents will also add some metadata about the source automatically. If you use &#8220;fluentd&#8221; or &#8220;logstash&#8221; (from elk-stack), you can use &#8220;<a href="https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html">grok patterns</a>&#8221; to parse logs and extract even more fields.</p><p>You can define a straightforward log format such as:</p><pre><code>&lt;timestamp&gt; &lt;log level&gt; &lt;message&gt; [key1=value1 key2=value2 ...]</code></pre><p>Your log agent can then parse this format, extract the timestamp, log level, message, and metadata fields, and forward them to your log server.</p><h2>Step 3: Define a correlation ID</h2><p>Your application will have multiple concurrent executions, each generating various logs. To make it easier to trace the logs generated by a single execution, you can define a correlation ID unique to each execution at the very start of invocation. You can pass this correlation ID from one component to another and print it in every logging statement to trace the logs generated by a single execution across multiple components.</p><p>Let's add a correlation ID to our log format:</p><pre><code>&lt;timestamp&gt; &lt;correlation ID&gt; &lt;log level&gt; &lt;message&gt; [key1=value1 key2=value2 ...]</code></pre><p>Your log agent can then extract the correlation ID field and forward it to your log server.</p><h2>Step 4: Define a trace ID</h2><p>In a distributed system, a single request can span multiple services. To make it easier to trace the logs generated by a single request across various services, you can define a trace ID unique to each request at the very start of the request. Your code can pass this trace ID between services, and you can reuse the same trace ID while printing logs.</p><p>Let's add a trace ID to our log format:</p><pre><code>&lt;timestamp&gt; &lt;trace ID&gt; &lt;correlation ID&gt; &lt;log level&gt; &lt;message&gt; [key1=value1 key2=value2 ...]</code></pre><h2>Step 5: Define log levels</h2><p>Logs can be noisy and generate a lot of information that is not useful. To make logs more useful, you can define a log level that indicates the importance of a log message. You can then filter logs based on their log level so that you only see the log messages that are important to you.</p><p>Define good log levels such as:</p><ol><li><p><code>DEBUG</code> verbose; valuable information for debugging and troubleshooting</p></li><li><p><code>INFO</code> good to know something happened</p></li><li><p><code>WARN</code> something unexpected happened, but the application can recover</p></li><li><p><code>ERROR</code>  something unexpected happened, and the application cannot recover</p></li><li><p><code>CRITICAL</code> something unexpected happened, and the application will crash</p></li></ol><p>Review the log lines introduced in your code base and ensure they use the correct log level. Logging too much information at a higher log level can make finding important logs harder.</p><h2>Step 6: Implement structured logging</h2><p>If logs are in free-form text format, they are challenging to search and filter. By implementing structured logging, you can define a standard schema that allows you to use off-the-shelf tools to extract essential fields from logs, such as timestamp, log level, message, and metadata.</p><p>For example, for logs associated with a particular order, you can add the order ID to the log message as structured metadata instead of embedding it in the log string itself so that you can search for all logs associated with that order.</p><pre><code>2021-01-01T00:00:00Z 1234 5678 INFO Order created order_id=ABCD1234</code></pre><p>To emit structured logs, you can use a structured logging library such as <a href="https://github.com/sirupsen/logrus">logrus</a> or <a href="https://github.com/uber-go/zap">zap</a> for Go, <a href="https://logback.qos.ch/">logback</a> for Java, or <a href="https://www.structlog.org/en/stable/">Structlog</a> for Python. JSON is a widely used format for structured logs, and <a href="https://brandur.org/logfmt">logfmt</a> is also a good option.</p><p>Your log agent can then parse this format, extract the timestamp, log level, message, and metadata fields, and forward them to your log server. Your log server can then index these fields and make them searchable. You can even create dashboards and alerts based on these fields.</p><p>In JSON format, the log message would look like this:</p><pre><code>{
  "timestamp": "2021-01-01T00:00:00Z",
  "correlation_id": "1234",
  "trace_id": "5678",
  "log_level": "INFO",
  "message": "Order created",
  "metadata": {
    "order_id": "ABCD1234"
  }
}</code></pre><p>While JSON is a great candidate format for structured logs, it can be verbose. You can use a more compact format such as &#8220;logfmt&#8221;:</p><pre><code>timestamp=2021-01-01T00:00:00Z correlation_id=1234 trace_id=5678 log_level=INFO message="Order created" order_id=ABCD1234</code></pre><p>If your logging library does not support structured logging, you can still use a log format by defining a simple wrapper for formatting your log messages accordingly.</p><p>Here is an example in Go that prints logs in <code>logfmt</code>:</p><pre><code>package main

import (
&#9;"fmt"
&#9;"time"
)

func logfmt(level string, message string, metadata map[string]string) {
&#9;fmt.Printf("timestamp=%s log_level=%s message=%q", time.Now().Format(time.RFC3339), level, message)

&#9;for k, v := range metadata {
&#9;&#9;fmt.Printf(" %s=%q", k, v)
&#9;}

&#9;fmt.Println()
}</code></pre><p>You can then use this wrapper to log messages in a structured format:</p><pre><code>logfmt("INFO", "Order created", map[string]string{"order_id": "ABCD1234"})</code></pre><blockquote><p>&#9888;&#65039; Some people find JSON unyielding, especially during development. You can configure your logging library to log in JSON format in production and a more human-readable format during development. Libraries like <a href="https://logback.qos.ch/">Logback</a> and <a href="https://www.structlog.org/en/stable/">Structlog</a> have a developer friendly &#8220;console&#8221; format that you can use during local development.</p></blockquote><h2>Step 7: Add context to your logs</h2><p>Log messages are context-dependent. They depend on the application's state when and where you generate the log message. By adding context to your log messages, you can provide additional information that can help you understand the log message better.</p><p>You can add context such as:</p><ul><li><p>Request URL</p></li><li><p>Request method</p></li><li><p>Source IP address</p></li><li><p>Status code</p></li><li><p>Any other relevant information that can help you understand the log message better</p></li></ul><p>Use a middleware to add this context to your log messages. This middleware can extract and add information from the request to the log message.</p><h2>Step 8: Cut down on log verbosity</h2><p>Don't log everything. Logs can generate a lot of information that is not useful. To make logs more useful, reduce log verbosity by logging only the vital information.</p><p>Log something only if something is:</p><ol><li><p>Useful for understanding the state of the application</p></li><li><p>Useful for debugging and troubleshooting</p></li><li><p>Useful for monitoring and alerting</p></li><li><p>Useful for auditing and compliance</p></li></ol><h2>Step 9: Check logs for sensitive data</h2><p>In 2019, Facebook (now Meta) was under fire for inadvertently logging the passwords of hundreds of millions of users due to a misconfiguration. While there was no evidence of abuse or unauthorized access, they received major flak from the global security and privacy community regarding the company's security practices and the broader implications for user privacy.</p><p>Logs can inadvertently capture and expose sensitive information such as passwords, API keys, or personal data. Regularly review your logs to ensure that sensitive data is either masked or excluded from logs entirely. Implement automated tools to scan logs for such information. Sanitizing logs is critical to prevent security breaches and comply with data protection regulations. </p><p>Many log management services provide tools to scan log files in real-time, flagging or redacting any information that matches known patterns, such as credit card numbers, social security numbers, emails, or other personally identifiable information (PII). Logging libraries also provide hooks and post-processors to enable such detection at the application level and also offer remediation features like automatically masking or removing sensitive data before it can be stored or transmitted. </p><h2>Step 10: Consider supporting changing log levels at runtime</h2><p>Changing log levels at runtime can be helpful for debugging and troubleshooting. It allows you to change the log level of your application without restarting it. This feature can be beneficial if you want to increase the log level of your application to debug an issue and then decrease it once you have resolved the issue.</p><p>You can implement a log-level change endpoint in your application to change the log level of your application at runtime. You must protect this endpoint by authentication and authorization to prevent unauthorized access.</p><p>Java libraries like Slf4j and Logback support changing log levels at runtime using JMX or Jolokia. You can easily implement this feature in other languages as well by exposing secured management endpoints.</p><h2>Step 11: Monitor your logs</h2><p>Logs can be a valuable source of information about your application's state. While they are no alternative to dedicated observability tooling like metrics and application traces, by monitoring your logs, you can detect issues before they become critical. You can also create dashboards and alerts based on log messages to monitor your application's health.</p><p>Log management services provide matching, filtering and aggregation capabilites to utilize patterns to match log occurences and emit metrics or trigger alerts.</p><p>For example, most databases provide something called as a slow query log, where they log queries with execution time greater than a configured value. By monitoring slow query occurences, your logging system can alert you about a potential issue in your application.</p><h2>Step 12: Implement log rotation and log retention policies</h2><p>Logs can consume a lot of storage which can get very expensive especially when using managed log management services. Define sensible archival and deletion policies for logs. You can choose to store logs in &#8220;hot&#8221; storage &#8212; like in ClickHouse DB for 60 days and then move it to &#8220;cold&#8221; storage like &#8220;S3&#8221; for storing it upto a year. Bulk object storage is cheaper than storing it in an actively searchable database. Tools like Amazon Athena and Loki still allow you to query archived logs when needed.</p><h2>&#8230; And the list doesn&#8217;t end here</h2><p>There are many more ways you can employ to make logs more useful. Following these steps one-by-one will give you a robust baseline to start from.</p><p>Do you employ some other creative ways to make the best use of logs? Let me know in the comments below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/p/taming-logs/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://recursivefunction.blog/p/taming-logs/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Arcane Mess of Cables]]></title><description><![CDATA[I spent my entire weekend and &#8377;4000 on cables so that you don't have to...]]></description><link>https://recursivefunction.blog/p/the-arcane-mess-of-cables</link><guid isPermaLink="false">https://recursivefunction.blog/p/the-arcane-mess-of-cables</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Fri, 05 Jul 2024 06:31:13 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you do a fair bit of fiddling with computer systems or electronic equipment (&#128075;&#127995; Hi, fellow audiophiles), you must have encountered cables of different forms and sizes. Considering the horrors we had in the past, the modern-day mantra of USB-for-everything, with some HDMI and whatever the weird contraption Apple throws at us, is usually enough to connect everything. <em>Except it isn't.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="4928" height="3264" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3264,&quot;width&quot;:4928,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;white usb cable on gray laptop computer&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="white usb cable on gray laptop computer" title="white usb cable on gray laptop computer" srcset="https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1627486144847-03f8c678f90d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxjb21wdXRlciUyMGNhYmxlc3xlbnwwfHx8fDE3MjAxMjExODN8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Immo Wegmann</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h1>The evolution(?) of cables &#128268;</h1><p>The great thing about the cables of yesteryears was that they either worked or did not work. And when they failed, they failed spectacularly due to their analogue signalling. If you ever had a loose mouse connector that skipped constant white noise from speakers or a bad VGA cable producing an occasional rainbow on your monitor, you know what I'm referring to.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>However, for good or bad, we have moved to a digital world where we can fail and gracefully degrade. You only know when you try to push the limits, and then you wonder whether it's a faulty cable, connector, or software!</p><blockquote><p>A few years ago at work, we had these Thunderbolt 3-enabled monitors that also supported USB C Displayport Alt mode + data (I will explain these later). Thunderbolt uses the same interface as USB C but only works if you plug it in a particular orientation. However, the monitor works virtually identically in both modes, with the refresh rate being lower and USB ports dropping to Gen 2 speeds (480 MBps) if you have connected it to the USB C mode by plugging the cable wrong. There is no other indication unless you notice the jankyness in the mouse (I wasn't used to 120Hz screens then, so I couldn't see much) or slowness in data transfer speeds over USB. How confusing it was!</p></blockquote><h1>One cable to rule them all</h1><p>USB Type C is everywhere! It charges devices, transfers data, connects peripherals, and has a powerful alt-mode that allows you to use some of the pins for transmitting signals other than USB. It will enable you to connect displays and even transmit analogue audio (which is how many Androids removed the headphone jack). It's the holy grail of connectors. However, it's a super convoluted mess!</p><p>The USB-IF <em>messed up big time</em>. With so many optional capabilities, it resulted in maddening permutations and finding cables that can support them is a task. Also, let's not talk about naming now!</p><p>As it turns out, <em>USB C is simply the connector form factor</em>. USB 1 - 4 are generations that determine speed, signalling, power delivery, and other capabilities. A USB C cable can be of any generation from 2 to 4 and support any power delivery mode, which gets electronically marked with a marker chip. If the chip fails, it gracefully degrades to USB 2 capabilities. The Thunderbolt standard also uses the USB C connector, another high-speed connectivity solution by Apple and Intel.</p><p>Here are some combinations that I know exist:</p><ol><li><p>A USB C cable, <strong>by default</strong>, supports <strong>5V/1.5A</strong> (7W) current, but most cables usually support 15W of power. If your phone charges slowly, try a different cable, like a laptop's power cable. Those things are rated at 100W (20V/5A). Have a look at the <a href="https://en.wikipedia.org/wiki/USB_hardware#USB_Power_Delivery">USB PD Wikipedia article</a> for more combinations of power delivery support. Your cable may support any power level.</p></li><li><p>USB C cables can support <strong>USB 2/3/4</strong>. The thin and long ones that come for charging are usually USB 2. They only support transmitting data @ 480 Mbps and don't support audio, video, or any other protocol in alt mode.</p></li><li><p>USB C cable "certified" for USB 3, which was first renamed USB 3.1 and then renamed <strong>USB 3.2 Gen 1</strong>, supports <strong>5 Gbps</strong> data transfer and transmitting audio or video. <strong>USB 3.2 Gen 2</strong> supports <strong>10 Gbps</strong>, <strong>USB 3.2 Gen 2 x2</strong> supports <strong>20 Gbps</strong>, and <strong>USB4</strong> supports <strong>40 Gbps</strong> in addition to alt-mode. (What the hell is this USB-IF!?) During alt-mode, the cables either <em>fall back to USB 2 or 3</em> speeds depending on the generation and the devices.</p></li><li><p><strong>Thunderbolt</strong> is a different beast. It looks like a USB cable but only supports Thunderbolt at <strong>40/80 Gbps</strong> for Gen 3 and Gen 4, respectively. If connected to non-Thunderbolt ports, it <em>falls back to USB 2</em>. Thunderbolt supports audio and video output, but you cannot use a Thunderbolt cable to connect to displays over DisplayPort alt mode.</p></li></ol><p>These are the combinations I have personally verified with different hardware and a box full of USB C cables. Want more madness? See the <a href="https://en.wikipedia.org/wiki/USB-C">Wikipedia article for USB C</a>.</p><h1>Display cables</h1><p>You can connect displays using HDMI or DisplayPort (DP). These are much more robust than analog-based VGA or component connectors. Currently, DP 2.0 is faster, with 80 Gpbs supporting 8k displays at a 120 Hz refresh rate. HDMI is widely used with media equipment and can transfer audio or video. DP is more popular on computers. Both standards are always playing catch-up in terms of capabilities. But simply connecting two devices with a compatible cable is not enough.</p><p>For HDMI and DP, devices typically advertise the version of the connector they support, each being backwards compatible. For example, <strong>DP 1.4</strong> supports <strong>4K@120 Hz</strong> at 10-bit colour, and HDMI 2.0 supports 4K@60 Hz at 8-bit colour. However, for some reason, god knows what, the HDMI and the VESA alliance explicitly forbid cable manufacturers from stating the maximum version the cable is certified to work with. You must now tally the speed or designator to find the corresponding version.</p><ol><li><p>A <strong>high-speed HDMI cable</strong> or an <strong>HBR DP cable</strong> supports <strong>4K@30 Hz</strong> using <strong>HDMI 1.4 </strong>or<strong> DP 1.2</strong></p></li><li><p>A <strong>premium high-speed HDMI cable</strong> or an <strong>HBR2 DP cable</strong> supports <strong>4K@60Hz</strong> using <strong>HDMI 2</strong> or <strong>DP 1.4</strong></p></li><li><p>An <strong>HBR3 DP cable</strong> supports <strong>4K@120 Hz</strong> using <strong>DP 1.4</strong></p></li><li><p>A <strong>UHBR DP cable</strong> or an <strong>ultra high-speed HDMI cable</strong> supports <strong>8K@60 Hz</strong> using <strong>DP 2.0 or HDMI 2.1</strong></p></li><li><p>You can connect a DP output to an HDMI input. The HDMI version would be the nearest compatible version that the port support.</p></li></ol><p>Also, both HDMI and DP can work in DisplayPort Alt-mode, adding another layer of compatibility complexity.</p><p>The harbringing of USB Type C and the evolution of HDMI and DisplayPort have simplified the cable conundrum by allowing a single connector to handle multiple functions. However, this advancement has also introduced a new layer of complexity with multiple modes of operation, power delivery support, and varying data transfer speeds. While these modern connectivity solutions offer incredible versatility, users need to clearly understand these cables' diverse capabilities and limitations to ensure seamless and optimal device connectivity.</p><p>As technology advances, we'll likely see further refinements in these standards to streamline the user experience and reduce cable compatibility confusion. Things are already in motion. The Thunderbolt and USB4 specifications merged, so there is one less permutation and some good news to cheer on!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Just F***ing use UTC]]></title><description><![CDATA[Enough of time zone madness and DST shenanigans!]]></description><link>https://recursivefunction.blog/p/just-fing-use-utc</link><guid isPermaLink="false">https://recursivefunction.blog/p/just-fing-use-utc</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Wed, 26 Jun 2024 10:26:24 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's 2024, and I'm still astounded to see applications riddled with local time usage for timestamps. Juggles between time zones, daylight saving time, and all the other hocus-pocus associated with it seem like unwanted baggage for developers. Today, we pledge to end this madness and swear by UTC for all our timestamp requirements.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="3992" height="2761" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2761,&quot;width&quot;:3992,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;brown wooden framed analog clock&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="brown wooden framed analog clock" title="brown wooden framed analog clock" srcset="https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1593278091708-261619ad2756?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxjbG9ja3N8ZW58MHx8fHwxNzE5Mzk3MjMyfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Lucian Alexe</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Timestamps and Time zones</h2><p>A timestamp is a label that indicates the specific date and time an event occurred. It's like you time-captured a moment and stamped it for reference. It denotes a point in history. But how you read it will depend on which &#8220;time zone&#8221; you are using. A timestamp which reads 12:00 am on British Standard Time, will read 5:30 am on Indian Standard Time.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Time zones are geographical regions that follow a standard time defined by their offset from the Universal Coordinated Time (UTC). However, the rules governing these time zones can sometimes be a convoluted conundrum to decipher.</p><h2>The UTC Advantage</h2><blockquote><p>Coordinated Universal Time (UTC) is the global standard for regulating clocks and time. It establishes a reference for the current time and forms the basis for civil time and time zones.</p></blockquote><p>UTC is deterministic and unambiguous. It's determined from highly precise atomic clocks and is not subject to the whims of time zones or daylight saving time.</p><p>Coding for UTC eradicates daylight saving time, saving you from the horrors of ambiguous time. Moreover, the fixed rules make operations like calculating differences between timestamps a breeze. Extra brownie points for no vanishing or extra hours!</p><h2>Users vs Servers</h2><p>Your users are everywhere in the world. Some of them are also wanderers, traveling across time zones. Their primary concern lies in knowing what time an event happened. Servers, on the contrary, remain static. If you tell a user that they withdrew money at 3:00 AM tomorrow, they will be perplexed.</p><h2>Daylight Saving Time: More Trouble Than It's Worth?</h2><p>Daylight Saving Time has long been thought to be a beneficial strategy. But the reality seems to tiptoe on the contrary. Opting for this leads to an unexpected hour popping out of or vanishing into oblivion&#8212;not to mention the excessive chaos it causes!</p><h2>Ignoring UTC: A Pandora's Box</h2><p>Should you shun the UTC and stick to local time, prepare for a swarm of bugs, angry users, and disgruntled developers on your plate. Your coded masterpiece could morph into an unsolvable labyrinth filled with timezone turbulence, Daylight Saving Time debacle, and other trickery of time manipulation. Consequently, a chunk of your time debugging, with little to spare for actual development.</p><h2>Using UTC in Your Code</h2><h3>Set your Server to UTC</h3><p>Ensure that you set your servers to UTC. This will save you the hassle of converting time zones and daylight saving time. By default, every piece of code you write will be using UTC.</p><p>To do this in a Linux server with <code>systemd</code>, you can set the timezone using the <code>timedatectl</code> command:</p><pre><code>timedatectl set-timezone UTC</code></pre><h3>Set your Application to UTC</h3><p>If you can't set your server to UTC, consider using a library to handle time in UTC. Java, for instance, has a <code>Clock</code> you can utilize to get the current time in UTC. Similarly, other languages allow you to set the timezone for your application.</p><p>Some examples:</p><pre><code>class Example {
    public static void main(String[] args) {
        // A clock that returns the current time using UTC time-zone.
        Clock clock = Clock.systemUTC();
        LocalDateTime now = LocalDateTime.now(clock);
        System.out.println(now);
    }
}</code></pre><pre><code>class Example {
    public static void main(String[] args) {
        // Set default timezone to UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        // Get the current time,
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
    }
}</code></pre><p></p><pre><code>from datetime import datetime
from pytz import utc

# Get the current time in UTC
# This will return a timezone-aware datetime object
# with the timezone set to UTC
now = datetime.now(utc)</code></pre><h3>Timestamps in databases</h3><p>When storing timestamps in databases, always use UTC. It will ensure consistency across your application and prevent timezone-related bugs. Most databases use the <code>TIMESTAMP</code> data type to store timestamps in UTC. When displaying them on the front end, you can format them to the user's timezone.</p><h3>Timestamps in APIs</h3><p>Adopt the ISO 8601 format with UTC timezone for your APIs. Make it a point to state the timezone explicitly in your response.</p><pre><code>{
  "timestamp": "2024-01-01T12:00:00Z"
}</code></pre><p>Using ISO 8601 with UTC timezone will ensure your timestamps are unambiguous and consistent across different systems.</p><h2>Displaying Time to Users</h2><p>When displaying timestamps to users, always convert them to the user's timezone. It will ensure that the user sees the time in their local time, making it easier to understand. You can use libraries like `moment.js` in JavaScript to handle timezone conversions easily.</p><pre><code>const timestamp = "2024-01-01T12:00:00Z";
const localTime = moment(timestamp).local();
console.log(localTime.format("YYYY-MM-DD HH:mm:ss"));</code></pre><p>If needed, you can also display the timezone and timestamp to avoid confusion. You can also provide an option for users to change the timezone in your application settings.</p><h2>Scheduling Events: An Exception to the Rule</h2><p>If future schedules are required, use user-defined time, date, and timezone. Do not convert them to UTC; store them as they are.</p><h3>But Why?</h3><p>Users anticipate the event to occur at that precise time in their respective time zones. An unexpected shift due to conversion to UTC, courtesy of the absurd rules of DST, can be a source of confusion.</p><h2>Timezone Handling: (not) A Cake Walk</h2><p>Correct and effective timezone is <em>hard</em>. However, therein lies the art of coding&#8212;tackling problems head-on and emerging victorious. And using UTC for your timestamps definitely makes that journey a tad bit less winding. So save yourself from the time zone madness and <em>just F**ing use UTC!</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building a custom Zsh prompt from scratch]]></title><description><![CDATA[This week, I got a new gorgeous space black MacBook Pro &#128187; and decided to start afresh and dive into the inner workings of ZSH to build a custom prompt that was lightning-fast and minimal.]]></description><link>https://recursivefunction.blog/p/building-a-custom-zsh-prompt-from</link><guid isPermaLink="false">https://recursivefunction.blog/p/building-a-custom-zsh-prompt-from</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Thu, 02 May 2024 04:57:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!z2XG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This week, I got a new gorgeous space black MacBook Pro &#128187; powered by an M3 Pro CPU and GPU. I was excited to set up my development environment, but this time, I decided not to copy over my decade-old ZSH setup with <a href="https://ohmyz.sh/">Oh my ZSH!</a> and <a href="https://starship.rs/">Starship</a>. It was an excellent chance to start afresh and learn more about the inner workings of ZSH.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!z2XG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!z2XG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 424w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 848w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!z2XG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png" width="1456" height="989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:989,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:794241,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!z2XG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 424w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 848w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!z2XG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56d83e15-b842-48ec-b081-0eb5bcc39fbb_2044x1388.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I wanted to build a custom Zsh prompt that was lightning-fast, minimalistic, and focused on performance. I wanted to understand how Zsh works under the hood and how I could leverage its features to build a fast and minimal prompt. </p><p>Let's go! &#128640;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>How Zsh prompts work</h1><p>Traditionally, shells define the prompt using the <code>PS1</code> environment variable. Zsh supports this for compatibility but also provides more advanced ways to define prompts using the <code>PROMPT</code> and <code>RPROMPT</code> variables.</p><p>The <code>PROMPT</code> variable defines the prompt's left side, while the <code>RPROMPT</code>  variable defines the right side. These variables can contain a mix of text and escape sequences that Zsh expands when the prompt is displayed.</p><p>Most Zsh prompts use a combination of escape sequences and custom functions that execute when the prompt is displayed. These functions can display dynamic information like the current Git branch, the current Python virtual environment, or the current NodeJS version.</p><h2>Building a custom&nbsp;Zsh prompt</h2><p>Through years of using Zsh, I've come to appreciate minimalistic prompts that are fast and discreet. I wanted to build a prompt that displayed only the essential information, like the current working directory, git branch and status. Something like this:</p><pre><code>~/projects/my-project (branch) [status]

$</code></pre><p>Zsh uses escape sequences to display dynamic information in the prompt. For example, the <code>%n</code> escape sequence expands to the current username, the <code>%~</code> escape sequence displays the current working directory, and the <code>%#</code> escape sequence expands to a <code>#</code> if the current user has superuser privileges.</p><p>To quickly iterate and test the prompt, you can set the `<code>PROMPT</code>` variable in the current shell session. For example, the default Zsh prompt in macOS is <code>"%n@%m %~%# "</code>, which displays the username, host name, working directory, and a `#` if the user has superuser privileges. It would be equivalent to setting the <code>PROMPT</code> variable to:</p><pre><code>PROMPT='%n@%m %~%# '</code></pre><p>Using this as a starting point, I decided to get rid of the username and host name (as I'm the only user on my machine) to end with something like this:</p><pre><code>PROMPT='%~ %# '</code></pre><h2>Going multi-line</h2><p>I'm a big fan of multi-line prompts, as they allow me to display long paths and more information without cluttering the prompt.</p><p>My first try was to sneak in a newline character (<code>\n</code>) in the <code>PROMPT</code> variable, but that didn't work. Zsh doesn't support escape sequences in the <code>PROMPT</code> variable, so I used the <code>precmd</code> hook to print the CWD and display the prompt on the following line.</p><p>The <code>precmd</code> hook executes before each prompt is displayed and can be used to run custom commands or functions. I used the <code>print</code> with <code>-P</code> inside it to interpret prompt expansion sequences, just like in the <code>PROMPT</code> variable.</p><pre><code>precmd() {
&nbsp;&nbsp;print -P "%~"
}

PROMPT='%# '</code></pre><p>It worked like a charm; I could display the current working directory on a new line.</p><h1>Adding Git information</h1><p>Zsh has built-in support for displaying Git information in the prompt. You can use the <code>vcs_info</code> module to display the current Git branch and status in the prompt.</p><p>To enable the <code>vcs_info</code> module, you must load it into your Zsh session using the <code>autoload</code> command.</p><pre><code>autoload -Uz vcs_info</code></pre><p>Once loaded, you must invoke the <code>vcs_info</code> function in the <code>precmd</code> hook to update the Git information before displaying each prompt. The function then populates the <code>vcs_info_msg_0_</code> variable with the Git information, which you can display in the prompt.</p><pre><code>precmd() {
&nbsp;&nbsp;vcs_info
&nbsp;&nbsp;print -P "%~ ${vcs_info_msg_0_}"
}</code></pre><p>Except that it didn't work. Zsh does not enable the expansion of variables in prompts by default, so I had to enable it using the <code>setopt prompt_subst</code> command.</p><pre><code>setopt prompt_subst</code></pre><p>Voil&#224;! I now had a custom Zsh prompt that displayed the current working directory and Git information.</p><h1>Customizing the VCS info</h1><p>You can customize the Git information displayed in the prompt by setting the <code>vcs_info_msg_0_</code> variable to a custom format string. The format string can contain escape sequences that Zsh expands when the prompt is displayed.</p><p>I wanted to have the current branch and staged/unstaged status. So, using zstyle, I changed the format string to:</p><pre><code>zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:git:*' formats '%b%f %m%u%c %a'</code></pre><p>This format string displays the current branch (<code>%b</code>), the staged (<code>%c</code>) and unstaged (<code>%u</code>) changes, any ongoing action like rebase (<code>%a</code>) and miscellaneous info (<code>%m</code>).</p><p>I wanted to display symbols instead of text for the Git status, so I set the <code>stagedstr</code> and <code>unstagedstr</code> variables to <code>+</code> and <code>!</code>.</p><pre><code>zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:*' stagedstr ' +'
zstyle ':vcs_info:*' unstagedstr ' !'</code></pre><h2>Colours and formatting</h2><p>I got the prompt working, but it was bland. I wanted to add some colours and formatting to make it pop &#10024;.</p><p>Zsh supports escape sequences for colours and formatting, which you can use to style the prompt. The <code>%F{color}</code> escape sequence sets the foreground colour, the <code>%K{color}</code> escape sequence sets the background colour, and the <code>%B</code> escape sequence sets bold text.</p><p>I wanted to display the current working directory in bold, the git branch in blue, and the git status in green and red for staged and unstaged changes. I also wanted to display Powerline symbols for the prompt.</p><p>So I headed over to <a href="https://www.nerdfonts.com/">Nerd Fonts</a>, downloaded the patched &#8220;Fira Code&#8221; font, and set it as the font in iTerm.</p><p>I then used escape sequences to set the colours and formatting in the <code>PROMPT</code> and <code>vcs_info</code> styles.</p><p>Here's the final prompt. The Powerline symbol for git branch <code>\uE0A0</code> is unfortunately not supported in any other font and hence appears as an unrecognized square.</p><pre><code>zstyle ':vcs_info:git*' formats "&#57504; %F{blue}%b%f %m%u%c %a "
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:*' stagedstr ' %F{green}&#10010;%f'
zstyle ':vcs_info:*' unstagedstr ' %F{red}&#9679;%f'

precmd() {
&nbsp; &nbsp;&nbsp;vcs_info
&nbsp; &nbsp;&nbsp;print -P '%B%~%b ${vcs_info_msg_0_}'
}

PROMPT='%B%(!.#.$)%b '</code></pre><p>And here's how it looks:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9PVv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9PVv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 424w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 848w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9PVv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png" width="1456" height="939" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:939,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:317467,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9PVv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 424w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 848w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!9PVv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65846aaf-94bd-4906-b8b5-7bac7d8e168e_1594x1028.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I use the <a href="https://github.com/sindresorhus/iterm2-snazzy">Snazzy</a> colour scheme in iTerm, which makes the prompt colours look even better. My Zsh configuration has some more customizations, like history search, syntax highlighting, and autosuggestions, but I'll save that for another post.</p><p>And that's it! I now have a custom Zsh prompt that displays the current working directory, Git branch and status in a minimalistic and colourful way.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Email Works Pt. 1 ✉️]]></title><description><![CDATA[Email is an essential part of how we communicate in the 21st century. It's also one of the most misunderstood technologies on the Internet. I'll explore how email sending works behind the scenes in this post.]]></description><link>https://recursivefunction.blog/p/how-email-works-pt-1</link><guid isPermaLink="false">https://recursivefunction.blog/p/how-email-works-pt-1</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Thu, 23 Nov 2023 16:00:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fw8W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You received this mail and are now reading this on an email client, but have you ever wondered how did this mail reach you?</p><p>Email is an essential part of how we communicate in the 21st century. It's also one of the most misunderstood technologies on the Internet. I'll explore how email sending works behind the scenes in this post.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fw8W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fw8W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 424w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 848w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fw8W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1914941,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fw8W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 424w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 848w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!fw8W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F676c237d-f993-4cdd-aeb8-06c8c573defe_4096x2048.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>&#129668; Trivia: History of email</h2><p>Email is the most common form of electronic communication, with over 2.5 billion users worldwide. It was invented in 1971 by Ray Tomlinson, who sent the first email from one computer to another. </p><p>It took place on ARPANET --the precursor to what we now know as the Internet, using a protocol that later developed into FTP,  a file-transfer protocol that was popular before HTTP took over. The early days of Email were much more rudimentary than what we use now.</p><h2>&#128270; Anatomy of an email address</h2><p>An email address is a string of characters that looks something like this: <code>joe@example.com</code></p><p>The domain name is the part after the @ symbol, which would be "example.com." The domain can be one word or multiple words, which functions like an address for your email. Anyone sending you an email will send it to the email servers associated with your domain. You can sign up at one of the many public email services like Gmail or your ISP's service, which provide you with a mail server, or you can build and run your own.</p><h2>&#128640; Sending email</h2><p>Sending email is a two-step process. First, you find the mail server of your recipient. Then, you use the SMTP protocol to connect with the mail server and send your message. Sounds simple? Yes, it is!</p><h3>&#128270; Finding the mail server</h3><p>You can find out which mail server to send your email to by looking up its domain name in DNS MX records: pick one randomly if it has multiple addresses listed. </p><blockquote><p>Technically speaking, there is a priority that servers should follow - the number before the email hostname, but we can ignore it for our example.</p></blockquote><p>If you have a Mac or a Linux system, you can run this command to see the MX records for a given domain. Here is an example for Gmail. You can see 5 SMTP servers listed in the DNS response.</p><pre><code>$ <strong>dig -t MX gmail.com</strong>

; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; -t MX gmail.com
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 25845
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;gmail.com.&#9;&#9;&#9;IN&#9;MX

;; ANSWER SECTION:
gmail.com.&#9;&#9;2531&#9;IN&#9;MX&#9;20 alt2.gmail-smtp-in.l.google.com.
gmail.com.&#9;&#9;2531&#9;IN&#9;MX&#9;10 alt1.gmail-smtp-in.l.google.com.
gmail.com.&#9;&#9;2531&#9;IN&#9;MX&#9;40 alt4.gmail-smtp-in.l.google.com.
gmail.com.&#9;&#9;2531&#9;IN&#9;MX&#9;5 gmail-smtp-in.l.google.com.
gmail.com.&#9;&#9;2531&#9;IN&#9;MX&#9;30 alt3.gmail-smtp-in.l.google.com.

;; Query time: 53 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Mon Nov 20 12:20:51 IST 2023
;; MSG SIZE  rcvd: 161</code></pre><h3>&#9993;&#65039; Sending Email</h3><p>Once connected, you can send emails using the SMTP protocol. The SMTP protocol is a simple text-based protocol running on TCP, which is how mail servers and clients talk to each other to send and receive email.</p><p>Since it's text-based, you can simulate an SMTP exchange using the telnet command. If you have telnet installed on a terminal, you can email yourself.</p><blockquote><p>&#9888;&#65039; Your success may vary based on whether your ISP allows you to send emails directly and your mail server enables you to send emails without a relay.</p></blockquote><pre><code>$ <strong>telnet gmail-smtp-in.l.google.com 25</strong>

Trying 2404:6800:4003:c1a::1b...
Connected to gmail-smtp-in.l.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP d12-20020a170902654c00b001c746b986e2si852655pln.346 - gsmtp
&gt; <strong>HELO localhost
</strong>250 mx.google.com at your service
&gt; <strong>MAIL FROM: &lt;root@localhost&gt;
</strong>250 2.1.0 OK d12-20020a170902654c00b001c746b986e2si852655pln.346 - gsmtp
&gt; <strong>RCPT TO: &lt;your.email@gmail.com&gt;
</strong>250 2.1.5 OK d12-20020a170902654c00b001c746b986e2si852655pln.346 - gsmtp
&gt; <strong>DATA
</strong>354 Go ahead d12-20020a170902654c00b001c746b986e2si852655pln.346 - gsmtp
&gt; <strong>Test message from telnet</strong>
&gt; <strong>.
</strong>250 Message accepted for delivery&nbsp;
&gt; <strong>QUIT</strong>&nbsp;
221 mx.google.com closing connection</code></pre><h2>&#128293; Modern-day email</h2><p>Most people use email through a web client, which abstracts away all the complexities of sending and receiving email by performing all necessary transactions behind the scenes. Email servers, however, offer IMAP, POP3 and SMTP access if you want to use other email software (including mobile apps like Gmail Outlook) to send and receive emails.</p><blockquote><p>You can still send and receive emails from a laptop by running an SMTP server on your local machine and setting the required MX records.</p></blockquote><p>It's hard to believe that email is more than 40 years old. The first message was sent in 1971; ever since then, it has become essential to our everyday lives. It's easy to forget how many things we do with our inboxes--from keeping track of bills and receipts to communicating with friends worldwide. But even though we take this technology for granted now, its origins were quite humble: They started with two people who just wanted an easier way to send messages back and forth between each other!</p><p>In the next part, we will discuss what happens when you receive the email, including verification, spam filtering and archiving until you access it on your computer.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/p/how-email-works-pt-1?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thank you for reading Recursive Function. This post is public, so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/p/how-email-works-pt-1?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://recursivefunction.blog/p/how-email-works-pt-1?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[How to test your code 🧑‍💻]]></title><description><![CDATA[Test your code, you must! Elevate software quality through strategic testing: fast unit tests, realistic integration tests, and thorough manual testing.]]></description><link>https://recursivefunction.blog/p/how-to-test-your-code</link><guid isPermaLink="false">https://recursivefunction.blog/p/how-to-test-your-code</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Fri, 18 Aug 2023 05:30:19 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It&#8217;s easy to forget about testing when first learning to code. It&#8217;s not like anyone will grade you on your test-writing skills, right &#128064;? But if something goes wrong with your code in production, it can be a real pain in the ass to figure out why.&nbsp;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="5184" height="3888" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3888,&quot;width&quot;:5184,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;text&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="text" title="text" srcset="https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8Y29kZXxlbnwwfHx8fDE2OTIyNjI1NzZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@6heinz3r">Gabriel Heinzer</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>We&#8217;ve all been there: You&#8217;re trying to find an error that doesn&#8217;t make sense! You changed this one thing last week, but now it&#8217;s broken? &#129324;WHAT IS GOING ON?!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fRZw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fRZw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 424w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 848w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 1272w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fRZw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png" width="482" height="481.0183299389002" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9073483-3f55-478e-83b5-40d341e1f89f_982x980.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:980,&quot;width&quot;:982,&quot;resizeWidth&quot;:482,&quot;bytes&quot;:1354366,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fRZw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 424w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 848w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 1272w, https://substackcdn.com/image/fetch/$s_!fRZw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9073483-3f55-478e-83b5-40d341e1f89f_982x980.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Testing is an integral part of developing software</h2><p>Testing enables you to make sure that your code works as expected, and it can even help with regression testing &#9989; &#8212; a process where you test existing features to ensure they still work after adding new ones or modifying existing ones. Ultimately, you catch bugs &#128030; before they reach production.</p><blockquote><p>Testing early and often is the best practice for writing software because it allows you to find problems before they become too large or complex for quick fixes later in development.</p></blockquote><h2>&#128309; Unit testing - test code in isolation</h2><p>Unit testing is the process of testing individual units of source code. Each time you complete working on a piece of code, you write a unit test for it. It checks the correctness of that particular piece of logic you wrote. <em>Or even better, come up with test cases before writing code, and make sure they pass!</em></p><p>&#129300; But code is rarely independent. Therefore in unit tests, you employ mocks and stubs to &#8220;fix&#8221; the behaviour of other parts of your code and focus on the behaviour of the code-under-test. Your language of choice would have a framework that would do it, like Mockito for Java, Jest and Sinon for JS etc.</p><p>For further reading:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d2e531b8-c943-4dfd-92f5-68bf806dbcd8&quot;,&quot;caption&quot;:&quot;I'll admit it: I'm a lazy developer. That's not to say I don't work hard or care about my craft, but when it comes to unit testing&#8212;a notoriously time-consuming part of software development&#8212;I want to spend as little time on it as possible. So when the codebase you're working on is significant or your tests require lots of setups, automation becomes essen&#8230;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Java Libraries to Supercharge Unit Testing&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:2660009,&quot;name&quot;:&quot;Amitosh Swain Mahapatra&quot;,&quot;bio&quot;:&quot;Computer Whisperer&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a8e1857-8ac8-4b79-a9a2-c7c9a3bb31ec_1170x1170.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-02-06T10:30:58.996Z&quot;,&quot;cover_image&quot;:&quot;https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://recursivefunction.blog/p/java-libraries-to-supercharge-unit&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:99781530,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Recursive Function&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ae6145-5c56-41e4-b76a-305b1f2423eb_1080x1080.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>&#128073; You should write unit tests to provide <em>quick feedback</em>. They should be fast &#9889;&#65039; and avoid expensive/external operations (using mocks and stubs).</p><h3>Example of a unit test</h3><p>You wrote some code that sorts a list of records based on the alphabetical order of first and last names in case of a tie. Your unit tests should contain cases for - empty, few records without a common first name, few records with the same first name etc. and then assert the output.</p><h2>&#128993; Integration testing - test code that interacts with other modules</h2><p>Integration testing is&nbsp;<em>a form of black box testing</em>&nbsp;that involves testing code against real-world scenarios. The right time to write integration tests is when you have just completed a feature with APIs and interactions.</p><p>&#128073; You should perform integration tests at the module level (micro-service level if you follow the micro-services pattern), where you validate the contract between components. You should validate your code with all its dependencies to start, run and serve your test expectations.</p><p>&#9888;&#65039; You should use real servers for internal dependencies, like &#128452;&#65039; databases and &#128451;&#65039; caches. You can use a mock server like Wiremock or point them against an actual deployment for external services.&nbsp;Using a mocking library to mock external interactions does not serve the purpose of an integration test.</p><blockquote><p>&#128051; Docker and test containers help in spinning up servers for integration testing.</p></blockquote><p>Also, as your code matures, you should build a test harness to test your application against real deployments automatically.</p><h3>Example of an integration test</h3><p>You call an API that stores some data in the DB, publishes several events to a queue and returns an HTTP 200 response. You write a test to assert these behaviours.</p><h2>End-to-end testing and manual testing</h2><p>&#128073; You perform end-to-end testing at your system&#8217;s outermost, user-accessible part. Suppose your system has multiple microservices powering a single dashboard. In that case, you should concentrate the bulk of your end-to-end testing efforts on testing the behaviour of the dashboard as a whole when deployed with other modules in a test environment.</p><p>&#128295; Manual testing is the most common type of software testing. It&#8217;s also the most expensive and time-consuming but also the most effective. As part of manual testing, you must manually test each feature in your application and ensure it works as expected. Lots of &#128433;&#65039; clicking around and &#9000;&#65039; entering data, just like the end user would. But those few clicks would touch lots of layers of code at the same time.</p><h2>The pyramid of testing</h2><p>The pyramid of testing <em>visually represents the order in which you should run tests</em>. You can also use it to help you decide what tests are appropriate for your codebase. The bottom level represents unit tests, followed by integration tests and manual (UI) testing at the top.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YcLk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YcLk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 424w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 848w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 1272w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YcLk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png" width="446" height="387.67692307692306" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:904,&quot;width&quot;:1040,&quot;resizeWidth&quot;:446,&quot;bytes&quot;:72568,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YcLk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 424w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 848w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 1272w, https://substackcdn.com/image/fetch/$s_!YcLk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3ca8838-010a-45b3-9b2e-7f4c5bd05aa2_1040x904.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The bottom levels are narrow and cover a limited amount of code at a time, but it is easy to write and debug failures. As you move up, the code under test increases with lots of logic stacking over each other. A single run covers a lot of code at a time, but debugging failures become proportionately &#127797; tricky due to many more &#9881;&#65039; moving parts.</p><h2>How to assert the quality of your test suite</h2><p>&#9989; <em><strong>You must design your tests according to the specification of your software</strong></em>. You should easily understand what each test is doing and why it&#8217;s essential. A wrong test is a source of bugs if not reviewed correctly.</p><p>&#9888;&#65039; <em><strong>If a test has any dependencies on external systems (such as databases), you should document or mock them</strong></em> to ensure they don&#8217;t affect the rest of your tests.</p><p>&#10071;&#65039;<em><strong>Your test should also be easy to write and read</strong></em> so that if you need to change something in your codebase, it&#8217;s easy for someone else (or yourself!) who isn&#8217;t familiar with all your classes right away can understand what&#8217;s going on in each unit test file.</p><blockquote><p>If you have a piece of code which is complicated to test, there is probably a code-smell hiding.</p></blockquote><p>&#9889;&#65039; <em><strong>Finally, all tests must run quickly</strong></em> &#8212; if a single test takes too long, developers will avoid running it frequently because it adds time to their build process and slows them down when making changes elsewhere in the codebase.</p><p>&#128064; <em><strong>Use code coverage to measure the quality of your test suite objectively</strong></em>. A high code coverage indicates that you have accounted for the various conditions and control flow. Test frameworks usually come with a bundled coverage tool like JaCoCo for Java, coverage.py for Python and Istanbul for JS.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f5B1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f5B1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 424w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 848w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 1272w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f5B1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png" width="370" height="425.1224489795918" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1126,&quot;width&quot;:980,&quot;resizeWidth&quot;:370,&quot;bytes&quot;:1566451,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!f5B1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 424w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 848w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 1272w, https://substackcdn.com/image/fetch/$s_!f5B1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9fb4b8d-21b0-4dfa-ad61-4528f5cbf8b7_980x1126.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While testing is essential to developing software, it can be challenging to get it right. There are many different types of tests, each serving another purpose. The most important thing is to understand what kind of testing is appropriate at the moment so that you can make sure that tests cover all of your code before deploying it into production and squash those &#128030; bugs!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive Function! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Indian Digital Payments are Broken]]></title><description><![CDATA[The payment app ecosystem in India is broken... this is going to be a "rant" today.]]></description><link>https://recursivefunction.blog/p/indian-digital-payments-are-broken</link><guid isPermaLink="false">https://recursivefunction.blog/p/indian-digital-payments-are-broken</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Fri, 28 Jul 2023 05:30:06 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The payment app ecosystem in India is dysfunctional. It's a daunting task to find an app that allows for easy UPI payments without being inundated with a plethora of products. Good luck convincing elderly individuals to use UPI now!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="6000" height="4000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4000,&quot;width&quot;:6000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding smartphone&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding smartphone" title="person holding smartphone" srcset="https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1483478550801-ceba5fe50e8e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxkaWdpdGFsJTIwcGF5bWVudHN8ZW58MHx8fHwxNjkyMjYyMzI2fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@frostroomhead">Rodion Kutsaiev</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>What's Wrong with "The Trinity"?</h2><p>&#128308; Take Phonepe. I can never find the "scan" or "pay to a phone number" button without spending a hot minute reading through their number of useless offers. It often would be tucked in a small corner among coloured buttons, littered across the screen, designed to draw your attention away from what you wanted to do &#8212; may a payment.</p><p>&#128308; Now take its major competitor, Paytm. They force full-page gambling ads and weird offer banners, hiding the payment buttons. And after you manage to locate it, you receive yet another barrage of ads, spin wheels, cashback animations, and more products to buy, even before confirming your payment &#129318;&#8205;&#9794;&#65039;. The only good thing is they have the best reliability.</p><p>&#128992; Google Pay fares better in this regard, but still too many distractions with energy, diyas, mangoes or whatever mini-game they want to develop that week &#8212; still a massive welcome change from the Phonepe-Paytm chaos. Unlike others, You can still locate the pay button, which isn't lost in the sea of offers. However, the only big negative of Google Pay &#8212; is the neverending saga of payment failures &#129335;&#8205;&#9794;&#65039;.</p><p>&#127974; Bank apps are a bug fest &#128030;, and by the time you reach the UPI page, they time out before you complete payment due to sEcUriTy &#128545;!!!</p><p>And BHIM, the OG UPI app, which was dead simple in 2016 even for my grand-parents has slowly turned to the Paytm or Phonepe way with an even worse experience because they don't use the system keyboard (for sEcurITy ofc) and typing anything is a nightmare.</p><p><em>Is there an app that is simple, easy to use and distraction-free?</em></p><p>At this point, I&#8217;m actually hoping if Apple Wallet launches in India with UPI. There has been talks about an Apple Card in partnership with HDFC Bank, I am going to assume they won&#8217;t have an agenda to sell products (other than the hardware), hence <em>probably</em> provide a service that&#8217;s simple and useful.</p><h2>Why?</h2><p>The real reason why all this happens is <em>the 0 MDR regime</em>. Unlike the case of MDR in credit or debit cards, our government doesn't allow UPI payment service providers to charge a transaction fee.</p><p>So, if you as an app want to make money, you must not allow users to make a payment and instead sell whatever you have at any cost and hope some users bite the bullet. Be it BNPL, loans or bill pay. &#128184; No wonder some payment apps are struggling too &#8212; remember FreeCharge and MobiQuik?</p><p>PayZapp is the only app I know that still maintains the sanity of a payments app and offers all essential features (Scan, Pay to mobile, Credit cards, BBPS etc.). It has been my primary for a while now. The transaction failure rates are slightly worse since it runs on the HDFC network.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[So, what is a language model?]]></title><description><![CDATA[The tech world is hyped about language models, so let's discover the excitement.]]></description><link>https://recursivefunction.blog/p/so-what-is-a-language-model</link><guid isPermaLink="false">https://recursivefunction.blog/p/so-what-is-a-language-model</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Wed, 17 May 2023 05:30:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pisF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey there &#128075;!</p><p>You might have heard a lot lately about &#8220;language models&#8221;. They&#8217;re computer programs that can understand and generate human-like language. Pretty impressive, right? Let&#8217;s see what all this hype is about &#128064;.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pisF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pisF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!pisF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!pisF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!pisF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pisF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png" width="1024" height="512" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51902b88-c680-4229-abdc-5da7905136fb_1024x512.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:512,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1273347,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pisF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 424w, https://substackcdn.com/image/fetch/$s_!pisF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 848w, https://substackcdn.com/image/fetch/$s_!pisF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 1272w, https://substackcdn.com/image/fetch/$s_!pisF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51902b88-c680-4229-abdc-5da7905136fb_1024x512.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL-E generated images with the prompt &#8220;artificial intelligence, surrealist style&#8221;. I&#8217;ll talk about AI image generation on another issue. </figcaption></figure></div><h2>What is a Language Model?</h2><p>A language model is a statistical model that predicts the next word in a sentence. It takes as input some context and outputs an embedding - a vector representation for each term in your vocabulary. &#128565;&#8205;&#128171;</p><blockquote><p>Simply put, language models predict what word will come next based on what they have already seen.</p></blockquote><p>For example, if you say, &#8220;I am hungry&#8221;, and then &#8220;I want to eat&#8221; &#129316;, the language model may predict that you are going to say, &#8220;Pizza&#8221; &#127829;.</p><h2>N-gram models</h2><p>N-gram models are one of the first language models developed by NLP researchers. </p><p>&#10145;&#65039; These models look at the previous n words in your sentence and predict what word will come next.</p><p>Unigram models ( n = 1) are the simplest language models. They are simply the probability of occurrence of each word in the corpora.</p><p>Models with higher n-count try to improve over unigram models and encode &#8220;context&#8221;. Bigram models (n = 2) and Trigram ( n = 3) look at your sentence&#8217;s previous two or three words and predict the next word.</p><p>&#9989; Auto-correction, text-to-speech and keyboard suggestions all utilize n-gram models.</p><h2>Markov Models</h2><p>Markov Models are an extension of n-gram models. They try to capture language structure by representing each word with a state and thus learning the probability distribution over states.</p><p>For example, if your sentence is &#8220;I like to drink coffee&#8221; &#9749;&#65039;, then the first word might be a state that represents the word &#8220;I&#8221;. Then, adding the following word (like &#8220;like&#8221;) becomes a new state representing both words together. It continues until you have added every possible word combination into states in your corpus.</p><h2>Continuous Space Language Models</h2><p>With computing becoming cheaper and more accessible, researchers started using neural networks to model language.</p><p>They found that training the neural network on a large text dataset could create &#8220;continuous space language models&#8221; that don&#8217;t need to be constrained by discrete states like the Markov Model does. Instead, they can represent all possible words in a sentence as points in a high-dimensional space.</p><p>&#10145;&#65039; This means that your model will learn higher-level features about your data and not just individual words or phrases alone to model language.</p><p>Word2Vec, published in 2013, was one of the first neural language models to use continuous space representations. The paper&#8217;s authors explained that this allowed them to model words more accurately and efficiently, which was a huge step forward in NLP research.</p><p>Later, recurrent neural networks like LSTMs took over the NLP world. The advent of attention mechanisms allowed language models to focus on specific parts of sentences while learning what they mean.</p><p>&#10145;&#65039; This makes it possible for them to learn about complex relationships between words and the meaning of sentences without requiring human intervention.</p><h2>Transformers</h2><p>The current state-of-the-art was introduced in 2017 by researchers from Google and the BERT (Bidirectional Encoder Representations from Transformers). Replacing the prior neural network models, they quickly overtook the NLP world.</p><p>&#10145;&#65039; They also allowed researchers to train models on much larger datasets than before, making it easier to build models from scratch using pre-trained model versions.</p><h2>So, what&#8217;s the hype about?</h2><p>Language models have been around for a while and are routinely used everywhere - from your keyboard auto-complete to the hundreds of chatbots &#129302; you see. </p><p>&#11088;&#65039; But they have started getting incredibly &#8220;large&#8221; in the past few years - thanks to the fantastic success of transformer models. BERT, RoBERTa, GPT 2/3/chatGPT, and LamBDA are all large language models, some with over several hundred billion training parameters.</p><p>&#11088;&#65039; Being trained on massive training data, these models performed impressively in generating responses to various prompts. They picked up a lot of patterns leading them to answer some complicated queries. </p><p>&#128073; They could come to simple logical conclusions that misled many people about &#8220;intelligence&#8221; and thus the hype.</p><blockquote><p>AI is not taking away our jobs anytime soon.</p></blockquote><p>The models are still pattern-matching algorithms with a lossy memory of a large chunk of the internet. They often regurgitate made-up or incorrect facts; we all saw what happened with the Google Bard demo, didn&#8217;t we?</p><h2>Is that all?</h2><p>Large language models are just the tip of the iceberg of what is yet to come. Their most significant contribution - easing communication with humans. </p><p>All of today&#8217;s tools and algorithms won&#8217;t be obsolete by them or any future AI model. Instead, future AI research will focus on bridging them.</p><p>&#11088;&#65039; A few weeks after the ChatGPT sensation, Meta (Facebook) researchers developed a new pattern for this, aptly called Tool Former. They aim to train language models to generate input for a specialized subsystem, retrieve the results and convert them back to natural language.</p><p>&#11088;&#65039; Frameworks like LangChain and Auto-GPT aim to build &#8220;agents&#8221; that work towards a particular goal by using the power of these LLMs. They start with a description and iteratively devise steps to reach their target by clever prompting. Some of the tasks the community has done are amazing. &#129668;</p><p>&#11088;&#65039; Similarly, OpenAI has made available plugins that integrate ChatGPT with external systems over a REST API. A task like asking for available flights will query a flight information system and respond with the most up-to-date results.</p><p>&#11088;&#65039; Google&#8217;s Bard is now being integrated across the entire spectrum of GSuite products &#8212; maybe finally, we will realize the dream we were sold through &#8220;Ok Google&#8221; and &#8220;Alexa&#8221; when they truly become smart assistants &#129302;.</p><p>The future is exciting! &#10024;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Java Libraries to Supercharge Unit Testing]]></title><description><![CDATA[Unit test, you must. Save time, you can. Become smart, you should.]]></description><link>https://recursivefunction.blog/p/java-libraries-to-supercharge-unit</link><guid isPermaLink="false">https://recursivefunction.blog/p/java-libraries-to-supercharge-unit</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Mon, 06 Feb 2023 10:30:58 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'll admit it: I'm a lazy developer. That's not to say I don't work hard or care about my craft, but when it comes to unit testing&#8212;a notoriously time-consuming part of software development&#8212;I want to spend as little time on it as possible. So when the codebase you're working on is significant or your tests require lots of setups, automation becomes essential.</p><p>Here are some libraries that will make your life easier by removing all the grunt work in writing unit tests.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="1080" height="720" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1576444356170-66073046b1bc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwzMDAzMzh8MHwxfHNlYXJjaHwxfHx1bml0JTIwdGVzdHxlbnwwfHx8fDE2NzUwNzgwMjc&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@flowforfrank">Ferenc Almasi</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><blockquote><p>Java provides several frameworks for writing tests, but one library stands out above all others: JUnit. There are alternative libraries like Spock, but JUnit is the most used, so I will limit my discussion to libraries that work well with JUnit.</p></blockquote><h2>Mocking frameworks: Mockito &amp; PowerMock</h2><p>It can be difficult to test when writing code with many dependencies. That's where mocking comes in. A mocking framework allows you to replace real objects with fake ones during testing to isolate the code under test from its dependencies and ensure it works correctly without worrying about external factors like network connections or databases.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://site.mockito.org/">Mockito</a> is the <em>de-facto</em> Java library used for creating and testing mocks in software development. It provides a simple and intuitive API for creating mock objects, which can be used in unit tests to simulate the behaviour of objects that are not yet available or are difficult to obtain.</p><p>With Mockito, you can mock classes, interfaces, and even individual methods. You can also specify how the mock objects should behave, such as returning a specific value for a given method call or verifying that a method was called a certain number of times.</p><pre><code>import org.junit.Test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

class CalculatorTest {
    @Test
    void testAdd() {
        Calculator calculator = mock(Calculator.class);
        when(calculator.add(1, 2)).thenReturn(3);

        assertEquals(3, calculator.add(1, 2));
    }

    @Test
    void testMultiply() {
        Calculator calculator = mock(Calculator.class);
        when(calculator.multiply(2, 3)).thenReturn(6);

        assertEquals(6, calculator.multiply(2, 3));
    }
}
</code></pre><p>Mockito works in most cases, but it expects you to write code in complete idiomatic gang-of-four style code. Often you will find yourself needing to test a piece of code which bends around those rules, such as needing to mock a static method. Mockito stays away from byte-code black magic required for these cases. Time to summon the dark wizard - PowerMock!</p><p><a href="https://powermock.github.io/">PowerMock</a> has helpful additional features, such as mocking final methods and static methods that Mockito does not support. It may be beneficial when writing tests involving these types of declarations (e.g., mocking static initializers). In this example, we will mock some static methods of <code>java.lang.Math</code></p><pre><code>import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class MathTest {
    @Test
    void testMax() {
        Math mockMath = mock(Math.class);
        when(mockMath.max(1, 2)).thenReturn(2);

        assertEquals(2, mockMath.max(1, 2));
    }
}
</code></pre><h2>Assertion libraries: AssertJ &amp; Hamcrest</h2><p>While JUnit's built-in assertions will suffice most of the trivial assertions, there are times when you need something more powerful. <a href="https://assertj.github.io/doc/">AssertJ</a> is an assertion library that provides additional features, like fluent assertions and method chaining, for better readability.</p><pre><code>import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;

import org.junit.jupiter.api.Test;

class AssertJAssertionTest {
    @Test
    void testAssertThat() {
        int actual = 1;
        assertThat(actual).isEqualTo(1);
    }

    @Test
    void testWithin() {
        double actual = 0.99;
        double expected = 1.0;
        double delta = 0.01;
        assertThat(actual).isCloseTo(expected, within(delta));
    }
}</code></pre><p><a href="https://hamcrest.org/JavaHamcrest/">Hamcrest</a> is a library for writing &#8220;matchers&#8221; of various kinds, including matching on the presence or absence of a particular element, matching against the text content of elements, and matching against the structure of elements. It provides a fluent interface for writing these matches in a way that is more readable than using JUnit's built-in Matchers class.</p><pre><code>import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.number.IsCloseTo.closeTo;

import org.junit.jupiter.api.Test;

class HamcrestAssertionTest {
    @Test
    void testIs() {
        int actual = 1;
        assertThat(actual, is(1));
    }

    @Test
    void testEqualTo() {
        String actual = "hello";
        assertThat(actual, equalTo("hello"));
    }

    @Test
    void testCloseTo() {
        double actual = 0.99;
        double expected = 1.0;
        double delta = 0.01;
        assertThat(actual, closeTo(expected, delta));
    }
}</code></pre><h2>Data faking: Instancio &amp; DataFaker</h2><p>While writing, your tests need a lot of fake data. You can hand-roll the objects, but it becomes tiresome rather quickly. <a href="https://www.instancio.org/">Instancio</a> is an open-source library that allows you to create random instances of your classes without writing a single line of code.</p><pre><code>public record User(String firstName, String lastName, String email) {}

// use Instantio to create a list of 10 users
List&lt;User&gt; user = Instancio.ofList(User.class).size(10).create();</code></pre><p>If you need real-looking data for populating a database or testing validations, use <a href="http://www.datafaker.net/documentation/getting-started/">DataFaker</a>. It generates real-looking data for unit tests, such as names and addresses or bank account numbers. It can also generate random data for integration tests.</p><pre><code>import net.datafaker.Faker;

// ...

Faker faker = new Faker();

String name = faker.name().fullName(); // Miss Samanta Schmidt
String firstName = faker.name().firstName(); // Emory
String lastName = faker.name().lastName(); // Barton

String streetAddress = faker.address().streetAddress(); // 60018 Sawayn Brooks Suite 449</code></pre><h2>Web service mocking: WireMock</h2><p><a href="https://wiremock.org/">WireMock</a> is a Java library that allows you to quickly mock, stub and validate web services. It helps you test the interaction between your application and the remote services it depends on when deployed. Using WireMock, you can define HTTP routes and test HTTP calls with their expected request bodies and mock responses.</p><pre><code>import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class ClientServiceTest {
    private static WireMockServer wireMockServer;

    @BeforeAll
    public static void setup() {
        wireMockServer = new WireMockServer(WireMockConfiguration.options().port(8080));
        wireMockServer.start();
    }

    @AfterAll
    public static void teardown() {
        wireMockServer.stop();
    }

    @Test
    public void testClientService() {
        wireMockServer.stubFor(get(urlEqualTo("/service/endpoint"))
            .willReturn(aResponse().withBody("{\"data\":\"mocked data\"}")));

        ClientService client = new ClientService();
        String response = client.callService();
        assertEquals("mocked data", response);
    }
}</code></pre><p>WireMock can simulate slow or unavailable services to test how your application handles them, and you can verify if your code responds correctly in such situations.</p><h2>Mocking application dependency with TestContainers</h2><p><a href="https://www.testcontainers.org/">TestContainers</a> is a lightweight, open-source library that lets developers quickly mock and test interactions between their code and popular software systems. It's great for testing with real, local dependencies like databases or caches. It also simulates third-party services like Amazon S3 or Google Cloud Storage (using their corresponding local emulators). Here is an example with a PostgreSQL container spun up for use during the test.</p><pre><code>import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class TestContainersExample {
    @Container
    private static final PostgreSQLContainer database = new PostgreSQLContainer();

    @BeforeEach
    public void setup() {
        String jdbcUrl = database.getJdbcUrl();
        // Use jdbcUrl to setup connection to the database container
    }

    @AfterEach
    public void teardown() {
        // Close the database connection
    }

    @Test
    public void testDatabaseOperations() {
        // Perform database operations and assertions
    }
}</code></pre><p>TestContainers is built on the Docker API and works with any Dockerfile or image. The library provides ready-made definitions for popular software such as PostgreSQL, MySQL, and Redis and allows you to define your mocks using a simple, declarative syntax. Then it uses these definitions in conjunction with the official docker run command to create new containers that behave like the real thing but provides sandboxed environments for testing purposes.</p><p>Unit testing can be a powerful tool for ensuring that your code works correctly, but it also takes time to learn how best to use it. I've covered some great libraries that make unit testing more effortless than ever, and they're free! You don't have any excuse not to try them out today!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Join my chat]]></title><description><![CDATA[A private space for us to converse and connect]]></description><link>https://recursivefunction.blog/p/join-my-chat</link><guid isPermaLink="false">https://recursivefunction.blog/p/join-my-chat</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Wed, 01 Feb 2023 06:00:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2H2-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I&#8217;m announcing a new addition to my Substack publication: <em>the chat</em>.</p><p>This is a conversation space in the Substack app that I set up exclusively for subscribers &#8212; like a group chat or live hangout. I&#8217;ll post short prompts, thoughts, and updates that come my way, and you can jump into the discussion. </p><p><strong>To join our chat, you&#8217;ll need to download the <a href="https://substack.com/app/app-store-redirect">Substack app</a>, now available for both iOS and Android.</strong> Chats are sent via the app, not email, so turn on push notifications, so you don&#8217;t miss a conversation as it happens.</p><div><hr></div><h2>How to get started</h2><ol><li><p><strong>Download the app by clicking <a href="https://substack.com/app/app-store-redirect">this link</a> or the button below.</strong> Substack Chat is now available on both iOS and Android.</p></li></ol><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.com/app/app-store-redirect&quot;,&quot;text&quot;:&quot;Get app&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://substack.com/app/app-store-redirect"><span>Get app</span></a></p><ol start="2"><li><p><strong>Open the app and tap the Chat icon.</strong> It looks like two bubbles in the bottom bar, and you&#8217;ll see a row for my chat inside.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2H2-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2H2-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 424w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 848w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 1272w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2H2-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png" width="542" height="276.5837912087912" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:743,&quot;width&quot;:1456,&quot;resizeWidth&quot;:542,&quot;bytes&quot;:501468,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!2H2-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 424w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 848w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 1272w, https://substackcdn.com/image/fetch/$s_!2H2-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F9a23d49f-76bd-4f75-baac-0ae5733774bd_1456x743.png 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ol start="3"><li><p><strong>That&#8217;s it!</strong> Jump into my thread to say hi, and if you have any issues, check out <a href="https://support.substack.com/hc/en-us/sections/360007461791-Frequently-Asked-Questions">Substack&#8217;s FAQ</a>.</p></li></ol><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://open.substack.com/pub/recrsn/chat&quot;,&quot;text&quot;:&quot;Join chat&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://open.substack.com/pub/recrsn/chat"><span>Join chat</span></a></p>]]></content:encoded></item><item><title><![CDATA[Bluetooth LE Sensors and React as Part of my Cycling Training]]></title><description><![CDATA[The story of the time when a tinkerer meets Bluetooth Low Energy sensors, gets access to cycling performance data and decides to do something about it ...]]></description><link>https://recursivefunction.blog/p/bluetooth-le-sensors-and-react-as</link><guid isPermaLink="false">https://recursivefunction.blog/p/bluetooth-le-sensors-and-react-as</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Sun, 29 Jan 2023 18:25:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rqVS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m a software engineer who has been cycling for a few years. I have a Giant Roam 2 2022 hybrid bike and recently moved to a Trek Domane AL3 for biking adventures.</p><h2>My (other) stack!</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rqVS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rqVS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rqVS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg" width="1456" height="546" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:546,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:678720,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rqVS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rqVS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b059186-3209-4238-939b-20aa4e0cd78a_2048x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">My Trek Domane AL3 (left) and Giant Roam 2 (right)</figcaption></figure></div><p>For a while, I have started recording my rides on Strava (a platform for cyclists) to get the most out of my training sessions. Initially, I started with an Apple Watch (Series 7, LTE model). The LTE connectivity allowed maps to work and helped to not take the phone out on every other turn to look at the directions. It worked fine until I started doing long rides (4 - 5h) and discovered that the watch&#8217;s battery life was not enough. The Apple Watch only provided GPS and heart-rate data for my rides. However, I wanted more data - such as speed, power &amp; cadence, and heart rate. I bought a Garmin Edge 520 Plus Bundle with Speed/Cadence sensor and a chest-strap heart-rate monitor.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!S6Yp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!S6Yp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 424w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 848w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!S6Yp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg" width="1456" height="1079" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1079,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1414971,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!S6Yp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 424w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 848w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!S6Yp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f4cc0bf-1bc6-45b1-be27-f4a469c63d54_3024x2240.jpeg 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Garmin Edge 520 Plus in action with a chest-strap heart rate monitor, speed sensor and cadence sensor connected</figcaption></figure></div><p>The Garmin Edge 520 Plus is a fabulous bike computer. It has a built-in GPS with navigation and interfaces with BLE and ANT+ sensors, so you don&#8217;t need to worry about bringing along your smartphone. It can upload rides directly to Strava, which I use for tracking my progress and sharing my rides with friends. It also interacts with dozens of interesting apps, such as Kumoot and Trailforks, which help plan and share routes.</p><p>The heart rate monitor makes it easy for me to keep an eye on how hard I work during each ride, ensuring that I&#8217;m not overdoing or getting lazy. I also use the power/cadence sensors to get more accurate information about my training performance!</p><p>Since I discovered the sensors run on Bluetooth Low Energy, I looked for documentation of their working and, to much joy, found out that entire specs are standardized and documented. I wanted to get creative to build beautiful charts and have fun with React hooks!</p><h2>A primer of Bluetooth LE and Web Bluetooth</h2><p>You have used Bluetooth Headphones and maybe Bluetooth Keyboard and Mice. Bluetooth LE (BLE), similar to Bluetooth, is a low-power wireless technology that allows heart rate monitors and power/cadence sensors to communicate with your phone. BLE devices require very little energy - literally! The cycling sensors run on CR2032 coin cell batteries for years.</p><p>Web Bluetooth is a new browser API that allows web pages to communicate directly with Bluetooth LE devices. That means you can use your browser to pair your heart rate monitor or bike computer! However, support for the API is currently limited to Google Chrome and other Chromium derivatives on all desktops and Android.</p><h2>How BLE devices work</h2><p>When your device searches for BLE devices, it discovers nearby devices and their advertised services using the&nbsp;<a href="https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gap">Generic Application Profile</a>&nbsp;protocol.</p><p>Once you initiate pairing, you can discover what &#8220;services&#8221; the devices expose. Specifications published by Bluetooth SIG define BLE services. Once you have discovered the services, you can send and receive data via a &#8220;characteristic&#8221; like an address on the street. The sensor sends data as characteristic values (e.g., heart rate). At the same time, your phone listens for changes in these values over time (e.g., heart rate readings every second). If you are interested to read more, look up&nbsp;<a href="https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt">the Generic Attribute Profile</a>.</p><p>Here&#8217;s the technical TLDR; Sensors act as GATT servers, and your devices as GATT central. Servers publish &#8220;characteristics&#8221; on multiple &#8220;services&#8221; that your device central continuously listens to. If you want to dig deeper into BLE services and characteristics,&nbsp;<a href="https://github.com/oesmith/gatt-xml">the GATT spec in XML is your friend.</a></p><h2>Using the Web Bluetooth API</h2><p>The Web Bluetooth API lets you interact with BLE devices from a web page. It&#8217;s pretty bleeding edge and only works in Chrome, but there are plans to support it in other browsers soon.</p><p>Check for Web Bluetooth support with:</p><pre><code><code>const bluetoothSupported = 'Bluetooth' in navigator;</code></code></pre><p>Once you have determined you have Bluetooth support available, it&#8217;s time to discover some BLE devices and pair them with them. The current shape of Web Bluetooth API allows you to request a single device at once that advertises a list of services, and the browser provides the pairing UI. However, improvements to auto-connect and remembering chosen devices are coming soon. Let&#8217;s search for a heart-rate monitor and pair it with it.</p><pre><code>async function readDeviceInfo(btDevice: BluetoothDevice): Promise&lt;DeviceInfo&gt; {
  const deviceInfo = await btDevice.gatt!.getPrimaryService(
    "device_information"
  );
  if (!deviceInfo) {
    return {};
  }

  const decoder = new TextDecoder("utf-8");

  const [manufacturer, model] = await Promise.all([
    deviceInfo.getCharacteristic("manufacturer_name_string"),
    deviceInfo.getCharacteristic("model_number_string")
  ]);

  return {
    manufacturer: manufacturer
      ? decoder.decode(await manufacturer.readValue())
      : undefined,
    model: model ? decoder.decode(await model.readValue()) : undefined
  };
}


const btDevice = await navigator.bluetooth.requestDevice({
  filters: [
    { services: ["heart_rate"] }
  ],
  optionalServices: ["device_information"]
});

const server = await btDevice.gatt!.connect();

const [services, deviceInfo] = await Promise.all([
  server.getPrimaryServices(),
  readDeviceInfo(btDevice)
]);</code></pre><p>The <code>readDeviceInfo</code> function decodes the <code>device_info</code> characteristic and returns basic info such as BLE device manufacturer, device name and model number. When requesting scanning devices, you can specify a string such as <code>heart_rate</code> or its corresponding UUID <code>0000180d-0000-1000-8000-00805f9b34fb</code>. <a href="https://github.com/oesmith/gatt-xml">The GATT XML specifications</a> contain the values and the corresponding UUIDs in the descriptors.</p><p>Once we have discovered the services, we can read the characteristics and display them on the screen.</p><pre><code>const service = await server.getPrimaryService("heart_rate");
const characteristic = await service.getCharacteristic("heart_rate_measurement");

await characteristic.startNotifications();

characteristic.addEventListener("characteristicvaluechanged", (event: Event) =&gt; {
  // @ts-ignore
  const value = event.target!.value as DataView;
  const flags = value.getUint8(0);
  const result: HeartRateMonitorValue = {
    heartRate: 0,
  };

  let index = 1;

  const rate16Bits = flags &amp; 0x1;
  if (rate16Bits) {
    result.heartRate = value.getUint16(index, true);
    index += 2;
  } else {
    result.heartRate = value.getUint8(index);
    index += 1;
  }
  const contactDetected = flags &amp; 0x2;
  const contactSensorPresent = flags &amp; 0x4;
  if (contactSensorPresent) {
    result.contactDetected = !!contactDetected;
  }
  const energyPresent = flags &amp; 0x8;
  if (energyPresent) {
    result.energyExpended = value.getUint16(index, true);
    index += 2;
  }

  document.querySelector('#heartRate').innerHTML = result;
});</code></pre><p>The <code>heart_rate_meaturement</code> characteristic emits 2 or 3 octets for each measurement. For the uninformed, one octet is 8 bits or 1 byte in popular convention, but historically, bytes had different lengths. The first octet is a flag, the first bit of which says whether the heart rate value is 1-bit or 16-bit (human values can fit in 8-bit, but what if you want to measure the heart rate of your cat?). The preceding two bits represent whether the sensor is in contact and whether it emits calories consumed as a value. Depending on the flag values, we read the heart rate as an 8-bit or 16-bit value and energy expended as a 16-bit value if present.</p><p>Similarly, we can also read cadence (rotation per minute) values from speed and cadence sensors (CSC). However, interpreting these sensor values involves a bit more calculation. Instead of providing the raw values like the heart rate monitor, the CSC sensors provide wheel rotations and crank rotations along with the last rotation time. Using these, we have to derive speed and cadence.</p><pre><code>const service = await server.getPrimaryService("cycling_speed_and_cadence");
const characteristic = await service.getCharacteristic("csc_measurement");

await characteristic.startNotifications();

const TYRE_CIRCUMFERENCE_METRES = 2.155; // I ride a 32c tyr
const MAX_UINT16 = 65535;
const CSC_TIME_FACTOR = 1024;

let lastWheelRevolutions = 0;
let lastWheelEventTime = 0;
let lastCrackRevolutions = 0;
let lastCrankEventTime = 0;

characteristic.addEventListener("characteristicvaluechanged", (event: Event) =&gt; {
  const value = event.target!.value as DataView;
  const flags = value.getUint8(0);

  let index = 1;

  const wheelRevolutionDataPresent = flags &amp; 0x1;
  if (wheelRevolutionDataPresent) {
    const cumulativeWheelRevolutions = value.getUint32(index, true);
    index += 4;
    const wheelEventTime = value.getUint16(index, true);
    index += 2;

    const speed =
      (((lastWheelRevolutions - cumulativeWheelRevolutions) *
        TYRE_CIRCUMFERENCE_METRES) /
        diff(
          lastWheelEventTime,
          wheelEventTime,
          MAX_UINT16
        )) *
      CSC_TIME_FACTOR;

    document.querySelector('#speed').innerHTML = speed;
    lastWheelRevolutions = cumulativeWheelRevolutions;
    lastWheelEventTime = wheelEventTime;
  }

  const crankRevolutionDataPresent = flags &amp; 0x2;
  if (crankRevolutionDataPresent) {
    const cumulativeCrankRevolutions = value.getUint16(index, true);
    index += 2;
    const crankEventTime = value.getUint16(index, true);
    index += 2;

    const crankCadence =
      (diff(
        lastCrackRevolutions,
        cumulativeCrankRevolutions,
        MAX_UINT16
      ) /
        diff(
          lastCrankEventTime,
          crankEventTime,
          MAX_UINT16
        )) *
      CSC_TIME_FACTOR;

    document.querySelector('#cadence').innerHTML = crankCadence;
    lastCrackRevolutions = cumulativeCrankRevolutions;
    lastCrankEventTime = crankEventTime;
  }
});</code></pre><p>Similar to <code>heart_rate_monitor</code> characteristics, the <code>cycling_speed_and_cadence</code> also emits a flag value that says whether or not wheel rotation data and crank rotation data are present. Many sensors (especially frame-integrated magnetic ones) have a single unit for wheel and crank revolution data. Still, some units, like Garmin, have separate wheel (speed) and crank (cadence) sensors, which connect individually.</p><p>If the wheel revolution data is present, we subtract the cumulative value as a 32-bit integer from the last value and multiply it by the tyre circumference to find the distance travelled. The difference in timestamps gives the time and thus speed (= distance/time). Note that the time is also a 16-bit value representing 1/1024 of a second and will overflow quickly. Similarly, the crank rotation value is also a 16-bit value (why?) along with a 16-bit timestamp which provides the current RPM (= rotations/time).</p><p>There is a separate diff function to calculate the difference between 16-bit values with consideration for overflow. Here it goes:</p><pre><code>function diff(last: number, current: number, overflow: number) {
  const diff = current - last;
  if (diff &lt; 0) {
    return diff + overflow;
  }
  return diff;
}</code></pre><p>It&#8217;s a simple function that checks if the last number is less than the current. If yes, it assumes an overflow and adds the overflow value to the difference.</p><h2>Putting it all together</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kn0E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kn0E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 424w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 848w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 1272w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kn0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png" width="1284" height="1772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1772,&quot;width&quot;:1284,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84750,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kn0E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 424w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 848w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 1272w, https://substackcdn.com/image/fetch/$s_!kn0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0faf8e50-d187-4811-b967-badc7a629ddc_1284x1772.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Putting all this as multiple React Hooks with a tiny front end gave me an excellent little bike computer that runs as a web app! It doesn&#8217;t have all the features that my Garmin has, but hey, it works, and with access to JS and raw data, we can go as creative as we want. Power meter support would be a relatively simple addition. Support for recording and exporting rides and showing multiple graphs at the end of the ride would be fantastic as well. Turn-by-turn GPS Navigation? Difficult, but still doable with OpenStreetMaps!</p><p>The online version is available at <a href="https://bike-computer.pages.dev/">bike-computer.pages.dev</a>, and the code is at <a href="https://github.com/recrsn/bike-computer">github.com/recrsn/bike-computer</a>.</p><p> I started this project as a playground for experimenting with HRM and CSC BLE characteristics, and it turned out well. The next step is to use iOS CoreBluetooth or Dart and build a fully functioning mobile app using the lessons learnt here. But this is a showcase of the modern web platform.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Feature Flags and Why you should use them]]></title><description><![CDATA[You just wrote a great deal of code and are about to deploy it. But you ask yourself, &#8220;Will you set the hamsters free to eat all our servers?&#8221;]]></description><link>https://recursivefunction.blog/p/feature-flags-and-why-you-should</link><guid isPermaLink="false">https://recursivefunction.blog/p/feature-flags-and-why-you-should</guid><pubDate>Tue, 15 Nov 2022 05:49:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xFyZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xFyZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xFyZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 424w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 848w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 1272w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xFyZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png" width="1056" height="792" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:792,&quot;width&quot;:1056,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81090,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xFyZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 424w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 848w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 1272w, https://substackcdn.com/image/fetch/$s_!xFyZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F46050367-9b4a-4480-acef-5dab3061d2c7_1056x792.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>If you find yourself wondering whether your last change will kill your servers or receive extreme bashing from your users, you need feature flags.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Recursive! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Feature flags are a great way to be fast and safe while developing software. They can be used for many things, including A/B testing, canary releases or staged rollouts. You can have your code do one thing for some customers and another for others; you can keep track of how well things are working out; and take appropriate action if something goes wrong.</p><p><em><strong>Feature flagging is an approach to continuous delivery in which we release features by modifying a system configuration rather than changing code. Rather than release all the features simultaneously, you can release specific features to specific users in different environments.</strong></em></p><p>Feature flags are configurable parameters that function as gatekeepers for enabling or disabling one or more features based on some condition that can be static or dynamic. They&#8217;re ideal for managing user experience or feature rollouts since they allow you to control when new features are activated or deactivated. Combining them with product analytics and error monitoring, you can also monitor how users interact with them before starting them for everyone.</p><pre><code>def get_homepage_content(request):
&nbsp;&nbsp;&nbsp; if feature_flags.enabled('new_homepage', request):
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new_homepage(request.user)
&nbsp;&nbsp;&nbsp; else:
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return homepage_content(request.user)</code></pre><h1>How are Feature Flags helpful to you?</h1><p>Feature flags are a tool that can help you deliver complex products faster. They provide safety, agility, release control, and experimentation support.</p><h2>Safety: Being able to turn off things that aren&#8217;t working immediately. The ability to scale up safely.</h2><p>Turning off things that aren&#8217;t working immediately is an obvious benefit, but it&#8217;s not the only one. You can also increase your team&#8217;s velocity by enabling them to implement changes faster without worrying whether those changes will ruin everything else. When you&#8217;re using feature flags and can scale down quickly, you don&#8217;t have to worry about removing unpopular features from your codebase if they don&#8217;t work out. And if there are any bugs or issues with a new piece of code or feature, it&#8217;s easy to disable it while you resolve them instead of having your whole system go down when something breaks unexpectedly at runtime!</p><h2>Agility and Release Control - release your new features faster while not affecting the functionality of your current features.</h2><p>You can now safely test new features without affecting the functionality of existing features. You can release features to a small subset of users and see how they work. If you find something that does not work as expected, you can quickly shut it off for all users or just for that subset.</p><p>You don&#8217;t need to code features that you want to experiment with in advance or choose between deploying sooner but potentially risking downtime or delaying your deployment and allowing other teams&#8217; work to back up behind you. You can test new features in an isolated environment and then release them as soon as they&#8217;re ready&#8212;without having to plan for it months in advance.</p><p>Feature flags work exceedingly well with the CI/CD philosophy, which empowers users to deploy code once it&#8217;s merged with the mainline and passes all acceptance tests by toggling off all the new changes. Once all the features pass QA, you can turn them on.</p><h2><strong>Experimentation:</strong> determine whether a new or changed feature works as intended and whether it should be a part of the general release.</h2><p>Another benefit of feature flags is experimenting with a new or revised feature and determining whether it works as intended. If you don&#8217;t need the new or revised feature for the general release, you can turn it off entirely and avoid any negative impact on the user experience.</p><p>In addition, if there are features that work well but aren&#8217;t making their way into your general releases, this allows you to test out new ideas and see if they are helpful before deploying them widely. And finally, by getting feedback from users who opt into these experiments, you can get a sense of what they want to help shape future updates.</p><ul><li><p><strong>A/B Testing</strong>: You might think you don&#8217;t need feature flags for this&#8212;you&#8217;re just going to run a couple of experiments simultaneously. But what if you wanted to run a test where not all customers see the same variation? Or what if you wanted to measure the impact of each variation independently? Feature flags let you do both of these things quickly!</p></li><li><p><strong>Canary Releases</strong>: Canary releases are a great way to get early feedback on new software. You can use feature flags to enable the new code for only part of your customer base and then gradually roll it out further as you collect data and ensure everything works as expected.&nbsp;</p></li><li><p><strong>Staged Rollouts</strong>: This is similar to A/B testing, except that instead of measuring how different groups respond differently to the same thing, you measure how the same group reacts differently over time.</p></li></ul><h1>Implementing feature flags</h1><h2>Classifying feature flags</h2><p>We can classify feature toggles into multiple classes based on their longevity and dynamism. Based on this, we can bin them into</p><ol><li><p>Release flags - set during build time to enable/disable code sections.</p></li><li><p>Experiment flags - enable A/B testing.</p></li><li><p>Ops flags - the ability to turn features off if something goes wrong.</p></li><li><p>Permission toggles - allow certain features for a subset of users.</p></li></ol><h3>Release flags</h3><p>They are the least dynamic of the bunch, and you set them during build time. You can use it to disable features that need to be conditioned or fail a late-stage QA review&#8212;often used by teams following trunk-based development to exclude changes that are not yet ready.</p><p>You can implement release flags by language/build tool features such as #IFDEF macros in C++ to include and exclude code.</p><p>A famous use of release flags is noticeable in APK teardowns, where insights into strings disabled in application code give a sneak peek of what&#8217;s coming in future versions but is not currently ready for prime-time.</p><h3>Experiment flags</h3><p>Used for A/B testing, enable a feature based on which cohort a user belongs to. You can serve two versions of your code to different cohorts of users and observe the performance of the versions. Since the flags depend on some properties of the current user, you decide the feature flag value on runtime. Typically, you call an API with user details, which gives the flag&#8217;s value. There are several libraries and third-party services that help implement feature flags.</p><h3>Ops Flags</h3><p>These flags control operational aspects of our code. You may consider an ops flag when rolling out a new feature that risks breaking a critical flow or has unclear performance implications. If the feature misbehaves, developers can quickly disable the recent changes using these flags.</p><p>Such flags are designed to be relatively short-lived - once we achieve confidence in the new change, we remove the flag. However, systems also have a few &#8220;Kill Switches&#8221; that allow disabling non-critical system functionality when the system is under unusually high load or dealing with a failing third party such as a payment gateway.</p><p>Since ops flags don&#8217;t depend on user context, You can implement ops flags by reading them from environment variables or configuration stores such as Consul or Etcd.</p><h3>Permission toggles</h3><p>You may want to expose newly introduced features to a small group of users - such as internal users or alpha/beta users for early feedback based on which you may further evolve the product. Such flags are the most dynamic of the bunch are depend on the user&#8217;s configuration.</p><p>A typical way of implementing this would be to tag features based on their availability and decide to expose them based on whether the user belongs to the group for which we expose this functionality.</p><h2>Considerations while implementing feature toggles</h2><h3>Separating decision points from decision logic</h3><p>While developing feature flags, it is increasingly tempting to splatter a bunch of if-else all over your code and control features based on this. It may seem reasonable, but it tightly couples feature flags with features. For example, consider yourself developing many changes for a v2 iteration of a workflow.</p><p><code>def generate_report(query):<br>&nbsp; &nbsp; if feature_flags.enabled("v2_release"):<br>&nbsp; &nbsp; &nbsp; &nbsp; add_performance_data()<br><br>&nbsp; &nbsp; # ...<br><br>&nbsp; &nbsp; if not feature_flags.enabled("v2_release"):<br>&nbsp;&nbsp;&nbsp; &nbsp; # do some deprecated work.</code></p><p>Such code forces you to release everything together or nothing at all. However, if you want to release a subset of features, you can only do so with extensive refactoring. Also, breaking into many small feature flags would be too much to handle.</p><p>Instead, a more innovative way would be to introduce another abstraction that does context-specific decisions using feature flags.</p><p>Let&#8217;s refactor the above code.</p><p><code># report_features.py<br>def performance_data_enabled():<br>&nbsp;&nbsp;&nbsp; return feature_flags.enabled("v2_release")<br><br>def query_source_table():<br>&nbsp;&nbsp;&nbsp; return not feature_flags.enabled("v2_release")<br><br># generate_report.py</code></p><p><code>def generate_report(query):<br>&nbsp; &nbsp; if report_features.performance_data_enabled():<br>&nbsp; &nbsp; &nbsp; &nbsp; add_performance_data()<br><br>&nbsp; &nbsp; # ...<br><br>&nbsp; &nbsp; If report_features.query_source_table():<br>&nbsp;&nbsp;&nbsp; &nbsp; # do some deprecated work</code></p><h3>Embracing Inversion of Control</h3><p>Decision-based strategies like the above work well for simple changes, but adding so many branching points would be a maintenance headache when changes become non-trivial.</p><p>Feature flags go well with the Open-Close principle of SOLID practices. Instead of changing existing code, you can develop and switch a new implementation based on feature flags.</p><p><code>class V1ReportGenerator:<br>&nbsp;&nbsp;&nbsp; def generate_report():<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; # do work<br><br>class V2ReportGenerator:<br>&nbsp;&nbsp;&nbsp; def generate_report():<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; # do more work<br><br>def create_report_generator():<br>&nbsp;&nbsp;&nbsp; if feature_flags.enabled("v2_release"):<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return V2ReportGenerator()<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return V1ReportGenerator()</code></p><h3>Storing feature flag data</h3><ol><li><p>If possible, prefer storing them alongside your code in your SCM and re-deploy whenever you enable or disable a feature. Managing flags in code gives the same benefit as infrastructure as code and contains a log of what changes you deployed at any time.</p></li><li><p>You can store flags in the app database for more dynamic features. The schema for storing flags can be straightforward &#8211; a feature name, value, and additional columns for conditions and audit metadata. You can also use other data stores such as Consul or Etcd.</p></li></ol><h3>Managing overrides</h3><p>There will be cases where you must toggle a flag for a minimal subset of users. Having a proper overriding mechanism can help here.</p><ol><li><p>If you are using a database, you can maintain an override table with additional constraints on which you perform a left join to get the value of a feature flag.</p></li><li><p>You can also opt for additional information from the request context - such as an API version or a special HTTP header to control feature flags. Doing this moves the control of flags to the consumer of your service.</p></li></ol><p>There are many different ways to use feature flags, but the main idea is simple: you can use them to roll out new features without breaking your site, which means no more worrying about code deployment and issues with testing. If a component isn&#8217;t ready for prime time, flip the switch off until it&#8217;s ready.</p><p>The concept of feature flags is pretty simple: they allow you to control which users see each feature on any given day or week as it rolls out slowly over time rather than all at once when deployed and live. This means fewer bugs in production because tests are more accessible and faster than ever!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://recursivefunction.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Enjoyed reading the story? Subscribe for free to receive new posts about programming.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Setting up Service Monitoring — The Why’s and What’s]]></title><description><![CDATA[You created an app, you are ready to release it to the world.]]></description><link>https://recursivefunction.blog/p/the-whys-and-what-s-of-setting-up-service-monitoring-cc1c165ee088</link><guid isPermaLink="false">https://recursivefunction.blog/p/the-whys-and-what-s-of-setting-up-service-monitoring-cc1c165ee088</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Sat, 22 May 2021 18:52:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4WLB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You created an app, you are ready to release it to the world. But are you monitoring it? How will you know if something is off or something simply stopped working? This is where monitoring comes into the picture.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4WLB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4WLB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4WLB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!4WLB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4WLB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F04936dea-29c8-4986-b17a-aa1a793dccf3_1024x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@ncheok?utm_source=medium&amp;utm_medium=referral">Noel Cheok</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>You will find lots and lots of guides about monitoring your applications. However, most of them are just focused on setting up a framework using some integration with some language. What they don&#8217;t discuss is what metrics you should be monitoring. This blog post exclusively targets that topic.</p><p>For monitoring our micro-services, we can start with the <a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/#xref_monitoring_golden-signals">four golden signals, as per the Google SRE book</a> &#8212; latency, traffic, errors, and saturation.</p><h3>Health checks</h3><p>This is the most basic form of monitoring that you should be implementing for your application. You are most likely already using health checks if you use a load-balancer or Kubernetes with liveness probes or Consul.</p><p>Implementing a health check is relatively simple. Your service should respond within a reasonable timeout. You can design a &#8220;ping&#8221; endpoint for an HTTP service that returns a 200 when called. For other protocols, you can similarly create a ping functionality. This checks the basic reachability of your application. Depending on your code, you may want to return success only when pings to required services are green.</p><h3>Infrastructure metrics</h3><p>Your second step should be to monitor infrastructure metrics exported by your platform. Collect and monitor the data collected from the OS, such as CPU utilization, memory utilization, IO utilization, and network utilization. Your cloud provider may provide some data as well. If you are using Docker or Kubernetes, then collect those metrics as well. Your monitoring system would likely have integrations that export this data with little effort. If you are using Prometheus, installing node_exporter will collect data from all machines in your infrastructure. Similarly, the Prometheus Kubernetes operator will fetch metrics from your Kubernetes</p><h3>Response codes and errors</h3><p>We want as few errors as possible. So we start counting them. A simple way of tracking these is by utilizing your framework and adding middleware to track requests and responses. For an HTTP micro-service, you may track total requests, success and failures, and the time taken by each request.</p><p>Depending on your monitoring system, you will want to assign different labels or tags to indicate the context of each event so that your monitoring system will allow you to drill down stats for each endpoint. Some tags that I frequently find adding are the URI, response status codes to requests, some shard indicators for sharded services (like country code, app version), etc. It depends on your requirements.</p><h3>Latency and timings</h3><p>Timing is a crucial indicator of performance &#8212; you certainly want to track this. You should monitor your request and response timings across your stack in a fine-grained manner as possible. The most accessible place to start is your application server. Similar to the request/response status monitoring, you can begin with a middleware that logs request and response times. Extend that to any proxies or load-balancers. You have to break down what time is spent on routing decisions, network latency, SSL, etc.</p><p>After you have a high-level picture, focus on the time spent by your supporting infrastructure and business logic. If you have some number crunching, you should track those timings separately. If you are hitting a database or another service, track their timings as well.</p><h3>Database performance metrics</h3><p>I have come across it multiple times &#8212; if something is slow, start checking with your database. Databases are massive beasts that do a lot of time-consuming IO and unfortunately don&#8217;t scale that well. However, your database likely exports a ton of data that can be very useful in debugging issues or predicting a future performance regression. Stats like table sizes, query timings, tuples read per query, tuples returned, etc., are some things you should be monitoring. Database monitoring is so vast that it deserves its own post altogether.</p><h3>Cache metrics</h3><p>Cache helps to speed up your services by keeping frequently-used data readily available. Monitor the cache size, hit rate, miss rate, evict rate, and cache miss ratio. The goal of a cache is to maximize the number of hits. The miss ratio should be as low as possible without risking the storage of slate data. If it is too high, you should reconfigure your cache and maybe even look at your caching strategy.</p><h3>Queue metrics</h3><p>If you are utilizing message queues to process jobs, you must monitor them. Producer and consumer counts, queue produce rates, consumption rates, lag, and failures are critical numbers to track. Setting alerts when any of these metrics are abnormal is also essential. It will provide an early warning to any potential failure.</p><p>Additionally, monitor the additional metrics published by your queue software (Kafka or RabbitMQ) related to your queue&#8217;s performance.</p><h3>Business Metrics</h3><p>Apart from the generic metrics we discussed above, your application will likely have custom metrics that you want to track. Things like the number of completed orders, transactions in your application are application-specific business-specific. These numbers give an assurance about how the overall system is performing. In case of an incident, these metrics also help to assess the real impact.</p><p>Such metrics vary on a case-to-case basis. It could be a counter or a gauge, or a timer. You can use the libraries provided by your metrics platform to expose these metrics from your application.</p><p>Having good metrics is essential for setting up a reliable architecture. Metrics form the source of truth of service alerts and performance numbers. It gives a direction of what to do next in order to improve, fine-tune and optimize the performance and stability of your services.</p>]]></content:encoded></item><item><title><![CDATA[Building Fast and Robust REST APIs using Conditional HTTP Requests]]></title><description><![CDATA[Conditional HTTP requests are one of the little-known but widely used features.]]></description><link>https://recursivefunction.blog/p/building-fast-and-robust-rest-apis-using-conditional-http-requests-e94298f337e2</link><guid isPermaLink="false">https://recursivefunction.blog/p/building-fast-and-robust-rest-apis-using-conditional-http-requests-e94298f337e2</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Fri, 07 May 2021 07:58:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dWCE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Conditional HTTP requests are one of the little-known but widely used features. An intelligent client can determine the status and content of HTTP requests without actually transmitting the body over the wire. With conditional HTTP requests, you can reduce the work done by your server by identifying whether existing data available with the client still valid, hence saving bandwidth.</p><blockquote><p>In conditional HTTP requests, the result of an HTTP request can be changed by comparing the affected resources with the value of a validator.</p></blockquote><p>REST uses HTTP extensively &#8212; HTTP verbs in combination with URLs have special implied meanings and status codes for errors. Conditional requests can be used to validate the content of a cache or determining whether the current document being edited is valid.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dWCE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dWCE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dWCE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!dWCE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dWCE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F44cca713-97fe-4d04-8650-133178c9f198_1024x682.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@casparrubin?utm_source=medium&amp;utm_medium=referral">Caspar Camille Rubin</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><h3>Making a conditional HTTP request</h3><p>Properly configured web servers and CDNs instruct browsers to cache most of the static content to avoid re-downloading big chunks of assets, improving page loading speed.</p><p>The easiest way to cache static content is to instruct the browser to cache something with the HTTP Expires header. It directs browsers, on an intermediate proxy (CDN Edge), to assume the downloaded content to be fresh until the given time and use the same when the subsequent identical request arrives.</p><p>This approach has some drawbacks, such as difficulty in changing content once cached by browsers, etc. These can be mitigated with well-known cache-busting techniques &#8212; the benefits outweigh the problems.</p><h4>Last-Modified, ETags and 304 Not Modified</h4><p>One of the ways to compromise between unconditional caching and not caching is validating cached content. By supplying a timestamp with the Last-Modified header, you can instruct browsers to send an If-Modified-Since header with the timestamp of the cached response. If the server has the same content, it responds with a 304 Not modified with an empty body, and the browser proceeds to use content from it&#8217;s cache. If the content has changed, the server sends a new body with a new Last-modified value with a 200 OK response and the new body.</p><p>Sometimes you don&#8217;t want to (or cannot) use timestamps to track the state of a resource. You can use a state-identifier such as a checksum (or anything) in a header called ETag which the browser uses with the If-None-Match header. If the value sent by the browser matches the Etag at the server, then the server sends a 304 Not modified status like before.</p><h3>Conditional requests in APIs</h3><p>Most API calls, however, are not cached by default. This is true for calls originating from the browser and server-to-server calls. Every time you repeat a GET request, the server fetches the resource from its data store and transfers the body over the network.</p><h4>Using the date headers &#8212; Expiry, Last-Modified and If-Modified-Since</h4><p>A good example would be an API GET /recommendations for new article recommendations. Your client polls the endpoint for new items to be available. For every call, your API server fetches content from the database, serializes them to JSON, and returns the result. The client then compares the response with the previous and detects changed items.</p><p>The recommendations are generated daily using a batch job at midnight, which means that the article recommendations are valid till midnight. You can attach an Expiry header to indicate that the content is fresh till midnight.</p><p>In some cases, you may not be able to determine the expiry in advance, such as the API GET /articles/&lt;id&gt; for returning the content of an article. In this case, you can return a Last-Modified header with the last updated timestamp from your database. For subsequent requests for the same article, the client sends an If-Modified-Since with the timestamp sent by the server earlier. The server responds with a 200 OK or with a response of 304 Not Modified depending on whether the article has been modified or not.</p><h4>Optimistic Locking with ETags/If-Match or If-Unmodified-Since</h4><p>Let&#8217;s continue with our example. We have an update API PATCH /articles/&lt;id&gt; which can be called simultaneously when multiple editors are working on the same article. Without any optimistic locking, we will have something called a <em>lost-update problem.</em></p><ol><li><p>A and B started editing the same article at 9:00 am. Both see identical contents.</p></li><li><p>A makes some changes and decides to save them at 9:15 am.</p></li><li><p>B takes a little longer and saves at 9:30 am.</p></li></ol><p>Without optimistic locking, the content of B will overwrite the changes of A, and the updates made by A will be lost.</p><p>To prevent this, you can use a version number as the ETag or serve the Last-Modified timestamp. While making the PATCH request to update the article, the client also sends If-Match or If-Unmodified-Since headers. The server now checks if there is any modification since the value specified those headers headers. The server either returns a 412 Precondition failed if there has been an update since 9:00 am. The client can decide to fetch the latest value and attempt to merge with the latest changes.</p><h3>Implementing conditional requests</h3><h4>The client</h4><p>If you are calling APIs from a browser, you do not have to set up anything special. Set the appropriate header, and the browser will do the heavy lifting for caching. If you need advanced control, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request/cache">you can control the cache behavior using fetch API</a>.</p><p>For server-side clients, it a bit complex. You need to configure a store, such as &#8220;Redis&#8221; to store the response and associated metadata. Before sending an HTTP request, you need to fetch the values for a given URL and add it to the headers. Libraries such as OKHttp contain pluggable providers to easily integrate such a mechanism.</p><h4>The server</h4><p>The server must understand these headers and perform the appropriate action. For example, in the GET /article/&lt;id&gt; API described above, your server can return 304 Not modified if the &#8220;last-modified&#8221; timestamp on the database is older than the timestamp sent an If-Modified-Since header.</p><p>To prevent &#8220;lost updates&#8221;, the server can use a version number as the Etag or use the If-Unmodified-Since header and assert it to be newer than the value in the your database. If the condition is violated, send a 412 Precondition Failed status. The client can then initiate appropriate conflict-resolution action such as displaying a diff and letting the user merge the differing versions of the article.</p><h3>Conclusion</h3><p>Leveraging HTTP conditional requests is an often ignored aspect of REST APIs. An adequately designed API can improve performance by reducing unnecessary network traffic. By implementing proper locking semantics, you can ensure APIs perform safe operations. This translates to thousands of dollars of money saved for a moderate-scale service.</p>]]></content:encoded></item><item><title><![CDATA[Full-text Search (FTS) with PostgreSQL and SQLAlchemy]]></title><description><![CDATA[If you are building a text-heavy web platform such as a blog or a documentation platform, your users expect a search functionality.]]></description><link>https://recursivefunction.blog/p/full-text-search-fts-with-postgresql-and-sqlalchemy-edc436330a0c</link><guid isPermaLink="false">https://recursivefunction.blog/p/full-text-search-fts-with-postgresql-and-sqlalchemy-edc436330a0c</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Mon, 26 Apr 2021 10:57:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JaJ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are building a text-heavy web platform such as a blog or a documentation platform, your users expect a search functionality. One of the simplest and effective way to build search experiences is to leverage the full-text search (FTS) capabilities exposed by your database.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JaJ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JaJ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JaJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JaJ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JaJ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6086d6af-bd8c-4570-8825-58e72def80bc_1024x682.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@loverna?utm_source=medium&amp;utm_medium=referral">Loverna Journey</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>In this blog, we will look at building a search system for a web app that indexes various videos. Each video has a title, description, and associated metadata. We want our system to be searchable over video title and description. We will be using SQLAlchemy as our ORM layer for the underlying PostgreSQL database.</p><h3>PostgreSQL and FTS</h3><p>PostgreSQL is a very capable RDBMS with batteries included. It comes with full-text functionality baked-in with tsquery and tsvector constructs.</p><p>Let&#8217;s build a table to store our videos.</p><pre><code>CREATE TABLE video ( id PRIMARY KEY BIGSERIAL, title VARCHAR(100) NOT NULL, description TEXT NOT NULL, published_at TIMESTAMP DEFAULT NOW() );</code></pre><p>The Video model will look like the following snippet.</p><pre><code>class Video(db.Model): id = db.Column(db.Integer(), primary_key=True, autoincrement=True) title = db.Column(db.String(255), nullable=False) description = db.Column(db.Text(), nullable=False) thumbnail_url = db.Column(db.String(2048), nullable=False) published_at = db.Column(db.DateTime(), nullable=False)</code></pre><p>To implement a basic search, we can use the SQL LIKE operator.</p><pre><code>select id, title, description, published_at FROM video WHERE title LIKE %<strong>term</strong>% OR description LIKE %<strong>term</strong>%</code></pre><p>To do that in SQLAlchemy,</p><pre><code>results = Video.query.filter(Video.title.like(<strong>term</strong>) | Video.description.like(f'%{<strong>term</strong>}%')).all()</code></pre><p>It&#8217;s a simple pattern matching. However, this is a start. For many purposes like username autocomplete, this is enough.</p><p>However, they have several shortcomings when we try to build a practical search solution.</p><ul><li><p>There is no language awareness. It is not practically feasible to search based on synonyms and derived words using patterns.</p></li><li><p>It is difficult to provide a ranking of search results, which renders them ineffective when thousands of matches are found.</p></li><li><p>They do not support indexes and hence are slow.</p></li></ul><h3>Enter tsquery and tsvector</h3><p>A tsvector is a sorted list of distinct <em>lexemes</em>, which have been <em>normalized</em> to merge different variants of the same word. It is a form optimized for search. It is used to store the target contents in a search system.</p><p>A tsquery is a set of <em>lexemes</em> meant to be searched against a group of vectors. They are used to represent search queries and can use boolean operators to combine the match effect.</p><p>Let&#8217;s make the above search a little more efficient.</p><pre><code>select id, title, description, published_at FROM video WHERE to_tsvector(title || ' ' || description) @@ to_tsquery(<strong>term</strong>)</code></pre><p>This does not have a direct SQLAlchemy equivalent, but you can perform full-text searches for a single column using matchfunction.</p><pre><code>results = Videos.query.filter(Video.description.match(<strong>term</strong>)).all()</code></pre><p>First, this query concatenates the title and description columns. It then converts the result to a tsvector and matches it against the search term. This gives good results, but given an average-sized database, such ad-hoc searches will not provide acceptable results.</p><h3>Inverted Indices, TSVector Columns, and Generated Columns</h3><p>To speed up the search, we need to pre-compute the tsvectors and store them in a gin index. A GiN or Generalized Inverted Index is a type of index used for full-text search. Like the &#8220;glossary&#8221; pages at the end of the book, an inverted index says which term is present in which documents.</p><p>PostgreSQL provides us with a column type called tsvector which we will be using to store our pre-computed search document. We will use generated columns which will automatically populate this column whenever we insert a new row. The contents will be to_tsvector('english', title || ' ' || description)</p><p>The english is essential as it enables PostgreSQL to generate a predictable tsvector every time. However, this also means that you will be limited in handling English documents only. To work around this limitation, you will need to build your own indexing mechanism, which is out of the scope of this article.</p><p>So our final alter table looks like this:</p><pre><code>ALTER TABLE video ADD COLUMN __ts_vector__ tsvector GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || description)) STORED;</code></pre><p>SQLAlchemy natively does not support tsvector so we need to create a custom type called TSVector, as shown in the following snippet.</p><pre><code>import sqlalchemy as sa from sqlalchemy.dialects.postgresql import TSVECTOR</code></pre><pre><code>class TSVector(sa.types.TypeDecorator): impl = TSVECTOR</code></pre><p>TypeDecorator is an abstract class with few abstract methods, your IDE and/or linter will warn about unimplemented methods, but you can safely ignore them. Since we will not be manipulating TSVector columns directly from Python, we can leave those undefined.</p><p>Now we will use the TSVector type in your Video model.</p><pre><code>from sqlalchemy import desc, Index</code></pre><pre><code>from .ts_vector import TSVector from ..db import db</code></pre><pre><code>class Video(db.Model): id = db.Column(db.Integer(), primary_key=True, autoincrement=True) title = db.Column(db.String(255), nullable=False) description = db.Column(db.Text(), nullable=False) thumbnail_url = db.Column(db.String(2048), nullable=False) published_at = db.Column(db.DateTime(), nullable=False)</code></pre><pre><code>    __ts_vector__ = db.Column(TSVector(),db.Computed( "to_tsvector('english', title || ' ' || description)", persisted=True))</code></pre><pre><code>    __table_args__ = (Index('ix_video___ts_vector__', __ts_vector__, postgresql_using='gin'),)</code></pre><p>We have defined a new column __ts_vector__, auto-generated from the title and description.</p><p>We can now use this with the match function to perform full-text searches. By default, it will be ordered by rank (relevance). However, you can add additional ORDER BY clauses as well.</p><pre><code>results = Video.query.filter(Video.__ts_vector__.match(<strong>term</strong>)).all()</code></pre><p>PostgreSQL full-text search is quite powerful and is sufficient for many use cases. It also allows further customization, such as weighted columns and keywords. Head over to the <a href="https://www.postgresql.org/docs/13/textsearch-intro.html">documentation</a> to learn more. For more advanced use-cases such as high-concurrency or analytics, you may need to invest in a dedicated search setup such as <a href="https://www.elastic.co/">Elasticsearch</a> or a SaaS such as <a href="https://www.algolia.com/">Algolia</a>.</p><p>You can find the demo project using PostgreSQL, SQLAlchemy and implementing full-text search at</p><p><a href="https://github.com/recrsn/video-gallery">recrsn/video-gallery</a></p>]]></content:encoded></item><item><title><![CDATA[An Introduction to Load Balancing]]></title><description><![CDATA[So, if you have been working on web services in this half of the decade, you must have come across the term &#8220;load balancing&#8221;.]]></description><link>https://recursivefunction.blog/p/an-introduction-to-load-balancing-30fe0da6722e</link><guid isPermaLink="false">https://recursivefunction.blog/p/an-introduction-to-load-balancing-30fe0da6722e</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Tue, 20 Apr 2021 08:20:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!OQ0n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, if you have been working on web services in this half of the decade, you must have come across the term &#8220;load balancing&#8221;. So what is load balancing? Let&#8217;s find out!</p><h3>What is load balancing?</h3><p>Load balancing, as the name suggests, is distributing work to multiple agents. In our case, we distribute incoming HTTP requests from browsers, apps or other servers between a pool of web servers that can serve this request.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OQ0n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OQ0n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 424w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 848w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 1272w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OQ0n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!OQ0n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 424w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 848w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 1272w, https://substackcdn.com/image/fetch/$s_!OQ0n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca528c5-65bb-40f2-a27d-e864cc401859_561x421.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Load balancing</figcaption></figure></div><h3>How does load balancing work?</h3><p>Load balancing works primarily in two ways:</p><ol><li><p>Client-side</p></li><li><p>Server-side</p></li></ol><h3>Client-side load balancing</h3><p>In client-side load balancing, we expect the client to distribute requests to each of the servers capable of serving the request. We provide the client with a list of target servers and the client chooses a server by some load balancing algorithm for each request it makes.</p><p>The simplest example is a DNS based load-balancing. Most websites (including Medium) use multiple web servers to handle the traffic. Every time you visit a website, the browser asks the DNS servers to provide a list of IP addresses of servers hosting the website&#8217;s content. If the DNS responds with multiple servers, the browser chooses one and sends the request. For subsequent requests, the browser would choose another server from the list of servers returned by the DNS earlier.</p><p>Client-side load balancing is also used in microservices utilizing a service discovery framework like Consul or a service mesh like Istio.</p><h4>Advantages</h4><ol><li><p>No additional infrastructure required</p></li><li><p>Simple to implement</p></li></ol><h4>Disadvantages</h4><ol><li><p>Unreliable, as we do not have control over external clients like browsers</p></li><li><p>There will be an uneven load distribution</p></li></ol><h3>Server-side load balancing</h3><p>In server-side load balancing, we run an additional server called a &#8220;Load Balancer&#8221;, a reverse-proxy that is responsible for distributing requests among application servers that can handle the request. Application servers register themselves with the load-balancer. Client requests first reach the load balancer and it routes to one of the registered application servers based on its configuration. Server-side load balancing is much more common. We use servers such as NGINX, Envoy, HA Proxy or services like Amazon ELB, Cloudflare etc for load balancing.</p><h4>Advantages</h4><ol><li><p>More control over load balancing</p></li></ol><h4>Disadvantages</h4><ol><li><p>Additional infrastructure requires cost and maintenance</p></li></ol><h3>Load balancing algorithms</h3><p>A load-balancing algorithm determines how requests are distributed among the origin servers.</p><h4>Round-robin</h4><p>This is the simplest load balancing algorithm. It assigns requests to servers in a pool one after the other. For example, in a pool of three servers, the first request will be sent to upstream 1, second to upstream 2, third to upstream 3 and again fourth to upstream 1 and so on.</p><h4>Least Connection</h4><p>This algorithm assigns requests based on which server has the least amount of active connections (load).</p><h4>Weighted</h4><p>This is a variant of any of the above load balancing algorithms with a slight modification. Every server known to the load balancer now has a weight associated with it. This weight can be based on how close or how powerful the application server is to the load balancer etc. The higher the weight, the higher number of requests get assigned to the origin server.</p><h4>Adaptive</h4><p>This algorithm adapts its routing based on metrics provided by the application servers usually involve monitoring response times or some form of telemetry from the application servers.</p><h4>Sticky</h4><p>Sometimes (esp legacy web-apps) web application maintains some server-side local state in the form of sessions. This forces you to route the subsequent requests to the server that handled the first request by the client. Such routing is called sticky load balancing. Avoid such architecture, it does not scale well.</p><p>This was a small intro about load balancing. The best way to learn is to spin-up a few servers and experiment. You can use ready-to-spin docker containers or virtual machines to deploy a copy of an app and a load-balancer and watch it in action.</p><p>If you liked this post, <a href="https://www.getrevue.co/profile/recrsn">subscribe to my newsletter</a> to never miss the next interesting round of tech stories.</p>]]></content:encoded></item><item><title><![CDATA[6 Gotchas of Hibernate You Wish You Knew Earlier]]></title><description><![CDATA[This post is about the confusions of a programmer familiar with many other ORMs was asked to use Hibernate for the first time.]]></description><link>https://recursivefunction.blog/p/6-gotchas-of-hibernate-you-wish-you-knew-earlier-ca774eaf2de</link><guid isPermaLink="false">https://recursivefunction.blog/p/6-gotchas-of-hibernate-you-wish-you-knew-earlier-ca774eaf2de</guid><dc:creator><![CDATA[Amitosh Swain Mahapatra]]></dc:creator><pubDate>Tue, 06 Apr 2021 04:02:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NAnm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post is about the confusions of a programmer familiar with many other ORMs was asked to use Hibernate for the first time.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NAnm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NAnm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NAnm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg" data-attrs="{&quot;src&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;An offline database&#8202;&#8212;&#8202;file archive&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="An offline database&#8202;&#8212;&#8202;file archive" title="An offline database&#8202;&#8212;&#8202;file archive" srcset="https://substackcdn.com/image/fetch/$s_!NAnm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 424w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 848w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!NAnm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F1c552e2b-e776-496f-a910-65b2abab0c05_1024x710.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@jankolar?utm_source=medium&amp;utm_medium=referral">Twitter: @jankolario</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Before you ask <em>&#8220;I thought you are a backend developer in working primarily on Java, how come you did not use Hibernate?&#8221;</em> Yes, I am a Java developer. I write Java code for a living daily and no, I don&#8217;t use Hibernate often. I create REST APIs as the part of my daily job, but I don&#8217;t use Spring or Hibernate often. My Company uses JDBi extensively so I never had the chance to fully learn how Hibernate works. I&#8217;m well aware of how Postgres and JDBC work, so on the first day when I jumped ship to learn Hibernate for fun, I was up for a shock!</p><h3>You need a private/protected default constructor</h3><p>Hibernate uses reflection to read and write classes to and from database rows. It uses proxy classes to facilitate lazy loading. For this, it needs to instantiate the class using a parameter-less constructor which must be protected or private. Without a parameterless constructor, you will meet a weird error message. IDEA has a warning, if enabled, will help you prevent these mistakes.</p><h3>Quoting table names</h3><p>Create an Entity called User, connect it with a Spring DaoUserDetailsProvider watch the world burn.</p><p>Because apparently, Hibernate doesn&#8217;t quote the table names while issuing queries when you try authenticating to your Spring application, your code will fail with org.hibernate.exception.SQLGrammarException: could not extract ResultSet. This means that your database gave a reply Hibernate didn&#8217;t expect. If you dig down the stack trace you will find the root exception &#8212; something similar to column user0.id doesn't exist.</p><p>This is cryptic indeed but the fix is simple, whenever you use a column or table that uses some SQL restricted keywords, don&#8217;t forget to quote it manually. For Postgres we use double quotes(&#8220;&#8221;). So go ahead a add a @Table("\"user\"") and everything will work!</p><h3>You have to create your own migrations</h3><p>Java has tools that will help you to run migrations but you must author them yourself. While I believe this is the safest approach as framework generated code may be sub-optimal, devoid of aesthetics and sometimes outright wrong (more on this later). If you have a dozen of new entities with dozen of new fields, authoring migrations manually quickly becomes a hassle.</p><p>There is no fix for it. Playing around with JPA properties and forcing Hibernate to dump the crude SQL to a file is a good start, you have to modify them to suite to your tastes. Add the following code to automatically dump what Hibernate is thinking to execute to update your schema. This file serves as a good starting point, and you will definitely want to review/edit these files and put it with a migration library such as Flyway.</p><pre><code>spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=<strong>src/main/resources/db/migrations/schema.sql</strong> spring.jpa.properties.javax.persistence.schema-generation.scripts.action=<strong>update</strong></code></pre><h3>Understand how two-way relationships behave</h3><p>If you are using a bidirectional one-to-many/many-to-one relationship to model your data, you must be careful. A bi-directional relationship is traditionally modeled as a field on the one-to-many side &#8212; called the owning side. The model with many-to-one relationship is called inverse-side. However, the database only stores data at one of the sides and the other side is populated by the ORM layer. Making changes on the owning side will not automatically make changes in the inverse side until you flush the data.</p><h3>Database ID generation is different</h3><p>Hibernate uses its own ID generator by default which uses a value from a table and computes IDs for each entity you insert. This means that your ID ranges will not be sequential or continuous. This is not an issue that affect applications (I use a UUID anyway), but is surprising. You can make hibernate to use your database&#8217;s native method of generating IDs though by specifying a different GenerationStrategy.</p><h3>Automatic code-generation for JPA repositories</h3><p>For JPA repositories, Hibernate can automatically generate SQL for you just from the function name and type parameters.</p><p>For example, if you have a method called Article findByCreatedBy(User u) Hibernate will generate a query like, SELECT * FROM article WHERE article.created_by_id = :user_id</p><p>But, as with any smart system that does things <em>&#8220;automagically&#8221;</em>, it may generate sub-par queries. Make sure to enable query logging to see what queries does Hibernate generate for you. If it is very suboptimal, write queries manually.</p><p>These are the few things that surprised me when I started using Hibernate. Hope this helps to remove some &#8220;surprises&#8221; from your experience with it.</p>]]></content:encoded></item></channel></rss>