<![CDATA[sahansera.dev RSS Feed]]>https://www.sahansera.devGatsbyJSSat, 21 Mar 2026 22:34:09 GMT<![CDATA[Introducing gh-weekly-updates - Automate Your Weekly GitHub Impact Summaries]]>https://www.sahansera.dev/introducing-gh-weekly-updates/https://www.sahansera.dev/introducing-gh-weekly-updates/Sun, 22 Feb 2026 00:00:00 GMT<p>If you’re anything like me, you’ve probably spent a Friday afternoon trying to remember everything you did that week. Maybe it’s for a standup, a 1:1 with your manager, or just to keep track of your own progress. You end up clicking through PRs, issues, and Slack threads, trying to piece together a coherent story. It’s tedious, and honestly, it’s time you could spend doing actual work.</p> <p>That’s why I built <a href="https://github.com/sahansera/gh-weekly-updates"><strong>gh-weekly-updates</strong></a> - a CLI tool that automatically collects your GitHub activity and generates a structured weekly summary using AI.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 22.499999999999996%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJElEQVR42n3Mz0oCYRSG8VEHQs0wldIQIqKSELqFbqVdhYkOqaMJkVborgipRRuDMrQpHMucMaUgoisIohZRF/L0KfRn5eLH+3LO4UjhHYPFQgX/VpeDh3f279/Y675Sevzg8OmT4+cvEvoL1vUWFkFOtBhOm9hVE1emjVN0h2rgzpoMpQwkX1pnTNWQ4zqz2ybTOaMvtHvHfKFDuNhhKmdiidV/yUodm7iXFR2bIMebfdZ4AymQvWEmb7JQaDO52SSUN5gTgmLuzzSwrFSRVoXoxZ+1n9SEKiMby0zklnAki0iu6Dnj0RMCsVP8QlDpOcMXKeOMVMSz2kC2SA23eoQ3U8KulJE8ySt8CQ1v4lLkfxq93WiqPpAnpeNTb/GqLdGv+QbwwOKzWswmrwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="introducing gh weekly updates 1" title="" src="/static/d4ecc35e8a1548fd7f4c85099433207a/5a190/introducing-gh-weekly-updates-1.png" srcset="/static/d4ecc35e8a1548fd7f4c85099433207a/772e8/introducing-gh-weekly-updates-1.png 200w, /static/d4ecc35e8a1548fd7f4c85099433207a/e17e5/introducing-gh-weekly-updates-1.png 400w, /static/d4ecc35e8a1548fd7f4c85099433207a/5a190/introducing-gh-weekly-updates-1.png 800w, /static/d4ecc35e8a1548fd7f4c85099433207a/c1b63/introducing-gh-weekly-updates-1.png 1200w, /static/d4ecc35e8a1548fd7f4c85099433207a/29007/introducing-gh-weekly-updates-1.png 1600w, /static/d4ecc35e8a1548fd7f4c85099433207a/c2d13/introducing-gh-weekly-updates-1.png 2560w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>💡 <strong>GitHub repo</strong>: <a href="https://github.com/sahansera/gh-weekly-updates">github.com/sahansera/gh-weekly-updates</a>. It’s open source and available on PyPI!</p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>The Problem</h2> <p>As engineers, we’re constantly shipping code, reviewing PRs, filing issues, and jumping into discussions. But when it comes time to reflect on the week, all that context is scattered across repos. I wanted something that could:</p> <ol> <li>Pull all my GitHub activity into one place</li> <li>Summarise it in a way that highlights what actually matters</li> <li>Run on a schedule so I don’t have to think about it</li> </ol> <p>I couldn’t find anything that did exactly this, so I built it.</p> <h2>What It Does</h2> <p><code class="language-text">gh-weekly-updates</code> connects to the GitHub API, collects your activity for a given period, and sends it to an AI model (via <a href="https://github.com/marketplace/models">GitHub Models</a>) to produce a structured Markdown summary.</p> <p>Here’s what it picks up:</p> <ul> <li><strong>Pull requests</strong> you authored (with merge status, additions/deletions, changed files)</li> <li><strong>Pull requests</strong> you reviewed</li> <li><strong>Issues</strong> you created</li> <li><strong>Issue comments</strong> you left</li> <li><strong>Discussions</strong> you started or participated in</li> </ul> <p>The output is grouped by project or theme and structured into sections like Wins, Challenges, and What’s Next. You can also customise the prompt to match whatever format your team uses.</p> <h2>Getting Started</h2> <p>It’s a Python CLI tool, so you can install it with pip:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">pip <span class="token function">install</span> gh-weekly-updates</code></pre></div> <p>Then just run it:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># If you're already logged in with the GitHub CLI</span> gh auth login gh-weekly-updates</code></pre></div> <p>That’s it. It will auto-discover repos you contributed to in the past week and generate a summary.</p> <p>You can also point it at specific repos and date ranges:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">gh-weekly-updates <span class="token parameter variable">--since</span> <span class="token number">2026</span>-02-09 <span class="token parameter variable">--until</span> <span class="token number">2026</span>-02-16 <span class="token parameter variable">--repos</span> my-org/my-repo</code></pre></div> <h2>Configuration</h2> <p>For more control, you can create a <code class="language-text">config.yaml</code>:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">org</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>org <span class="token key atrule">repos</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> my<span class="token punctuation">-</span>org/api<span class="token punctuation">-</span>service <span class="token punctuation">-</span> my<span class="token punctuation">-</span>org/web<span class="token punctuation">-</span>app <span class="token key atrule">model</span><span class="token punctuation">:</span> openai/gpt<span class="token punctuation">-</span><span class="token number">4.1</span> <span class="token comment"># Automatically push the summary to a repo</span> <span class="token key atrule">push_repo</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>user/my<span class="token punctuation">-</span>weekly<span class="token punctuation">-</span>updates <span class="token comment"># Customise the AI prompt</span> <span class="token key atrule">prompt_file</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>prompt.txt</code></pre></div> <p>The config supports everything from repo lists to custom prompts. You can even swap out the AI model if you have a preference.</p> <h2>Running on a Schedule with GitHub Actions</h2> <p>This is where it gets really useful. You can set up a GitHub Actions workflow to run it every Monday morning and push the summary to a repo automatically:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Weekly Summary <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token key atrule">schedule</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">cron</span><span class="token punctuation">:</span> <span class="token string">'0 9 * * 1'</span> <span class="token comment"># Every Monday at 9am UTC</span> <span class="token key atrule">workflow_dispatch</span><span class="token punctuation">:</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">summarise</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>python@v5 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">python-version</span><span class="token punctuation">:</span> <span class="token string">'3.12'</span> <span class="token punctuation">-</span> <span class="token key atrule">run</span><span class="token punctuation">:</span> pip install gh<span class="token punctuation">-</span>weekly<span class="token punctuation">-</span>updates <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Generate summary <span class="token key atrule">env</span><span class="token punctuation">:</span> <span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.GH_PAT <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token comment"># must be named GITHUB_TOKEN</span> <span class="token key atrule">run</span><span class="token punctuation">:</span> gh<span class="token punctuation">-</span>weekly<span class="token punctuation">-</span>updates <span class="token punctuation">-</span><span class="token punctuation">-</span>config config.yaml</code></pre></div> <p>Now every Monday, you get a fresh summary committed to your repo. No manual effort required.</p> <h2>Custom Prompts</h2> <p>The default prompt produces a summary with Wins, Challenges, and What’s Next sections. But you can tailor it to your needs. For example, if your team does impact-style updates, you might want sections like Strategic Influence or Next Steps.</p> <p>Just create a text file with your prompt and reference it in your config:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">prompt_file</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>custom<span class="token punctuation">-</span>prompt.txt</code></pre></div> <p>The prompt receives all your raw activity data as context, so you can shape the output however you like.</p> <h2>Why Open Source?</h2> <p>I initially built this for myself to automate my own weekly updates at work. But I figured other engineers probably have the same problem, so I cleaned it up and open-sourced it. The tool is intentionally simple - it does one thing and tries to do it well.</p> <p>If you find it useful, give it a ⭐ on <a href="https://github.com/sahansera/gh-weekly-updates">GitHub</a>. And if you have ideas for improvements, PRs and issues are always welcome!</p> <h2>What’s Next</h2> <p>A few things I’m thinking about for future releases:</p> <ul> <li><strong>More activity sources</strong>: Picking up commit messages, release notes, and code review comments</li> <li><strong>Multiple output formats</strong>: Slack messages, email digests, Notion pages</li> <li><strong>Team summaries</strong>: Aggregate activity across a whole team, not just one person</li> </ul> <p>If any of these sound interesting to you, feel free to open an issue or start a discussion on the repo.</p> <h2>Links</h2> <ul> <li><strong>GitHub</strong>: <a href="https://github.com/sahansera/gh-weekly-updates">github.com/sahansera/gh-weekly-updates</a></li> <li><strong>PyPI</strong>: <a href="https://pypi.org/project/gh-weekly-updates/">pypi.org/project/gh-weekly-updates</a></li> <li><strong>GitHub Models</strong>: <a href="https://github.com/marketplace/models">github.com/marketplace/models</a></li> </ul> <p>Thanks for reading! If you have any questions, feel free to reach out on <a href="https://twitter.com/_SahanSera">Twitter</a> or drop a comment below. 🤗</p><![CDATA[Deploying GitHub Self-Hosted Runners on Your Home Kubernetes Cluster with ARC]]>https://www.sahansera.dev/github-self-hosted-runners-on-kubernetes-home-lab/https://www.sahansera.dev/github-self-hosted-runners-on-kubernetes-home-lab/Sun, 27 Jul 2025 00:00:00 GMT<p>If you followed my last post, <a href="https://sahansera.dev/building-home-lab-kubernetes-cluster-old-hardware-k3s/">Building a Home Lab Kubernetes Cluster with Old Hardware and k3s</a>, you now have a proper x86 Kubernetes cluster humming away on your old laptops. So, what’s next? Time to put that cluster to work—let’s run GitHub Actions jobs on your own hardware!</p> <p>Why? Because self-hosting your runners is faster, gives you full control (no GitHub minutes limit!), and lets you run bigger jobs (CI, builds, ML, you name it) on your home infra. And with <a href="https://github.com/actions/actions-runner-controller">Actions Runner Controller (ARC)</a>, managing runners at scale on Kubernetes is surprisingly easy.</p> <p>Here’s how to set it all up.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>What is ARC and Why Should You Care?</h2> <p><a href="https://github.com/actions/actions-runner-controller">ARC (Actions Runner Controller) </a>is an open-source Kubernetes operator from GitHub. It spins up and manages GitHub Actions runners as Kubernetes pods—no more manually registering runners, no more pets, just cattle. Runners auto-scale up and down as jobs arrive. It’s perfect for CI/CD, especially on clusters you own.</p> <p>Here’s a high level view of how it works under the hood</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9klEQVR42oVTTW8TMRDd//8rOCBxABUODYgqRRWCIkRVggLtBSiQJpv9sHfXu157E9BjnrMOqXrgMBrP2H7z5nmcZN8eQf06hhs2OFu+xXT5GrZ32G7+4N3tBZ5+neBWZ3g+LzGZ55j9VHhwvsDFjcL0usTRLEfXe5xcFXhyuUbSmArrLBXLscrWKGuNqm5QNybEi3SJUmvopkVZSd50UHWLSnxZGcl3GDZbtNbDiCV+2IYKBLDWoW3loNLB2taGmHvGtPDDgEE6offR+9065hM3Jjbb38HHmNY7D13VaASMoG1n0dk+5BhzP4CNd3s/AkaQw/U+HouxLSNsyZSScM1CWlehgBY5qP1/AXmJzA5ZBuDRCFaUKhS5BxiNbGhcByYjG+oaH8yMWrPAoVz3GFIjahONcdSORhbRK70rEIsHwENWTLByvHwIEPMxZjEyzYvyjkRJPEwW0SIY85zP2CZbVOMjMOaaUtxp2faDDKXbV+66f8w4h1lW7LQTEKVUeIB6BNTSspWzBOrlvPcy2M41cK7dPYCT13ScfA6qDLwXlt6G+WrsgFp+Aovnugm/JhO/SAtkeSE+hzY9kvz7Y/nLk3DpbHWOV6s3+7/8IZ3h+McJ0qbEyy8KLz7nmC8qPHy/wuWNxul1gWef5C+LnlNZH31c4y+WYN7Q5u5YkgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ARC Architecture" title="" src="/static/18a16af479784ba8b25f1540c9e2dd7b/5a190/arc-self-hosted-github-runners-k8s-3.png" srcset="/static/18a16af479784ba8b25f1540c9e2dd7b/772e8/arc-self-hosted-github-runners-k8s-3.png 200w, /static/18a16af479784ba8b25f1540c9e2dd7b/e17e5/arc-self-hosted-github-runners-k8s-3.png 400w, /static/18a16af479784ba8b25f1540c9e2dd7b/5a190/arc-self-hosted-github-runners-k8s-3.png 800w, /static/18a16af479784ba8b25f1540c9e2dd7b/c1b63/arc-self-hosted-github-runners-k8s-3.png 1200w, /static/18a16af479784ba8b25f1540c9e2dd7b/29007/arc-self-hosted-github-runners-k8s-3.png 1600w, /static/18a16af479784ba8b25f1540c9e2dd7b/02607/arc-self-hosted-github-runners-k8s-3.png 3338w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Prerequisites</h2> <ul> <li>Working Kubernetes cluster (see <a href="https://sahansera.dev/building-home-lab-kubernetes-cluster-old-hardware-k3s/">previous post</a>)</li> <li><code class="language-text">kubectl</code> and <a href="https://helm.sh/"><code class="language-text">helm</code></a> installed on your machine</li> <li>A GitHub Personal Access Token (PAT) with <code class="language-text">repo</code> and <code class="language-text">admin:org</code> scopes</li> </ul> <h2>1️⃣ Pre-Setup: Quick Checks</h2> <p>Make sure you have what you need:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">which</span> helm kubectl version <span class="token parameter variable">--client</span> helm list <span class="token parameter variable">-A</span></code></pre></div> <p>If those commands work, you’re good to go.</p> <h2>2️⃣ Install ARC Controller</h2> <p>Let’s install the ARC controller into your control plane namespace:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">NAMESPACE</span><span class="token operator">=</span><span class="token string">"actions-runner-controller"</span> helm <span class="token function">install</span> arc <span class="token punctuation">\</span> <span class="token parameter variable">--namespace</span> <span class="token string">"<span class="token variable">${NAMESPACE}</span>"</span> <span class="token punctuation">\</span> --create-namespace <span class="token punctuation">\</span> oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller</code></pre></div> <p>This deploys the controller which manages your runners.</p> <h2>3️⃣ Deploy a Runner Scale Set</h2> <p>Time to create the runners that will actually do the work. Replace the example GitHub URL and PAT with your details:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">INSTALLATION_NAME</span><span class="token operator">=</span><span class="token string">"arc-runner-set"</span> <span class="token assign-left variable">NAMESPACE</span><span class="token operator">=</span><span class="token string">"arc-runners"</span> <span class="token assign-left variable">GITHUB_CONFIG_URL</span><span class="token operator">=</span><span class="token string">"https://github.com/youruser/yourrepo"</span> <span class="token assign-left variable">GITHUB_PAT</span><span class="token operator">=</span><span class="token string">"ghp_123456..."</span> helm <span class="token function">install</span> <span class="token string">"<span class="token variable">${INSTALLATION_NAME}</span>"</span> <span class="token punctuation">\</span> <span class="token parameter variable">--namespace</span> <span class="token string">"<span class="token variable">${NAMESPACE}</span>"</span> <span class="token punctuation">\</span> --create-namespace <span class="token punctuation">\</span> <span class="token parameter variable">--set</span> <span class="token assign-left variable">githubConfigUrl</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${GITHUB_CONFIG_URL}</span>"</span> <span class="token punctuation">\</span> <span class="token parameter variable">--set</span> <span class="token assign-left variable">githubConfigSecret.github_token</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${GITHUB_PAT}</span>"</span> <span class="token punctuation">\</span> oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set</code></pre></div> <ul> <li><code class="language-text">GITHUB_CONFIG_URL</code>: The repo or org you want to run jobs for.</li> <li><code class="language-text">GITHUB_PAT</code>: Your Personal Access Token.</li> </ul> <h2>4️⃣ Check That It’s Working</h2> <p>Verify the controller and runner pods are up:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># Controller</span> kubectl get pods <span class="token parameter variable">-n</span> actions-runner-controller <span class="token comment"># Runners</span> kubectl get pods <span class="token parameter variable">-n</span> arc-runners <span class="token comment"># Runner set status</span> kubectl get AutoscalingRunnerSet <span class="token parameter variable">-A</span> kubectl describe AutoscalingRunnerSet arc-runner-set <span class="token parameter variable">-n</span> arc-runners</code></pre></div> <p>You should see your runners show up as pods. If you trigger a workflow in your GitHub repo, you’ll see a pod spin up, do the job, and then shut down—magic.</p> <h2>5️⃣ Testing It Out</h2> <p>Here’s the fun part. Create a simple GitHub Actions workflow in your repo to test the runners:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Test ARC Runners <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>push<span class="token punctuation">]</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> arc<span class="token punctuation">-</span>runners <span class="token comment"># This tells GitHub to use your self-hosted runners. Use the NAMESPACE name you defined in step 3.</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout code <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Run a script <span class="token key atrule">run</span><span class="token punctuation">:</span> echo "Hello from ARC Runner<span class="token tag">!</span>" <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> List files <span class="token key atrule">run</span><span class="token punctuation">:</span> ls <span class="token punctuation">-</span>la </code></pre></div> <p>Here’s ci.yaml from my githubstats repo if you need a working example: <a href="https://github.com/sahansera/githubstats/blob/main/.github/workflows/ci.yml">ci.yaml</a></p> <p>Make a commit to trigger the workflow. You should see the runner pod spin up, execute the job, and then terminate.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 456px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 86%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACBElEQVR42qWU6W7aQBSFeYVmYScFjG1sQ4wXjBcgGAImqd2WhIpKTdT3f4rTuZdWLWAUKf1xwHhmvnvuwhSqLQ1OOIc/TdBUTXwoN3Fd7+CqJr1LhVpbhxstEMYPaGsWLiqt/wMWGzLKH1XUJQOVZheXAnhZbb8fSG7qbeO3emhIfdZ1XcZFdQ9/SwfAUkPBMFzi8/NPxKsN1tl3/k3QWkt/U5Wmdggs36hQeyPIXQ+S6qKlWJC6DksxPHT7/onovaLv12TdPXBZoPrJqgd3M0OYrbFYbjFbfkWS7jAar8SBIQf8V5YXw/EX6FljDnx1ABQOKVovijAI7+CFCaxRDHs0x60zZTfHQAKRdDNERzsHDCL0vAiGGTGIgNptkAskkDGIOOVT4J+Un2aYbB6RfXnFPHkSzdkJaHySMkHc4F6UI0FfBJa69qlD2qhooimKw92VVAdt1c6tH6mjueyM3NPzaVMUD94uxnSbIs1esHz8hnTzA/5kfd6hWOvbkzMOjRE0K4A+CLg2VCM6SN95Dv+OUJBfQ0rXXIxhz2Zw/SU8MS7jOOVDeUByZrp3HFDKBYqhtj9N4T+scJ9sMZlnWKyf2W1ely0xAY5IO3cOSzeK2OSJTg+5KU1lwHWhf4xiDHntWDI3xeVnSvmgKfRRFBdEsbEXBSiJG4jE7/PEe/frx1fdL2/M9kI5gR94AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="arc self hosted github runners k8s 1" title="" src="/static/dad2eab36f13b0443c6ab2c6ef038b6b/7f664/arc-self-hosted-github-runners-k8s-1.png" srcset="/static/dad2eab36f13b0443c6ab2c6ef038b6b/772e8/arc-self-hosted-github-runners-k8s-1.png 200w, /static/dad2eab36f13b0443c6ab2c6ef038b6b/e17e5/arc-self-hosted-github-runners-k8s-1.png 400w, /static/dad2eab36f13b0443c6ab2c6ef038b6b/7f664/arc-self-hosted-github-runners-k8s-1.png 456w" sizes="(max-width: 456px) 100vw, 456px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"> <i>Here's the workflow running on my ARC self-hosted runner</i> </center> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>5️⃣ Monitoring (Bonus: Grafana)</h2> <p>Want to geek out and monitor your runners? If you’ve set up Prometheus/Grafana (see my upcoming post if not!), you can:</p> <ul> <li>Check pod CPU/memory usage</li> <li>Track how many runners are running</li> <li>See logs for each pod</li> </ul> <p>Handy queries for Grafana dashboards:</p> <div class="gatsby-highlight" data-language="promql"><pre class="language-promql"><code class="language-promql"><span class="token comment"># Number of ARC runner pods</span> kube_pod_status_phase<span class="token context-labels"><span class="token punctuation">{</span><span class="token label-key attr-name">namespace</span><span class="token punctuation">=</span><span class="token label-value attr-value">"arc-runners"</span><span class="token punctuation">,</span> <span class="token label-key attr-name">phase</span><span class="token punctuation">=</span><span class="token label-value attr-value">"Running"</span><span class="token punctuation">}</span></span> <span class="token comment"># Pod CPU usage</span> <span class="token function">rate</span><span class="token punctuation">(</span>container_cpu_usage_seconds_total<span class="token context-labels"><span class="token punctuation">{</span><span class="token label-key attr-name">namespace</span><span class="token punctuation">=</span><span class="token label-value attr-value">"arc-runners"</span><span class="token punctuation">}</span></span><span class="token context-range"><span class="token punctuation">[</span><span class="token range-duration number">5m</span><span class="token punctuation">]</span></span><span class="token punctuation">)</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 24%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA5UlEQVR42m2QWW7DMAxEdYcktmVZmyXvq5SlzW/uf6cprQBFgfTjgeSI4JBiyjSolIMg8lL9S8YlskIi5+oXXmrkpHFhoLRH0weM8QXGhcbpwnG+lDhn4oMT6QXXyfQY+lc/6tqP0KalOKEyI5hQNQoa+kGlUUpDzR7DFLDsD9pUkWbBK4OS4In3FceWeSHAlO3I3UOSi6Zc0hcoS45NC995tH1PeZ+GHu+m7tNW1g2ptr6FcR1cM5CJBYu3J7b4lVj3O3aK+/X7DeWBYjvMGOeQeqY1Jo56Xq+Ylohlu2ELDzg/4AcvTotTK5yzHQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="arc self hosted github runners k8s 2" title="" src="/static/db87f26e2e182a69cd493ed3209dbe4a/5a190/arc-self-hosted-github-runners-k8s-2.png" srcset="/static/db87f26e2e182a69cd493ed3209dbe4a/772e8/arc-self-hosted-github-runners-k8s-2.png 200w, /static/db87f26e2e182a69cd493ed3209dbe4a/e17e5/arc-self-hosted-github-runners-k8s-2.png 400w, /static/db87f26e2e182a69cd493ed3209dbe4a/5a190/arc-self-hosted-github-runners-k8s-2.png 800w, /static/db87f26e2e182a69cd493ed3209dbe4a/c1b63/arc-self-hosted-github-runners-k8s-2.png 1200w, /static/db87f26e2e182a69cd493ed3209dbe4a/29007/arc-self-hosted-github-runners-k8s-2.png 1600w, /static/db87f26e2e182a69cd493ed3209dbe4a/75d38/arc-self-hosted-github-runners-k8s-2.png 1851w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"> <i>Here's what the workflow looks like in action</i> </center> <h2>6️⃣ Useful Commands for Day-to-Day Ops</h2> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># Watch runner scaling in real-time</span> kubectl get AutoscalingRunnerSet <span class="token parameter variable">-n</span> arc-runners <span class="token parameter variable">-w</span> <span class="token comment"># See events and troubleshoot</span> kubectl get events <span class="token parameter variable">-n</span> arc-runners --sort-by<span class="token operator">=</span>.metadata.creationTimestamp <span class="token comment"># Pod logs (for a specific runner)</span> kubectl logs <span class="token parameter variable">-n</span> arc-runners <span class="token operator">&lt;</span>pod-name<span class="token operator">></span></code></pre></div> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>Troubleshooting Tips</h2> <ul> <li><strong>Runner not connecting?</strong> Double-check your PAT and network access.</li> <li><strong>Pods stuck or crash-looping?</strong> Check logs for clues and make sure your cluster has enough resources.</li> <li><strong>Can’t see runners in GitHub?</strong> Make sure the config URL matches your repo/org and the PAT has correct scopes.</li> </ul> <hr> <h2>That’s It! You’re Running GitHub Actions on Your Own Cluster</h2> <p>You now have GitHub Actions jobs running <em>at home</em> on your cluster, scaling up and down automatically. No more slow or limited runners. Your home lab just levelled up—CI/CD, builds, ML, you name it.</p> <p>Stay tuned for my next post where I’ll show you how to get beautiful observability dashboards and set up alerting for your home cluster.<br> Happy automating!</p> <hr> <p><em>Questions? Want to show off your setup? Ping me on <a href="https://twitter.com/_SahanSera">X (Twitter)</a> or drop a comment below!</em></p><![CDATA[Building a Home Lab Kubernetes Cluster with Old Hardware and k3s]]>https://www.sahansera.dev/building-home-lab-kubernetes-cluster-old-hardware-k3s/https://www.sahansera.dev/building-home-lab-kubernetes-cluster-old-hardware-k3s/Sat, 26 Jul 2025 00:00:00 GMT<p>If you’ve read my previous <a href="https://sahansera.dev/building-your-own-private-kubernetes-cluster-on-a-raspberry-pi-4-with-k3s/">post</a> about building a Raspberry Pi k3s cluster, you know I’m a huge fan of home labs. There’s something uniquely satisfying about getting distributed systems running on a bunch of hardware you already own. This time, though, I wanted something a bit more powerful-a cluster that could handle not just learning and tinkering, but also heavier dev, CI/CD, and even ML workloads.</p> <p>And as it turns out, there’s a ton you can do with a handful of old laptops, a simple switch, and Ubuntu Server. If you’re thinking about upgrading your home cluster or want to avoid vendor lock-in, this one’s for you.</p> <hr> <h2>Why Move From Raspberry Pi to x86?</h2> <p>Don’t get me wrong-RPi clusters are awesome for learning, hacking, and even some light home automation. But if you’ve ever tried running real dev pipelines, CI/CD, or any data-heavy ML stuff, you’ll hit those limits <em>fast</em>. Plus, WiFi can get a bit flaky when you’re trying to keep nodes connected under load.</p> <p>I had a few spare laptops sitting around, and it made perfect sense to give them a second life and push them to their limits. Bonus: with Ethernet and a decent switch, you get much more reliable connectivity.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Network Setup: Keep It Simple (and Reliable)</h2> <p>For this build, I kept things straightforward: all nodes are wired to a simple network switch. This gives much better performance and reliability compared to WiFi, but honestly, you could still pull this off over wireless if needed.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABe2MnLcMgX//EABsQAQABBQEAAAAAAAAAAAAAAAEAAgQUMUFE/9oACAEBAAEFAqUmSELhT0dNf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAECAQE/AUf/xAAbEAACAQUAAAAAAAAAAAAAAAAAAgEQIUFCkf/aAAgBAQAGPwLBduGwpFP/xAAdEAABAgcAAAAAAAAAAAAAAAARAAEQITFBYYGh/9oACAEBAAE/IRjUbJ3c4YIwOgT941//2gAMAwEAAgADAAAAEJT/AP/EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/EKf/xAAXEQEAAwAAAAAAAAAAAAAAAAAAESFB/9oACAECAQE/EIW1/8QAHBABAAICAwEAAAAAAAAAAAAAAQARITFBYaHw/9oACAEBAAE/EEtVfJV04wQla1eDyXawrVNO8bn09kPVmif/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Network Switch" title="" src="/static/d0d19d5cc033ec8bb8d3c4fccc23f08c/4b190/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg" srcset="/static/d0d19d5cc033ec8bb8d3c4fccc23f08c/e07e9/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 200w, /static/d0d19d5cc033ec8bb8d3c4fccc23f08c/066f9/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 400w, /static/d0d19d5cc033ec8bb8d3c4fccc23f08c/4b190/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 800w, /static/d0d19d5cc033ec8bb8d3c4fccc23f08c/e5166/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 1200w, /static/d0d19d5cc033ec8bb8d3c4fccc23f08c/b17f8/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 1600w, /static/d0d19d5cc033ec8bb8d3c4fccc23f08c/d2602/building-home-lab-kubernetes-cluster-old-hardware-k3s-2.jpg 4032w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"> <i>My network switch - how it all started 🛜</i> </center> <p><strong>A quick tip:</strong></p> <ul> <li>Reserve static IPs for each node using your router’s DHCP reservations.</li> <li>This makes node management, SSH, and Kubernetes networking so much easier.</li> <li>Check your DHCP table and make sure every machine has a unique, predictable IP address.</li> </ul> <hr> <h2>Getting Ubuntu Server Set Up on Each Node</h2> <p>Here’s a high level diagram of what my setup looks like:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 42%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABmklEQVR42m2SX2/TMBTF89GREHwAkDbxhMYDbN0LBSpNYgxNmqY+IKF2KxqDLc1om6ZpnDip46RJ88NJmLSiXen6+u/xOce2MFFVVd20+V9UtHPVv7WiKFBKkec5qandi4CXJzYvvtzSHUksHonqIbDplnlBtanYmPkwDJvUWpPEkt5lwKtTh92vNp9GUQsYTacEgwFKiG1AU35Lm5PwjIF/iQwlZVluXZ5nCq1Ek7lOsIosY3F8zO3BAct+nzhJCERAIhMc945D7yN74w77bpebhU0SxYhQkKbKVLPXrN18e8rP/hNC560BXK8R5+f4vR5yOKQ07GqfNuUGqSTv50e8dvbpuB+YRW7DOsuzhmmWl8TeKdOrPSZXb4hmn7HW5nBspEaOgzLs6vFDDz3lM1A/mOsFq3hFqtMtyVor8zitZJ2usGqDfd9HRFFTQwN+71PNNPADvMmcpbckM/ZIKQmCZSM5kinC6fBn+Jzx92fEk3dY92yqR75MDThzZ1z/usYe2wakZVdfWJZFIzmcHjEZ7eBc7CDuuvwFqRVZ+rxmjRQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Home Lab Setup Diagram" title="" src="/static/db96de884d4862ccda26c8496b0b079c/5a190/home-lab-setup-diagram.png" srcset="/static/db96de884d4862ccda26c8496b0b079c/772e8/home-lab-setup-diagram.png 200w, /static/db96de884d4862ccda26c8496b0b079c/e17e5/home-lab-setup-diagram.png 400w, /static/db96de884d4862ccda26c8496b0b079c/5a190/home-lab-setup-diagram.png 800w, /static/db96de884d4862ccda26c8496b0b079c/c1b63/home-lab-setup-diagram.png 1200w, /static/db96de884d4862ccda26c8496b0b079c/29007/home-lab-setup-diagram.png 1600w, /static/db96de884d4862ccda26c8496b0b079c/40e0a/home-lab-setup-diagram.png 2261w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s talk about prepping each machine. My old laptops had a new lease on life running Ubuntu Server. I recommend Ubuntu Server LTS for its simplicity and compatibility. If you’re using VMs, just make sure to set the NIC to bridged mode so each VM acts as a full member of your home LAN.</p> <p>Here’s my go-to checklist for each node:</p> <ol> <li> <p><strong>Update the system and install SSH:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> update <span class="token operator">&amp;&amp;</span> <span class="token function">sudo</span> <span class="token function">apt</span> upgrade <span class="token parameter variable">-y</span> <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> openssh-server <span class="token parameter variable">-y</span> <span class="token function">sudo</span> systemctl <span class="token builtin class-name">enable</span> <span class="token function">ssh</span> <span class="token function">sudo</span> systemctl start <span class="token function">ssh</span></code></pre></div> </li> <li> <p><strong>Set a memorable hostname:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> hostnamectl set-hostname master <span class="token comment"># or worker-1, etc.</span></code></pre></div> </li> <li> <p><strong>(Optional) Update /etc/hosts for local resolution:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">nano</span> /etc/hosts</code></pre></div> <p>Change any lines like <code class="language-text">127.0.1.1 old-hostname</code> to your new hostname.</p> </li> <li> <p><strong>Find your node’s IP address:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">ip</span> a</code></pre></div> <p>Use this IP for your DHCP reservation so it always stays the same.</p> </li> <li> <p><strong>Test SSH from another machine:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> <span class="token operator">&lt;</span>username<span class="token operator">></span>@<span class="token operator">&lt;</span>node-ip<span class="token operator">></span></code></pre></div> </li> </ol> <hr> <h2>Installing k3s: Lightweight, Powerful, Familiar</h2> <p>This process feels pretty magical the first time you see it all come together. I stuck with <a href="https://k3s.io/">k3s</a>-lightweight and perfect for home or edge clusters.</p> <h3>On the Control Plane Node (“master”)</h3> <ol> <li> <p><strong>Install k3s:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-sfL</span> https://get.k3s.io <span class="token operator">|</span> <span class="token function">sh</span> -</code></pre></div> </li> <li> <p><strong>Check that k3s is running:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> systemctl status k3s</code></pre></div> </li> <li> <p><strong>Get your cluster join token:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">cat</span> /var/lib/rancher/k3s/server/node-token</code></pre></div> <p>Copy this somewhere safe-you’ll need it for your worker nodes.</p> </li> <li> <p><strong>Get the node’s LAN IP:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">ip</span> a</code></pre></div> <p>Let’s say your master node IP is <code class="language-text">192.168.0.200</code>.</p> </li> <li> <p><strong>Check your cluster status:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl get nodes</code></pre></div> </li> </ol> <p><em>If you ever want to reset/reinstall k3s, just run:</em></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> /usr/local/bin/k3s-uninstall.sh</code></pre></div> <h3>On Each Worker Node (“worker-1”, “worker-2”, etc.)</h3> <ol> <li> <p><strong>Join the cluster:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-sfL</span> https://get.k3s.io <span class="token operator">|</span> <span class="token assign-left variable">K3S_URL</span><span class="token operator">=</span>https://192.168.0.200:6443 <span class="token assign-left variable">K3S_TOKEN</span><span class="token operator">=</span><span class="token operator">&lt;</span>your-token-here<span class="token operator">></span> <span class="token assign-left variable">INSTALL_K3S_EXEC</span><span class="token operator">=</span><span class="token string">"--node-ip=&lt;worker-ip>"</span> <span class="token function">sh</span> -</code></pre></div> <p>Replace <code class="language-text">192.168.0.200</code> with your control plane’s IP.<br> Replace <code class="language-text">&lt;your-token-here></code> with the token from your master node.<br> Replace <code class="language-text">&lt;worker-ip></code> with this worker’s IP, e.g., <code class="language-text">192.168.0.201</code>.</p> </li> <li> <p><strong>That’s it!</strong> The node will auto-register with your cluster. No manual kubeconfig needed.</p> </li> <li> <p><strong>Check the cluster again from the master:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl get nodes</code></pre></div> <p>You should see both <code class="language-text">master</code> and your worker(s) as <code class="language-text">Ready</code>!</p> </li> </ol> <hr> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Troubleshooting and Gotchas</h2> <p>As with any home lab project, there are always a few snags-here’s what I ran into and how to fix it:</p> <ul> <li> <p><strong>Node not joining?</strong></p> <ul> <li>Double-check your token (no spaces, copy the whole thing).</li> <li>Make sure you can <code class="language-text">ping</code> the control plane from your worker node.</li> <li>Firewalls can get in the way. Ensure port 6443 is open between nodes.</li> <li>If you get hostname conflicts, just change the hostname and restart the k3s agent (<code class="language-text">sudo systemctl restart k3s-agent</code>).</li> </ul> </li> <li> <p><strong>Cluster state looks weird after hostname change?</strong></p> <ul> <li>You might see both the old and new hostnames in <code class="language-text">kubectl get nodes</code>. Just delete the old one: <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl delete <span class="token function">node</span> <span class="token operator">&lt;</span>old-node-name<span class="token operator">></span></code></pre></div> </li> </ul> </li> <li> <p><strong>IP address keeps changing?</strong></p> <ul> <li>Make a DHCP reservation for each node’s MAC address in your router.</li> </ul> </li> <li> <p><strong>WiFi unreliable?</strong></p> <ul> <li>Ethernet is always the best bet for clusters, especially for heavy workloads.</li> </ul> </li> </ul> <hr> <h2>What’s Next?</h2> <p>With the basics in place, you’re ready to:</p> <ul> <li>Run and test real-world apps and dev environments</li> <li>Build your own CI/CD pipelines</li> <li>Experiment with ML workloads on dedicated nodes</li> </ul> <p>This way, you get all the power of a real Kubernetes lab, but full control and no monthly surprises from a cloud provider.</p> <hr> <h2>Lessons Learned</h2> <ul> <li><strong>RPi clusters are great for edge computing and learning,</strong> but once you need real muscle, x86 hardware makes a <em>huge</em> difference.</li> <li><strong>WiFi is convenient, but Ethernet is still king</strong> for stable clusters-especially if you’re doing anything performance-sensitive.</li> <li>Old laptops/desktops are a goldmine for home lab builds. Don’t let them collect dust!</li> </ul> <hr> <h2>Final Thoughts</h2> <p>If you have old machines and a bit of curiosity, you can build a cluster that’s surprisingly capable-no Raspberry Pis required.<br> And if you’ve already done it with RPis, this is the perfect upgrade path.<br> Keep an eye out for my next post where I’ll deep-dive into adding observability and tooling!</p> <p>Happy clustering! 🫡</p><![CDATA[How Does the Python Virtual Environment Work?]]>https://www.sahansera.dev/how-does-python-venv-work/https://www.sahansera.dev/how-does-python-venv-work/Sun, 06 Jul 2025 00:00:00 GMT<p>When you start working with Python, one of the first recommendations you’ll hear is to use a “virtual environment.” But what exactly is a Python virtual environment, and how does it work under the hood?</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>The Problem: Dependency Hell</h2> <p>Python projects often rely on third-party libraries. If you install packages globally, different projects can end up fighting over package versions. This is called “dependency hell.” For example, Project A might require <code class="language-text">requests==2.25</code>, while Project B needs <code class="language-text">requests==2.31</code>. Installing both globally can cause conflicts and break your projects.</p> <h2>The Solution: Virtual Environments</h2> <p>A virtual environment is an isolated workspace for your Python project. It lets you install packages locally, so each project can have its own dependencies, regardless of what’s installed elsewhere on your system.</p> <h2>How Does It Work?</h2> <p>When you create a virtual environment (using <code class="language-text">python -m venv myenv</code> or <code class="language-text">virtualenv myenv</code>), Python does the following:</p> <ol> <li> <p><strong>Creates a Dedicated Directory Structure</strong></p> <ul> <li>A bin/ or Scripts/ directory with a Python executable and activation scripts</li> <li>A lib/ directory with a copy of the Python standard library</li> <li>A pyvenv.cfg config file for metadata</li> </ul> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">myenv/ ├── bin/ <span class="token comment"># (Note: Scripts\ on Windows)</span> │ ├── activate <span class="token comment"># Shell script to activate the environment (Unix)</span> │ ├── activate.bat <span class="token comment"># Batch script (Windows CMD)</span> │ ├── Activate.ps1 <span class="token comment"># PowerShell script (Windows PowerShell)</span> │ ├── pip <span class="token comment"># Environment-specific pip</span> │ └── python <span class="token comment"># Environment-specific Python interpreter</span> ├── lib/ │ └── pythonX.Y/ │ └── site-packages/ <span class="token comment"># Installed packages go here</span> ├── pyvenv.cfg <span class="token comment"># Configuration file with environment metadata</span></code></pre></div> </li> <li> <p><strong>Configures a Standalone Python Interpreter:</strong></p> </li> </ol> <p>The environment includes its own Python executable (or a symlink to it), ensuring that all commands run from within the environment use the correct interpreter. - On most systems, this is a <strong>symlink or copy</strong> of the base Python binary - This interpreter respects only the packages installed within the environment</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"> <span class="token function">which</span> python /path/to/myenv/bin/python</code></pre></div> <ol start="3"> <li> <p><strong>Sets Up Local Package Management:</strong></p> <p>Each environment gets its own site-packages directory:</p> <ul> <li>When you run pip install, packages go here instead of the global location</li> <li>This isolation prevents version conflicts and makes dependency management predictable</li> </ul> </li> <li> <p><strong>Creates Activation Scripts:</strong></p> <p>Activation scripts help you <em>enter</em> the environment by:</p> <ul> <li>Modifying your <code class="language-text">$PATH</code> so that python and pip point to the virtual environment</li> <li>Optionally updating your shell prompt (e.g., showing (myenv))</li> <li>Ensuring commands are scoped to the environment</li> </ul> <p>These scripts are OS-specific:</p> <ul> <li>Unix/macOS: <code class="language-text">source myenv/bin/activate</code></li> <li>Windows CMD: <code class="language-text">myenv\Scripts\activate.bat</code></li> <li>PowerShell: <code class="language-text">myenv\Scripts\Activate.ps1</code></li> </ul> </li> <li> <p><strong>Includes a Configuration File:</strong></p> <p>The <code class="language-text">pyvenv.cfg</code> file records metadata about the environment, including the Python version and the location of the base interpreter.</p> <p>This file stores:</p> <ul> <li>The Python version used</li> <li>The path to the base interpreter</li> <li>Whether system site packages are accessible (default: no)</li> </ul> <p>This metadata is used when running the environment to preserve consistent behavior.</p> </li> </ol> <h2>Command Resolution</h2> <p>So what happens when you are in a venv as opposed to running python command globally? The diagram below illustrates how Python and pip commands are resolved with and without a virtual environment. When no virtual environment is active, your system’s PATH directs these commands to the globally installed Python interpreter and packages.</p> <p>However, once a virtual environment is activated, the PATH is modified to point to the environment’s own executables. This ensures that all Python commands and package installations stay isolated within the virtual environment, avoiding conflicts with system-wide installations.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 79.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC8klEQVR42m2UCXPqNhSF+f9/ptOZl3aa5j1C2LcAIewBjME7xgZswwvLV0lA6LTvzhxLlo6u73Ks1GazYb/fkyQJSZwQx7Ea97s9QRBgmiau6yoYhoHv++x2uy+u4ouzck3OU/JxMydyycwLvOhF0loWL16q9fP5fBm5jEESkjVKpPUcHbf/df5wOJDabiP14kc+I3dMbfpKpp2jNKoy9qb48YrT+aQ4x9MRe+Og+TrlcY1sv0DXGmCEJpt9xPFwJBVFF4fd5YCiWaVqNMhrJWpWg7L9Ss8b3iM4Hags6uRmZTKjPC/jAkW9SnZeZLExOR/PpPTlnJrbpDirUtIqVOYiwqGIcFqlrNUpz+rU7CZ+smIYjK+8KrlJkafOM/lJmYJAyaip6FPmymYQjul6AzpOn3e3R23WoDQW0Yr0X803Wu47buQxCibUjSb1WfOypzfp2H2Frj8QZVvdm3Ir/L/t+PNAw2yTnmV5mmR4nKQZ2R9ig1/a5+cnqVvLt9GWtZBQuF5/QUpqFa7wgiV+6OOtlgRhwGZ74UpITiTOKtkJqKZIbAUpjjYk2/UVwnkYYlkWptCfsTCwLVs5iLd3nnR28yGhHMpsz0ISrZnP967DU9ugMPI4nU5KWzKVG6QaO4uAxzeDv1s63iZRa5KrhC3DlHY6nRmYa3I9g+c3jZa+4vNw+n+dhNaGVkhGcMpDCydMrqKHWKZ8c7j7eeTPhs7v5RG/Ffp8q01x1rtrw+5N09w1D4L3UJ/wx+uMdNdmryLnEqHpr4VMQsofjoBLfmDx3NbVvCKhhZhBgraMKX4sxR/kUPrw+N6ckHnXVUZyrSZ4hhtcHFbGPrnunEJvQV4Q/qr2yL5rIq0pLz2bhR8x9WJVV8XrW/xoTSiKj8uPlkTqNS3A8IRDedP81y7pnYVMQh7bFt+aCx4EfvRccQOFouAHzr/Qoeqy1JQsZhTFQotSPndInS3sJbrpoBsOC2cpOLHiy3pFV8TiKkuElqWk/gFWe6hnXufViQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Command Resolution" title="" src="/static/e9c4cc82628211a7216ed3c49933067c/5a190/how-does-the-python-virtual-environment-work-1.png" srcset="/static/e9c4cc82628211a7216ed3c49933067c/772e8/how-does-the-python-virtual-environment-work-1.png 200w, /static/e9c4cc82628211a7216ed3c49933067c/e17e5/how-does-the-python-virtual-environment-work-1.png 400w, /static/e9c4cc82628211a7216ed3c49933067c/5a190/how-does-the-python-virtual-environment-work-1.png 800w, /static/e9c4cc82628211a7216ed3c49933067c/c1b63/how-does-the-python-virtual-environment-work-1.png 1200w, /static/e9c4cc82628211a7216ed3c49933067c/29007/how-does-the-python-virtual-environment-work-1.png 1600w, /static/e9c4cc82628211a7216ed3c49933067c/6368f/how-does-the-python-virtual-environment-work-1.png 3550w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Why Is This Powerful?</h2> <ul> <li><strong>Isolation:</strong> Each project gets its own dependencies and versions.</li> <li><strong>Reproducibility:</strong> You can lock dependencies with a <code class="language-text">requirements.txt</code> or <code class="language-text">pyproject.toml</code> file, making it easy for others (or yourself in the future) to recreate the environment.</li> <li><strong>No Admin Rights Needed:</strong> You don’t need system-wide permissions to install packages.</li> </ul> <h3>Advanced Use Cases</h3> <ul> <li><strong>Multiple Python Versions:</strong> Use virtual environments to test your code against different Python versions.</li> <li><strong>Custom Activation Scripts:</strong> Modify the activation script to set environment variables specific to your project.</li> <li><strong>Integration with CI/CD:</strong> Virtual environments are essential for setting up isolated builds in CI/CD pipelines.</li> </ul> <h2>Under the Hood: What’s Really Happening?</h2> <ul> <li>The virtual environment is just a directory with a specific structure.</li> <li>No containers or VMs are involved-just clever manipulation of paths and environment variables.</li> <li>Deleting the virtual environment directory removes all installed packages for that project.</li> </ul> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Debugging Tips</h3> <ul> <li>If activation doesn’t work, check your shell configuration.</li> <li>Use <code class="language-text">python -m site</code> to inspect the site-packages directory.</li> <li>Verify the <code class="language-text">pyvenv.cfg</code> file for any misconfigurations.</li> </ul> <h2>Conclusion</h2> <p>Python virtual environments are a foundational tool for modern Python development. They solve the problem of dependency conflicts, make projects more portable, and keep your system clean. Whether you’re building a quick script or a large application, understanding how virtual environments work will save you countless headaches down the road.</p> <h2>References</h2> <ul> <li><a href="https://docs.python.org/3/library/venv.html">Official Python venv Documentation</a></li> <li><a href="https://peps.python.org/pep-0405/">PEP 405: Python Virtual Environments</a></li> </ul><![CDATA[Deep Dive - How Chunked Transfer Encoding Works]]>https://www.sahansera.dev/understanding-chunked-transfer-encoding/https://www.sahansera.dev/understanding-chunked-transfer-encoding/Wed, 02 Apr 2025 00:00:00 GMT<p>Chunked transfer encoding is a key HTTP/1.1 feature that allows servers to stream data incrementally without knowing the total size of the response upfront. It’s particularly useful in streaming APIs, live updates, and large or dynamically-generated responses.</p> <p>In this post, we’ll practically explore how chunked transfer encoding works using the backend we developed in my previous blog post on <a href="https://sahansera.dev/streaming-apis-python-nextjs-part2/">Streaming APIs with FastAPI and Next.js - Part 2</a>.</p> <h2>🤔 What is Chunked Transfer Encoding?</h2> <p>Chunked transfer encoding modifies HTTP responses into a series of <strong>chunks</strong>, each prefixed with its size in bytes. It allows servers to start sending response data immediately, without having to calculate the full content length beforehand.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>When <code class="language-text">Transfer-Encoding: chunked</code> is present, the client receives data <strong>incrementally</strong> and knows the response has ended when a <strong>zero-length</strong> chunk appears.</p> <h2>💻 Hands-on Example</h2> <p>Let’s use the <a href="https://github.com/sahansera/streaming-apis/tree/main/backend">FastAPI backend</a> we built.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">make</span> start-backend</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 36%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABaklEQVR42p2QT0/CQBDFty1ChbKl21JKC7VloYsE5F9MTIx+Jo4QvqjxLglXFTVh2XHa4M3ExMMvbzKbfW9mSIu0ale+vWp4dGuZ5rparW4a1Nq0GNswhFK6yXu56rpeYJpm8ZZruVxel01zy2x7RQipkU6z079dTj/H9w8g7h6VmC7AExMIhjfQzSJodh0Igja4ngeO4wAaFlQqFbAtCzzHUVYQwCWl72iYETesi07SOgTtUPFMHLtxLOO0J5Mel5ynkg+47KWp9FxXRlEkwzCUOJ20bVtigPSb/pG5LjiMveK0Q1L3SEYD48N1GSzm89N4PFbL+VwNZzPVmUwU51z5QigWx4rW6wqNVKlUUoZhKJw01xMCyFthmIhQeFHtC28Eo9FIJUkCIssg5X1gvg+4Bmi4ItG0ov4FddYDGl4TTEzwHs/Y2GPii6ZpuxxD13c/9R/kf/bIE3qkCLlA4uKghAz+SXb2KH0DaUp6Q/Xy3uEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding chunked transfer encoding 1" title="" src="/static/4a24d04fc40a4d915b26dcced1b22c06/5a190/understanding-chunked-transfer-encoding-1.png" srcset="/static/4a24d04fc40a4d915b26dcced1b22c06/772e8/understanding-chunked-transfer-encoding-1.png 200w, /static/4a24d04fc40a4d915b26dcced1b22c06/e17e5/understanding-chunked-transfer-encoding-1.png 400w, /static/4a24d04fc40a4d915b26dcced1b22c06/5a190/understanding-chunked-transfer-encoding-1.png 800w, /static/4a24d04fc40a4d915b26dcced1b22c06/3fca6/understanding-chunked-transfer-encoding-1.png 1112w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s hit the <code class="language-text">/stream</code> endpoint with <code class="language-text">curl</code> to see how chunked transfer encoding works in practice.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-i</span> <span class="token parameter variable">--raw</span> http://localhost:8000/stream</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 71.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0UlEQVR42qWT3UvbUBjGT5M0H00Tc5o2KYXSYrvaiyEddOocDJ2Wxo/d7UMGwpQhmxfuYtNtgv4jnov9m1vIefeczAkKVqkXP85HTp48533ystmq96g/1xI+58LQdWHbtvA8TzQaDcGxZ5qmYIwJx3GE67o56nkYhvk8xJl6s3URtzq/LMvqsF6vlwwXntJcv0/1el1ChOI4zgmCgCBIEKRCoUCapl2h6zr2NHJdh2aiKHPDkHTGRqzdbo+Hw6EcDAZpt9v902w201qtlkI4NQwjhdh9+K0+iHGd4cUkiiJSziqVSu4Q175ypNxNQp0D2aXgiMFNUq1WqVQqke/7EvXJBe8SusHtguDhggiDPAgFnOPKFRTapYIqPgp/TzI1IqhRXsMZ36Ni0SDbMqVlmWTo2vQO558sJbufv9OHo3PaOzqT+1/OaffwlN59/Aq+0c7BMb3/9OMOfmY7Byc0WHwxYq1OL3m2uknLL7dpeW1bPl97hfkWLWFvcWWD1Hp16y14M4lsZfM1dfuP/9WQ84DUVUuOLQEVDeNhocQqFA+hBFwiGCTuEsv/L+1ad0wgu+yeXHCMviT0KpXLZQng1prW4TpgHMpjjBv/wXoalAb/C3qiKG8yP4e/AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding chunked transfer encoding 2" title="" src="/static/9dcc9e6b31efcd7758f65d83d8c09ba3/5a190/understanding-chunked-transfer-encoding-2.png" srcset="/static/9dcc9e6b31efcd7758f65d83d8c09ba3/772e8/understanding-chunked-transfer-encoding-2.png 200w, /static/9dcc9e6b31efcd7758f65d83d8c09ba3/e17e5/understanding-chunked-transfer-encoding-2.png 400w, /static/9dcc9e6b31efcd7758f65d83d8c09ba3/5a190/understanding-chunked-transfer-encoding-2.png 800w, /static/9dcc9e6b31efcd7758f65d83d8c09ba3/c1b63/understanding-chunked-transfer-encoding-2.png 1200w, /static/9dcc9e6b31efcd7758f65d83d8c09ba3/5d942/understanding-chunked-transfer-encoding-2.png 1343w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <ul> <li><code class="language-text">-i</code>: Include headers in output.</li> <li><code class="language-text">--raw</code>: Disable curl’s automatic decoding, revealing raw chunked encoding.</li> </ul> <p><strong>Expected output:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-i</span> <span class="token parameter variable">--raw</span> localhost:8000/stream HTTP/1.1 <span class="token number">200</span> OK date: Mon, <span class="token number">31</span> Mar <span class="token number">2025</span> 09:51:47 GMT server: uvicorn content-type: text/plain<span class="token punctuation">;</span> <span class="token assign-left variable">charset</span><span class="token operator">=</span>utf-8 Transfer-Encoding: chunked 1f Waiting <span class="token keyword">for</span> new log entries<span class="token punctuation">..</span>. 1f Waiting <span class="token keyword">for</span> new log entries<span class="token punctuation">..</span>. 1f Waiting <span class="token keyword">for</span> new log entries<span class="token punctuation">..</span>. 1f Waiting <span class="token keyword">for</span> new log entries<span class="token punctuation">..</span>. 1f Waiting <span class="token keyword">for</span> new log entries<span class="token punctuation">..</span>. <span class="token number">30</span> Simulated log entry at Mon Mar <span class="token number">31</span> <span class="token number">20</span>:21:53 <span class="token number">2025</span> <span class="token number">0</span></code></pre></div> <p>Here’s a diagram to help visualize the chunked transfer encoding:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIklEQVR42m1UCZLiMAzk/z+c2tkaSNhALsfO4SM+eiUxsDCsq7rsyHarJVk5FAekMcO2HnFMyCaDRymMImudIy67xXlbZFYxiB1+RtY17FAhThVgRxyyK4gqYzhNcP2OZG4kz+NGuOGjveLsVoy7fxBGVaE/fSAMR5RtwAF8n7AuG7z1SDFBjROu1w6XS4dhUIhks95DmwXv7kiYtYh7EKLD3TjPC6xzcHRxXTcsyyrgdQgBjvYmpeGsQ9v26LpBsK4WM51LKQnPg1BrI6QxRtqMoorBB42ZRenvzyP6fsRIETDua47kjZBDZAW3gpQX7PsuYTEpf4ewk2Iv4GEoFWx7EPIhrWeZc86CZ0L+5sFq+PKl6XCuG9QEjood7Xt8Vdj3Si7wobpqcDqeZd00Vxy/asmXogh+OuMxTVqieMvhMq9SgGd4KhKHY6kY7HDbrBRlJFWcJlY40z3O/QshH2Tv3ofvKm8yc554L6VMSgwMpYbJON8DFeVGuPwrCntnDBQye26aVsCVY3Al2c5kDPz3HTp5EULI3icK9+tY4XJtoSgfrObn4Hyx4hfbI7onQvBdgh5n+CXIuuA16ZFmm+k9knNX6H2Wb4dkQ6TnQz2eqCVBLXoo5DTSz0FVBp57eXpXp/KOhnr5c+zwh+Yh3nvZ0PkTxvoX9fIXSaVeLqEg6Yy1swiKCJd3woU8t8GinrXM5v63IfJkLlj7M6JuUKzGX9X2jKJ+TIy1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding chunked transfer encoding 3" title="" src="/static/5716de2818d51f9c882d46d6471a115e/5a190/understanding-chunked-transfer-encoding-3.png" srcset="/static/5716de2818d51f9c882d46d6471a115e/772e8/understanding-chunked-transfer-encoding-3.png 200w, /static/5716de2818d51f9c882d46d6471a115e/e17e5/understanding-chunked-transfer-encoding-3.png 400w, /static/5716de2818d51f9c882d46d6471a115e/5a190/understanding-chunked-transfer-encoding-3.png 800w, /static/5716de2818d51f9c882d46d6471a115e/c1b63/understanding-chunked-transfer-encoding-3.png 1200w, /static/5716de2818d51f9c882d46d6471a115e/252a4/understanding-chunked-transfer-encoding-3.png 1314w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Here’s what’s happening:</p> <ul> <li>Each chunk starts with its length in hexadecimal (<code class="language-text">1f</code> = 31 bytes).</li> <li>The data follows the length, and the next chunk starts after a newline.</li> <li>The chunk with 30 represents a simulated log entry (<code class="language-text">30</code> = 48 bytes).</li> <li>The response ends with a zero-length chunk (<code class="language-text">0</code>).</li> </ul> <blockquote> <p><em>Note:</em> This aligns with the techniques demonstrated in my previous blog series <a href="https://sahansera.dev/streaming-apis-python-nextjs-part1/">Streaming APIs with FastAPI and Next.js (Part 1)</a> and <a href="https://sahansera.dev/streaming-apis-python-nextjs-part2/">Part 2</a>.</p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>🛠️ <strong>Step-by-Step Breakdown of Chunking</strong></h3> <p>We’ll be using the <a href="https://github.com/sahansera/streaming-apis/blob/main/backend/api/index.py">index.py</a>. Here’s exactly what’s happening under the hood:</p> <p><strong>1. Generator (<code class="language-text">yield</code>)</strong>:</p> <ul> <li>Every time <code class="language-text">yield</code> is executed, the Starlette framework (used internally by FastAPI) receives a new piece of data to stream to the client.</li> <li>Each yielded data segment corresponds <strong>directly to one HTTP chunk</strong>.</li> </ul> <p>For example, the <a href="https://github.com/sahansera/streaming-apis/blob/main/backend/api/index.py#L34">yielded line</a>:</p> <div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">yield</span> <span class="token string">"Waiting for new log entries...\n"</span></code></pre></div> <p>is packaged into one HTTP chunk.</p> <p><strong>2. Starlette’s StreamingResponse Handling</strong>:</p> <ul> <li>The <code class="language-text">StreamingResponse</code> from Starlette wraps the async generator.</li> <li>Starlette doesn’t wait until the generator finishes (which might be infinite). Instead, it immediately pushes each yielded chunk to the underlying ASGI server, typically Uvicorn.</li> </ul> <p><strong>3. Uvicorn’s Chunk Formatting</strong>:</p> <p>Uvicorn (the ASGI server you’re using) receives the yielded chunk from Starlette and <strong>formats it according to the HTTP/1.1 chunked transfer encoding specification</strong>:</p> <p>Each chunk is transmitted as follows:</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">&lt;chunk-size in hexadecimal>\r\n &lt;chunk-data>\r\n</code></pre></div> <p>Here’s how one of your actual data chunks might look:</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">1f\r\n Waiting for new log entries...\n\r\n</code></pre></div> <ul> <li><code class="language-text">1f</code> = 31 bytes, the exact length of <code class="language-text">"Waiting for new log entries...\n"</code></li> </ul> <p><strong>4. Continuous Chunk Transmission</strong>:</p> <ul> <li>Uvicorn immediately sends each formatted chunk down the TCP connection.</li> <li>Your client (like <code class="language-text">curl</code>) receives each chunk as soon as it’s sent, which allows incremental processing.</li> </ul> <p><strong>5. Ending the Stream</strong>:</p> <ul> <li>If your generator ever completes (or if the server shuts down the connection), Uvicorn sends a special <strong>zero-length chunk</strong> (<code class="language-text">0\r\n\r\n</code>) to indicate that transmission has ended.</li> </ul> <p>Example final chunk:</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">0\r\n \r\n</code></pre></div> <h2>🙋 What about HTTP/2 and HTTP/3?</h2> <p>The short answer: HTTP/2+ does not use chunked encoding at all. In fact, the HTTP/2 specification explicitly forbids the use of the <code class="language-text">Transfer-Encoding: chunked</code> header; if a client incorrectly tries to send it, it’s considered a protocol error.</p> <p>Instead, HTTP/2 uses a more efficient binary framing layer that allows multiplexing multiple streams over a single connection. This means that chunked transfer encoding is not necessary in HTTP/2 and HTTP/3, as the protocol itself handles streaming more efficiently.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>Key Takeaways</h2> <p>Through these practical examples, you’ve seen firsthand how chunked transfer encoding enables incremental streaming of data:</p> <ul> <li>Responses are sent as a series of chunks, each with a defined size.</li> <li>The end of data transmission is indicated by a zero-length chunk.</li> <li>Tools like <code class="language-text">curl</code>, Python frameworks like FastAPI, and browser developer tools help visualize and debug chunked encoding.</li> </ul> <p>Understanding this helps you build better streaming APIs and debug complex HTTP interactions effectively.</p> <p>Happy Streaming! 🚀</p> <hr> <h2>Further Reading</h2> <ul> <li><a href="https://datatracker.ietf.org/doc/html/rfc9112#section-7.1">RFC 9112 – HTTP/1.1 Specification</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding">MDN Web Docs: Transfer-Encoding</a></li> <li><a href="https://sahansera.dev/streaming-apis-python-nextjs-part1/">Streaming APIs with FastAPI and Next.js (Part 1)</a></li> <li><a href="https://sahansera.dev/streaming-apis-python-nextjs-part2/">Streaming APIs with FastAPI and Next.js (Part 2)</a></li> </ul><![CDATA[Upgrading sahansera.dev to Gatsby 5]]>https://www.sahansera.dev/upgrading-gatsby-5/https://www.sahansera.dev/upgrading-gatsby-5/Sun, 30 Mar 2025 00:00:00 GMT<h1>How I Upgraded My Blog to Gatsby V5</h1> <p>The time is now 1:45 AM and I’m finally done upgrading my blog to Gatsby V5. It’s been one heck of a ride, but it was worth it.</p> <p>This blog post is a reflection of my experience upgrading my blog to Gatsby V5. I’ll cover the challenges I faced, the solutions I found, and the lessons learned along the way.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>The Upgrade Journey</h2> <p>After a two year long hiatus from blogging, I decided to dust off my old blog and give it a much-needed facelift. When I tried to run <code class="language-text">gatsby develop</code>, I was greeted with a slew of warnings and errors. It was clear that my blog was long overdue for an upgrade.</p> <p>I took a crack at it a few months ago, but I quickly got overwhelmed by the number of breaking changes and decided to put it on hold. This time it’s different because I was determined to fully utilize LLMs for my advantage.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 10%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAhklEQVR42mNQTtj1XzPnzH+VjBP/pROP/2cNOYiCWYL3/9eLX/RfLuXof+bgg2Ask3z0vyyQLxJ35L9S+rH/qhlH/wvEHP3P6r/1P4NC/I7/qlkn/yulHf0vHHsYaMABDAO1Yhb9V0gFGXgAyD/4XzrpCNgCkbjD/1Uzj/9XSQfqjTsGNhAAhG9Xl3TBk9cAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="upgrading gatsby 5 1" title="" src="/static/7beaebea2e858bc71ff5f796bf3d1a7b/5a190/upgrading-gatsby-5-1.png" srcset="/static/7beaebea2e858bc71ff5f796bf3d1a7b/772e8/upgrading-gatsby-5-1.png 200w, /static/7beaebea2e858bc71ff5f796bf3d1a7b/e17e5/upgrading-gatsby-5-1.png 400w, /static/7beaebea2e858bc71ff5f796bf3d1a7b/5a190/upgrading-gatsby-5-1.png 800w, /static/7beaebea2e858bc71ff5f796bf3d1a7b/c1b63/upgrading-gatsby-5-1.png 1200w, /static/7beaebea2e858bc71ff5f796bf3d1a7b/a8a6f/upgrading-gatsby-5-1.png 1516w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>Sneak peak of my last 3 commits</i> 😅</center> <p>Being a weekend, which is filled with family time and chores, I decided to timebox it just for a few hours. I thought, “How hard can it be? Just a few package updates and some code tweaks, right?” Little did I know that this would turn into a full-blown adventure.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Dependency Overhauls</h3> <p>One of the first tasks was to ensure all my plugins were updated to their latest non-breaking minor versions. I used <a href="https://www.npmjs.com/package/npm-check-updates"><code class="language-text">npm-check-updates</code></a> to help with this.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">ncu <span class="token parameter variable">--interactive</span> <span class="token parameter variable">--format</span> group</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 95.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACuUlEQVR42pVU226bQBDlDgYCay6OAdtcDMgBbMdO3Kj/UvWtUT606g/EUn6jrVr6EnY6aydRk1YyfTia2WXm7JmdYTmO4zSiCR+lgXAncMItrl9BluVbVdYR6q2qqn99R3xC3AmC8AGtzs3OZ+m6Ln/GdQV+taFBWcF00cCo2cJ4sYKszqG8iqG4LCHOcvA8D8IwhCAIwDRNcF2XIkDTtG9IWHKKohTl4qJtliso66arlivarNf06uY9rVeXlDiEDj1CXd+jtk0oEtLRaEQty6KoikqS1ImiCOh/R8ILBJfbhLRRFEIUBHQ2nUIURTAkBGzbBvx+CpRZnucZ4eJAqGpqa6B8BDsRBDwRA16Snn1m/8Q/CS2VyyPrrPVRTYxlBcQGoipgSSIMNRXcgQoCJssCj7anwnA0aterFeRpQkeeCyqq5DFoEgYwT2KQcZ2YAzBEoR+h4zht0zQQRhGdTCYwmUxBkZVDN/OiQF+GzNT7E47H43az2UCSJJQ1JMSRMAwDptigoiwPhHNTA/OJkD9FiHPUVlUFRVHQEgnSNAXf9w9gB4hY8swYgC7w/RRiYrvdbqGua4qkL8Gs5HmWgYjlJ2c6nPVVyAh3ux0QQij+asAGlQWx+2SKmcIUm2JJPQlx6tvFYgF4l8c5FI6Js9mM3SuwvfjQ5Z4lszu8eXcDy+WSsjJZQ54VFnkOknIs2fyfsbneXUOcxJSRWbYFuqGjwilk2Rw0JJwb2OU+JeOTlBOL/HCHLviO9+i7fuc6Xje0h51tkc4y7U4UxG6gyJ3I8x0mvcXjE+HXZ4XFuX/+q64aWOFLE4UROEMHVQjgez5McchZk9hsDlDpiZIPr80Em/BFlMQHTLznOX6Pe3sM2OP+HvcOPrNszfw3uEc8ID5jXswIJV3XAyw9RSSvwB0txpxCiggY12932mkEUUXkJAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="upgrading gatsby 5 2" title="" src="/static/773ee307995f2870e8a0224733903bc7/5a190/upgrading-gatsby-5-2.png" srcset="/static/773ee307995f2870e8a0224733903bc7/772e8/upgrading-gatsby-5-2.png 200w, /static/773ee307995f2870e8a0224733903bc7/e17e5/upgrading-gatsby-5-2.png 400w, /static/773ee307995f2870e8a0224733903bc7/5a190/upgrading-gatsby-5-2.png 800w, /static/773ee307995f2870e8a0224733903bc7/67fe0/upgrading-gatsby-5-2.png 1101w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>Example of ncu CLI tool in action</i></center> <blockquote> <p>💡 Tip: Always stay on a separate branch, use commits for each change you make, while doing updates.</p> </blockquote> <p>There were bunch of warnings, but nothing major. Next, I upgraded React and ReactDOM to v18. This was a bit tricky because I had to ensure that all my dependencies were compatible with React 18. I also had to update my Babel configuration to support the new JSX transform.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> react@^18.0.0 react-dom@^18.0.0</code></pre></div> <p>After that, I bumped up the Gatsby version to <code class="language-text">^5.0.0</code> and ran <code class="language-text">npm install</code>. This is where the fun really began 😅</p> <h3>Plugin Upgrades</h3> <p>Many of my plugins were still on versions designed for Gatsby V4. I had to update or replace:</p> <ul> <li> <p><strong>gatsby-plugin-catch-links, gatsby-plugin-feed, gatsby-plugin-google-gtag, gatsby-plugin-layout, gatsby-plugin-manifest, gatsby-plugin-offline, gatsby-plugin-react-helmet, gatsby-plugin-sharp, and gatsby-transformer-sharp etc..</strong><br> Each of these required an upgrade to versions like <code class="language-text">^5.14.0</code> or <code class="language-text">^6.14.0</code> to resolve the peer dependency conflicts with Gatsby V5.</p> </li> <li> <p><strong>Image Handling:</strong><br> I migrated from the deprecated <code class="language-text">gatsby-image</code> to the modern <a href="https://www.gatsbyjs.com/docs/reference/release-notes/image-migration/"><code class="language-text">gatsby-plugin-image</code></a>. This required updating my GraphQL queries from using <code class="language-text">fluid</code> and <code class="language-text">fixed</code> fragments to using the <code class="language-text">gatsbyImageData</code> field. I updated imports, replaced <code class="language-text">&lt;Img></code> with <code class="language-text">&lt;GatsbyImage></code>, and used <code class="language-text">getImage()</code> to extract image data.</p> </li> </ul> <p>Much of this is covered in the official migration <a href="https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v4-to-v5/">guide</a>.</p> <p>Then I ran into the infamous TypeComposer error.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Cannot create as TypeComposer the following value: GraphQLScalarType<span class="token punctuation">(</span><span class="token punctuation">{</span> name: <span class="token string">"Date"</span>, description: <span class="token string">"A date string, such as 2007-12-03, compliant with the ISO 8601 standard for representation of dates and times using the Gregorian calendar."</span>, specifiedByURL: undefined, serialize: <span class="token punctuation">[</span>function String<span class="token punctuation">]</span>, parseValue: <span class="token punctuation">[</span>function String<span class="token punctuation">]</span>, parseLiteral: <span class="token punctuation">[</span>function parseLiteral<span class="token punctuation">]</span>, extensions: <span class="token punctuation">{</span> <span class="token punctuation">}</span>, astNode: undefined, extensionASTNodes: <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>.</code></pre></div> <blockquote> <p>💡 The tl;dr here is there were multiple versions of <code class="language-text">graphql</code> being used by other dependencies. I had to ensure that all my dependencies were using the same version of <code class="language-text">graphql</code>. I did this by adding a <code class="language-text">resolutions</code> field in my <code class="language-text">package.json</code>:</p> </blockquote> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"resolutions"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"graphql"</span><span class="token operator">:</span> <span class="token string">"^16.6.0"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then I ran <code class="language-text">rm -rf node_modules package-lock.json &amp;&amp; npm install</code> again to ensure all dependencies were using the same version of <code class="language-text">graphql</code>. TypeComposer error was gone, but I still had a few warnings.</p> <p>Next, I transformed my old GraphQL queries to the new format using the Gatsby codemod.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx gatsby-codemods@latest sort-and-aggr-graphql <span class="token builtin class-name">.</span></code></pre></div> <p>This worked like a charm and fixed most of the query related issues. Everything else, I just asked ChatGPT to help me with. I had to tweak a few queries here and there, but nothing major.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Styling Challenges</h3> <p>I also relied heavily on <a href="https://github.com/vercel/styled-jsx">styled-jsx</a> for component-scoped CSS. However, Gatsby’s official plugin, <code class="language-text">gatsby-plugin-styled-jsx</code>, only supports styled-jsx v3-and I needed styled-jsx v5 for React 18 compatibility. After some consideration, I decided to remove the plugin entirely and instead configured Babel to handle styled-jsx directly.</p> <p>This is where ChatGPT had most of problems. It went through a diamond dependency resolution problem and got stuck in a loop. I had to manually intervene and guide it through the process.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h4>Enabling Nested CSS with styled-jsx</h4> <p>Even after removing the old plugin, I ran into issues when my <code class="language-text">&lt;style jsx></code> blocks used nested CSS rules. By default, styled-jsx doesn’t support nesting. I resolved this by integrating <a href="https://github.com/vercel/styled-jsx-plugin-postcss">styled-jsx-plugin-postcss</a> along with the PostCSS Nested plugin:</p> <ol> <li> <p><strong>Installed the Packages:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> styled-jsx-plugin-postcss postcss-nested --save-dev</code></pre></div> </li> <li> <p><strong>Created a Configuration File:</strong><br> I added a <code class="language-text">styled-jsx.config.js</code> at the project root:</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'postcss-nested'</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> </li> <li> <p><strong>Updated Babel Config:</strong><br> In my <code class="language-text">.babelrc</code>, I ensured the styled-jsx plugin was configured to use the PostCSS plugin:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"presets"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"babel-preset-gatsby"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span><span class="token string">"styled-jsx/babel"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"styled-jsx-plugin-postcss"</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">]</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span></code></pre></div> </li> </ol> <p>This allowed my nested CSS in components like the header, footer, and blog items to compile correctly. I would say this the part where LLM helped me the most! 💪</p> <h3>PostCSS Configuration Adjustments</h3> <p>During the upgrade, I also encountered warnings about duplicate autoprefixer instances. My initial <code class="language-text">postcss.config.js</code> was using <code class="language-text">postcss-cssnext</code>, which is now deprecated. After some experiments and research, I updated the config to use <code class="language-text">postcss-preset-env</code>-a modern alternative that handles vendor prefixes efficiently.</p> <hr> <h2>Lessons Learned</h2> <ol> <li> <p><strong>Upgrade in Small Steps:</strong><br> Tackling dependency conflicts one by one and verifying functionality helps isolate problems.</p> </li> <li> <p><strong>Read Plugin Changelogs:</strong><br> Understanding what’s changed in plugin APIs (like the migration from <code class="language-text">fluid</code>/<code class="language-text">fixed</code> to <code class="language-text">gatsbyImageData</code>) is crucial.</p> </li> <li> <p><strong>Be Ready to Reconfigure:</strong><br> Sometimes it’s necessary to remove old plugins (like <code class="language-text">gatsby-plugin-styled-jsx</code>) and configure Babel or PostCSS directly to maintain compatibility with the latest React and Gatsby versions.</p> </li> <li> <p><strong>Test Thoroughly:</strong><br> Both in development mode and via production builds (<code class="language-text">gatsby build</code> and <code class="language-text">gatsby serve</code>), to ensure SSR and dynamic behaviors work as expected.</p> </li> </ol> <hr> <h2>Final Thoughts</h2> <p>Upgrading to Gatsby V5 has not only improved performance and introduced new features, but it also forced me to re-examine and modernize the entire toolchain-from image handling and styling to PostCSS configurations.</p> <p>If you’re planning a similar upgrade, take your time, tackle one dependency at a time, and don’t hesitate to experiment with configurations until everything clicks.</p> <p>Prepare to do some A/B testing by having a develop branch and a production branch. This way, you can compare the performance and functionality of your old setup with the new one.</p> <p>Happy coding, and enjoy your faster, modernized Gatsby blog! 💜</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>References</h2> <ul> <li><a href="https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v4-to-v5/">Gatsby V5 Migration Guide</a></li> </ul> <hr> <p>Feel free to leave a comment if you have any questions or if you’d like to share your upgrade experiences!</p><![CDATA[Streaming APIs with FastAPI and Next.js - Part 2]]>https://www.sahansera.dev/streaming-apis-python-nextjs-part2/https://www.sahansera.dev/streaming-apis-python-nextjs-part2/Sat, 29 Mar 2025 00:00:00 GMT<p>In <a href="https://sahansera.dev/streaming-apis-python-nextjs-part1">Part 1</a>, we explored how to stream data into a React component using modern browser APIs. Now it’s time to build the other half: the <strong>FastAPI backend</strong> that makes it all work.</p> <p>In this post, we’ll walk through setting up a streaming endpoint using FastAPI and discuss how chunked transfer encoding works on the server side.</p> <blockquote> <p>💡 <strong>Code Repository</strong>: The complete code is available on <a href="https://github.com/sahansera/streaming-apis">GitHub</a>. You can clone it and run it locally to follow along.</p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>🛠️ Building the Streaming Backend with FastAPI</h2> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 61.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB13TMoon/xAAaEAACAgMAAAAAAAAAAAAAAAAAAREhAgMi/9oACAEBAAEFAp62UYWoGpFR/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFhEBAQEAAAAAAAAAAAAAAAAAAAEx/9oACAECAQE/AbiP/8QAFhAAAwAAAAAAAAAAAAAAAAAAAAEg/9oACAEBAAY/AhT/AP/EABsQAAICAwEAAAAAAAAAAAAAAAERACExcYGh/9oACAEBAAE/ISQofRCSy6IRaS+zNsytZGoCJvc//9oADAMBAAIAAwAAABB/P//EABYRAQEBAAAAAAAAAAAAAAAAAAEAMf/aAAgBAwEBPxBxjL//xAAXEQEAAwAAAAAAAAAAAAAAAAAAATFR/9oACAECAQE/EMlIf//EABsQAQADAAMBAAAAAAAAAAAAAAEAESFBUbEx/9oACAEBAAE/EEBSXwEdBdu0ey8l27R8iXu+4ZDWOVTPLX1Wz//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="streaming apis python nextjs part2 1" title="" src="/static/05ca100ab436a1990c8d1f5ae0f6a7a8/4b190/streaming-apis-python-nextjs-part2-1.jpg" srcset="/static/05ca100ab436a1990c8d1f5ae0f6a7a8/e07e9/streaming-apis-python-nextjs-part2-1.jpg 200w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/066f9/streaming-apis-python-nextjs-part2-1.jpg 400w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/4b190/streaming-apis-python-nextjs-part2-1.jpg 800w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/a768a/streaming-apis-python-nextjs-part2-1.jpg 1037w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>How the data will flow from Backend to the Frontend</i></center> <p>FastAPI makes it really easy to return a streaming response using its <a href="https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse?"><code class="language-text">StreamingResponse</code></a> class from <code class="language-text">starlette.responses</code>.</p> <p>Let’s build a <code class="language-text">/stream</code> endpoint in <a href="https://github.com/sahansera/streaming-apis/blob/main/backend/api/index.py"><code class="language-text">index.py</code></a> that simulates real-time data like server logs or chat messages.</p> <h3>🚀 Simulating a Real-Time Log Stream</h3> <p>Here’s a minimal FastAPI app with a streaming endpoint:</p> <div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># backend/index.py</span> <span class="token keyword">from</span> typing <span class="token keyword">import</span> Any<span class="token punctuation">,</span> Generator <span class="token keyword">from</span> fastapi <span class="token keyword">import</span> FastAPI <span class="token keyword">from</span> fastapi<span class="token punctuation">.</span>responses <span class="token keyword">import</span> StreamingResponse <span class="token keyword">from</span> fastapi<span class="token punctuation">.</span>middleware<span class="token punctuation">.</span>cors <span class="token keyword">import</span> CORSMiddleware <span class="token keyword">import</span> time <span class="token keyword">import</span> os <span class="token keyword">import</span> uvicorn <span class="token comment"># Import uvicorn for running the server</span> <span class="token keyword">import</span> threading app <span class="token operator">=</span> FastAPI<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># Add CORS middleware to allow cross-origin requests</span> <span class="token comment"># ...</span> LOG_FILE_PATH <span class="token operator">=</span> <span class="token string">"logs/server.log"</span> <span class="token keyword">def</span> <span class="token function">log_stream</span><span class="token punctuation">(</span>log_file_path<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Generator<span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">]</span><span class="token punctuation">:</span> <span class="token keyword">try</span><span class="token punctuation">:</span> <span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span>log_file_path<span class="token punctuation">,</span> <span class="token string">"r"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> log_file<span class="token punctuation">:</span> <span class="token comment"># Move to the end of the file</span> _ <span class="token operator">=</span> log_file<span class="token punctuation">.</span>seek<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> os<span class="token punctuation">.</span>SEEK_END<span class="token punctuation">)</span> <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span> line <span class="token operator">=</span> log_file<span class="token punctuation">.</span>readline<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> line<span class="token punctuation">:</span> <span class="token keyword">yield</span> line <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string">"Waiting for new log entries...\n"</span> <span class="token comment"># Heartbeat message</span> time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment"># Wait for new lines to be written</span> <span class="token keyword">except</span> FileNotFoundError<span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string">"Log file not found.\n"</span> <span class="token keyword">except</span> Exception <span class="token keyword">as</span> e<span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string-interpolation"><span class="token string">f"Error reading log file: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token builtin">str</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string">\n"</span></span> <span class="token keyword">def</span> <span class="token function">simulate_log_generation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">"""Simulate log entries being written to the log file."""</span> <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span> <span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span>LOG_FILE_PATH<span class="token punctuation">,</span> <span class="token string">"a"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> log_file<span class="token punctuation">:</span> log_file<span class="token punctuation">.</span>write<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Simulated log entry at </span><span class="token interpolation"><span class="token punctuation">{</span>time<span class="token punctuation">.</span>ctime<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string">\n"</span></span><span class="token punctuation">)</span> time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment"># Write a new log entry every 5 seconds</span> <span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>on_event</span><span class="token punctuation">(</span><span class="token string">"startup"</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">start_log_simulation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">"""Start the log simulation in a background thread."""</span> threading<span class="token punctuation">.</span>Thread<span class="token punctuation">(</span>target<span class="token operator">=</span>simulate_log_generation<span class="token punctuation">,</span> daemon<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span><span class="token punctuation">.</span>start<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>get</span><span class="token punctuation">(</span><span class="token string">"/stream"</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> StreamingResponse<span class="token punctuation">(</span>log_stream<span class="token punctuation">(</span>LOG_FILE_PATH<span class="token punctuation">)</span><span class="token punctuation">,</span> media_type<span class="token operator">=</span><span class="token string">"text/plain"</span><span class="token punctuation">)</span> </code></pre></div> <h2>🧠 How It Works</h2> <p>Let’s break it down:</p> <h3>1. <strong>Synchronous Generator</strong></h3> <div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">log_stream</span><span class="token punctuation">(</span>log_file_path<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Generator<span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">]</span><span class="token punctuation">:</span> <span class="token keyword">try</span><span class="token punctuation">:</span> <span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span>log_file_path<span class="token punctuation">,</span> <span class="token string">"r"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> log_file<span class="token punctuation">:</span> _ <span class="token operator">=</span> log_file<span class="token punctuation">.</span>seek<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> os<span class="token punctuation">.</span>SEEK_END<span class="token punctuation">)</span> <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span> line <span class="token operator">=</span> log_file<span class="token punctuation">.</span>readline<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> line<span class="token punctuation">:</span> <span class="token keyword">yield</span> line <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string">"Waiting for new log entries...\n"</span> time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">except</span> FileNotFoundError<span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string">"Log file not found.\n"</span> <span class="token keyword">except</span> Exception <span class="token keyword">as</span> e<span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string-interpolation"><span class="token string">f"Error reading log file: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token builtin">str</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string">\n"</span></span></code></pre></div> <p>We define a synchronous generator function that yields data one chunk at a time. Each <code class="language-text">yield</code> becomes a chunk sent to the client. The <code class="language-text">time.sleep(1)</code> simulates delay between events (like logs being written).</p> <h3>2. <strong>StreamingResponse</strong></h3> <div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">StreamingResponse<span class="token punctuation">(</span>log_stream<span class="token punctuation">(</span>LOG_FILE_PATH<span class="token punctuation">)</span><span class="token punctuation">,</span> media_type<span class="token operator">=</span><span class="token string">"text/plain"</span><span class="token punctuation">)</span></code></pre></div> <p><code class="language-text">StreamingResponse</code> tells FastAPI to send the data as it becomes available, rather than waiting for the entire response to be generated. The <code class="language-text">media_type</code> is optional but helps inform the browser how to handle the content.</p> <h2>⚙️ Running the Server</h2> <p>To run the server:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">make</span> start-backend</code></pre></div> <p>Then hit:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">http://localhost:8000/stream</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABIUlEQVR42rWSX0uEQBTFx2nQ9Q8p/oHJjRFHx4i+WtDzDvUBe+grFNRbG7E+r+t0FCljH8og4cfl3jn3eEYk54S4Z5F9szpd3VJKN4QQPScIAh3HsU7TVEdRpBljGrovGNswSu/ANfQB4REvri7Vbl0KkyZpn2WZGYDJWGFkPM8bcV3XWJZlsDjiOo7hSdKHnBsvSbaYVSQMw7JW6r2qaiMKsVdKdUKIzvf9DunGCpMO4iOGOVLuLUoNeMWsIViWF03TFkVh8jzvpZSG443zJD/QDxX6N9u2G4JEEtdq8W0MBr2Da6B+LvzC+LshHommnR8u5MiwRLObDg+TYAmHyXD7bwnXaB7AM3gETwsZdl7APbzEYMgA/m+iQP1H1ORx8gE4cqSGR/aL5gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="streaming-apis-python-nextjs-part2-2.png" title="" src="/static/e8560d80474db7fc252b8f2e7cd3d05f/5a190/streaming-apis-python-nextjs-part2-2.png" srcset="/static/e8560d80474db7fc252b8f2e7cd3d05f/772e8/streaming-apis-python-nextjs-part2-2.png 200w, /static/e8560d80474db7fc252b8f2e7cd3d05f/e17e5/streaming-apis-python-nextjs-part2-2.png 400w, /static/e8560d80474db7fc252b8f2e7cd3d05f/5a190/streaming-apis-python-nextjs-part2-2.png 800w, /static/e8560d80474db7fc252b8f2e7cd3d05f/c1b63/streaming-apis-python-nextjs-part2-2.png 1200w, /static/e8560d80474db7fc252b8f2e7cd3d05f/29007/streaming-apis-python-nextjs-part2-2.png 1600w, /static/e8560d80474db7fc252b8f2e7cd3d05f/e04e6/streaming-apis-python-nextjs-part2-2.png 2152w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You should see logs appear one line at a time in the terminal or browser, depending on how you call the endpoint.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>💡 Tips for Production</h2> <h3>✅ <strong>Keep the Stream Alive</strong></h3> <p>In a real app, your data stream might be longer-running. Our example already implements this with a heartbeat:</p> <div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">if</span> line<span class="token punctuation">:</span> <span class="token keyword">yield</span> line <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token keyword">yield</span> <span class="token string">"Waiting for new log entries...\n"</span> <span class="token comment"># Heartbeat message</span> time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment"># Wait for new lines to be written</span></code></pre></div> <p>This ensures the client knows the connection is still alive even when there’s no new data.</p> <h3>🧹 <strong>Handle Disconnects Gracefully</strong></h3> <p>If the client disconnects, your generator should stop yielding. Starlette handles this internally, but you can wrap the stream in a <code class="language-text">try/except</code> block to catch cancellations if needed.</p> <h3>🔐 <strong>Secure Your Stream</strong></h3> <ul> <li>Add authentication if you’re streaming sensitive data.</li> <li>Rate-limit the endpoint to avoid abuse.</li> </ul> <hr> <h2>🧪 Testing with Curl</h2> <p>You can test the streaming endpoint with:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> http://localhost:8000/stream</code></pre></div> <p>You should see each message appear line by line, every second.</p> <hr> <h2>🔗 Hooking it up with the Frontend</h2> <p>Now that our backend is streaming correctly, the frontend from Part 1 will handle it smoothly:</p> <div class="gatsby-highlight" data-language="tsx"><pre class="language-tsx"><code class="language-tsx"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"http://localhost:8000/stream"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> reader <span class="token operator">=</span> response<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">getReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ...</span></code></pre></div> <p>As each <code class="language-text">yield</code> in the backend emits data, your React UI will update in near real-time. ✨</p> <h2>🧠 Recap</h2> <p>In this post, we built a real-time streaming API using FastAPI with just a few lines of code. Here’s what we did:</p> <ul> <li>Built a synchronous generator to simulate streaming logs</li> <li>Used <code class="language-text">StreamingResponse</code> to stream text over HTTP</li> <li>Connected it to our frontend from Part 1</li> </ul> <p>Together, the backend and frontend make a simple but powerful full-stack streaming system.</p> <h2>🧱 Next Steps</h2> <ul> <li>🔌 Add dynamic data (e.g. logs from a file or DB)</li> <li>📦 Stream structured data (like JSON Lines)</li> <li>📈 Use this setup for real-time dashboards or log viewers</li> </ul> <h2>🛠️ Useful Links</h2> <ul> <li><a href="https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse">FastAPI: StreamingResponse</a></li> <li><a href="https://docs.python.org/3/glossary.html#term-generator">Python Generators</a></li> <li><a href="https://www.uvicorn.org/">Uvicorn Docs</a></li> <li><a href="https://github.com/sahansera/streaming-apis">GitHub Repo</a></li> </ul> <hr> <p>If you enjoyed this series, feel free to star the repo ⭐ or share it with a friend. Got ideas or feedback? Hit me up on <a href="https://twitter.com/sahansera">Twitter</a> or drop an issue in the GitHub repo.</p> <p>Happy streaming! 🚀</p><![CDATA[Streaming APIs with FastAPI and Next.js - Part 1]]>https://www.sahansera.dev/streaming-apis-python-nextjs-part1/https://www.sahansera.dev/streaming-apis-python-nextjs-part1/Sun, 23 Mar 2025 00:00:00 GMT<p>Streaming data in the browser is one of those things that feels magical the first time you see it: data appears live - no need to wait for the full response to load. In this two-part series, we’ll walk through building a small full-stack app that uses <strong>FastAPI</strong> to stream data and a <strong>Next.js</strong> frontend to consume and render it in real-time.</p> <blockquote> <p>💡 <strong>Code Repository</strong>: All the code for this post is available on <a href="https://github.com/sahansera/streaming-apis">GitHub</a>. Feel free to explore, clone, and experiment with it!</p> </blockquote> <p>There are many ways to stream data in the browser, from WebSockets to Server-Sent Events (SSE). But in this post, we’ll focus on a lesser-known method: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding">chunked transfer encoding</a>. This technique allows the server to send data in small, manageable chunks, which the browser can process as they arrive.</p> <p>If you are interested in learning about Websockets, check out my post on <a href="https://sahansera.dev/understanding-websockets-with-aspnetcore-5/">Understanding WebSockets with ASP.NET</a>. It’s targeted at .NET developers, but the concepts are similar. Again, the focus here is on streaming data in the browser and WebSockets is a different protocol altogether.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>This post focuses on the <strong>frontend</strong> bit. In <a href="https://sahansera.dev/streaming-apis-python-nextjs-part2/">Part 2</a>, we’ll dive into building the <strong>FastAPI backend</strong>.</p> <hr> <h2>🔧 The Setup</h2> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 61.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB13TMoon/xAAaEAACAgMAAAAAAAAAAAAAAAAAAREhAgMi/9oACAEBAAEFAp62UYWoGpFR/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFhEBAQEAAAAAAAAAAAAAAAAAAAEx/9oACAECAQE/AbiP/8QAFhAAAwAAAAAAAAAAAAAAAAAAAAEg/9oACAEBAAY/AhT/AP/EABsQAAICAwEAAAAAAAAAAAAAAAERACExcYGh/9oACAEBAAE/ISQofRCSy6IRaS+zNsytZGoCJvc//9oADAMBAAIAAwAAABB/P//EABYRAQEBAAAAAAAAAAAAAAAAAAEAMf/aAAgBAwEBPxBxjL//xAAXEQEAAwAAAAAAAAAAAAAAAAAAATFR/9oACAECAQE/EMlIf//EABsQAQADAAMBAAAAAAAAAAAAAAEAESFBUbEx/9oACAEBAAE/EEBSXwEdBdu0ey8l27R8iXu+4ZDWOVTPLX1Wz//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="streaming apis python nextjs part1 1" title="" src="/static/05ca100ab436a1990c8d1f5ae0f6a7a8/4b190/streaming-apis-python-nextjs-part1-1.jpg" srcset="/static/05ca100ab436a1990c8d1f5ae0f6a7a8/e07e9/streaming-apis-python-nextjs-part1-1.jpg 200w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/066f9/streaming-apis-python-nextjs-part1-1.jpg 400w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/4b190/streaming-apis-python-nextjs-part1-1.jpg 800w, /static/05ca100ab436a1990c8d1f5ae0f6a7a8/a768a/streaming-apis-python-nextjs-part1-1.jpg 1037w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>How the data will flow from Backend to the Frontend</i></center> <p>Let’s say you have a streaming API running locally at:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">http://localhost:8000/stream</code></pre></div> <p>This endpoint sends back a stream of text data - think server logs, chat messages, or real-time updates. The goal is to connect to this endpoint from a React component and display data <strong>as it arrives</strong>.</p> <p>Here’s a simplified version of the key parts of our React component (<a href="https://github.com/sahansera/streaming-apis/blob/main/frontend/pages/index.tsx"><code class="language-text">index.tsx</code></a>):</p> <div class="gatsby-highlight" data-language="tsx"><pre class="language-tsx"><code class="language-tsx"><span class="token comment">// Key imports</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> useEffect<span class="token punctuation">,</span> useState<span class="token punctuation">,</span> useCallback <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">IndexPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>dataChunks<span class="token punctuation">,</span> setDataChunks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token punctuation">{</span> timestamp<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> log<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>isLoading<span class="token punctuation">,</span> setIsLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>error<span class="token punctuation">,</span> setError<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> fetchStream <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Reset state</span> <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setError</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setDataChunks</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment">// 1. Connect to the stream</span> <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"http://localhost:8000/stream"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok <span class="token operator">||</span> <span class="token operator">!</span>response<span class="token punctuation">.</span>body<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Stream connection failed</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 2. Get a reader from the stream</span> <span class="token keyword">const</span> reader <span class="token operator">=</span> response<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">getReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> decoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextDecoder</span><span class="token punctuation">(</span><span class="token string">"utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. Read chunks until done</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">,</span> done <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> reader<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>done<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token comment">// 4. Decode and update UI with each chunk</span> <span class="token keyword">const</span> chunk <span class="token operator">=</span> decoder<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setDataChunks</span><span class="token punctuation">(</span><span class="token punctuation">(</span>prev<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token operator">...</span>prev<span class="token punctuation">,</span> <span class="token punctuation">{</span> timestamp<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLocaleTimeString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> log<span class="token operator">:</span> chunk <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setError</span><span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">Error</span> <span class="token operator">?</span> error<span class="token punctuation">.</span>message <span class="token operator">:</span> <span class="token function">String</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Connect to stream on component mount</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">fetchStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>fetchStream<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Rendering components (simplified)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Server Log Viewer</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token punctuation">{</span><span class="token comment">/* Error handling and rendering of streamed data */</span><span class="token punctuation">}</span><span class="token plain-text"> </span><span class="token punctuation">{</span><span class="token comment">/* See full code on GitHub */</span><span class="token punctuation">}</span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <hr> <h2>🧠 What’s Really Happening?</h2> <p>Let’s break this down and understand the key concepts behind streaming in the browser:</p> <h3>1. <strong>Fetching the Stream</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"http://localhost:8000/stream"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Unlike regular <code class="language-text">fetch()</code> calls that wait for the entire response before handing it over, this gives you access to the <strong>ReadableStream</strong> - letting you process data chunk-by-chunk.</p> <h3>2. <strong>Getting a Reader</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> reader <span class="token operator">=</span> response<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">getReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This gives us a <code class="language-text">ReadableStreamDefaultReader</code>, which lets us manually pull chunks of data from the response. This is part of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams API</a>, now supported in all major browsers.</p> <h3>3. <strong>Reading Chunks</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">,</span> done <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> reader<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ul> <li><code class="language-text">value</code>: a <code class="language-text">Uint8Array</code> representing a chunk of binary data.</li> <li><code class="language-text">done</code>: <code class="language-text">true</code> when the stream is finished.</li> </ul> <p>We loop until <code class="language-text">done</code> becomes <code class="language-text">true</code>.</p> <h3>4. <strong>Decoding the Text</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> decoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextDecoder</span><span class="token punctuation">(</span><span class="token string">"utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> chunk <span class="token operator">=</span> decoder<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token punctuation">{</span> stream<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Streaming responses may split characters across chunks, especially for multi-byte encodings like UTF-8. <code class="language-text">TextDecoder</code> handles this for us, making sure our text is correctly reconstructed.</p> <h3>5. <strong>Updating the UI</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token function">setDataChunks</span><span class="token punctuation">(</span><span class="token punctuation">(</span>prev<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token operator">...</span>prev<span class="token punctuation">,</span> chunk<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We append each new chunk to our state array. React re-renders the component with every update, giving us that real-time feel.</p> <h3>6. <strong>One-Time Effect</strong></h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The fetch logic runs once when the component mounts. Perfect for one-time side effects like opening a connection.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <hr> <h2>✅ Recap</h2> <p>With just a few lines of code, we’ve created a streaming experience in the browser using modern Web APIs. The key things were:</p> <ul> <li><code class="language-text">fetch()</code> with a streaming response</li> <li><code class="language-text">ReadableStream</code> + <code class="language-text">TextDecoder</code></li> <li>Updating React state to progressively display data</li> </ul> <p>In <a href="https://sahansera.dev/streaming-apis-python-nextjs-part2/">Part 2</a>, we’ll build the <strong>FastAPI backend</strong> to power this stream - including how to set up a streaming route and control flush timing for chunked responses.</p> <hr> <h2>💡 Gotchas to Watch Out For</h2> <ul> <li><strong>CORS</strong>: Make sure your FastAPI server has CORS enabled if frontend and backend are on different ports/domains.</li> <li><strong>Buffering</strong>: Some servers (or even browsers like Safari) buffer streaming responses - so test in Chrome/Edge for best results.</li> <li><strong>Cleanup</strong>: If you’re adding WebSocket or long-running fetches, remember to cancel or clean them up in <code class="language-text">useEffect</code> cleanup.</li> </ul> <h2>📚 References</h2> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API?utm_source=sahansera.dev">Streams API</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder?utm_source=sahansera.dev">TextDecoder</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream?utm_source=sahansera.dev">ReadableStream</a></li> <li><a href="https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse?utm_source=sahansera.dev">Streaming Response</a></li> </ul><![CDATA[My plans for sahansera.dev in 2024]]>https://www.sahansera.dev/my-plans-for-sahanseradev-2024/https://www.sahansera.dev/my-plans-for-sahanseradev-2024/Sun, 31 Dec 2023 00:00:00 GMT<h2>Reflecting Back on 2023</h2> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFBP/EABUBAQEAAAAAAAAAAAAAAAAAAAID/9oADAMBAAIQAxAAAAHNQjPhVxPGP//EABsQAAIDAAMAAAAAAAAAAAAAAAECAAMSERMx/9oACAEBAAEFAq14hdu23O1JjQ+//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPwFPCn//xAAXEQADAQAAAAAAAAAAAAAAAAAAAQIh/9oACAECAQE/AanRSf/EABkQAAMAAwAAAAAAAAAAAAAAAAABESAhof/aAAgBAQAGPwKvokpIbmH/xAAaEAADAQEBAQAAAAAAAAAAAAAAAREhMUGh/9oACAEBAAE/IXskJfBSUtJPC2lzx0o6zSR0P//aAAwDAQACAAMAAAAQG/8A/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAEhQf/aAAgBAwEBPxDIm7//xAAYEQACAwAAAAAAAAAAAAAAAAAAEQEhMf/aAAgBAgEBPxBlhEaf/8QAHRABAAICAgMAAAAAAAAAAAAAAQARITFhcZGxwf/aAAgBAQABPxAt9Vj4nUrEowBwO66jW4tdo9QR1ayxKXn5Meyf/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="my plans for sahanseradev 2024 1" title="" src="/static/be8a2d214703192e494297ba8680d1c5/4b190/my-plans-for-sahanseradev-2024-1.jpg" srcset="/static/be8a2d214703192e494297ba8680d1c5/e07e9/my-plans-for-sahanseradev-2024-1.jpg 200w, /static/be8a2d214703192e494297ba8680d1c5/066f9/my-plans-for-sahanseradev-2024-1.jpg 400w, /static/be8a2d214703192e494297ba8680d1c5/4b190/my-plans-for-sahanseradev-2024-1.jpg 800w, /static/be8a2d214703192e494297ba8680d1c5/e5166/my-plans-for-sahanseradev-2024-1.jpg 1200w, /static/be8a2d214703192e494297ba8680d1c5/b17f8/my-plans-for-sahanseradev-2024-1.jpg 1600w, /static/be8a2d214703192e494297ba8680d1c5/0f98f/my-plans-for-sahanseradev-2024-1.jpg 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>It’s no secret that my blogging took a backseat this year - becoming a first-time dad has been a joyful, yet the most challenging journey of my life. It has been a year of new beginnings: a precious addition to the family, moving into a new home, and even transitioning to a new job. Through all this, my commitment to being the best dad possible remained unwavering.</p> <p>Despite the fewer posts, 2023 had its highlights, including some popular posts and milestones that resonated well with you, my readers. Your support and engagement have been the driving force behind every article, and for that, I am incredibly grateful.</p> <h2>Vision for 2024</h2> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABv2R8POIH/8QAGxAAAQUBAQAAAAAAAAAAAAAAAAECAxMjEjL/2gAIAQEAAQUCt0llqOiBc18Mc5D/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwFX/8QAHRAAAQQCAwAAAAAAAAAAAAAAAAEREjECECFRYf/aAAgBAQAGPwKGKP34PHgvSln/xAAZEAEAAwEBAAAAAAAAAAAAAAABABEhQWH/2gAIAQEAAT8hc3urXIUCT2nJvQJGD7GtsEQxc//aAAwDAQACAAMAAAAQCy//xAAXEQADAQAAAAAAAAAAAAAAAAAAAREh/9oACAEDAQE/EMo0f//EABYRAQEBAAAAAAAAAAAAAAAAABEBEP/aAAgBAgEBPxBpj//EABwQAQACAgMBAAAAAAAAAAAAAAEAEUGBIVGRof/aAAgBAQABPxA0igN0L3GMJp3tiyKBYmSJA0q2sy8C8V8imgOjRP/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="my plans for sahanseradev 2024 2" title="" src="/static/3206247c41c235ecd357bec94845d705/4b190/my-plans-for-sahanseradev-2024-2.jpg" srcset="/static/3206247c41c235ecd357bec94845d705/e07e9/my-plans-for-sahanseradev-2024-2.jpg 200w, /static/3206247c41c235ecd357bec94845d705/066f9/my-plans-for-sahanseradev-2024-2.jpg 400w, /static/3206247c41c235ecd357bec94845d705/4b190/my-plans-for-sahanseradev-2024-2.jpg 800w, /static/3206247c41c235ecd357bec94845d705/e5166/my-plans-for-sahanseradev-2024-2.jpg 1200w, /static/3206247c41c235ecd357bec94845d705/b17f8/my-plans-for-sahanseradev-2024-2.jpg 1600w, /static/3206247c41c235ecd357bec94845d705/0f98f/my-plans-for-sahanseradev-2024-2.jpg 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>As I look to 2024, I’m excited to dive back into more consistent and engaging content for sahansera.dev. Here’s what you can look forward to:</p> <ul> <li> <p><strong>Advanced Software Engineering Topics:</strong> I’m planning to delve into more complex issues like event-driven systems and distributed computing. Expect in-depth analyses and practical advice drawn from my personal experiences.</p> </li> <li> <p><strong>Meta Engineering Insights:</strong> I’ll share more about career growth, overcoming industry challenges, and the importance of soft skills. This will be a mix of advice, personal stories, and strategies to help you navigate your career path, all shared freely and openly.</p> </li> <li> <p><strong>Expanding to Visual Tutorials:</strong> Leveraging my position at a leading online graphic designing platform, I plan to produce more visual content, possibly reviving my <a href="https://www.youtube.com/@SahanSeraDEV/featured">YouTube channel</a>. This means tutorials, walkthroughs, and maybe some behind-the-scenes content!</p> </li> <li> <p><strong>Gaming and Inspiration:</strong> I’ve rekindled my passion for gaming and might share how it intersects with creativity and programming. If you’ve ever been inspired by gaming in your tech journey, you’ll find these anecdotes and insights particularly relatable.</p> </li> </ul> <h2>Engagement and Community Building</h2> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAACAf/aAAwDAQACEAMQAAAB0oUA2iop/8QAGxAAAgIDAQAAAAAAAAAAAAAAAQIAAxESEzL/2gAIAQEAAQUCOrKXVZ3Eu92KBFrTH//EABcRAQADAAAAAAAAAAAAAAAAAAABETH/2gAIAQMBAT8B1Uv/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQIBAT8Bi4//xAAaEAADAQADAAAAAAAAAAAAAAAAARExAgOR/9oACAEBAAY/Ak6W+GHI64tRh//EABwQAAICAgMAAAAAAAAAAAAAAAARASExQVFhsf/aAAgBAQABPyGEEjzYkwpTEhNeyqqSQ/Iydmtn/9oADAMBAAIAAwAAABBMz//EABcRAQEBAQAAAAAAAAAAAAAAAAEAESH/2gAIAQMBAT8QE4Z2v//EABcRAAMBAAAAAAAAAAAAAAAAAAABETH/2gAIAQIBAT8QUYIP/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARIUExgcH/2gAIAQEAAT8Qt20Ld52p56AB6PXZccUdAPyUKQWADMJrEvqvEcVudW3Wf//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="my plans for sahanseradev 2024 3" title="" src="/static/d2f70e5889216dd446b518b89dd2b675/4b190/my-plans-for-sahanseradev-2024-3.jpg" srcset="/static/d2f70e5889216dd446b518b89dd2b675/e07e9/my-plans-for-sahanseradev-2024-3.jpg 200w, /static/d2f70e5889216dd446b518b89dd2b675/066f9/my-plans-for-sahanseradev-2024-3.jpg 400w, /static/d2f70e5889216dd446b518b89dd2b675/4b190/my-plans-for-sahanseradev-2024-3.jpg 800w, /static/d2f70e5889216dd446b518b89dd2b675/e5166/my-plans-for-sahanseradev-2024-3.jpg 1200w, /static/d2f70e5889216dd446b518b89dd2b675/b17f8/my-plans-for-sahanseradev-2024-3.jpg 1600w, /static/d2f70e5889216dd446b518b89dd2b675/0f98f/my-plans-for-sahanseradev-2024-3.jpg 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>My goal is to not only grow the blog to 20,000 monthly active users (a 33% increase in MAU) by the end of 2024 but also to foster a more engaged and interactive community. Here’s how I plan to do it:</p> <ul> <li> <p><strong>Consistent and Quality Content:</strong> Focusing on the themes mentioned above, I aim to publish regularly and maintain the high-quality, insightful content you expect.</p> </li> <li> <p><strong>YouTube Channel Growth:</strong> While I don’t have a specific subscriber count in mind yet, creating and sharing at least 10 quality videos over the year is a target I’m setting for myself.</p> </li> </ul> <h2>Closing Thoughts</h2> <p>As we wrap up another year, I want to reiterate my commitment to this blog and to you, my readers. Your engagement, feedback, and support are what make sahansera.dev thrive.</p> <p>I’m optimistic about what 2024 holds and excited to embark on this journey with you. Here’s to another year of growth, learning, and sharing in the vast world of software engineering! 🤗</p><![CDATA[Basics of Apache Kafka - An Overview]]>https://www.sahansera.dev/introduction-to-apache-kafka/https://www.sahansera.dev/introduction-to-apache-kafka/Tue, 03 Jan 2023 00:00:00 GMT<p>A few months ago I wrote an article on <a href="https://sahansera.dev/setting-up-kafka-locally-for-testing/">setting up a local Apache Kafka instance</a>. In this article, I will briefly introduce Apache Kafka and some of its core concepts. We will look at the these concepts from a high level and understand how they are put together.</p> <h2>What is Kafka?</h2> <p>Apache Kafka is an open-source distributed event streaming platform for building real-time data pipelines and streaming applications. It is designed to handle high volumes of data and enable real-time processing of data streams.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>Kafka is typically used in scenarios where data needs to be processed in real-time, such as in real-time analytics, event-driven architectures, and other streaming applications. It can be integrated with various systems, such as databases, storage systems, and stream processing frameworks, to support a wide range of use cases.</p> <h2>Event Streaming vs Messages Queues</h2> <p>I have seen people using Kafka interchangeably with message queues. However, they are two things. Kafka is a distributed event streaming system whereas message queuing systems like RabbitMQ, Amazon SQS, and Azure Queue Storage.</p> <p>I suppose confusion must have stemmed mainly from the fact that the feature sets of these systems are getting more or less the same and the line between them is getting blurred. However, it is advisable to understand trade-offs among these systems and pick one that suits you best without overcomplicating the overall solution.</p> <h2>Why Kafka?</h2> <p>There are several reasons why Apache Kafka is a popular choice for building real-time data pipelines and streaming applications:</p> <ol> <li>Scalability: Kafka is designed to handle high volumes of data and to scale horizontally, making it suitable for handling large amounts of data.</li> <li>Real-time processing: Kafka enables real-time processing of data streams, allowing applications to react to new data as soon as it arrives.</li> <li>Fault-tolerant: Kafka is fault-tolerant and highly available, meaning it can continue operating even if some of its components fail.</li> <li>Flexibility: Kafka can be integrated with many systems and frameworks, making it a versatile platform for building streaming applications.</li> <li>High performance: Kafka has been designed to provide high throughput and low latency, making it suitable for applications that require fast processing of data streams.</li> </ol> <p>In a nutshell, imagine you have the following tightly coupled spaghetti of systems (could be deployed internally within your organization or a public-facing app)</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAIF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAdsVQj//xAAWEAEBAQAAAAAAAAAAAAAAAAAhABD/2gAIAQEAAQUCML//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAQ/9oACAEBAAY/An//xAAbEAACAQUAAAAAAAAAAAAAAAAAEQEhMUFhcf/aAAgBAQABPyHBMpu5HR//2gAMAwEAAgADAAAAEIQv/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8QiP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABkQAAIDAQAAAAAAAAAAAAAAAAEhABExQf/aAAgBAQABPxAHFpIwFUAwjsOhrrtz/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-1" title="" src="/static/0e8d501da99f5781f4298dcea9c16834/4b190/basics-of-apache-kafka-an-overview-1.jpg" srcset="/static/0e8d501da99f5781f4298dcea9c16834/e07e9/basics-of-apache-kafka-an-overview-1.jpg 200w, /static/0e8d501da99f5781f4298dcea9c16834/066f9/basics-of-apache-kafka-an-overview-1.jpg 400w, /static/0e8d501da99f5781f4298dcea9c16834/4b190/basics-of-apache-kafka-an-overview-1.jpg 800w, /static/0e8d501da99f5781f4298dcea9c16834/e5166/basics-of-apache-kafka-an-overview-1.jpg 1200w, /static/0e8d501da99f5781f4298dcea9c16834/b17f8/basics-of-apache-kafka-an-overview-1.jpg 1600w, /static/0e8d501da99f5781f4298dcea9c16834/9ebbf/basics-of-apache-kafka-an-overview-1.jpg 2338w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>For instance, you could imagine these services to be separate microservices or even monoliths that need to defer their workloads to some other systems. As we can see services 1 - 5 talk to services A - E. This becomes extremely difficult to maintain and will cause massive tech debt.</p> <p>Following is a refactored communication flow utilizing Kafka. Your line of business applications, or microservices can be added or removed as you go. It also provides a consistent API for both produces and consumers. But, could this be a single point of failure? What if Kafka goes down? or can it go down? You’ll find the answers in the upcoming sections, why that’s not the case.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAdyFUH//xAAVEAEBAAAAAAAAAAAAAAAAAAAAIf/aAAgBAQABBQKIj//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAABD/2gAIAQEABj8Cf//EABYQAQEBAAAAAAAAAAAAAAAAAADBAf/aAAgBAQABPyGlMP/aAAwDAQACAAMAAAAQd9//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPxCI/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxAAAgMAAwAAAAAAAAAAAAAAETEAAUEhUZH/2gAIAQEAAT8QOQEbhDPTlhvs8uf/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-2.jpg" title="" src="/static/566369b9507dbd2d5dec218b28ac4c22/4b190/basics-of-apache-kafka-an-overview-2.jpg" srcset="/static/566369b9507dbd2d5dec218b28ac4c22/e07e9/basics-of-apache-kafka-an-overview-2.jpg 200w, /static/566369b9507dbd2d5dec218b28ac4c22/066f9/basics-of-apache-kafka-an-overview-2.jpg 400w, /static/566369b9507dbd2d5dec218b28ac4c22/4b190/basics-of-apache-kafka-an-overview-2.jpg 800w, /static/566369b9507dbd2d5dec218b28ac4c22/e5166/basics-of-apache-kafka-an-overview-2.jpg 1200w, /static/566369b9507dbd2d5dec218b28ac4c22/b17f8/basics-of-apache-kafka-an-overview-2.jpg 1600w, /static/566369b9507dbd2d5dec218b28ac4c22/9c36d/basics-of-apache-kafka-an-overview-2.jpg 2323w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Putting everything together</h2> <p>Below is a diagram that I’ve created to show you a typical request flow in Kafka. The numbers correspond to each subheading that I will describe in much detail below. In each section, I will zoom in on a particular concept to provide any relevant context.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB3YuQsYL/AP/EABUQAQEAAAAAAAAAAAAAAAAAACBB/9oACAEBAAEFAof/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAZEAADAAMAAAAAAAAAAAAAAAAAAREQIVH/2gAIAQEAAT8hd2KV9zD/2gAMAwEAAgADAAAAECT/AP/EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPxCNj//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/EGf/xAAaEAEAAgMBAAAAAAAAAAAAAAABABEhMVEQ/9oACAEBAAE/EFyY0mYK7uNjlvviFmDcpwn/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-3.jpg" title="" src="/static/944a6b539b82f36993d8a14d04886d20/4b190/basics-of-apache-kafka-an-overview-3.jpg" srcset="/static/944a6b539b82f36993d8a14d04886d20/e07e9/basics-of-apache-kafka-an-overview-3.jpg 200w, /static/944a6b539b82f36993d8a14d04886d20/066f9/basics-of-apache-kafka-an-overview-3.jpg 400w, /static/944a6b539b82f36993d8a14d04886d20/4b190/basics-of-apache-kafka-an-overview-3.jpg 800w, /static/944a6b539b82f36993d8a14d04886d20/e5166/basics-of-apache-kafka-an-overview-3.jpg 1200w, /static/944a6b539b82f36993d8a14d04886d20/b17f8/basics-of-apache-kafka-an-overview-3.jpg 1600w, /static/944a6b539b82f36993d8a14d04886d20/15051/basics-of-apache-kafka-an-overview-3.jpg 4264w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Before we jump in, I need to mention that Kafka exposes a client API that could be used by users to write and read messages. You could use your programming language of choice to interact with the cluster using these API calls.</p> <h2>1. Producers</h2> <p>Producers write data (or events) to topics, and consumers read data from topics. Producers could be anything like an IoT sensor, activity tracking system, a frontend server sending metrics etc. You can specify a key/id in your message in order to make sure that the messages always end up in the same partition. More on these will be covered in Events, Topics and Partitions sections.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAQACBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHvGyCY/8QAFhAAAwAAAAAAAAAAAAAAAAAAAAEg/9oACAEBAAEFAhz/AP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABgQAQADAQAAAAAAAAAAAAAAADEAAREg/9oACAEBAAE/IcvWBlDx/9oADAMBAAIAAwAAABBb7//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/EGf/xAAbEAABBAMAAAAAAAAAAAAAAAABABEgMUFxgf/aAAgBAQABPxC49ERYDq0b5OQ//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-4.jpg" title="" src="/static/3239f05bb5137045d21af46069eab3d0/4b190/basics-of-apache-kafka-an-overview-4.jpg" srcset="/static/3239f05bb5137045d21af46069eab3d0/e07e9/basics-of-apache-kafka-an-overview-4.jpg 200w, /static/3239f05bb5137045d21af46069eab3d0/066f9/basics-of-apache-kafka-an-overview-4.jpg 400w, /static/3239f05bb5137045d21af46069eab3d0/4b190/basics-of-apache-kafka-an-overview-4.jpg 800w, /static/3239f05bb5137045d21af46069eab3d0/e5166/basics-of-apache-kafka-an-overview-4.jpg 1200w, /static/3239f05bb5137045d21af46069eab3d0/b17f8/basics-of-apache-kafka-an-overview-4.jpg 1600w, /static/3239f05bb5137045d21af46069eab3d0/e8a7c/basics-of-apache-kafka-an-overview-4.jpg 2283w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Other than that, it’s worth noting that the Producers can choose 3 methods to send messages (fire and forget, Synchronous send &#x26; Asynchronous send) and also they can wait for acknowledgements (none, any or all) as well.</p> <h2>2. Events</h2> <p>An event in Apache Kafka is a record of an occurrence or state change in a system. Sometimes it’s also called a <strong>record</strong> or a <strong>message</strong> as well. This could be a user interaction, a change in data, or a notification from another system. Events are stored in Kafka topics and are typically published by producers and consumed by consumers. Events in Kafka are <strong>immutable</strong>, such that they cannot be changed once they have been published.</p> <p><strong>Data formats</strong></p> <p>Kafka sees messages are just byte arrays. However, we humans need a way to serialize and deserialize this data. Therefore, you could use a <strong>schema</strong> for it such as JSON, Protocol Buffers, or Apache Avro (used in Hadoop).</p> <p>What’s important to understand is that you use a consistent data format in your applications.</p> <p>Here is an example of a Kafka event with a key and value that are both strings:</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">Key: "user_id" Value: "12345" Timestamp: "20 Dec 2022" Headers: {}</code></pre></div> <h2>3. Topics and Partitions</h2> <p>In Kafka, a <strong>topic</strong> is a way to separate out different messages coming in from different systems. Each topic is partitioned, meaning that the data in a topic is split into multiple <strong>partitions</strong> distributed across the Kafka cluster. This allows for redundancy and scalability within Kafka.</p> <p>If a given topic is partitioned across multiple nodes (i.e. servers), how does Kafka know which message goes to which partition also while preserving the order? This is where the <strong>key</strong> of a message comes in handy. Kafka uses the key of a message and runs it through a hash function and mod (%) the resulting number so that it knows where to write that message.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 89.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB7jGWmkqRqFf/xAAWEAEBAQAAAAAAAAAAAAAAAAAgARH/2gAIAQEAAQUC0wf/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ah//xAAaEAEAAwEBAQAAAAAAAAAAAAABABARITFB/9oACAEBAAE/IdawWGvdida8U/K//9oADAMBAAIAAwAAABDb5wD/xAAWEQEBAQAAAAAAAAAAAAAAAAABABD/2gAIAQMBAT8QSd//xAAWEQEBAQAAAAAAAAAAAAAAAAABABD/2gAIAQIBAT8QGN//xAAcEAEAAwACAwAAAAAAAAAAAAABABEhEDFBUbH/2gAIAQEAAT8Qaoqhh8729gLUvxRkTqe4G6MCG/XH24//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-5.jpg" title="" src="/static/a23099ad33bcabace5c63b8058975177/4b190/basics-of-apache-kafka-an-overview-5.jpg" srcset="/static/a23099ad33bcabace5c63b8058975177/e07e9/basics-of-apache-kafka-an-overview-5.jpg 200w, /static/a23099ad33bcabace5c63b8058975177/066f9/basics-of-apache-kafka-an-overview-5.jpg 400w, /static/a23099ad33bcabace5c63b8058975177/4b190/basics-of-apache-kafka-an-overview-5.jpg 800w, /static/a23099ad33bcabace5c63b8058975177/e5166/basics-of-apache-kafka-an-overview-5.jpg 1200w, /static/a23099ad33bcabace5c63b8058975177/b17f8/basics-of-apache-kafka-an-overview-5.jpg 1600w, /static/a23099ad33bcabace5c63b8058975177/408fb/basics-of-apache-kafka-an-overview-5.jpg 2311w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>What if a message doesn’t have a key (key being NULL)? Well, in that case, Kafka defaults to a Round-Robin approach to write the messages to the partitions.</p> <blockquote> <p>💡 Messages with the same key go to the same partition thus guaranteeing the order.</p> </blockquote> <p>It’s also worth mentioning that a partition is not strictly tied to a single server. They can also scale out horizontally depending on your requirements.</p> <p><strong>Brokers</strong></p> <p>if you start working on Kafka, you’d come across the term brokers. A broker is just a computer, server, container, or cloud instance that’s running a <strong>Kafka broker process</strong>. So what do they do? They are responsible for managing the partitions that we talked about in the previous section. They also handle any read/write requests.</p> <p>Remember I mentioned that a partition can be replicated across multiple nodes? Brokers manage that replication too. But there has to be some kind of way to orchestrate this replication, right? In Kafka, they follow a leader-follower approach to tackle that. Generally speaking, reading and writing happens at the leader level. Then the leader and followers work among themselves to get the replication done.</p> <h2>4. Consumers &#x26; Consumer Groups</h2> <p>The “consumer” is a complex concept in Kafka. Consumers are applications that <strong>subscribe</strong> to a set of Kafka topics and read from topic partitions. Consumers track their offsets as they progress through topic partitions and periodically <strong>commit offsets</strong> to Kafka, which ensures that they can resume from the last processed offset in the event of a restart. These offsets are incremental and the brokers will know which messages to send when the consumer requests more data.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 100.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHvTsy60lyUB//EABgQAAMBAQAAAAAAAAAAAAAAAAABEBEh/9oACAEBAAEFAtGI6MUyf//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABQQAQAAAAAAAAAAAAAAAAAAADD/2gAIAQEABj8CH//EABsQAAMAAgMAAAAAAAAAAAAAAAABERAxIUFx/9oACAEBAAE/Idh7DV89koqqmr9w6Eof/9oADAMBAAIAAwAAABAzN33/xAAXEQADAQAAAAAAAAAAAAAAAAAAATER/9oACAEDAQE/EFg6Kjp//8QAFREBAQAAAAAAAAAAAAAAAAAAICH/2gAIAQIBAT8Qo//EAB0QAQACAgIDAAAAAAAAAAAAAAEAESExEEFRYXH/2gAIAQEAAT8QSUprtmFFBvUvsNYXFLCSrvU6nufXBMVRPEMoK23mf//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="basics-of-apache-kafka-an-overview-6.jpg" title="" src="/static/330c7c4ed2dcc03c5a58c14eb146ef97/4b190/basics-of-apache-kafka-an-overview-6.jpg" srcset="/static/330c7c4ed2dcc03c5a58c14eb146ef97/e07e9/basics-of-apache-kafka-an-overview-6.jpg 200w, /static/330c7c4ed2dcc03c5a58c14eb146ef97/066f9/basics-of-apache-kafka-an-overview-6.jpg 400w, /static/330c7c4ed2dcc03c5a58c14eb146ef97/4b190/basics-of-apache-kafka-an-overview-6.jpg 800w, /static/330c7c4ed2dcc03c5a58c14eb146ef97/e5166/basics-of-apache-kafka-an-overview-6.jpg 1200w, /static/330c7c4ed2dcc03c5a58c14eb146ef97/b17f8/basics-of-apache-kafka-an-overview-6.jpg 1600w, /static/330c7c4ed2dcc03c5a58c14eb146ef97/a9387/basics-of-apache-kafka-an-overview-6.jpg 1718w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>As shown above, same consumer can also listen to mutiple topics as well.</p> <blockquote> <p>💡 What sets Kafka apart from traditional Message Queues is that reading a message does not destroy it.</p> </blockquote> <h2>Why is Kafka fast?</h2> <p>Apache Kafka uses a combination of in-memory storage and disk storage to achieve high scalability. Kafka stores data in memory in a log structure, which allows for fast access and processing.</p> <p>However, as the amount of data in a Kafka cluster grows, it can eventually exceed the available memory. In this case, Kafka will automatically flush data to disk to free up memory. This allows Kafka to scale horizontally by adding more nodes to the cluster, which can provide additional memory and disk storage capacity. By combining in-memory and disk storage, Kafka can achieve high scalability while still providing fast access and processing of data.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>How do I build a Kafka cluster?</h2> <p>If your organization has a need to deploy your own cluster at a data center you can do that. Kafka’s deployment targets range from bare metal servers to cloud providers. If you are rolling out your own, beware that it’s not easy to set it up it <strong>will</strong> take time.</p> <p><strong>Locally (Development)</strong></p> <ul> <li>Using Docker is the easiest I’ve found. Follow this <a href="https://sahansera.dev/setting-up-kafka-locally-for-testing/">guide</a> to understand how you can do that.</li> <li>Or you could YOLO and <a href="https://kafka.apache.org/quickstart">install the binaries</a> yourself 😀</li> </ul> <p><strong>Cloud (Production)</strong></p> <p>For your production needs, your best bet would be to use</p> <ul> <li><a href="https://www.confluent.io/confluent-cloud/?utm_medium=sem&#x26;utm_source=google&#x26;utm_campaign=ch.sem_br.brand_tp.prs_tgt.confluent-brand_mt.mbm_rgn.apac_lng.eng_dv.all_con.confluent-cloud&#x26;utm_term=%2Bconfluent%20%2Bcloud&#x26;creative=&#x26;device=c&#x26;placement=&#x26;gclid=CjwKCAiAkfucBhBBEiwAFjbkr2K1kTxef5V7TbtFMCskzax9yIBTsKrmZkSWlc0gT-uEH61Tf7RWRhoCriAQAvD_BwE">Confluent Cloud</a></li> <li><a href="https://learn.microsoft.com/en-us/azure/hdinsight/kafka/apache-kafka-introduction">Kafka on Azure</a></li> <li><a href="https://aws.amazon.com/msk/">Amazon Managed Streaming for Kafka (MSK)</a></li> </ul> <p>Different cloud providers might charge you differently. It’s always wise to consider the pricing options before locking yourself into a specific cloud provider.</p> <h2>Conclusion</h2> <p>In this article we looked at Apache Kafka is an open-source distributed event streaming platform for building real-time data pipelines and streaming applications. We went over the main concepts that should be enough to get you started. I’m planning to write an article series on implementing producers and consumers in .NET, Go &#x26; Python. So stay tuned! Until next time 👋</p> <h2>References</h2> <ol> <li><a href="https://kafka.apache.org/documentation/#intro_topics">Intro Topics</a></li> <li><a href="https://kafka.apache.org/33/documentation/streams/core-concepts">Core Concepts</a></li> </ol><![CDATA[Serverless Go with Azure Functions and GitHub Actions]]>https://www.sahansera.dev/serverless-go-with-azure-functions-github-actions/https://www.sahansera.dev/serverless-go-with-azure-functions-github-actions/Mon, 12 Sep 2022 00:00:00 GMT<p>In this article, we are going to learn how to host a serverless Go web service with Azure Functions leveraging custom handlers in Azure functions. We will also automate the deployments by using GitHub Actions 🚀 You can even apply the same concepts to other languages Rust as long as you can self-contained binary.</p> <h2>Walkthrough video</h2> <p>If you prefer to watch a video, I have created a walkthrough video on my YouTube channel 😊</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/0FqD8LTjHbg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <h2>Introduction</h2> <p>Azure Functions is Microsoft’s offering for serverless computing that enables you to run code on-demand without having to explicitly provision or manage infrastructure. Azure Functions is a great way to run your code in response to events, such as HTTP requests, timers, or messages from Azure services.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>As of today, they support multiple <a href="https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages">runtimes</a> such as .NET, Java, JavaScript, Python, TypeScript. But what if we want to write our app in Go? Well, now you can do that too - by using Custom Handlers.</p> <p>Before we move on, so what really is a custom handler and how does it work? Custom handlers let your Function app to accept events (eg. HTTP requests) from the Global host (aka <a href="https://github.com/Azure/azure-functions-host">Function host</a> - that powers your Function apps) - as long as your chosen language supports HTTP primitives.</p> <p>Here’s a great overview from Microsoft on how this is achieved.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhklEQVR42oWSW27TQBSGvcdKLINK7IQHFlDUB7YAvCBVLAFE1CoSaUQSJ45jz+3czXFcRErTcjQa/R778/znUokIPx9EhETDoFc/1q8+zy5v5q9v5m++zi8+zT7MVtXwv1AbWIePy+7d9/r9XXN121zfNW+/1V+W7UuwXxtDSIDbTA2In0QSX5lVbDhkfAlGxPVmk3M2G3oSMetJXUQWUttn+AvbCfagzVpgs/HJRcxlf+jarj/0IRTcxVK5G1bzNcUETZck1kDie4/qVhPxKmGHHIBG/4BVDbzJtAMOyE3TLBaLlNIWrCuhTft5oHUaP/VfsGi93fVdN93hvagcq4vXg3vkGKPzpZS6yC7nHpILx/YgPwNk4hjccVDVB/hpwsdkhyKKOppHMddq/5bzEfy0YIhw/2vlRk4P/9TlBD7fZ8SlwzmfffssPNn2dR/J6+zCzsI+2+MAH2MSPuvHbCWNMGYxbxiInoEBwLPyMfIO5WMgALK0hQ6FNhF8bwsC+0w+CkT8Dea3LRqnPDpKAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="serverless-go-with-azure-functions-github-actions-1" title="" src="/static/ea7a2923719d4c72709b548b69167ecf/5a190/serverless-go-with-azure-functions-github-actions-1.png" srcset="/static/ea7a2923719d4c72709b548b69167ecf/772e8/serverless-go-with-azure-functions-github-actions-1.png 200w, /static/ea7a2923719d4c72709b548b69167ecf/e17e5/serverless-go-with-azure-functions-github-actions-1.png 400w, /static/ea7a2923719d4c72709b548b69167ecf/5a190/serverless-go-with-azure-functions-github-actions-1.png 800w, /static/ea7a2923719d4c72709b548b69167ecf/77800/serverless-go-with-azure-functions-github-actions-1.png 855w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><em>Source: <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers">Microsoft Docs</a></em></p> <p>So, in our case, we are going to wrap a Go binary as a Function app and deploy it to Azure. Sounds good? Let’s jump right into it.</p> <h2>Prerequisites</h2> <p>Make sure you have the following setup locally.</p> <ul> <li><a href="https://go.dev/dl/">Go SDK</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools">Azure Functions Core Tools</a></li> <li>Prefererrably, Azure Functions <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions">VS Code extension</a></li> </ul> <h2>The plan</h2> <ol> <li>Clone the repo or create one</li> <li>Create the Azure function resources</li> <li>Test out the app</li> <li>Deploy to Azure</li> </ol> <h2>1. Clone the repo or create one</h2> <p>Our code is going to be pretty simple. All it does is, whenever we make a request, it will return a random quote on programming.</p> <blockquote> <p>💡 You can clone the repo I have created from <a href="https://github.com/sahansera/go-azure-functions">here</a>.</p> </blockquote> <div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go"><span class="token keyword">package</span> main <span class="token comment">// Removed for brevity</span> <span class="token keyword">var</span> quotes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span> <span class="token string">"Talk is cheap. Show me the code."</span><span class="token punctuation">,</span> <span class="token string">"First, solve the problem. Then, write the code."</span><span class="token punctuation">,</span> <span class="token string">"Experience is the name everyone gives to their mistakes."</span><span class="token punctuation">,</span> <span class="token string">"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">quotesHandler</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Get a random quote</span> message <span class="token operator">:=</span> quotes<span class="token punctuation">[</span>rand<span class="token punctuation">.</span><span class="token function">Intn</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>quotes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token comment">// Write the response</span> fmt<span class="token punctuation">.</span><span class="token function">Fprint</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> message<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> listenAddr <span class="token operator">:=</span> <span class="token string">":8080"</span> <span class="token keyword">if</span> val<span class="token punctuation">,</span> ok <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">LookupEnv</span><span class="token punctuation">(</span><span class="token string">"FUNCTIONS_CUSTOMHANDLER_PORT"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span> listenAddr <span class="token operator">=</span> <span class="token string">":"</span> <span class="token operator">+</span> val <span class="token punctuation">}</span> http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/api/GetQuotes"</span><span class="token punctuation">,</span> quotesHandler<span class="token punctuation">)</span> log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"About to listen on %s. Go to https://127.0.0.1%s/"</span><span class="token punctuation">,</span> listenAddr<span class="token punctuation">,</span> listenAddr<span class="token punctuation">)</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span>listenAddr<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Explanation</strong></p> <ul> <li>We setting port <code class="language-text">8080</code> as the default port for our app. But if we are running this in Azure Functions, we will be using a different port. So, we are checking if the <code class="language-text">FUNCTIONS_CUSTOMHANDLER_PORT</code> environment variable is set. If it is, we will use that port instead.</li> <li>We are registering a handler for the <code class="language-text">/api/GetQuotes</code> endpoint. This is the endpoint that we will be using to make requests to our app.</li> <li><code class="language-text">quotesHandler</code> is a simple function that returns a random quote from the <code class="language-text">quotes</code> array.</li> </ul> <h2>2. Create the Azure function resources</h2> <p>You can create your own Azure function from <a href="http://portal.azure.com">portal.azure.com</a> or using ARM templates. Below are the configuration I chose.</p> <ul> <li>Publish: Code</li> <li>Runtime Stack: Custom Handler</li> <li>Operating System: Linux</li> <li>Plan type: Consumption (Serverless)</li> </ul> <h2>3. Test out the app</h2> <p>If you have cloned the project you should see a folder structure similar to what’s shown below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 706px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 74%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACIklEQVR42o2USXLbMBBFdYNEMQeAIAhwlkhRlGVNsZO4yilHydKrnCP3X/x8UJLLC1PlRZOFBvCA7t/oSRAkiOMSRblCVvQoqzWqeo0s71DyX5T9MC7KWwhhEQQabs+YTdwn5ebN9gmLdovN5gm77U+0zR6Hr0esbh+xv/+N3eEIKTP4/ghw8GsHNAhuJGS1R//yD2GcISw7qMUGvi4w/RRg+llgOhXnTe/DBKMMo+x0Q+dIGdb++S9U3iLpdkjaDXTVQ+oKUhUI3cFjoTpglCOU6QnoexpzAp7/vEAUHdK7bycoTTd3iLKGa+LrQHfoBRj4MXxVI2ifENkaqu4HkJ6vh38QmqtCDEDNkFV+Anp0aA4a3k7Y2Qm22CKhRXaOIPgYUDhgSMcX36CWMX7FHgTLxK4fBqgL2SX6arhnoLwAB4cXIUzmUKsjVNWdYP2ByuXwbxRDTs71N2JMmWBFDEAhUkiRQek5dE0hqLZtd0iXB2jWZ+RUlvl1c/sNL5TMMJFRCU2YsgsEyweY/p4h/6DSj7C0bPUdSlWIdUObv2sqZt5zCpktmUMq6DPpJrboaovWzLBimXQUo08byNAOKocj5uYiRlknPPRSNp6fUGWDpjTIWPGVqVElNWrCJRe7Na+Ad0QJmWND8STnX4FJbNDPDPPQwOZLJLaFyTo2iX6wnI3D0jemtCs9/9IchtdCqOedH/nlzb55u25+tDG86Tb/AV3tl/Zkdyt2AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="serverless-go-with-azure-functions-github-actions-2.png" title="" src="/static/e359e32c161b4ba7a9ff08f5e10a60e1/9f21b/serverless-go-with-azure-functions-github-actions-2.png" srcset="/static/e359e32c161b4ba7a9ff08f5e10a60e1/772e8/serverless-go-with-azure-functions-github-actions-2.png 200w, /static/e359e32c161b4ba7a9ff08f5e10a60e1/e17e5/serverless-go-with-azure-functions-github-actions-2.png 400w, /static/e359e32c161b4ba7a9ff08f5e10a60e1/9f21b/serverless-go-with-azure-functions-github-actions-2.png 706w" sizes="(max-width: 706px) 100vw, 706px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>At the root of the project folder let’s run the following commands.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">go build handler.go <span class="token comment"># To build a binary</span> func start <span class="token comment"># Start the Function app service</span></code></pre></div> <p>You should see the output like so:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 74.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACi0lEQVR42p2Tz27aQBDGSZtygfSAWlQphCYEsNf2YscEJ8H/1wbbQEOipO0lfZ1Uap8o58h9pajWStNZA0oa9ZJa+mk+drzfemaWSgUfvdfbbQ1aWrXdVqqND0ql3viLRrurNLoGasw11mzy1TqpvKlplcZuq7J+XvckcrdnKvBWog87h0pR+9gvavvSigO5aCpHRZNQ1B3MHSK9otYhRR3frXfVh50+hXpHukOvbWFYG7M4PwmnoKdX3FjewCiew3D+GYzsGozZNegZancEo+wIrMAEfezBKF2Cc/EF15Z8iJrYQS68SkMzSvPT86syOZpdwDBZIJ9ADRLQwgRURPEnK7wIVG8CxI1Adhj0xwGXnQg6w/GjoeKw3EzOQY9SbsQzoGEKWjAFk03hOErBCCZA0UBHM81lQEV0QlDtAKgTcOrH0LeeGJpOkJ+lC7DihJ9NUrDjKZzEwigCEzlCdB91uIoCo1xjYLoBN9BQfmqo2U5+PE0AT+KaPwUNS6IY+/iVfQ9BLYmILehjTmg5EGtRWTLxYuiOnhjKlpUbYQAD1+Gq7YHirFAFrsAttea4eJhXQmwf5HGIBFx5bti1nFwJM9BZyA2GPQoZDCIGGsNeid+oaYxrschh30I80A3LoSBcwa/tWvajIU2yfHj1Fejikg8Wl0DTc6BsBiSagYwDIjgoMpljnGOpKcjYAsISIDh1Emdcmy2hh4N97KEf52qUgexGXFyH8krgBLEcjOGmtJV+zr9KPjw+uxcm0tgvJHzhhfwWe9HwfmNYl0+9nLIML23MVSzjJYg9Yq906v7aGFbf7+5/a+4d/HzX2r9Fvr+QW9z7o9k+uBFewnALebX+Y7/5T7bXHlt/AH+odXydgn+AAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="serverless-go-with-azure-functions-github-actions-3.png" title="" src="/static/ff84465da4d2aa34c75ab1f599764e54/5a190/serverless-go-with-azure-functions-github-actions-3.png" srcset="/static/ff84465da4d2aa34c75ab1f599764e54/772e8/serverless-go-with-azure-functions-github-actions-3.png 200w, /static/ff84465da4d2aa34c75ab1f599764e54/e17e5/serverless-go-with-azure-functions-github-actions-3.png 400w, /static/ff84465da4d2aa34c75ab1f599764e54/5a190/serverless-go-with-azure-functions-github-actions-3.png 800w, /static/ff84465da4d2aa34c75ab1f599764e54/c1b63/serverless-go-with-azure-functions-github-actions-3.png 1200w, /static/ff84465da4d2aa34c75ab1f599764e54/29007/serverless-go-with-azure-functions-github-actions-3.png 1600w, /static/ff84465da4d2aa34c75ab1f599764e54/21e8f/serverless-go-with-azure-functions-github-actions-3.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s run through each file now.</p> <ul> <li><a href="https://github.com/sahansera/go-azure-functions/blob/main/GetQuotes/function.json">GetQuotes/function.json</a>: This file defines what happens when a request comes in and what should go out from the function. These are known as <em><strong>bindings.</strong></em> Our function is triggered by HTTP requests and we will return a response</li> <li><a href="https://github.com/sahansera/go-azure-functions/blob/main/handler.go">handler.go</a>: This is our Go web service where our main logic lives in. We listen on port 8080 and expose an HTTP endpoint called <code class="language-text">/api/GetQuotes</code></li> <li><a href="https://github.com/sahansera/go-azure-functions/blob/main/host.json">host.json</a>: Take note under <code class="language-text">customHandler.description.defaultExecutablePath</code> is set to handler which says where to find the compiled binary of our Go app and <code class="language-text">enableForwardingHttpRequest</code> where we tell the function host to forward our traffic</li> </ul> <h2>4. Deploy to Azure with GitHub Actions</h2> <p>Now that we have everything ready to go let’s deploy this to Azure! 🚀  To get this done, we are going to use the <a href="https://github.com/marketplace/actions/azure-functions-action">Azure Functions Action</a> from the GH Actions Marketplace.</p> <p>From a high-level this is what we need to do in order to deploy this.</p> <ol> <li>Authenticate with Azure</li> <li>Build the project</li> <li>Deploy</li> </ol> <p><strong>Authenticate with Azure</strong></p> <p>Since our app is written in “Go” which is not really supported out of the box, we won’t be able to use the Publish Profile method for this. So we are going to focus on using an Azure Service Principal for RBAC.</p> <blockquote> <p>💡 Remember to follow the steps according to <a href="https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential">this guide</a> to create an SP.</p> </blockquote> <ol> <li> <p>Once you have created the service principal, we need to add that as a secret in our repo so that it can be used for authenticating with Azure Resource Manager during the deployment step. Head over to your repo → Settings → Secrets → Actions</p> </li> <li> <p>Create a secret a called <code class="language-text">AZURE_RBAC_CREDENTIALS</code> and update its content with what you got when you created the service principal.</p> </li> </ol> <p>Now the that the credentials are in place we can refer to that from the GitHub Action workflow file.</p> <p><strong>Build the project</strong></p> <p>This step is pretty straightforward as we are using the Go SDK to build the project with <code class="language-text">GOOS=linux GOARCH=amd64</code> config.</p> <p>This is what the final workflow file should look like:</p> <p><a href="https://github.com/sahansera/go-azure-functions/blob/main/.github/workflows/deploy.yml">deploy.yml</a></p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> CI/CD <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"main"</span> <span class="token punctuation">]</span> <span class="token key atrule">pull_request</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"main"</span> <span class="token punctuation">]</span> <span class="token key atrule">env</span><span class="token punctuation">:</span> <span class="token key atrule">AZURE_FUNCTIONAPP_NAME</span><span class="token punctuation">:</span> azgofuncapp <span class="token comment"># set this to your application's name</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v3 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Login via Azure CLI'</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> azure/login@v1 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">creds</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.AZURE_RBAC_CREDENTIALS <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Set up Go'</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>go@v3 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">go-version</span><span class="token punctuation">:</span> <span class="token number">1.18</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build <span class="token key atrule">run</span><span class="token punctuation">:</span> GOOS=linux GOARCH=amd64 go build handler.go <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">'Deploy to Azure'</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> Azure/functions<span class="token punctuation">-</span>action@v1 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">app-name</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> env.AZURE_FUNCTIONAPP_NAME <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div> <p>Once everything is done, head over to the following URL and refresh a couple of times to see our programming quotes! 😀</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">https://<span class="token operator">&lt;</span>your_app_URL<span class="token operator">></span>.azurewebsites.net/api/GetQuotes</code></pre></div> <p>Here’s the an example.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 29.000000000000004%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABUUlEQVR42oXQa0vCUBjA8ZMkBoWhjBQvedkyUvKyrd2crTU3r9NUNhKLss8V9C30jd+kgqJvUbOn41SQetGLH/+HczgHzkFEItr0JGLXaD9soV3CXgrY69kTStreCEZEbOQP2SgQWnY9L875D4YoEK4jhLYRI0vvlFQGUlTnlKzBEZY5190e4xaNNpQME+hGF1KCAlFahDgrYWWIM2WI0dL88EyGNCu8ZAUjiLRO701uXYFqDb/00b1j3Iyd+u2DUx3eOcZo7FbHvbBGDlsznVNFd/JqzSms5C9rnyWtDoyqPWcYhUCVpvnBNzogmoNvrt0Hvj3A+sC1eiB2LbdFvQWFahNyig4nFQ2rbnaewy/JivLrXiZDIJLhnlI0N0vRwiRZYKfJIjddlnWL91b4aZoR/yAZcUIywoyk+ccgRfnxPyIvtvOL758130bX8+KerR/dGIYvLEN9gQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="serverless-go-with-azure-functions-github-actions-4.png" title="" src="/static/3319e08116dcac501fe741acd7da9027/5a190/serverless-go-with-azure-functions-github-actions-4.png" srcset="/static/3319e08116dcac501fe741acd7da9027/772e8/serverless-go-with-azure-functions-github-actions-4.png 200w, /static/3319e08116dcac501fe741acd7da9027/e17e5/serverless-go-with-azure-functions-github-actions-4.png 400w, /static/3319e08116dcac501fe741acd7da9027/5a190/serverless-go-with-azure-functions-github-actions-4.png 800w, /static/3319e08116dcac501fe741acd7da9027/c1b63/serverless-go-with-azure-functions-github-actions-4.png 1200w, /static/3319e08116dcac501fe741acd7da9027/29007/serverless-go-with-azure-functions-github-actions-4.png 1600w, /static/3319e08116dcac501fe741acd7da9027/fbae3/serverless-go-with-azure-functions-github-actions-4.png 2260w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Conclusion</h2> <p>Well, that’s it folks! Today we built a small Go web service, wrapped it in an Azure Function and deployed it to Azure by using GitHub Actions!</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Troubleshooting</h2> <p>I ran into couple of issues when creating this project.</p> <ol> <li>Are you getting the <code class="language-text">Value cannot be null. (Parameter 'provider')</code> error?</li> </ol> <p>I was able to resolve it by following the exact config as below.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"2.0"</span><span class="token punctuation">,</span> <span class="token property">"logging"</span><span class="token operator">:</span> <span class="token punctuation">{</span>...<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"customHandler"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"defaultExecutablePath"</span><span class="token operator">:</span> <span class="token string">"handler"</span><span class="token punctuation">,</span> <span class="token property">"workingDirectory"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token property">"arguments"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"enableForwardingHttpRequest"</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre></div> <ol start="2"> <li>Are you getting the <code class="language-text">Azure Functions Runtime is unreachable</code> error?</li> </ol> <p>For me, this went away when I did the first deployment. If it still doesn’t go away, check out this <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-recover-storage-account">link</a> for more info.</p> <h2>References</h2> <ul> <li><a href="https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos">https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/azure-functions/event-driven-scaling">https://docs.microsoft.com/en-us/azure/azure-functions/event-driven-scaling</a></li> <li><a href="https://github.com/Azure/azure-functions-dotnet-worker/issues/810">https://github.com/Azure/azure-functions-dotnet-worker/issues/810</a></li> <li><a href="https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential">https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential</a></li> </ul><![CDATA[Deploying a .NET gRPC Server on Azure App Service]]>https://www.sahansera.dev/deploying-dotnet-grpc-service-azure-app-services/https://www.sahansera.dev/deploying-dotnet-grpc-service-azure-app-services/Mon, 05 Sep 2022 00:00:00 GMT<p>In this article we are going to be deploying a gRPC web service written in .NET to an Azure App Service. We will then interact with it and also automate deployment process of the Azure resources and the build.</p> <h2>Introduction</h2> <p>About a few months ago I wrote a blog series on creating a simple gRPC server and a client locally. This time we are going to take the next steps by deploying it to the cloud.</p> <p>If you missed the series you can get yourself up to speed with the below links 🚀</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a></li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC Server in .NET</a></li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC Client in .NET</a></li> </ul> <h2>Walkthrough video</h2> <p>If you like to watch a video walkthrough instead of this article, you can follow along on my Youtube channel too 😊</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/jm6aeRmTKCU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>The Plan</h2> <ol> <li>Clone the above repo.</li> <li>We will create an App Service instance on Azure and deploy it (automated).</li> <li>Getting ready for deployment.</li> <li>Deploying the BookshopServer code.</li> <li>Testing the web service.</li> </ol> <h2>1. Clone the repo (or build one)</h2> <p>Since we already have an app in handy we are going to use the same thing and make this work. More specifically we are going to use the exact code we looked at in the <a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC Server in .NET</a> blog post.</p> <blockquote> <p>💡 The code can be found under <a href="https://github.com/sahansera/dotnet-grpc/tree/main/BookshopServer">this repo</a>.</p> </blockquote> <p>We are going to be using the <code class="language-text">BookshopServer</code> as our web service and <code class="language-text">BookshopClient</code> as the client to interact with it.</p> <p>Once you have it cloned, you can set the branch to app-service with,</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">git checkout app<span class="token operator">-</span>service</code></pre></div> <h2>2. Create an App Service instance in Azure</h2> <p>You can head over to <a href="http://portal.azure.com">portal.azure.com</a> and create the App Service instance manually or you could use the ARM template down below.</p> <blockquote> <p>💡 To save you some time and trouble, I’d stay away from the F1 tier - it was unreliable for me and had a hard time deploying my code. I went ahead with B1 (and you get 30 days free for Linux). It costs you a little bit, but heaps more reliable for Dev/Testing work.</p> </blockquote> <h3>ARM template for easy deployment</h3> <p>If you haven’t got <code class="language-text">az cli</code> head over <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">here</a> to get it set up. Once installed, do a <code class="language-text">az login</code> and select the account you want to use for deployments.</p> <p>I have created a shell script to automate all the steps. This is the usage of the script.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> BookshopServer/Infrastructure/ ./deploy.sh net6-grpc australiasoutheast template.json parameters.json</code></pre></div> <p>The parameters to pass in are as follows:</p> <ol> <li>Resource group name (eg: <code class="language-text">net6-grpc</code>)</li> <li>Region you want to create the resources in (eg: <code class="language-text">australiasoutheast</code>)</li> <li>Name of the ARM template file (eg: <code class="language-text">template.json</code>)</li> <li>Name of the parameters file (eg: <code class="language-text">parameters.json</code>)</li> </ol> <p>If you are unsure, you can see what’s what just by typing <code class="language-text">./deploy</code> as well. Once deployed, you should be able to see something like below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 89.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAACmUlEQVR42p2SW08aURDHd7mIKMKCgHgBQYGlcne7XFzZ5aIVb23Txi+jiY99qA9I0qQPNT60adpo09rasC9tmvihTDY5yXTOAom62Aub/DPnnOz8zsz5D8Pgd9Vp+s5+1vi3v5To6WUxdvw+11Mx9vqTUad39icXK/zZVdPH9L/2ifLxzY91aF3Wrl99X9PaX9e144uG1vpc11pfatrRuaK9PJO1Iypcd/c9nSvX7W91ePFO/tDnWUb4pDqSFmAsIxBOlMAvKeAtV8AllMC5XARbMg+2VL4bDcoRW0YAG7/UQZaVAq1s6IHKRJfBHMuT8fQKOPMVsCdLYFsqAruQBeaPyhCay4ZuAsMI5JeBiaRIsLoFie09mMM4vboB3EMZk9LARLP3CIFxBIYHARfSZFbehPjmc1jceAqzyiZ4S3Vse3V4IK1ovr4Dcwjzl9dgIidh6yIwi5n/BOIhJhG3ICOogYbI4BYVcGRXwIKXUQ0FnJLWIdp8BuHGLvhKDfAUqjCRl8CeLt1T5R2g0AOyWIE1IRAWk1j80RTP6ZGlhkRS/94yBTKhhDqaKdO2iCmWA0tCABpN8Ty9BMwYKZDFM6q/A4O86sb2Io1dQludqWxAQHoEM3ITQrVtPY5hy3q1hhG6D1ioA7+1h8DH+vxRpwP4npxQQcn6+NA3pWbdrnKAKZaFlOoSq+ARFeJDh6m7LgrCERpNFvTE/lOYDW4PAkaSamjtCa2ITGO749kyQqu6w45c1+HRVEE3yWiOEWhxpcSOH9/MkS2TSQS5aWU4zHSg7QjCcz3qrhuUISwCTTeA5slgZNcTjh86puf3ncHIwVggeMA4PAcmpxejV18zTtxz/kHaN3GBQzM3tUNZFMj2RDeWIWXuMZjf1saumfRS+3EAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="deploying-dotnet-grpc-service-azure-app-services" title="" src="/static/4dc6c05a87143db4ea8494b0adac1220/5a190/deploying-dotnet-grpc-service-azure-app-services.png" srcset="/static/4dc6c05a87143db4ea8494b0adac1220/772e8/deploying-dotnet-grpc-service-azure-app-services.png 200w, /static/4dc6c05a87143db4ea8494b0adac1220/e17e5/deploying-dotnet-grpc-service-azure-app-services.png 400w, /static/4dc6c05a87143db4ea8494b0adac1220/5a190/deploying-dotnet-grpc-service-azure-app-services.png 800w, /static/4dc6c05a87143db4ea8494b0adac1220/c1b63/deploying-dotnet-grpc-service-azure-app-services.png 1200w, /static/4dc6c05a87143db4ea8494b0adac1220/29007/deploying-dotnet-grpc-service-azure-app-services.png 1600w, /static/4dc6c05a87143db4ea8494b0adac1220/5eb90/deploying-dotnet-grpc-service-azure-app-services.png 1612w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>App Service configuration</h3> <p>The ARM template takes care of most of the configuration for us. However there’s one more thing that we need to enabled, called <code class="language-text">HTTP 2.0 Proxy</code></p> <p>This is what your configuration should look like:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 58.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABk0lEQVR42pVSW1LDQAzLJWiSfebdB2lpKZACPQAw9P7HEZbTwEB/yodmvMmuLFlOQtUhFA18KBG7O8R2I3WBULZwLsDmGazJr0ay296h72/hrBUCJ/DjT2v+RfRN2LWizlk9eGdQlQVM/nPBCTGbXU3Y366UJM9SJe7a9kxgFKynhlcTUuWoJkfT1IghIMsy5KLUnImNzPIvrLmcb9LUFVohUUIJIYQohB4xsvZKXrD2bvx+hvcya5n5BWFZRBB8YH2Bum7QShOqZqMJdVViak4UZQ0n23BB+Piwx2bdy+UaJpup7SxNkc5ukKYzBedL0OZPncpKEfkv+6ow00dCFmv06w2Wi04bEKpGLDMYuqBSnkdECZQOSzhxxwCTedeqZXZ0MpeC9jkj3UmrpC/PzzidPvHx/qZujsdXDMMTDsMgO7zSe84H3d1kLUtNFST0sUJZNZJ0hcNhwMN+j912i/39DsPTIxbzTm1RJUNjYFSl9ifL4+IanYeLEsR8qZa5TqvlQppV6oAWua9TQFM4U3hTKF/TTkZkbkUCCgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="deploying-dotnet-grpc-service-azure-app-services-2" title="" src="/static/4151149b65a157ba10003618ae74e0a5/5a190/deploying-dotnet-grpc-service-azure-app-services-2.png" srcset="/static/4151149b65a157ba10003618ae74e0a5/772e8/deploying-dotnet-grpc-service-azure-app-services-2.png 200w, /static/4151149b65a157ba10003618ae74e0a5/e17e5/deploying-dotnet-grpc-service-azure-app-services-2.png 400w, /static/4151149b65a157ba10003618ae74e0a5/5a190/deploying-dotnet-grpc-service-azure-app-services-2.png 800w, /static/4151149b65a157ba10003618ae74e0a5/c1b63/deploying-dotnet-grpc-service-azure-app-services-2.png 1200w, /static/4151149b65a157ba10003618ae74e0a5/29007/deploying-dotnet-grpc-service-azure-app-services-2.png 1600w, /static/4151149b65a157ba10003618ae74e0a5/a878e/deploying-dotnet-grpc-service-azure-app-services-2.png 2048w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>3. Getting ready for deployment</h2> <p>Before we do any deployment there are a couple of things we need to change in our code. These are all done for you in my repo. But for the curious,</p> <p><a href="https://github.com/sahansera/dotnet-grpc/blob/d931d19cbe5a91e12cc8380e0b93f646afe7ce64/BookshopServer/Program.cs#L7">BookshopServer/Program.cs</a></p> <p>Add/Update the configuration to tell Kestrel to listen on all IPs on port <code class="language-text">8080</code> and also on port <code class="language-text">8585</code> but with HTTP 2.0 only.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// ...</span> builder<span class="token punctuation">.</span>WebHost<span class="token punctuation">.</span><span class="token function">ConfigureKestrel</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span><span class="token function">ListenAnyIP</span><span class="token punctuation">(</span><span class="token number">8080</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">ListenAnyIP</span><span class="token punctuation">(</span><span class="token number">8585</span><span class="token punctuation">,</span> listenOptions <span class="token operator">=></span> <span class="token punctuation">{</span> listenOptions<span class="token punctuation">.</span>Protocols <span class="token operator">=</span> Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Server<span class="token punctuation">.</span>Kestrel<span class="token punctuation">.</span>Core<span class="token punctuation">.</span>HttpProtocols<span class="token punctuation">.</span>Http2<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ...</span></code></pre></div> <p>Now that we have that config we no longer need the configuration specified in appSettings.json. Let’s remove the following lines like so.</p> <p><a href="https://github.com/sahansera/dotnet-grpc/blob/91df33edc33b8bc32cfe22338a6cdf337f6896c4/BookshopServer/appsettings.json#L9">BookshopServer/appSettings.json</a></p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json">... <span class="token comment">// Delete the following lines</span> <span class="token property">"Kestrel"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"EndpointDefaults"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"Protocols"</span><span class="token operator">:</span> <span class="token string">"Http2"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> ...</code></pre></div> <h2>4. Deploying the BookshopServer code</h2> <p>Let’s go ahead and deploy our code! 🚀 There are many ways to do this. If you are setting it up for testing you can simply deploy from your local Git repo. To keep the scope of this blog post small I’m not going to be covering <a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-continuous-deployment?tabs=github">other continous deployment methods</a>. Let me know if you like me to cover them too!</p> <p>You can follow <a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-local-git?tabs=cli#deploy-the-web-app">these steps</a> from the official docs to quickly deploy from locally. There are some configurations that you need to do.</p> <ol> <li> <p>Go to you <em><strong>app service</strong></em> instance > click <em><strong>Deployment Center</strong></em></p> </li> <li> <p>Select Local Git from the <em><strong>Source</strong></em> dropdown</p> </li> <li> <p>Click <em><strong>Local Git/FTPS Credentials</strong></em></p> </li> <li> <p>Set a username and a password under <em><strong>User Scope</strong></em></p> </li> <li> <p>Hit <em><strong>Save</strong></em></p> </li> <li> <p>(Important) Go back to the app service <em><strong>Overview</strong></em></p> </li> <li> <p>Copy the URL under <em><strong>Git Clone URL</strong></em></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABSklEQVR42n1S2XLCMAz0e9MZaAA7PpM4bhKOAOX/v20rOYFhptCHHVm2tF6vLOrgwXA+UAwI3qFt6hyNrqCrKkej9RL/hzjsR+zHHnUbMY5DXk+nI4bUwVKBdxZOK4Kco9Fw9j1EpWRmDt5CyR04r9QOJrTQIWLztYJpe7jhApdOsNbALKqt+QvBB46Khv4b36TKO3oqkfp0gBlvkHUPpQ209bAu5KeHoOH9awvEfJPOhOPQI8YWnqSbOmEtDcrtFmW5xm5TQktJCiWOPwpNVPQiSR6r14R78o8JmzqgI9KWIntYrlfYEhkjks/X24BpGpC6lHu49pn0QcgHqYt5zV6yYk+TLooPfBZFVsPDul4mnKcTgddnXM4TXdQ8SAX7x+BvwshTXXImv9/M+6z+/s2ewbV3HsFNz41vc1NlFRl8Vi05faV5wnPdL8Q+8SwY1UYLAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="deploying-dotnet-grpc-service-azure-app-services-3" title="" src="/static/5db4665392b2c6c081e39f588fe0981c/5a190/deploying-dotnet-grpc-service-azure-app-services-3.png" srcset="/static/5db4665392b2c6c081e39f588fe0981c/772e8/deploying-dotnet-grpc-service-azure-app-services-3.png 200w, /static/5db4665392b2c6c081e39f588fe0981c/e17e5/deploying-dotnet-grpc-service-azure-app-services-3.png 400w, /static/5db4665392b2c6c081e39f588fe0981c/5a190/deploying-dotnet-grpc-service-azure-app-services-3.png 800w, /static/5db4665392b2c6c081e39f588fe0981c/c1b63/deploying-dotnet-grpc-service-azure-app-services-3.png 1200w, /static/5db4665392b2c6c081e39f588fe0981c/29007/deploying-dotnet-grpc-service-azure-app-services-3.png 1600w, /static/5db4665392b2c6c081e39f588fe0981c/e0dbc/deploying-dotnet-grpc-service-azure-app-services-3.png 3378w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> </li> <li> <p>Go to your <em><strong>local repo</strong></em></p> </li> <li> <p>Do a <code class="language-text">git remote add azure &lt;Git URL you copied before></code></p> </li> <li> <p>Change deployment branch with <code class="language-text">git push azure &lt;local branch name>:master</code></p> </li> </ol> <p>This might take a couple of minutes for the initial deployment. After the last step you should be able to see a log like so:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 57.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABfUlEQVR42o2T63KCMBCFfYiqoIlALgSQi9paay/v/1jbPRuGYm1n/HGGJBO+7NmcLIbjFzk3UF2/UNe/k7O9jJv2QlX1TG33Rsa0pJSjNC1I61KklOe5ofU6o9VqN2khEAZmeUPW9fIzlBeNCGtlOFFZHmm7dRQwZmFd74IccgMcDp/k/cCbPe14Q1RFm43hQ2oZJ0kusCyr5avGKpdLLboDGtuJPVRbN2fWq9iGXQjVYQ44lMtB4QY0Afvhgy33Am27K4XqRMZ11ABcn/nnRmwp7bkaNZP+HwjL+Ak2k6SQZseG59J0sfak/gTcAWEZFfryQBXbcgyPVq9UmL30L27OHgOiQtwYbhR27dhPWMWlRKB+CDaLTS9xwIUAKn1kcNz0OOzHsj9I05UqORqIjJXgTv2bxWM+/70+Aw4SUsQDsvxaYBf2EXrY93yopIETgDGEBxDCUYJe8BhuFvvxaWEDLgJq9hdZw0ZEB+3APB0TgDTEJBQcdDulAhV+A2s8TM16NLjTAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="deploying-dotnet-grpc-service-azure-app-services-4.png" title="" src="/static/c02f5886c75a167c394229ab831c19a5/5a190/deploying-dotnet-grpc-service-azure-app-services-4.png" srcset="/static/c02f5886c75a167c394229ab831c19a5/772e8/deploying-dotnet-grpc-service-azure-app-services-4.png 200w, /static/c02f5886c75a167c394229ab831c19a5/e17e5/deploying-dotnet-grpc-service-azure-app-services-4.png 400w, /static/c02f5886c75a167c394229ab831c19a5/5a190/deploying-dotnet-grpc-service-azure-app-services-4.png 800w, /static/c02f5886c75a167c394229ab831c19a5/97bfd/deploying-dotnet-grpc-service-azure-app-services-4.png 1078w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Although it my seem a little daunting at first, this is only a one-off config. For any code changes you make, you just need to commit your changes to your local branch and then do a <code class="language-text">git push</code> like we saw in step 10 above.</p> <h2>5. Testing the web service</h2> <p>Now to the interesting part. Let’s see how we can interact with our web service 🤔</p> <p>For some reason I couldn’t get <code class="language-text">grpcurl</code> to work, thankfully, we already have the BookshopClient project - so let’s use that!</p> <p>When you open up the project, make sure to update the URL to point to your App service - and that’s all you need to do!</p> <p><a href="https://github.com/sahansera/dotnet-grpc/blob/91df33edc33b8bc32cfe22338a6cdf337f6896c4/BookshopClient/Program.cs#L5">BookshopClient/Program.cs</a></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> channel <span class="token operator">=</span> GrpcChannel<span class="token punctuation">.</span><span class="token function">ForAddress</span><span class="token punctuation">(</span><span class="token string">"https://your_app_service.azurewebsites.net/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>(make sure you add the <code class="language-text">/</code> at the end of the URL)</p> <p>and finally let’s run the project.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run <span class="token parameter variable">--project</span> BookshopClient</code></pre></div> <p>That should return back the list of books as shown below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABPUlEQVR42mOYv8vFYv1Zr1mrTnpMWXPafeq6025TVwPpNaddpq4+4Th1+WGnqcuPOE1dtt9p6qKDblOXH3ObuvKkCxivBqoF6Vl9ynXyiuMuc2Zts7dgCEjzSIuqCPjvU+T/J7A0+H9IScD/kKqI/6FlAf/DSl3+h5W5/Q8rdvsfXuD+36fQ7793kd9/3xL//4FlQf/9SwOBOOC/Z77XH8dMl/9WUTapDFxqFincOg7/ObXsfnBp2v3mULf5zaJk/ptF2QJIm4HZzIpAWtkcii1+s6pYQtQomP5mkNIHYoMfDFKG/5nEtZMZ2FX1U5k1jP8zqxn8YVIz+M+oqv+fUUUPAzMg0QzIciD1qvp/mNSN/jMpa6UysMupKjFJq8UyyKhGMsmoRIGxvDoSVoFidewYrAeoV1Y1jl1OTREAPLyT128KeDAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="deploying-dotnet-grpc-service-azure-app-services-5.png" title="" src="/static/ab62dde7919f63e6f612d1f07a7f437e/5a190/deploying-dotnet-grpc-service-azure-app-services-5.png" srcset="/static/ab62dde7919f63e6f612d1f07a7f437e/772e8/deploying-dotnet-grpc-service-azure-app-services-5.png 200w, /static/ab62dde7919f63e6f612d1f07a7f437e/e17e5/deploying-dotnet-grpc-service-azure-app-services-5.png 400w, /static/ab62dde7919f63e6f612d1f07a7f437e/5a190/deploying-dotnet-grpc-service-azure-app-services-5.png 800w, /static/ab62dde7919f63e6f612d1f07a7f437e/c1b63/deploying-dotnet-grpc-service-azure-app-services-5.png 1200w, /static/ab62dde7919f63e6f612d1f07a7f437e/29007/deploying-dotnet-grpc-service-azure-app-services-5.png 1600w, /static/ab62dde7919f63e6f612d1f07a7f437e/defc9/deploying-dotnet-grpc-service-azure-app-services-5.png 2046w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>That’s it! now you know how to deploy a gRPC .NET web service in Azure 🎉</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Conclusion</h2> <p>In this article we looked at how we can leverage Azure App Service to deploy a gRPC server and interact with it. Until next time 👋</p> <h2>References</h2> <ol> <li><a href="https://github.com/Azure/app-service-linux-docs/blob/master/HowTo/gRPC/use_gRPC_with_dotnet.md">https://github.com/Azure/app-service-linux-docs/blob/master/HowTo/gRPC/use_gRPC_with_dotnet.md</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-local-git?tabs=cli">https://docs.microsoft.com/en-us/azure/app-service/deploy-local-git?tabs=cli</a></li> </ol><![CDATA[Setting up a local Apache Kafka instance for testing]]>https://www.sahansera.dev/setting-up-kafka-locally-for-testing/https://www.sahansera.dev/setting-up-kafka-locally-for-testing/Thu, 01 Sep 2022 00:00:00 GMT<p>I recently started digging into Apache Kafka for a project I’m working on at work. The more I started looking into it I realized I need to get myself a local Kafka cluster to play around with. This article walks you through that process and hopefully save your some time 😄.</p> <p>If you don’t know what Kafka is, <a href="https://www.youtube.com/watch?v=FKgi3n-FyNU&#x26;ab_channel=Confluent">here’s a great video</a> from Confluent.</p> <p>You could also check out my blog post to understand the main concepts of Kafka if you are interested from here:</p> <ul> <li><a href="https://sahansera.dev/introduction-to-apache-kafka/">Basics of Apache Kafka - An Overview</a></li> </ul> <blockquote> <p>💡 You can follow along by cloning the repo I have created over <a href="https://github.com/sahansera/kafka-docker">here</a>.</p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Installing the binaries</h2> <p>There’s a quickstart <a href="https://kafka.apache.org/quickstart">guide</a> over at the official docs. To be honest, I didn’t want to install anything locally in order to keep the Kafka instance fairly separate my dev environment, hence went with the Docker way which you will find below.</p> <h2>The Docker way</h2> <p>This is by far the easiest I have found and saves you ton of time. Once you have cloned the repo just spin it up!</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker-compose</span> up <span class="token parameter variable">-d</span></code></pre></div> <p>If you want to know what’s going on in here let’s take a look at the docker-compose.yaml file.</p> <p><a href="https://github.com/sahansera/kafka-docker/blob/main/docker-compose.yaml">docker-compose.yaml</a></p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">---</span> <span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3'</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">zookeeper</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> confluentinc/cp<span class="token punctuation">-</span>zookeeper<span class="token punctuation">:</span>7.0.1 <span class="token key atrule">container_name</span><span class="token punctuation">:</span> zookeeper <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token key atrule">ZOOKEEPER_CLIENT_PORT</span><span class="token punctuation">:</span> <span class="token number">2181</span> <span class="token key atrule">ZOOKEEPER_TICK_TIME</span><span class="token punctuation">:</span> <span class="token number">2000</span> <span class="token key atrule">broker</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> confluentinc/cp<span class="token punctuation">-</span>kafka<span class="token punctuation">:</span>7.0.1 <span class="token key atrule">container_name</span><span class="token punctuation">:</span> broker <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token comment"># To learn about configuring Kafka for access across networks see</span> <span class="token comment"># https://www.confluent.io/blog/kafka-client-cannot-connect-to-broker-on-aws-on-docker-etc/</span> <span class="token punctuation">-</span> <span class="token string">"9092:9092"</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> zookeeper <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token key atrule">KAFKA_BROKER_ID</span><span class="token punctuation">:</span> <span class="token number">1</span> <span class="token key atrule">KAFKA_ZOOKEEPER_CONNECT</span><span class="token punctuation">:</span> <span class="token string">'zookeeper:2181'</span> <span class="token key atrule">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</span><span class="token punctuation">:</span> PLAINTEXT<span class="token punctuation">:</span>PLAINTEXT<span class="token punctuation">,</span>PLAINTEXT_INTERNAL<span class="token punctuation">:</span>PLAINTEXT <span class="token key atrule">KAFKA_ADVERTISED_LISTENERS</span><span class="token punctuation">:</span> PLAINTEXT<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">9092</span><span class="token punctuation">,</span>PLAINTEXT_INTERNAL<span class="token punctuation">:</span>//broker<span class="token punctuation">:</span><span class="token number">29092</span> <span class="token key atrule">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR</span><span class="token punctuation">:</span> <span class="token number">1</span> <span class="token key atrule">KAFKA_TRANSACTION_STATE_LOG_MIN_ISR</span><span class="token punctuation">:</span> <span class="token number">1</span> <span class="token key atrule">KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR</span><span class="token punctuation">:</span> <span class="token number">1</span></code></pre></div> <ol> <li>We spin up a <a href="https://zookeeper.apache.org/">Zookeeper</a> instance at port <code class="language-text">2181</code> internally within the Docker network (i.e. not accessible from the host)</li> <li>Next we spin up our broker which is the Kafka instance which has a dependency on Zookeeper. This is exposed at port <code class="language-text">9092</code> and accessible from the host.</li> </ol> <p>Everything else is set to default. If you want to learn what each environment variable does, head over <a href="https://docs.confluent.io/platform/current/kafka/multi-node.html">here</a>. You can add multiple brokers (Kafka instances) if you want in the same docker-compose file.</p> <p>This approach is quite beneficial if you want to stand up an instance for testing on the go or even in a CI environment.</p> <h2>Interacting with the cluster</h2> <p>Before we publish anything, let’s spin up a consumer so that we know our setup is working. To do that, run the following command from the CLI.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token builtin class-name">exec</span> <span class="token parameter variable">--interactive</span> <span class="token parameter variable">--tty</span> broker <span class="token punctuation">\</span> kafka-console-consumer --bootstrap-server broker:9092 <span class="token punctuation">\</span> <span class="token parameter variable">--topic</span> example-topic <span class="token punctuation">\</span> --from-beginning</code></pre></div> <p>Now let’s create some messages and see them in action!</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token builtin class-name">exec</span> <span class="token parameter variable">--interactive</span> <span class="token parameter variable">--tty</span> broker <span class="token punctuation">\</span> kafka-console-producer --bootstrap-server localhost:9092 <span class="token punctuation">\</span> <span class="token parameter variable">--topic</span> example-topic</code></pre></div> <p>This is what it will look like from the terminal. To your left is the <code class="language-text">producer</code> and <code class="language-text">consumer</code> to your right hand side.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 48.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3UlEQVR42oWT627aQBCFjW2MDdjYISmBGPANsNc2xFwCSQkRUlRV/Vepz1Cp7/8Ap2eBplFbqT+OxrM7881lZaU3W+I2XZ/kF1v42QaB2GIcLTAvXxBWe5iDIdzpAv1yj2vxAC+t4NC3JyUaQYpOMIMXZ3CuB1D8p1dczR/hEna7fkFvuUd3voOXb9AKCwyrA5Lnz+jc72AvHuHx/mZ7RHf9TH8HI13CTEo0WaTdD6A4Yg1ncg8zyGEFBZqhVA47KuHQT6sjVq/fYIgKejKHFlNRAZUxSpChFgrUAgGFea3eEEqDl7KTVly+qU05TJTQ4XSNsHhigoAa5RdoefquXaRGAvUpC0qgyYD2HzApk4XqcQF9PIPux1Bj2ZU42xOweAMa9G0WNeTIFoH2O5BF6bKqHIVWG02hSeDlTI76CyjPZOFWwgcqPnJFGZQmFyqBTap+ChKXxPOIfwNZiFBdNjBZwCWswzcwuKLDl+9QWuzQIkgL34H+A5SxNmE9scHVrIJHYD3McPj0A4pK4Ln9f4hdaOMU2jBBLZY7Y2Fag7I4mZet4KYr2OxSrmnx8BVKbRBA649PUi/293fAl/OhfbhDw0/QvovgstsudTNK0B9NILIKGX+OwSjDJj/iJ623D9jJwedGAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-kafka-locally-for-testing-1.png" title="" src="/static/018146d435429a3497576c80172e22e3/5a190/setting-up-kafka-locally-for-testing-1.png" srcset="/static/018146d435429a3497576c80172e22e3/772e8/setting-up-kafka-locally-for-testing-1.png 200w, /static/018146d435429a3497576c80172e22e3/e17e5/setting-up-kafka-locally-for-testing-1.png 400w, /static/018146d435429a3497576c80172e22e3/5a190/setting-up-kafka-locally-for-testing-1.png 800w, /static/018146d435429a3497576c80172e22e3/c1b63/setting-up-kafka-locally-for-testing-1.png 1200w, /static/018146d435429a3497576c80172e22e3/29007/setting-up-kafka-locally-for-testing-1.png 1600w, /static/018146d435429a3497576c80172e22e3/db1b2/setting-up-kafka-locally-for-testing-1.png 2900w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>A GUI perhaps?</h2> <p>Not a fan of the CLI? There’s many tools out there. I wanted to find a nice, lightweight, OSS solution for this. I decided to go ahead with <a href="https://marketplace.visualstudio.com/items?itemName=jeppeandersen.vscode-kafka">Tools for Apache Kafka for VS Code</a>.</p> <p>Once you have that going you could interact with it from the side bar. There will be a new icon with Kafka’s logo on it!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 99%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACQElEQVR42o2U6XbaMBCFeZOy2RjvG16BsjhAyhKWtKSn7VP0/c90roxdExPIj+8ICc/V1cxIjS9NjVptg9ods6TV1gXNll777xENSXbJ86fUU1yBqgVi7g9mNAgyspwR9dWBWH+P0vdrNLqSQx4Hm/aQDCsVHw7COYXxggXnpBsRaXp4k57i1Wh0uhaZLKSbSf5R3yOLxeEMa9gVDm9RFZJ7bi6IHBlWQq4/YadTst0Ru+QjcoDGbuHwMxiXzYWgzW7idEVRshS/4RSjYcYfHhdU02FaF0FUJhcYizzCNo4Txk/kemNeT5j0A/C9L2LKokCwWjHkAhusN99puzvX2FzYvbzRYnUgRc1jrgQhoGohOwvEn7oR0sv+7S77w096/nbkmBuChUMcFSPmcZLdJUkzCsJZGVOMoihoGyRVrVQ1z819IFCtdCnoeF+5XcZl5QA2eAi3im7Eol/Ru0IQdxlHWG/O3My5MHLSleyHSIzcc0iWnXpRNHbV14Kyypvt+R0/rkDFF8s92f6K7GDL8WNScFOqgiqPEESC0+GiRpI+lWAeRjPSrTEZzpT6eswOvaLKecsoLIQjYIPV84nb4rXCideOJZjPsy1Z/oIdrtnQ6L9DOINgUXoU5Xj6Q6fXa46n3yWYo/ntwZrs6ECaOWFBN69yTzZFgxZO4TCKsxphNL/C58fEsPnKOhN2eLnLkhLRZPeXTG/JOzilU+TyEeIOCzjvRZWbHYfc4S+S1JSfe42fcUs85XgnP0O74BL3D3b7f/v3TWv3AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-kafka-locally-for-testing-2.png" title="" src="/static/5d851500362a638d86358d395c19a699/5a190/setting-up-kafka-locally-for-testing-2.png" srcset="/static/5d851500362a638d86358d395c19a699/772e8/setting-up-kafka-locally-for-testing-2.png 200w, /static/5d851500362a638d86358d395c19a699/e17e5/setting-up-kafka-locally-for-testing-2.png 400w, /static/5d851500362a638d86358d395c19a699/5a190/setting-up-kafka-locally-for-testing-2.png 800w, /static/5d851500362a638d86358d395c19a699/c1b63/setting-up-kafka-locally-for-testing-2.png 1200w, /static/5d851500362a638d86358d395c19a699/b5a09/setting-up-kafka-locally-for-testing-2.png 1360w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Here’s what it looks like when it finds your instance.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 632px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABzklEQVR42m1T2XKDMAzMNxACBGNzmBtCc9B2Jg/NY///i1StwJmkyYNGNtir3ZW1CcOUyvJITXuhLOupqk8SXf9F2jTk+4p2O01BoCW7te8nfL6hPO+orPh8d6RpmmkTRRmFHPt9TrEqKY4tqaSUwNpFFOUSOCdn44Jzsd6zsk/4jgDiQqJrqpmZLSdq25n64ZsrH6ntZo5PStOe2QxU2APnkfNEGe+DwKysl7yBZFTBzyw/CCgqb7exyJXYKpG4SE4ksAfI/xBARJq29HP7FQZgo5mxMR3LqLlQz986KeQsUGwP7r0AugUArtcbA1RkWY5rDmSjQQ3boE0rwGnWiSJ4/5YhJBs+bO1JGEKm50Ui2/P2Ek8WrOHkOw/vDNE9xZ0ax5nyYuAY+Sl9CAsUyLgJaJphW8ASKiAZJMy6B6kHwKXT1g4CVDcXySm/S4DjfQJA60YAlKrkPOSjCHx28u9NgeFgYQFYn0nF5V0qMrqKbi/Sly679ZPkMDQCiB/odN2cZWoKZmZ4UtAsyE50JZMDxvgG9vj/psupeAgf8DxwoSgOkvNsELmQGa3TBCXOIufby7MBIC5VPJPwDhmAkI+pgXfLQ9ZPU/Eo1QH+AaMfYCMSWJm/AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-kafka-locally-for-testing-3.png" title="" src="/static/1de368b211cb45a6fe43f6591f4dedd7/084e2/setting-up-kafka-locally-for-testing-3.png" srcset="/static/1de368b211cb45a6fe43f6591f4dedd7/772e8/setting-up-kafka-locally-for-testing-3.png 200w, /static/1de368b211cb45a6fe43f6591f4dedd7/e17e5/setting-up-kafka-locally-for-testing-3.png 400w, /static/1de368b211cb45a6fe43f6591f4dedd7/084e2/setting-up-kafka-locally-for-testing-3.png 632w" sizes="(max-width: 632px) 100vw, 632px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><a href="https://github.com/sahansera/kafka-docker/blob/main/producer.kafka">producer.kafka</a></p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json">PRODUCER keyed-message topic<span class="token operator">:</span> example-topic key<span class="token operator">:</span> mykeyq record content ### PRODUCER non-keyed-json-message topic<span class="token operator">:</span> example-topic <span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"my_test_event-{{random.number}}"</span> <span class="token punctuation">}</span></code></pre></div> <p><a href="https://github.com/sahansera/kafka-docker/blob/main/consumer.kafka">consumer.kafka</a></p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json">CONSUMER consumer-group-id topic<span class="token operator">:</span> example-topic partitions<span class="token operator">:</span> <span class="token number">0</span> from<span class="token operator">:</span> <span class="token number">1</span></code></pre></div> <p>You can invoke the producers and consumers right within VS Code, which is pretty easy and cool!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA9UlEQVR42nWQUW6DMBBEuUPbADbY2IbFwRhICI1SqVLa+99pukblr/0YeaX1PO1MRmbCs1/xYQK2ilAWFkXpUPGcixZLu+Cru0A1AUJ2yAuDt5PGy6tGDAbn+I7GTshzs/sypQbcr9/YwgPEs6x6iF+VooPrVtzmJzpaIesBuhnQ+xnjeMUUA9z4gHIT+2j3ZrI+o6EbqibukgwVleel54sIBX868ZvgpSDUyjPsgjitsDZCGZYdoU2PWicgG62/w48biBY4HThuv8P+UgLnRctyHLEFzZ8wXEvJ9aR9Jo+P3E+aE0z+AzuUoh2q92RchfV8occPktaMWSUEWLUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-kafka-locally-for-testing-4" title="" src="/static/e4fd10918691b2d4db95f2f18502b26f/5a190/setting-up-kafka-locally-for-testing-4.png" srcset="/static/e4fd10918691b2d4db95f2f18502b26f/772e8/setting-up-kafka-locally-for-testing-4.png 200w, /static/e4fd10918691b2d4db95f2f18502b26f/e17e5/setting-up-kafka-locally-for-testing-4.png 400w, /static/e4fd10918691b2d4db95f2f18502b26f/5a190/setting-up-kafka-locally-for-testing-4.png 800w, /static/e4fd10918691b2d4db95f2f18502b26f/c1b63/setting-up-kafka-locally-for-testing-4.png 1200w, /static/e4fd10918691b2d4db95f2f18502b26f/29007/setting-up-kafka-locally-for-testing-4.png 1600w, /static/e4fd10918691b2d4db95f2f18502b26f/de720/setting-up-kafka-locally-for-testing-4.png 3452w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Conclusion</h2> <p>Welll that’s it! Standing up a docker container certainly did save me some time just for testing purposes. Hope you enjoyed this post and see you next time 👋</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>References</h2> <ol> <li><a href="https://developer.confluent.io/quickstart/kafka-docker/">https://developer.confluent.io/quickstart/kafka-docker/</a></li> <li><a href="https://marketplace.visualstudio.com/items?itemName=jeppeandersen.vscode-kafka">https://marketplace.visualstudio.com/items?itemName=jeppeandersen.vscode-kafka</a></li> <li><a href="https://docs.confluent.io/platform/current/kafka/multi-node.html">https://docs.confluent.io/platform/current/kafka/multi-node.html</a></li> </ol><![CDATA[Adding intelligence to React Apps with Azure Cognitive Services]]>https://www.sahansera.dev/adding-intelligence-react-azure-cognitive/https://www.sahansera.dev/adding-intelligence-react-azure-cognitive/Sun, 21 Aug 2022 00:00:00 GMT<p>Hi everyone 👋 This blog post is based on a conference talk I delivered at the <a href="https://events.geekle.us/react2/">React Global Summit 2022</a> on the 21 April 2022. React Global Summit is a global conference that brings together the best minds in the React community to share ideas, learn from each other, and build awesome products. I presented under teh Senior Developer track.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>YouTube Recording</h2> <p>This time I don’t have any text to write, because there’s a video instead 😀 Check out the recording below and don’t forget to like and subscribe.</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/TLr_cxg4EhI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <p>It’s a bit lengthy so I have segmented it into sections with timestamps. Let me know what you think of the format? Should I continue doing this in the long run? I’d love to hear your thoughts. Thanks!</p> <blockquote> <p>💡 You can follow along with demo code on <a href="https://github.com/sahansera/rgs-2022-demo">GitHub</a></p> </blockquote> <h2>Slide Deck</h2> <p>The slides can be found at my SpeakerDeck <a href="https://speakerdeck.com/sahansera/infusingintelligence">profile</a>.</p> <iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/b3ff395fa9d244b69c4bafc37e0118ad" title="Adding intelligence to React Apps with Azure Cognitive Services | React Global Summit 2022" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 314;" data-ratio="1.78343949044586"></iframe> <h3>Conclusion</h3> <p>Let me know what you think of this new format. Until next time 👋</p><![CDATA[Tips for Junior Developers]]>https://www.sahansera.dev/tips-and-advices-for-jnr-devs/https://www.sahansera.dev/tips-and-advices-for-jnr-devs/Tue, 31 May 2022 00:00:00 GMT<p>Hi everyone 👋 this blog post kind of different from my usual ones since this is based on a recent talk I delivered at the Adelaide Jnr Developer Meetup hosted by <a href="https://twitter.com/juniordev_io">juniordev_io</a> on the 18 May 2022.</p> <p>The slides can be found at my SpeakerDeck <a href="https://speakerdeck.com/sahansera/adelaide-jnr-dev-meetup-may-2022">profile</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACe0lEQVR42nWSyU8TYRiHu9Bthm7TZabTlZax0IG2UJaWICAIVCCyIxTQIIkxhogmKhgBJRpi8GCiiRfvHkyMF48evPtHPU5rghLx8Mvv9D153/f5TLGJPdTpI+TZl6gLJ7QsviI9s0dq7A6J8gpq5xihTAl/LEuzFEVs9qC47VjMJkym3zGbzY1WFAVTZOY57VN3GZ6uUZ7eRl8+pn3xgGz1HtrQGsnuCdRsH6FkDncwhs/rQ5cFbBbzf4DLp3QNTbN3JczOZIGe1QO6Vo8pztxHH91A679GQi8TadGR5Dgu0WM8/gP7B9g6+4zBq3O8mZI5WSoyurHP8OYhAws7lKqb6ANVMvkKiUt5QtEUott7BrgQeLi7yeGizve9KN+eZHm6MsjthcvMVSt0dOgkElES6QzhSLxxP7vDiSiK2O12nE7nWc6An0+3+bo/wKfHZT7sDHCy1ceL5S6GO+MUtQgZNUAs4MYr2mh2WBEcNvL5POl0mlQq1WhN0xpAWZYxbU1qHN3qZ/+6xuFaid01lS3dTTGh0N4SopQMU4qHCEo+BJfLmMxGJpOhra2t0XVooVBAEATC4TCm9w+G+fJQ58e7Cj8/rvN63RCkd9DqlejVJEZyPnKtCh5JxmZ3YLFaGyvX4/V6cTgcjdQnVFUV0/xYhflCiLc1gdOan+VKhEeLrdRGNHraVaKJFgSfgsMl0NRkw2ptIhAIIEkSwWCwIeS85aSfyf4AN6thVib8zI+LzM3KaIU2PPEiTn/EmMCGzZBhNlvO2b3Q8o1xhaVrMiNDXnrLBqi7Fzk3hpIdQo4l8flE/MZEHuND19d0GXf8O3XD9bZYLCSTSX4B5HA+2E/djeoAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs.png" title="" src="/static/956d6502e8f62ab27a05f42ada09a0b6/5a190/tips-and-advices-for-jnr-devs.png" srcset="/static/956d6502e8f62ab27a05f42ada09a0b6/772e8/tips-and-advices-for-jnr-devs.png 200w, /static/956d6502e8f62ab27a05f42ada09a0b6/e17e5/tips-and-advices-for-jnr-devs.png 400w, /static/956d6502e8f62ab27a05f42ada09a0b6/5a190/tips-and-advices-for-jnr-devs.png 800w, /static/956d6502e8f62ab27a05f42ada09a0b6/c1b63/tips-and-advices-for-jnr-devs.png 1200w, /static/956d6502e8f62ab27a05f42ada09a0b6/29007/tips-and-advices-for-jnr-devs.png 1600w, /static/956d6502e8f62ab27a05f42ada09a0b6/5e1f2/tips-and-advices-for-jnr-devs.png 2134w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>The following is a short summary of my notes for each slide I spoked about. I structured my talk around 3 main topics.</p> <ul> <li>Maintaining Motivation</li> <li>Dealing with Imposter Syndrome</li> <li>Staying Productive</li> </ul> <h2>Slide Deck</h2> <p>Here are the slides from my talk.</p> <iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/e13c2ea26ae44476a0f00367e05afc61" title="Adelaide Jnr Dev Meetup - May 2022" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 314;" data-ratio="1.78343949044586"></iframe> <h2>Maintaining Motivation</h2> <h3>Put in the work</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaklEQVR42oVT207CQBBtaYF2226LQEuplUvDncQYgpKIEvTBF6OJD7z7/x9x7EwtF6Hx4WR3Z3fPnD0zq5RKJSiKcgaKW5bFME2TIYQF27ZRdIdxaZNiUkqUy+WzPYp5nodKpcLr+lUNk+GA56qqnhIahsGKgiBAtVrdx6Wlw7UP5ETW7XZ4LlLlfrOREf5VKIRAFEWI45iz6brG8YeJifeV86te5bHf76HeqB+UFT2ZlBHoUJIkrJbUdOIIYRhiNB6xwkaqajof7y0qJCTzc38cx2EQ8Ww2Z+/IW9prhQHuFrfFhK60MUo6UNO149gnBfJ9n5XmiQjj6fAyoaZlPg16N/jefWTkrtwfOvaH5ip5mI5fu0+0WgFMw0TOUdg2pISeWtRvm5c1njaPaQtl5/4lzEnb7RBezYOwBOzUBj9o4vVti+VqwYWjriCcEFKG/Eccg4ojpctVvV8tWdHzdo345hq6pl+8Q/gBWpb2SKyRYpgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-1.png" title="" src="/static/73a9ac3ab1ac43654d212177a86afad8/5a190/tips-and-advices-for-jnr-devs-1.png" srcset="/static/73a9ac3ab1ac43654d212177a86afad8/772e8/tips-and-advices-for-jnr-devs-1.png 200w, /static/73a9ac3ab1ac43654d212177a86afad8/e17e5/tips-and-advices-for-jnr-devs-1.png 400w, /static/73a9ac3ab1ac43654d212177a86afad8/5a190/tips-and-advices-for-jnr-devs-1.png 800w, /static/73a9ac3ab1ac43654d212177a86afad8/c1b63/tips-and-advices-for-jnr-devs-1.png 1200w, /static/73a9ac3ab1ac43654d212177a86afad8/29007/tips-and-advices-for-jnr-devs-1.png 1600w, /static/73a9ac3ab1ac43654d212177a86afad8/5e1f2/tips-and-advices-for-jnr-devs-1.png 2134w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Developing software is a creative endeavour. This is no secret. Regardless of your background, whether you are coming from a Bootcamp, uni degree, or doing it as a hobby, you have to do the work yourself to gain experience. You can read a 300-page book or binge-watch a 4-hour-long Youtube tutorial on building the next million dollar idea, but it would be a mere waste if you don’t take any action. If you follow a tutorial or do what your professor or instructor is telling you to do, you won’t grow as a developer if you don’t try things on your own.</p> <p>It’s so easy to “get stuck in a <a href="https://www.urbandictionary.com/define.php?term=Tutorial%20Hell">tutorial hell</a>,” which can create a false sense of accomplishment. So what should you do about it? My suggestion is to carve out some of your free time, maybe 30 minutes a day. Start small. It might be a simple to-do app that you want to build with a programming language you want to learn. Learn the basics first, then focus on different building blocks of it. E.g., Think about the UI; how would you make it go live? How would you save the data? How would you handle multiple users accessing your app?</p> <p>30 minutes/day actually ends up being 14hrs/month which is a considerable amount when you think about it!</p> <blockquote> <p>💡 Don’t wait until motivation hits you.</p> </blockquote> <p>Another correlated point to this is waiting until motivation hits you. You may feel overwhelmed and think that the barrier to entry is high. But once you get through that, your curiosity will grow, and you’d learn things quicker. Most importantly, enjoy and have fun.</p> <h3>Adopt a Growth Mindset</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABeUlEQVR42oVTSW+CUBAGd2VVQJaIIgS0YtFQuxxMV5MmTZo0aZpee2t66P8/fmVeA9WK7WHyZuF975uZD65SqYDjuD2jvCiKEAQB7XYbnU6HGeV+3+F5np22bYMrA6ScLMuo1+t7tVqtBlVV0Wg0WKz1uphGIfMdx9kFbLVajJFpmmg2m0V+5HnYXN8VMdU8b8R8IWNtm/1yQGqJkq7rsjhnmC6W+Hz/KNjT6Qdj9LTeTq60ZXqdmJKv6zp834dlWYjnc4RhmDHzWK2fsTqaTZlfrVYPA9IC8jkFQYDJZIIoijDPAJMkQRzH7KLt2FimyWFAWRIRjocZ/SqbI7VvGAY0TUO324WiKIwxzZc2PZ1FBSARKABzdAJ7e3lkvqLIOCSnXCbPr08wLbPo6E/ZkCQkSToIenW7xuXNmn0ny1I5IM//CDSXkOsOoOkaA1dUJZubhfuHDU4vTtjycrH/yzA3ko07HOB4EWN1lmJ1nkI3tO/61uPbf8oXXkD8Bxd9+BwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-2.png" title="" src="/static/78268f5be3581cc893a929f85b4e9bbf/5a190/tips-and-advices-for-jnr-devs-2.png" srcset="/static/78268f5be3581cc893a929f85b4e9bbf/772e8/tips-and-advices-for-jnr-devs-2.png 200w, /static/78268f5be3581cc893a929f85b4e9bbf/e17e5/tips-and-advices-for-jnr-devs-2.png 400w, /static/78268f5be3581cc893a929f85b4e9bbf/5a190/tips-and-advices-for-jnr-devs-2.png 800w, /static/78268f5be3581cc893a929f85b4e9bbf/c1b63/tips-and-advices-for-jnr-devs-2.png 1200w, /static/78268f5be3581cc893a929f85b4e9bbf/29007/tips-and-advices-for-jnr-devs-2.png 1600w, /static/78268f5be3581cc893a929f85b4e9bbf/6c960/tips-and-advices-for-jnr-devs-2.png 2135w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Not sure what to focus on next? It is okay not to understand what is going on when you join a company or a new project. That’s fine. It takes time. Usually, you start small - maybe it’s a minor bug or a feature request to change the button’s color. Start from there and try to understand how things tie together. Ask questions, and reach out to your peers. This is also one of the mistakes I made earlier on. I just put my head down and smash through the tickets. But it’s not enough. By doing this, you will begin to understand more and more.</p> <blockquote> <p>💡 Learning has a compounding effect</p> </blockquote> <p>What do I mean by compounding effect? Simply put, initially will take a lot of time, might seem slow, and your growth will seem linear. But once it picks up the pace, it will grow exponentially. And when you stop learning, that cadence breaks, and you need to get back up again. So, Learning is a lifelong process.</p> <p>This doesn’t mean you always have to be learning something every minute. There will be days you will feel like you didn’t know anything, just be answering emails or slack messages. That’s fine. You need to make sure you don’t do that every single day. By the end of the week, I usually just look back and do a small retro to understand what I learned that week. If not, plan for the next week. <em>Consistency is key.</em></p> <p>Few more tips:</p> <ul> <li>On-the-job training is great since you will be spending most of your time during the week at work. Read through technical documents that your team has, code reviews are great to get feedback and also understand what others are working on, run pair programming sessions with your team mates</li> <li>Utilize the Learning and Development budget your company provides</li> <li>Perhaps you might be thinking of pursuing a certification?</li> <li>Network with other peers</li> </ul> <h2>Imposter Syndrome</h2> <h3>You need to be an expert in something to teach others</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABiElEQVR42nWSyVLCUBBFgZCRmEpCBjIzhsEFMpRaIoq6cKdVLtj7/x9xff2oRCNk0ZU39cm93d1ot9toNBpn0Wq10Ol0eKiqCk2j0KDrOgRBuJjD4xKQYIZhQBTFszt6b5omJEnie9sysZjlfN1sNqtARVG4It/3Ictyee5YCu6XvQo0y1K+1ph633MvKyRrYRgiSRKuwrZtyIqKqNfF+jqBqumwLItbH42HcJzur7I6y2SFwI7jIE1TRFGI4WCIrD/AfD5n+wiu6yJJE8wW07JEtUCyTUGPqAF5nmM6nWKxmDObGTzP400Jwh5Wm5t6oMGSJ4OUXQpldwlAJSBly+WSr0m1ZVrIZxOstyegIPwBFp0c9RN8Hz9OcNbhYjSoPmQ/jmOunCexs6/jJ4KgB5XVuDJGl8aG6kjQihUOOn33Lzs8v+4hiVLl57XAAhqGAeuqCa2j4cq4gue7eHs/YHO7KptHUQFS4WnmimYUQWd0N8nH2N5t8Pj0gP1hhziJuXIC/c+hkvwARhT65WK1fGUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-3.png" title="" src="/static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/5a190/tips-and-advices-for-jnr-devs-3.png" srcset="/static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/772e8/tips-and-advices-for-jnr-devs-3.png 200w, /static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/e17e5/tips-and-advices-for-jnr-devs-3.png 400w, /static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/5a190/tips-and-advices-for-jnr-devs-3.png 800w, /static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/c1b63/tips-and-advices-for-jnr-devs-3.png 1200w, /static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/29007/tips-and-advices-for-jnr-devs-3.png 1600w, /static/a28fe0d8fc8e7ad2fdccea85fc8aeaf6/dec58/tips-and-advices-for-jnr-devs-3.png 2133w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>As mentioned in my previous slide, you have to make learning a critical part of your life. There’s a lot to learn and keep up with. During the last ten years, I have worked with many technologies, from Ruby, JavaScript, Java, Angular, Android, iOS, .NET, SQL, NoSQL database; some technologies don’t even exist now - J2ME Java Micro Edition, ActionScript, Windows Phone 💀. As you can see, it’s a never-ending process. You will pick up new things now and then, from your job, your side project, from your colleagues. Even today, I still need to look up certain things, Google stuff, when I cannot seem to figure out something.</p> <blockquote> <p>💡 The most important thing is <strong>No one knows everything</strong>.</p> </blockquote> <p>How can we overcome this feeling though? When you are learning something new, don’t feel embarrassed to share anything you learned. Don’t think of it as bragging either.</p> <p>I initially felt this when I started this blog. I didn’t even think whether anyone would want to read my blog. I just did it anyway. I started it about 2.5 years ago; I initially had 1 or 2 readers – now I have around 10,000/month users and recently passed 200,000 users, which is not a massive number, but I feel happy about it. Adding to that, it’s a great feeling when you get to help someone, and they put a comment saying they have been struggling with it for days, and my blog post solved it. You will get sponsorships and get invited to talks, to name a few.</p> <p>You must’ve figured out a pattern already - gain experience by doing things. You don’t need to be an expert in something to guide others. You can even share how many times you failed to do something and let others know how you overcame that. Put a unique spin on things. Everyone is unique.</p> <p>Moreover, you will also learn to say “I don’t know” and that’s 100% fine.</p> <h3>Feeling ‘Everyone else is better than me’</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgUlEQVR42oWT226CQBCGRQ5yECSo4AG0hSiGIhprD0lj2sakSZuYJk3aa9//Jf7uDoWqxfRiMrsz8O3M/rO1Wq2GKqvX6zAMg0zTNOi6Vu557vBbQRDID4dD7qthlmVBluU/OR6zbRuKkuc6bQez6aQaqKoqVeB5HhqNRhkPfQO750G557nxeExrw9DRc91qoK7rFAyCAKIoQqEKRSwnBvavbbYWIEtSfkgUwnGcsqOzLfMqi7ayLMN0MkHX7cMfRQjDEHEcw3M9uF4XsySmf/jhZ4FcAEVRqKUkSeD7PgNdIpunBIuiiCyMWGw5Pw9sWSam4RgCK980TTSbTaqSH8ChaZpSvFA6zRIsVjlQ+rkGAhaST8ML7D93tOYKH44Gh7rs4otvuf/4emfiudBU7RhYNTZcxVMomZD7x+0Gm6cHJppCVVcCBeF3QAtxgsCHw+bMZNdh2y30Bz28vG1xc39NeT4V3P6tsBxiScZoFGC+uML6doX13QpOxzl6Hacv5RtxpPx+U5md7QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-4.png" title="" src="/static/accb24768a5a76a3b6f8f90b2c8e1eee/5a190/tips-and-advices-for-jnr-devs-4.png" srcset="/static/accb24768a5a76a3b6f8f90b2c8e1eee/772e8/tips-and-advices-for-jnr-devs-4.png 200w, /static/accb24768a5a76a3b6f8f90b2c8e1eee/e17e5/tips-and-advices-for-jnr-devs-4.png 400w, /static/accb24768a5a76a3b6f8f90b2c8e1eee/5a190/tips-and-advices-for-jnr-devs-4.png 800w, /static/accb24768a5a76a3b6f8f90b2c8e1eee/c1b63/tips-and-advices-for-jnr-devs-4.png 1200w, /static/accb24768a5a76a3b6f8f90b2c8e1eee/29007/tips-and-advices-for-jnr-devs-4.png 1600w, /static/accb24768a5a76a3b6f8f90b2c8e1eee/6c960/tips-and-advices-for-jnr-devs-4.png 2135w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>On to the second myth. Having unrealistic goals and comparing yourself with others is a recipe for this. It’s in human nature to compare one’s self to another. You wouldn’t know what they have gone through so we can’t really compare ourselves with others. When you set a standard for success, you’d think you have to achieve it, and if you don’t, you’d think you are a failure. Well, that’s not the case. Because there’s no single path to success, there are multiple paths, and your journey might change from time to time, and that’s totally fine. If you feel that you can learn from the people surrounding you, that’s a good sign! Maybe you have an idea to start a side project, a blog, or a Youtube channel to share your learnings. Follow your passion and do what you love.</p> <h2>Staying Productive</h2> <h3>Don’t obsess about the programming language</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjUlEQVR42n1S2W6CUBDFBRcWAcECglCgGlCrrVpTE2O6JH1o0qRJ0/S1//8Tp8wkENc+TO7MnZlzz5y5QrVaxbFVKhXUajWoqsomyzIUhUzhmHJUs98jCAJc16VTwLFRATXW6/WTHN3pug5RFDm2TBPpaMi+4ziHgK1Wi9nYto1Go1Hee5aEl4VTxs1mE+F1yL4sSXCueucBpTzp+z4GgwGPpOsaWpKCqK9jMzEgySoMw+DaOIlgdI1yIjqJyMnIxJIY8DiWhSAHvxmO0LM9TKcTJEkCsS7C6pnIJinXkaYXAWnkQh8yYjweZ/A9D1EUcZ7Hc23cL+eXATuqgjjwyoUU4FmWYT6fI03TnOGU5aBcmo1wt5iViyoBSSsuyDX5/f5gX9O0Uhf6KmEYIo5jBGHAd9Tz9fMJ27FZon9HLrZILIvHCivi3fMWu6dtLk2DHzxgWBbvNZC1223WzzS76HRU6IYGt+/g7f0V682K8yQJ/YyzgOeMlhOEA9zOJlitF3h4XMK0zAO2+z4B/gEhEvt4dDB7HQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-5.png" title="" src="/static/7ee223f6d763910d0aba1d41896619ab/5a190/tips-and-advices-for-jnr-devs-5.png" srcset="/static/7ee223f6d763910d0aba1d41896619ab/772e8/tips-and-advices-for-jnr-devs-5.png 200w, /static/7ee223f6d763910d0aba1d41896619ab/e17e5/tips-and-advices-for-jnr-devs-5.png 400w, /static/7ee223f6d763910d0aba1d41896619ab/5a190/tips-and-advices-for-jnr-devs-5.png 800w, /static/7ee223f6d763910d0aba1d41896619ab/c1b63/tips-and-advices-for-jnr-devs-5.png 1200w, /static/7ee223f6d763910d0aba1d41896619ab/29007/tips-and-advices-for-jnr-devs-5.png 1600w, /static/7ee223f6d763910d0aba1d41896619ab/dec58/tips-and-advices-for-jnr-devs-5.png 2133w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>When starting, yes, you need to know some programming language. Maybe it’s the language of choice of your current employer or a module you are taking currently. The reason is that it’s like learning a human language. Once you know the structure of a language, for instance, grammar, syntax, predicate, whatever that might be, it will be reasonably straightforward to understand and transfer the concepts to a different language. Yes, there will be differences here and there, but you can learn them. Just pick one first and run with it. Get comfortable writing code with it. Don’t try to learn as many languages as possible, there’s too many out there. If you don’t know where to start, JavaScript is a good starting point because you can work on both client-side driven applications (vanilla JS then React, Angular when you are comfortable) as we as the server (e.g., with Node.js). Start to identify those building blocks first. You will then form opinions about things, and perhaps start contributing to open source projects</p> <h3>Learn to use a universal IDE</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfUlEQVR42oWS2U7CUBCGu0ChKwW0FWgplrJYEpXikrihRBNvvDDReOv7v8RvZ+JpaFPjxeTMdv5OvzmSqqqomqIoaDQacByHzbIsNhHv99BJsSRJCMOQTglVoybbtvlCtUa5TqcDTWtyfNDvYTlP2B+NRmXBdrsN0zTh+z5arRbnZFku6sKnWhRF7JuGAd87rBfUdZ2Tv6MXApPJBE+7XSkXT4/R63eLP6oVFFNqmsasXNeFkX/E8zyeWrDU9XyqIx/LdMF3BMNaQSP/BWogTrPZDMPhEFmWIUkSnjRNU8RxjMFwgPPs9G9Bx7aQRCGPTxwJ/ng8RhAELDCdTjlerVawTAuzRYL15qxYVCEomMzjCN+fb+zTdCRMtX0TOUVW8PH1nmPwckR6WbDu2RBDejYCdtUenx9wt71Fs6kx038FhSjxc7tufsmE03HyRXh4ed1hc7Xmp0O86WWUBClZZ7Rt2vTyZIHL64t8ohtsd/cIxwFURWXOolcwp8X9AL5G/H30cQ3aAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-6.png" title="" src="/static/fd0b287814d509626c4a20d7203a2545/5a190/tips-and-advices-for-jnr-devs-6.png" srcset="/static/fd0b287814d509626c4a20d7203a2545/772e8/tips-and-advices-for-jnr-devs-6.png 200w, /static/fd0b287814d509626c4a20d7203a2545/e17e5/tips-and-advices-for-jnr-devs-6.png 400w, /static/fd0b287814d509626c4a20d7203a2545/5a190/tips-and-advices-for-jnr-devs-6.png 800w, /static/fd0b287814d509626c4a20d7203a2545/c1b63/tips-and-advices-for-jnr-devs-6.png 1200w, /static/fd0b287814d509626c4a20d7203a2545/29007/tips-and-advices-for-jnr-devs-6.png 1600w, /static/fd0b287814d509626c4a20d7203a2545/6c960/tips-and-advices-for-jnr-devs-6.png 2135w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>This is where you will spend your time during your day-to-day job. There are many options out there. IDEs range from VS, JetBrains, or even VIM, to EMACS. Sometimes one is better than the other for a particular stack. Usually, pick something you are most comfortable with and learn how to navigate around, interact with the terminal and the key bindings, and configure it the way you want it to function. Usually, when you join a team, you would have a sharable configuration such as an editorconfig file for different IDEs; if not, you can create one. Talk to your peers about what they are using. Not everybody has to use the same IDE too. Also, be mindful of the capabilities of different IDEs as well. First, try the options out there and get a feel for them. It will take time; eventually, you will land on one. It will become a natural part of your development process, and it’s one less thing to worry too. I use VS Code these days since I’m working on multiple projects because I have all the plugins and a familiar environment. Less context switching.</p> <h3>Take regular breaks</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABb0lEQVR42oVS2U7DMBDsnTRHk6ZpHBIKIbSENlRAWw5RqEC88IKEhBCv/P9PDF6DraQHRBrZu86OZ9ZbqVarkKjVagK0r9frsG0blmXBMAwB0zRFjs6KdUVU+Id1ECkVNpvNjbNGowHXddFqtUTc63nIRkOx3yDUdV2oYIypAkJb1+C7HRVrmoYkORR7kytnff+XsFImJFtRFGEwGCj7lI9YH6ubmVJPazpM4XF1BWUSZUukkmx5nsdVJPB9H5PJBHEcI01T5HkOFjD0A57Px6VLthKSZSIkOI4jCKfTKSc6Q5ZlCMNQtCPcY5gtLnYTOh0bw+RAPYjqX7uNIAhEG7rdrrI3zjNczs83CeUPp8dH+Pp4/SHnyuT4FPsjR4vW9883rjIULfrTMoEskcq1Zqt49XQvQGNFc/ovoRwNenHHdWCYBr/AEg/x/PKEq9u5OJcDXyIkydtAKun2k2yExfUcy9UdHh6XiPcjQbCr7htxh/dEXHRbBwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-7.png" title="" src="/static/d2344edabe78ba9071b630d79cebbfe7/5a190/tips-and-advices-for-jnr-devs-7.png" srcset="/static/d2344edabe78ba9071b630d79cebbfe7/772e8/tips-and-advices-for-jnr-devs-7.png 200w, /static/d2344edabe78ba9071b630d79cebbfe7/e17e5/tips-and-advices-for-jnr-devs-7.png 400w, /static/d2344edabe78ba9071b630d79cebbfe7/5a190/tips-and-advices-for-jnr-devs-7.png 800w, /static/d2344edabe78ba9071b630d79cebbfe7/c1b63/tips-and-advices-for-jnr-devs-7.png 1200w, /static/d2344edabe78ba9071b630d79cebbfe7/29007/tips-and-advices-for-jnr-devs-7.png 1600w, /static/d2344edabe78ba9071b630d79cebbfe7/8170e/tips-and-advices-for-jnr-devs-7.png 2132w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>This one’s a bit interesting. It may seem a bit counterintuitive as well at first. Research shows that the human mind can juggle seven things at a time. Imagine you have your project opened up, and there are many classes and methods, and you are going through them to find where the bug is? It’s normal to feel overwhelmed. Perhaps you are debugging a notorious bug and where it’s crashing if you feel like you’ve hit a wall. Just take a break. Go for a walk around the block. You will be amazed by the time you resume your work; you will start to see the answer. I do this very often without being too hard on myself. The same applies to taking a break from your work too. Utilize that PTO, go on a vacation, take some downtime, and take care of yourself. It will pay dividends in the long run and avoid burnout too.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>Hope these advice and tips will help you to be a better developer. Until next time 👋</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACdklEQVR42m2SaU8aURiFx0SroiIwrA6DbMKIFtSCsjpsDovsa9UYrU3s8qkf+v+fvtqYNG0/nOTe5Oa555z3VcbzJYPpgr6oN5nTHc9pDWdcDabUemPMzoDS1TX5eods1SJTrpO6uCTxoUgsc04odUrQOEYNxcgUKiij+UeGM4HO3qAL2qPfwLoALwVYafUoNLvkqi0ylca/wOQfwMGLwzeg6FpcvgFrvRFmd0DZEocNcWhapMs1jFyFg7Mi0f8Be9MlvbfIos4rbPIKq/fHVDtDqt0RJatHXlxmaxYn5QaJbJFIOotuZNASR6h6lExegN3x4rW39miKJaCORO7ObmgMJXJ/IrEn3Hz+xuzhmcXTV/o3jxSsAYcXFfaPs9Jfmr1ECpceEWAZxZIBdAS4fHhidv+J++fvfPnxk/Hdo/Q35LI9ZLC4o9m+JmEkicTi6FHRgUHwIEUgfog/lsSphUlflFAa4qopfXXnt7QmSxpyrl6PKb5MViKWZCC3ix43gxrF0xhHUTeHEQ/xoMp+wEV4TyUU0tjxBXmfK6DUhxOq0tVlty89taT8Ouc1U1akQq5uStE5tD0PUbeLQnxfpJNPBjk3NE4OAkQFGlTteLxujnNFlNRJjgvTpNlvYbZrVCyTUrNCvlaSNSlgGBG2N9bYXF0lqjpJ+72cBnycBfxkfF5Sbg9J3cfOto1YwkDZC4bRQjoB3Y/qU3GoDnacdrbs29h2tljf2EBRVlhZUdhYX8XtsBH02An7don7nYTUXTbfrckbBU3TUBLJI8LhKLpcNPnV7/Xidau4XU5cDgdOker8Lfdf8ntcuJy77Nrt2Gw2kkmDXy4xYHOxpp2QAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-advices-for-jnr-devs-8.png" title="" src="/static/bf17d5a4048fcad8171dc13db691edf5/5a190/tips-and-advices-for-jnr-devs-8.png" srcset="/static/bf17d5a4048fcad8171dc13db691edf5/772e8/tips-and-advices-for-jnr-devs-8.png 200w, /static/bf17d5a4048fcad8171dc13db691edf5/e17e5/tips-and-advices-for-jnr-devs-8.png 400w, /static/bf17d5a4048fcad8171dc13db691edf5/5a190/tips-and-advices-for-jnr-devs-8.png 800w, /static/bf17d5a4048fcad8171dc13db691edf5/c1b63/tips-and-advices-for-jnr-devs-8.png 1200w, /static/bf17d5a4048fcad8171dc13db691edf5/29007/tips-and-advices-for-jnr-devs-8.png 1600w, /static/bf17d5a4048fcad8171dc13db691edf5/6c960/tips-and-advices-for-jnr-devs-8.png 2135w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p><![CDATA[Building a gRPC Client in .NET]]>https://www.sahansera.dev/building-grpc-client-dotnet/https://www.sahansera.dev/building-grpc-client-dotnet/Fri, 18 Mar 2022 00:00:00 GMT<h2>Introduction</h2> <p>In this article, we will take a look at how to create a simple gRPC client with .NET and communicate with a server. This is the final post of the blog series where we talk about building gRPC services.</p> <h2>Motivation</h2> <p>This is the fifth part of an articles series on gRPC. If you didn’t catch the previous ones please feel free to do so. The links are down below.</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a></li> <li><a href="https://sahansera.dev/building-grpc-server-go/">Building a gRPC server with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC server with .NET</a></li> <li><a href="https://sahansera.dev/building-grpc-client-go">Building a gRPC client with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC client with .NET</a> (You are here)</li> </ul> <p>Please note that this is intended for anyone who’s interested in getting started with gRPC. If you’re not, please feel free to skip this article.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Plan</h2> <p>The plan for this article is as follows.</p> <ol> <li>Scaffold a .NET console project.</li> <li>Implementing the gRPC client.</li> <li>Communicating with the server.</li> </ol> <p>In a nutshell, we will be generating the client for the server we built in our previous post.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABa0lEQVR42nVS21KEMAzl/7/LBx90xgdf1nF3AHFdurQUWqD3YyjedtUwbROanJwkLXAlo+mhvc56om8VuywI3l86pogU3HU4ik/FWoeu4zi1R7COQakNNMSEx0bhuZ2yHcmWwwDGWvAzgxA9nPOXgDElWBewGAdjPS1HCTz9R7ZvngTuy4HAYk5gCWAx9tuXYlP6CUhOq+NVTXk/HY/Y7/eoqwojMft59ylr7AWg94Foh1/9SOQ1TxPmeYJWCsu84C9ZWYawxRfWWpy6FkwwzH1JbA2iaZHMG2KYM5eZynPeZj16iWg7Wj3StMfQv1JPFXjXZdBCjxq78gm7egdR38KHESN7gGrvoMc3KGL3QmWfWgZN09ZDBS12mOiM8g7H+pHuzqiqEsYYFOtWlRVe6gbGEfUYcOY9uJDopaSTQ9DUZS/AOYccRwg5QOntFWg6D4cDmqbZGG5DiXn9J4mSfHX96i2uA1pj08f9O2/xu67hJPFcAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-dotnet-1.png" title="" src="/static/5981709a41957cdb1d4562a667108034/5a190/building-grpc-client-dotnet-1.png" srcset="/static/5981709a41957cdb1d4562a667108034/772e8/building-grpc-client-dotnet-1.png 200w, /static/5981709a41957cdb1d4562a667108034/e17e5/building-grpc-client-dotnet-1.png 400w, /static/5981709a41957cdb1d4562a667108034/5a190/building-grpc-client-dotnet-1.png 800w, /static/5981709a41957cdb1d4562a667108034/c1b63/building-grpc-client-dotnet-1.png 1200w, /static/5981709a41957cdb1d4562a667108034/29007/building-grpc-client-dotnet-1.png 1600w, /static/5981709a41957cdb1d4562a667108034/37114/building-grpc-client-dotnet-1.png 2300w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>💡  As always, all the code samples documentation can be found at: <a href="https://github.com/sahansera/dotnet-grpc">https://github.com/sahansera/dotnet-grpc</a></p> </blockquote> <h2><strong>Prerequisites</strong></h2> <ul> <li>.NET 6 SDK</li> <li>Visual Studio Code or IDE of your choice</li> <li>gRPC compiler</li> </ul> <p>Please note that I’m using some of the commands that are macOS specific. Please follow this <a href="https://grpc.io/docs/languages/go/quickstart/">link</a> to set it up if you are on a different OS.</p> <p>To install Protobuf compiler:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">brew <span class="token function">install</span> protobuf</code></pre></div> <h2><strong>Project Structure</strong></h2> <p>We can use .NET’s tooling to generate a sample gRPC project. Run the following command at the root of your workspace. Remember how we used <code class="language-text">dotnet new grpc</code> command to scaffold the server project? For this one though, it can simply be a console app.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new console <span class="token parameter variable">-o</span> BookshopClient</code></pre></div> <p>Your project structure should look like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 788px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 82%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAACKUlEQVR42p2TW0/bQBSE89i3Jti73hB7fb8n2CYXpxSJqqUCqRXqX6j6A/r/n4bZBVWkBEH7MNq1nXw7s+ecSRCUGMcbXOy+Ylh/wXZ/a6VUCiFDSBlR4TPpsEEULRHFSwRBhTBqkSRnmCh+VHEH//onIl0hSzvkaQ9FkOcGlH6EHsocOJ9nVMp9AuXF9nniSY15uMTy+y/ookfU7BDVG0jCRVhCBAWk99ylEPqPpFlVDDVPMBEiRMg/jvsrTMtzTFc7zJoNTvoLOB1VryH542OxD+TnUKcpHfJ019HYX39Dv7lE4OXwThaQlDc75f4UktGl6z+sLwH53cRm5AiO9NEHI9rsErN6gCgGuGUPt1lz5T5b0ek53HR1vEhMKRcZ1CJ7ALrCR+nvkOaXmJ5tIdotnNVI4IbwDiJdPgKXR+/T3KFktyjGnpgXii+myYD3+TmcssOM93hSDXDiGi5jS8eHR5n1aFwLLOgwN0XR7KUWn25+sF06VNUORbVF235AVY8ouTetIV4rCjmKxZuY/DpqMF7fIUnPkNHlgicZiBUr573Q3IfSth9t22javRo/QusGWdbD9wt4rJgQgdXrsCdA14yRF+CzN2P0FcqakcuNHSnbtG+CHTjkpCxSxE3H2WztnRXF2s7m22FPgA4fYhXgLnR4l611Vzcj47PCbvCfDjmD+26HhDGzbLDQgED1lpH7G+hxZFzd4t3tbwurGNk4NLGzfPhn4D2daLFrYGwZ2QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-dotnet-2.png" title="" src="/static/9046d8d539f797996d9ca14be022e291/ea7fb/building-grpc-client-dotnet-2.png" srcset="/static/9046d8d539f797996d9ca14be022e291/772e8/building-grpc-client-dotnet-2.png 200w, /static/9046d8d539f797996d9ca14be022e291/e17e5/building-grpc-client-dotnet-2.png 400w, /static/9046d8d539f797996d9ca14be022e291/ea7fb/building-grpc-client-dotnet-2.png 788w" sizes="(max-width: 788px) 100vw, 788px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You must be wondering if this is a console app how does it know how to generate the client stubs? Well, it doesn’t. You have to add the following packages to the project first.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> BookshopClient.csproj package Grpc.Net.Client dotnet <span class="token function">add</span> BookshopClient.csproj package Google.Protobuf dotnet <span class="token function">add</span> BookshopClient.csproj package Grpc.Tools</code></pre></div> <p>Once everything’s installed, we can proceed with the rest of the steps.</p> <h2>Generating the client stubs</h2> <p>We will be using the same Protobuf files that we generated in our previous step. If you haven’t seen that already head over to my previous <a href="https://sahansera.dev/building-grpc-server-dotnet/">post</a>.</p> <p>Open up the <code class="language-text">BookshopClient.csproj</code> file you need to add the following lines:</p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml">... <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ItemGroup</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Protobuf</span> <span class="token attr-name">Include</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>../proto/bookshop.proto<span class="token punctuation">"</span></span> <span class="token attr-name">GrpcServices</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Client<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ItemGroup</span><span class="token punctuation">></span></span> ...</code></pre></div> <p>As you can see we will be reusing our <a href="https://github.com/sahansera/dotnet-grpc/blob/main/Proto/bookshop.proto">Bookshop.proto</a> file. in this example too. One thing to note here is that we have updated the <code class="language-text">GrpcServices</code> attribute to be <code class="language-text">Client</code>.</p> <h2>Implementing the gRPC client</h2> <p>Let’s update the Program.cs file to connect to and get the response from the server.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Grpc<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Client</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Bookshop</span><span class="token punctuation">;</span> <span class="token comment">// The port number must match the port of the gRPC server.</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> channel <span class="token operator">=</span> GrpcChannel<span class="token punctuation">.</span><span class="token function">ForAddress</span><span class="token punctuation">(</span><span class="token string">"http://localhost:5000"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Inventory<span class="token punctuation">.</span>InventoryClient</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reply <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">GetBookListAsync</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GetBookListRequest</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Greeting: "</span> <span class="token operator">+</span> reply<span class="token punctuation">.</span>Books<span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Press any key to exit..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">ReadKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This is based on the example given on the Microsoft docs site btw. What I really like about the above code is how easy it is to read. So here’s what happens.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 23.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABOUlEQVR42o2P3SuDYRTA379EivLRQiszJFvbGNvY2KKkleTGDRfKFTcoLl34D3yl2TJjm2sXKLQ1NosU2zvuUOwjfp73pd24cerX7zznOec8PRK/USqVyOc/+G8o/cVC4U9dysg5MjmB/IP89MxjVubl9a3clBX3seskiZs0idSNIE369k61wpWox5Mp7h8ekLRWJ+3OYTpcQ+j7PTjHxtFaHSyurqnLvvhkbnkZjbmHVocHfZ8bvcONeXhUdbNtAJ19EI2pF+/UDFKl3kiDODRabNQbrdhHvFS3GZhdWqFYypMvvDM9v0BVq4kWMdhid9EkHqzptFBn7KJWsaGbCl0HnolJpPUdH9u7AbYEmz4/wYMwG75dzi4uy18+j8Xxhw4IRY8IRqLshSOqg4rDUfZFHggdcnxyyjcE4yz7NE4D0QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-dotnet-3.png" title="" src="/static/089cb4f9a16aa3be867149f273309c49/5a190/building-grpc-client-dotnet-3.png" srcset="/static/089cb4f9a16aa3be867149f273309c49/772e8/building-grpc-client-dotnet-3.png 200w, /static/089cb4f9a16aa3be867149f273309c49/e17e5/building-grpc-client-dotnet-3.png 400w, /static/089cb4f9a16aa3be867149f273309c49/5a190/building-grpc-client-dotnet-3.png 800w, /static/089cb4f9a16aa3be867149f273309c49/c1b63/building-grpc-client-dotnet-3.png 1200w, /static/089cb4f9a16aa3be867149f273309c49/29007/building-grpc-client-dotnet-3.png 1600w, /static/089cb4f9a16aa3be867149f273309c49/aa43b/building-grpc-client-dotnet-3.png 3113w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <ol> <li>We first create a gRPC channel with <code class="language-text">GrpcChannel.ForAddress</code> to the server by giving its URI and port. A client can reuse the same channel object to communicate with a gRPC server. This is an expensive operation compared to invoking a gRPC method on the server. You can also pass in a <code class="language-text">GrpcChannelOptions</code> object as the second parameter to define client options. Here’s a <a href="https://docs.microsoft.com/en-us/aspnet/core/grpc/configuration?view=aspnetcore-6.0#configure-client-options">list</a> for that.</li> <li>Then we use the auto-generated method <code class="language-text">Inventory.InventoryClient</code> by leveraging the channel we created above. One thing to note here is that, if your server has multiple services, you can still use the same <code class="language-text">channel</code> object for all of those.</li> <li>We call the <code class="language-text">GetBookListAsync</code> on our server. By the way, this is a Unary call, we will go through other client-server communication mechanisms in a separate post.</li> <li>Our <code class="language-text">GetBookList</code> method gets called on the server and returns the list of books.</li> </ol> <p>Now that we know how the requests work, let’s see this in action.</p> <h2>Communicating with the server</h2> <p>Let’s spin up the server that we built in my previous post first. This will be up and running at port <code class="language-text">5000</code>.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run <span class="token parameter variable">--project</span> BookshopServer/BookshopServer.csproj</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 48%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9klEQVR42qWSbWvaUBiGsxY0vk1j4kui4kuwGpWpSU1WnUY/tMWMssGgYKklpR8Eof05Y7+l/dQfsbHQ/ZKSw71zzrpS2GAfduDifg4n3Od57hPh+vzT2PdXJ5dnl8ur9drbrE69re9726sbzs2W6mb7rAyfnq9Xa89f+97F2cXSP/dPNpvNRGDLW0y/zpdHGE/mxHEPMTj8AOfoIxbHUyzev8W76QQLd4HJeAJ36mLuzjGbzfj+wDlgStyZC3tkf6d2McEw9MA0TRRVjWSVHHJqCWlZgSRLkJQMZEVGMplEVIwiFotBFEUkkgmkUikkElwJ26fT6R/U8LWg76nBm36X3UDs0QgOxRwOYVkWVRODwQC9Xg+GYaDRaKDdbqNSqUBVVeTzeZTLZcLqQqHwQA1TgliIB61eG5ZpEWbCDBi2baPb7aJer6NUKrEOONlslnfMunuCMJUk6UGW5ZSwm9kN6s06hoMhMToG+v0+74aZVqtVdjM0TUOxWEQ8HudjvzD703Ans/OtsdfAvrX/SEcLaZ5hp9MJHccJm81mSD8OaUccVv+FxyfDgBvmyrlAK2totVqEZaTr+nNWrGZ51Wo1rjQvrr9hcdAzwiZQFOXXoyhZ5XMkErmn3FLuXkJf9I6O+C9uKfc03y/8t6FL5M7/T5Ty6ifbVuGp7ID/BwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-dotnet-4.png" title="" src="/static/bb98b2bcc1c445963106da33e75e8512/5a190/building-grpc-client-dotnet-4.png" srcset="/static/bb98b2bcc1c445963106da33e75e8512/772e8/building-grpc-client-dotnet-4.png 200w, /static/bb98b2bcc1c445963106da33e75e8512/e17e5/building-grpc-client-dotnet-4.png 400w, /static/bb98b2bcc1c445963106da33e75e8512/5a190/building-grpc-client-dotnet-4.png 800w, /static/bb98b2bcc1c445963106da33e75e8512/20751/building-grpc-client-dotnet-4.png 1037w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>For the client-side, we invoke a similar command.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run <span class="token parameter variable">--project</span> BookshopClient/BookshopClient.csproj</code></pre></div> <p>And in the terminal, we will get the following outputs.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhklEQVR42o2QTW8BURSGLyphMD6KUarBQqM7LHQnEhtJqX5YNkRmJj7HMKv+jkaTdlHb+gUk/ASxs/BDKti8NVeotE3am7y5Ofc8931PDsHBWa/XWHws8J+zWq2wXC5/vJNu9wlNqY2m8gipLaHZqENqtCBLLZRLZfTf+xR8fXkGL1YgSgrqcgOVqgiBF1Cr1qiKD0X03nog1/k8XG43OK8PrI2FxWLeyAK73Q5CCGRZpob3hVscO53wnwXg9Xlx4vXAarVSVpXKlkolkHQ6Dd2RDrZN08SYYDJtxbIshTqdDjXMFrKIXERwmUggHovDf+qHcxPAWlnYbdtwURRBkskkNBoNTdmZqVLTVUhRlK3h3RXC52FEo1HEYjGEQiFwHAeHw0FvvV4PQRC+DM1mMxiG2ev7hLmb3GY1LgSDQQQCAXg8Hsqo/3bhPM+DpFIpaLXaPyfMZDK0VnsGg4HKaDTS8N0OqeFkMsFwOMR4PMZoNNpLrQeDAebzOTWcTqe/cofsbDbDJ4PMV0RT86GGAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-dotnet-5.png" title="" src="/static/9da5464a7469cbc83af57113d4e10e3f/5a190/building-grpc-client-dotnet-5.png" srcset="/static/9da5464a7469cbc83af57113d4e10e3f/772e8/building-grpc-client-dotnet-5.png 200w, /static/9da5464a7469cbc83af57113d4e10e3f/e17e5/building-grpc-client-dotnet-5.png 400w, /static/9da5464a7469cbc83af57113d4e10e3f/5a190/building-grpc-client-dotnet-5.png 800w, /static/9da5464a7469cbc83af57113d4e10e3f/c1b63/building-grpc-client-dotnet-5.png 1200w, /static/9da5464a7469cbc83af57113d4e10e3f/29007/building-grpc-client-dotnet-5.png 1600w, /static/9da5464a7469cbc83af57113d4e10e3f/e872c/building-grpc-client-dotnet-5.png 2606w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Nice! as you can see it’s not that hard to get everything working 🎉 One thing to note is that we left out the details about TLS and different ways to communicate with the server (i.e. Unary, streaming etc.). I will cover such topics in-depth in the future.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Conclusion</h2> <p>In this article, we looked at how to reuse our Protobuf files to create a client to interact with the server we created in the previous post.</p> <p>I hope this article series cleared up a lot of confusion that you had about gRPC. Please feel free to share your questions, thoughts, or feedback in the comments section below. Until next time 👋</p> <h2>References</h2> <ul> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&#x26;tabs=visual-studio-code">https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&#x26;tabs=visual-studio-code</a></li> </ul><![CDATA[Building a gRPC Client in Go]]>https://www.sahansera.dev/building-grpc-client-go/https://www.sahansera.dev/building-grpc-client-go/Sat, 12 Mar 2022 00:00:00 GMT<h2>Introduction</h2> <p>In this article, we will take a look at how to create a simple gRPC client with Go. We will be using this client to interact with the gRPC server that we created in my previous <a href="https://sahansera.dev/building-grpc-server-go/">post</a>. So let’s get into it!</p> <h2>Motivation</h2> <p>This is the fourth part of an articles series on gRPC. If you want to jump ahead, please feel free to do so. The links are down below.</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a></li> <li><a href="https://sahansera.dev/building-grpc-server-go/">Building a gRPC server with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC server with .NET</a></li> <li><a href="https://sahansera.dev/building-grpc-client-go">Building a gRPC client with Go</a> (You are here)</li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC client with .NET</a></li> </ul> <p>Please note that this is intended for anyone who’s interested in getting started with gRPC, therefore, we will keep things simple.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Plan</h2> <p>The plan for this article is as follows.</p> <ol> <li>Scaffold the client-side stubs and go modules</li> <li>Implementing the gRPC client</li> <li>Communicating with the server</li> </ol> <p>In a nutshell, we will be generating the client for the server we built in our previous post.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABa0lEQVR42nVS21KEMAzl/7/LBx90xgdf1nF3AHFdurQUWqD3YyjedtUwbROanJwkLXAlo+mhvc56om8VuywI3l86pogU3HU4ik/FWoeu4zi1R7COQakNNMSEx0bhuZ2yHcmWwwDGWvAzgxA9nPOXgDElWBewGAdjPS1HCTz9R7ZvngTuy4HAYk5gCWAx9tuXYlP6CUhOq+NVTXk/HY/Y7/eoqwojMft59ylr7AWg94Foh1/9SOQ1TxPmeYJWCsu84C9ZWYawxRfWWpy6FkwwzH1JbA2iaZHMG2KYM5eZynPeZj16iWg7Wj3StMfQv1JPFXjXZdBCjxq78gm7egdR38KHESN7gGrvoMc3KGL3QmWfWgZN09ZDBS12mOiM8g7H+pHuzqiqEsYYFOtWlRVe6gbGEfUYcOY9uJDopaSTQ9DUZS/AOYccRwg5QOntFWg6D4cDmqbZGG5DiXn9J4mSfHX96i2uA1pj08f9O2/xu67hJPFcAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-go-1.png" title="" src="/static/5981709a41957cdb1d4562a667108034/5a190/building-grpc-client-go-1.png" srcset="/static/5981709a41957cdb1d4562a667108034/772e8/building-grpc-client-go-1.png 200w, /static/5981709a41957cdb1d4562a667108034/e17e5/building-grpc-client-go-1.png 400w, /static/5981709a41957cdb1d4562a667108034/5a190/building-grpc-client-go-1.png 800w, /static/5981709a41957cdb1d4562a667108034/c1b63/building-grpc-client-go-1.png 1200w, /static/5981709a41957cdb1d4562a667108034/29007/building-grpc-client-go-1.png 1600w, /static/5981709a41957cdb1d4562a667108034/37114/building-grpc-client-go-1.png 2300w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In the above diagram, we will look at how to achieve the components on the left side.</p> <blockquote> <p>💡 As always, the completed code can be found at: <a href="https://github.com/sahansera/go-grpc/tree/main/client">https://github.com/sahansera/go-grpc/tree/main/client</a></p> </blockquote> <h2>Creating client-side Stubs and Go modules</h2> <p>We will be using the same Protobuf files that we generated in our previous step. If you haven’t seen that already head over to my previous <a href="https://sahansera.dev/building-grpc-server-go/">post</a>.</p> <p>We will create a new folder called <code class="language-text">client</code> at the root of the project and initialize it with a new Go module.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">mkdir</span> client <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">cd</span> client go mod init bookshop/client</code></pre></div> <p>Once we have the go modules we can now generate the Protobufs for the client side.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">protoc <span class="token parameter variable">--proto_path</span><span class="token operator">=</span>proto proto/*.proto <span class="token parameter variable">--go_out</span><span class="token operator">=</span>client --go-grpc_out<span class="token operator">=</span>client</code></pre></div> <p>This is very similar to our previous blog post.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 419px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABFklEQVR42p1SS1LDMBTLlm1T20mcf2I3jZ0mhLYUOsDwOQBnYAfn4b7ixQN0ScNCI8/YI0t6zxM8Aw9r+GoHHhSQUjkIUYBPd38idxyGpWNPhhVE0YN/fCLu7rAxNxjGR+S5BVsmZ4qe4P0ctL1Gv3tARB/EsZ4t9CsoBFlmGcbDE17f3qFXW7TmgMVCgrGEkM53yKYOWA7DFGLqL0kbJEmDLDcIo2qWqBNc8hSlqPEieqz0FYy9hf5mKfV8wclhRFPtggYpOZsGkmWtY1fJ3MjOIa/w7LdQ9YBmvYcih0qP/xMUFMmPFS66PfW3hlKX5M6grDZuJ2dFDoISAcUVFNXvjzQIC2uP0M0Wm+HefTAtuXt3Br4A1a4MY+7XwrQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-go-2.png" title="" src="/static/43c8f3a1817cce48febef28e8fa5aa47/d587d/building-grpc-client-go-2.png" srcset="/static/43c8f3a1817cce48febef28e8fa5aa47/772e8/building-grpc-client-go-2.png 200w, /static/43c8f3a1817cce48febef28e8fa5aa47/e17e5/building-grpc-client-go-2.png 400w, /static/43c8f3a1817cce48febef28e8fa5aa47/d587d/building-grpc-client-go-2.png 419w" sizes="(max-width: 419px) 100vw, 419px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Implementing the gRPC client</h2> <p>Now that we have the modules we can go and implement the code for the client.</p> <p>In order for us to talk to the server, we first need to create a connection. For that, we can use the <code class="language-text">grpc.Dial()</code> method.</p> <blockquote> <p>💡 Note that we are not using TLS here, however, in production environments you <strong>must</strong>!</p> </blockquote> <p>The rough skeleton of the client code looks like the following.</p> <p><a href="https://github.com/sahansera/go-grpc/blob/main/client/main.go">main.go</a></p> <div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go"><span class="token comment">// ...</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> conn<span class="token punctuation">,</span> err <span class="token operator">:=</span> grpc<span class="token punctuation">.</span><span class="token function">Dial</span><span class="token punctuation">(</span><span class="token string">"localhost:8080"</span><span class="token punctuation">,</span> grpc<span class="token punctuation">.</span><span class="token function">WithTransportCredentials</span><span class="token punctuation">(</span>insecure<span class="token punctuation">.</span><span class="token function">NewCredentials</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Code removed for brevity</span> client <span class="token operator">:=</span> pb<span class="token punctuation">.</span><span class="token function">NewInventoryClient</span><span class="token punctuation">(</span>conn<span class="token punctuation">)</span> <span class="token comment">// Note how we are calling the GetBookList method on the server</span> <span class="token comment">// This is available to us through the auto-generated code</span> bookList<span class="token punctuation">,</span> err <span class="token operator">:=</span> client<span class="token punctuation">.</span><span class="token function">GetBookList</span><span class="token punctuation">(</span>context<span class="token punctuation">.</span><span class="token function">Background</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>pb<span class="token punctuation">.</span>GetBookListRequest<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"book list: %v"</span><span class="token punctuation">,</span> bookList<span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 33.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABrUlEQVR42l2RTU8TURiFh0BUCoNlWtppO/2iAnVaUikEWpDEqIlhYfo7pAmuiRtctAvjshv5J1ZJcNs/Iv3Q4qRlZtraxzsXNcY3eXJPbt6cnLxHsawfDAYDut0uvV5PasuyGI1G/D+T8RjbvsF1XbnT7/eldhxHMp1OUdLrGQpbO5j5TcxcnkJhi0QyRaPRuDWZTCTevH9Xo7Bd5ODwCaXyAbu7Jfnu7z9mr1Sm1WqhzM4vsBKNsxyOElgJo0diKDNznJ29lSZjkcrDm+PqMTN3fAQicZYCIfSoQSKRRPUHWFjy87HZRLmn3sdIpgnFEsTTGTZzOXy+RWq1mjS5tvp8v/4m9cnrE+ZVP0ZqVe4bq2ukM2tSB/UYny8uUJS5u2jiQ9UNkdRAC0cIhHXq9bo0cUYOtmtL/apaZXbRTyieQgtFWA7q+EU6TQuiimDN5ieU7b0yz14ccfj0OS8rFSqCR8UdPpyf/77hz783PD19gy7MzEKRDTNPVrCRNck+NMk8WOfy8guKK9px3VvsG1u0aMvm/tzt3xkOh7SvvtJpX9FutyWdTkfiaa/pXyAkbP635oAAAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-go-3" title="" src="/static/94b16896599aa784f59a9b16730e3347/5a190/building-grpc-client-go-3.png" srcset="/static/94b16896599aa784f59a9b16730e3347/772e8/building-grpc-client-go-3.png 200w, /static/94b16896599aa784f59a9b16730e3347/e17e5/building-grpc-client-go-3.png 400w, /static/94b16896599aa784f59a9b16730e3347/5a190/building-grpc-client-go-3.png 800w, /static/94b16896599aa784f59a9b16730e3347/c1b63/building-grpc-client-go-3.png 1200w, /static/94b16896599aa784f59a9b16730e3347/29007/building-grpc-client-go-3.png 1600w, /static/94b16896599aa784f59a9b16730e3347/3b2f8/building-grpc-client-go-3.png 2790w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The explanation of this code is as follows:</p> <ol> <li><code class="language-text">grpc.Dial</code> is a way to create a client connection to a given target. In our case, we can send in the path of our server along with its port. Note how we are passing in an option to turn off TLS by using <code class="language-text">WithTransportCredentials</code></li> <li>We then call the server procedure/method just as you’d normally do when calling a local method in your program. This is the thing that sold me on gRPC because we know exactly what we have to pass and how to invoke the call.</li> <li>Now on the server side, we have a request handler that will respond to the incoming requests.</li> <li>We finally log the response that we got from the server.</li> </ol> <h2>Communicating with the server</h2> <p>And in the terminal, we will get the following outputs.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVR42oWQXUsCQRSGR101d1zd9fPCioKCUHQTyV+aoJFuulqhdBF4LeofUC/1VtdfoKGJvs3MdmFiNfCynMNznjOzBHtnu91itVrhv7Pb7bBefwr+8JBKpYKa+QLz9Q1Vs4Zy+QEN8xn1Wh2lYgmtZgsfyyXa7XcYT1U8NZowzDoejSIMNmtUDMEV7guYz+cgkUgUl1c3OLtO4uLqEonTOKKsFw6H4fV6kclkYM0s5PN3kNweKFoENBiEGgogEAgIxul0ghCCTqcDIvv9iJ+eI5y4QCwRQzSuQWUDftb3eDzQb3VMp1MhliQXFNanVIYsU/alguNit9uNXq/HxYRBFLLvhMVngyyKojCBhFQqhdlsBl3XRa2wYSGitpCHsy6XC91u1xbyDZRvZmKR7837QvuGkhime7KjQj7sY7fbjyzL4t8kk0khTKfTouaCQ5b3HA6HLeRvD4VCUFX1RzRNE3A2m4VlWcjlcqL+i+33+yDD4RCj0ehoBoMBxuMxNpsNJpOJqH9jeRaLBb4AqJB5/ykr4o4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-client-go-4" title="" src="/static/140fb5225fe1f32563e39c2c7abf6322/5a190/building-grpc-client-go-4.png" srcset="/static/140fb5225fe1f32563e39c2c7abf6322/772e8/building-grpc-client-go-4.png 200w, /static/140fb5225fe1f32563e39c2c7abf6322/e17e5/building-grpc-client-go-4.png 400w, /static/140fb5225fe1f32563e39c2c7abf6322/5a190/building-grpc-client-go-4.png 800w, /static/140fb5225fe1f32563e39c2c7abf6322/c1b63/building-grpc-client-go-4.png 1200w, /static/140fb5225fe1f32563e39c2c7abf6322/29007/building-grpc-client-go-4.png 1600w, /static/140fb5225fe1f32563e39c2c7abf6322/ec556/building-grpc-client-go-4.png 2029w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Nice! as you can see it’s not that hard to get everything working 🎉 One thing to note is that we left out the details about TLS. But I guess, that will be posted for another day 😊</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Conclusion</h2> <p>In this article, we looked at how to reuse our Protobuf files to create a client to interact with the server we created in the previous post.</p> <p>I hope this article cleared up a lot of confusion that you had about gRPC. Please feel free to share your questions, thoughts, or feedback in the comments section below. Until next time 👋</p> <h2>References</h2> <ul> <li><a href="https://www.youtube.com/watch?v=RHWwMrR8LUs&#x26;ab_channel=NicJackson">https://www.youtube.com/watch?v=RHWwMrR8LUs&#x26;ab_channel=NicJackson</a></li> <li><a href="https://learning.oreilly.com/library/view/grpc-up-and/9781492058328/">https://learning.oreilly.com/library/view/grpc-up-and/9781492058328/</a></li> <li><a href="https://grpc.io/docs/languages/go/basics/#client">https://grpc.io/docs/languages/go/basics/#client</a></li> </ul><![CDATA[Building a gRPC Server in .NET]]>https://www.sahansera.dev/building-grpc-server-dotnet/https://www.sahansera.dev/building-grpc-server-dotnet/Sat, 05 Mar 2022 00:00:00 GMT<h2>Introduction</h2> <p>In this article, we will look at how to build a simple web service with gRPC in .NET. We will keep our changes to minimal and leverage the same Protocol Buffer IDL we used in my previous <a href="https://sahansera.dev/building-grpc-server-go/">post</a>. We will also go through some common problems that you might face when building a gRPC server in .NET.</p> <h2>Motivation</h2> <p>For this article also we will be using the Online Bookshop example and leveraging the same Protobufs as we saw before. For those who aren’t familiar with or missed this series, you can find them from <a href="https://sahansera.dev/introduction-to-grpc/">here</a>.</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a></li> <li><a href="https://sahansera.dev/building-grpc-server-go/">Building a gRPC server with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC server with .NET</a> (You are here)</li> <li><a href="https://sahansera.dev/building-grpc-client-go">Building a gRPC client with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC client with .NET</a></li> </ul> <p>We will be covering steps 1 and 2 in the following diagram.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZElEQVR42nVS2W7DIBD0//9Upahvlao+VIriNIlzNIc5jO1gMAZPF3JUqdKVzOJldpnZJcMfG4OnZbz9pdUOHaRl18h4x7auxjD6h/zstgkhQMgK5fEAVp6gVE2xS/Kan/GxEb+F2jNhGLb7JRhnMJ19LBgJOR9gbQ/bOxjrko+xaNODweuiJdyYLundAGN69L2nnIgd7ryzGzvvRzyztlGYzhb4nC0hOX+K8WG8q7kUpJuHfwrWdQ1BspQUkNSSpwUp99b2LLJTSiXwuSlpKD2C1wiDIt8lKVGidTaNJARHZ006C72A0dR3yVGKEgPhMmMMttsNjieB7/wFwTFoOYU+TNCpL3TO4cRK7I8naMJqzaHLN+hmDc8n4Nt3zHcF5kVO7WmRee+x2+2Q5znJaxJtRTIZ44m5EOQriVpV9AokxSpUFI+YaJFQsSqwWq7S/jrlMQ3mP4tvMwzu2cm1hz59sYc/rsG7uTvIp7kAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-1.png" title="" src="/static/c1481df614d654fbbcaede50e8cf20db/5a190/building-grpc-server-dotnet-1.png" srcset="/static/c1481df614d654fbbcaede50e8cf20db/772e8/building-grpc-server-dotnet-1.png 200w, /static/c1481df614d654fbbcaede50e8cf20db/e17e5/building-grpc-server-dotnet-1.png 400w, /static/c1481df614d654fbbcaede50e8cf20db/5a190/building-grpc-server-dotnet-1.png 800w, /static/c1481df614d654fbbcaede50e8cf20db/c1b63/building-grpc-server-dotnet-1.png 1200w, /static/c1481df614d654fbbcaede50e8cf20db/29007/building-grpc-server-dotnet-1.png 1600w, /static/c1481df614d654fbbcaede50e8cf20db/27fe5/building-grpc-server-dotnet-1.png 2287w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Plan</h2> <p>So this is what we are trying to achieve.</p> <ol> <li>Generate the <code class="language-text">.proto</code> IDL stubs.</li> <li>Write the business logic for our service methods.</li> <li>Spin up a gRPC server on a given port.</li> </ol> <p>In a nutshell, we will be covering the following items on our initial diagram.</p> <blockquote> <p>💡  As always, all the code samples documentation can be found at: <a href="https://github.com/sahansera/dotnet-grpc">https://github.com/sahansera/dotnet-grpc</a></p> </blockquote> <h2>Prerequisites</h2> <ul> <li>.NET 6 SDK</li> <li>Visual Studio Code or IDE of your choice</li> <li>gRPC compiler</li> </ul> <p>Please note that I’m using some of the commands that are macOS specific. Please follow this <strong><a href="https://grpc.io/docs/languages/go/quickstart/">link</a></strong> to set it up if you are on a different OS.</p> <p>To install Protobuf compiler:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">brew <span class="token function">install</span> protobuf</code></pre></div> <h2>Project Structure</h2> <p>We can use the the .NET’s tooling to generate a sample gRPC project. Run the following command in at the root of your workspace.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new grpc <span class="token parameter variable">-o</span> BookshopServer</code></pre></div> <p>Once you run the above command, you will see the following structure.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 604px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB/UlEQVR42o2TSY/aQBCFueaQ8YC72zsGb9geGy/YLBMCYpJLDpFyySH//4e8qW5jxEQRyqHU1a3S53qvyhPHzbCMGhwvP2EWBxjtCUZ9hNnQ2b/BrCgvX+F4OWTtP8NJ4YS1qpnYdgKXHg+nH4jbrzDTHtaqhRE3MLIORlRTXsOy48fhpeqcGIYPzjzU2zO6wxtcM4JjLMGnFsTUhtDdIcT8cXBPnQRc0MVHu7ugef2Gp6QGK7bQqz309R7CDqnQfxycgPMVBLFuwLo/od5foEUlOAFZtAb3Uwhz+X9AN/4IXHdnlFsCBoUC8kV+leqrQhX3kPv7PdAkD5/1BL+/2/jzq8SneAuRNdAlVMqYORDksYLfzmsuYwR6ydihD8YD1PkcXbOClrTgWat8ZElFnWaD/JCsCAswGVIFvfPgZbBkBFKuJHOxROR7CP05psUerCTYSw+93EGXZ9EP9+pAbzSwbAOWd/TWQVjh0OnV72FtqMM4NBFFFqY5ASWAJixls7RRMNmRoZkwnmmdZlfZo8eyQwm0gqFDXXgonAabxRFPJX252KkOFJDAMufxmqSXZENNqxR9HMwIHCUzAiZ2i3JxhlaQh/SnyM4UUMojD1lcDTIp1EQl5O89lED5FYsetKDB54Sk5hvMmi+01DvlkZR6m/Qo8x42AmWdFeAdEmRkGw8hWP8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-2.png" title="" src="/static/fcbb13c0b3a020db98468fbf0c1e6f9d/87254/building-grpc-server-dotnet-2.png" srcset="/static/fcbb13c0b3a020db98468fbf0c1e6f9d/772e8/building-grpc-server-dotnet-2.png 200w, /static/fcbb13c0b3a020db98468fbf0c1e6f9d/e17e5/building-grpc-server-dotnet-2.png 400w, /static/fcbb13c0b3a020db98468fbf0c1e6f9d/87254/building-grpc-server-dotnet-2.png 604w" sizes="(max-width: 604px) 100vw, 604px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We also need to configure the SSL trust:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet dev-certs https <span class="token parameter variable">--trust</span></code></pre></div> <p>As you might have guessed, this is like a default template and it already has a lot of things wired up for us like the <code class="language-text">Protos</code> folder.</p> <h2>Generating the server stubs</h2> <p>Usually, we would have to invoke the protocol buffer compiler to generate the code for the target language (as we saw in my previous article). However, for .NET they have streamlined the code generation process. They use the <a href="https://www.nuget.org/packages/Grpc.Tools/">Grpc.Tools</a> NuGet package with MSBuild to provide automatic code generation, which is pretty neat! 👏</p> <p>If you open up the Bookshop.csproj file you will find the following lines:</p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml">... <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ItemGroup</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Protobuf</span> <span class="token attr-name">Include</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Protos\greet.proto<span class="token punctuation">"</span></span> <span class="token attr-name">GrpcServices</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Server<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ItemGroup</span><span class="token punctuation">></span></span> ...</code></pre></div> <p>We are going to replace greet.proto with our <a href="https://github.com/sahansera/dotnet-grpc/blob/main/Proto/bookshop.proto">Bookshop.proto</a> file.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAACKaAAAimgG+3fsqAAABtklEQVR42oWSQU/bMBSA+SEcJhhCpZRuZWlJW5rUcePETlwnTVsoSCOTgG7SpEncd92v2O9pephGBwKWdFwQ06ZdSXtqmVuv2lYmzfpkvSe9z8+y38L9dI3H47vBMI4H8eA/DIZDXiysBRF9//bj6PUhfenTg2PqH1H/WFD9A/aibT8/bL16c3P7VfT7JX8Oo6cAJ8tGQjOSwFguwMW89qgAVwra4xkrRbgkl1MaPru45MpoNJrJUV82aznkbJlMQqyIWbPlmHWWgoRXCzYg4adnMTu/vJqTozxxQW0P1FppgwJK3594Jz5bVY11YCanrGs4oaJn5kM5jGTb1fcO0L5f9nbzprUKSAKQ5MwU8pqKpIdy2O9LBk1DktGtTMWSdLKJSBqac/JSCW2gf11bIQ0VN4C1o5Cmau1uETdnuTL1coYt6fgJslMQVxn2Gs6ni7nOUZRFVLa8EtuRcTVbQRJEOd3YhBw0SSuT9F2bvm3XP5z//dpfrq9BrVmkbsnxtqmrUKc0ZdueIGKl6mRMptT3r8JwIvN/FrMSx/HH016nEwRBN+h2OzOC7u+UB3zr9U55sZiwny7Kb3J0R2pHAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-3" title="" src="/static/378d6ab2ca89a5fafc8a261295613f8c/5a190/building-grpc-server-dotnet-3.png" srcset="/static/378d6ab2ca89a5fafc8a261295613f8c/772e8/building-grpc-server-dotnet-3.png 200w, /static/378d6ab2ca89a5fafc8a261295613f8c/e17e5/building-grpc-server-dotnet-3.png 400w, /static/378d6ab2ca89a5fafc8a261295613f8c/5a190/building-grpc-server-dotnet-3.png 800w, /static/378d6ab2ca89a5fafc8a261295613f8c/c1b63/building-grpc-server-dotnet-3.png 1200w, /static/378d6ab2ca89a5fafc8a261295613f8c/29007/building-grpc-server-dotnet-3.png 1600w, /static/378d6ab2ca89a5fafc8a261295613f8c/f4fac/building-grpc-server-dotnet-3.png 2999w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We will also update our <a href="https://github.com/sahansera/dotnet-grpc/blob/main/BookshopServer/BookshopServer.csproj">csproj</a> file like so:</p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ItemGroup</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Protobuf</span> <span class="token attr-name">Include</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>../proto/bookshop.proto<span class="token punctuation">"</span></span> <span class="token attr-name">GrpcServices</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Server<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ItemGroup</span><span class="token punctuation">></span></span></code></pre></div> <h2>Implementing the Server</h2> <p>The implementation part is easy! Let’s clean up the GreeterService that comes default and add a new file called <code class="language-text">InventoryService.cs</code></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">rm</span> BookshopServer/Services/GreeterService.cs code BookshopServer/Services/InventoryService.cs</code></pre></div> <p>This is what our service is going to look like.</p> <p><a href="https://github.com/sahansera/dotnet-grpc/blob/main/BookshopServer/Services/InventoryService.cs">InventoryService.cs</a></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAACKaAAAimgG+3fsqAAAByUlEQVR42nWSXU8TQRSG+RXeqZRK0u9tNy277XanO/vRnZ3d7c4Wt5L4U4yRj16AQFu2Eqq94I47QoJGooiSEPwHxsBvoa2nGDCaNXnnzXsxz5w5Z2ZmMpmMx2Pwn9dXhycfjz+fvT89Oz6d+j/68OXr0cmnH1fX98gMrJubEfjq1vacWOWJH5e0uKTGyhg0K+JHojJXwaD5qvaAE16+7sDm0Wh0B9+m1U6YRWbZDXiruUDZAvV400WuZ7JGSqVZjeYNJ1ZWlrd3IuC1bpiS68h7VmUtyQtk1qqwQPGfyraTUEhKtTIajYnKei+MgNvdflLWRdsvEjdv2CC+bmd1O61RCAWDFuoOr+oH+3uRlfucShBryV6A/KDiNQWHYd9Xmc+TBpwIjSSR0Y6sDHAS6UrwXGKB3AwE188Rx2Su6dI4Imk8vfZDAf2v536yZtQWl4q2VyAwJCutWQlMoOEMZNXK6fasWFuJhNu9NwCjxaWS4xUdxtUd6DanU06nEKBs7nbaK50o+NVm93FJKpEGp5kZbCaQHpfwvISfSPjPO+eFF+tbf8G/v8v55ffNcDccDHcGw/Dt1Ht770DdO/UGw41w99vF5f0P+wUcM2qUun7IXgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-4" title="" src="/static/c2466f61384b1486a03db79f46080db6/5a190/building-grpc-server-dotnet-4.png" srcset="/static/c2466f61384b1486a03db79f46080db6/772e8/building-grpc-server-dotnet-4.png 200w, /static/c2466f61384b1486a03db79f46080db6/e17e5/building-grpc-server-dotnet-4.png 400w, /static/c2466f61384b1486a03db79f46080db6/5a190/building-grpc-server-dotnet-4.png 800w, /static/c2466f61384b1486a03db79f46080db6/c1b63/building-grpc-server-dotnet-4.png 1200w, /static/c2466f61384b1486a03db79f46080db6/29007/building-grpc-server-dotnet-4.png 1600w, /static/c2466f61384b1486a03db79f46080db6/f4fac/building-grpc-server-dotnet-4.png 2999w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s go through the code step by step.</p> <ol> <li><code class="language-text">Inventory.InventoryBase</code> is an abstract class that got auto-generated (in your <code class="language-text">obj/debug</code> folder) from our protobuf file.</li> <li><code class="language-text">GetBookList</code> method’s stub is already generated for us in the <code class="language-text">InventoryBase</code> class and that’s why we are overriding it. Again, this is the RPC call we defined in our protobuf definition. This method takes in a <code class="language-text">GetBookListRequest</code> which defines what the request looks like and a <code class="language-text">ServerCallContext</code> param which contains the headers, auth context etc.</li> <li>Rest of the code is pretty easy - we prepare the response and return it back to the caller/client. It’s worth noting that we never defined the <code class="language-text">GetBookListRequest</code> <code class="language-text">GetBookListResponse</code> types ourselves, manually. The gRPC tooling for .NET has already created these for us under the <code class="language-text">Bookshop</code> namespace.</li> </ol> <p>Make sure to update the <a href="https://github.com/sahansera/dotnet-grpc/blob/main/BookshopServer/Program.cs">Program.cs</a> to reflect the new service as well.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// ...</span> app<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapGrpcService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>InventoryService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ...</span></code></pre></div> <p>And then we can run the server with the following command.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run <span class="token parameter variable">--project</span> BookshopServer/BookshopServer.csproj</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABwElEQVR42p2R32vTUBTHk9wwb2/H2tkm3iZpou+mbkvb+WOsUxTUf8J/QvDRh13Y32NllCIVMukURDe20s0+TFC3h637B7bcHk/uMhDRBw18+J5zuPdzcxPtZesZ/fj21YtPu1tro9H31Z3RNzE8OBT9D59Fvx+Ld3FP9N70RLfTEZ3X66K73hWb8abY39kXg+2BGA6Gq3u7e2txL37ebreZdsO2rz198ni8vPIAFpdWJouth9BsPYLm0n24u3wb7txrQqPRgLAWQhjehFpYg7lbcxBFdViYjyBaiGS93kjnR67rehqzGL9anj0uFmdhOj99XpiZSfKMJSyHsHxCKUsIMRGSGAZJiGkmBrnIdIacIYD9D03TqojGi2Vr7Ho+VP1g4lUDuKivQ9mywbJtmJq6AmqTwvwticzySAkZY7zkumPH88BxvQkCjqNqoJQqWQou/hsyy0xoWbxQKp3wSgV4xUkcx5WcV6RX9WWhUJQolblcTkEpJmMSP4Wk2Ou6nsrOM+GhEgZBwHHRqabrYBjGBFFXuUzT/PWKpuovZ398Q3zKyHvkAPmCjP6RdM9XZCP9H6mQZIWfnfA/+JmD/ASbkspiCCCYqwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-5.png" title="" src="/static/2d078f119b64e3731370c7f2afce9b0b/5a190/building-grpc-server-dotnet-5.png" srcset="/static/2d078f119b64e3731370c7f2afce9b0b/772e8/building-grpc-server-dotnet-5.png 200w, /static/2d078f119b64e3731370c7f2afce9b0b/e17e5/building-grpc-server-dotnet-5.png 400w, /static/2d078f119b64e3731370c7f2afce9b0b/5a190/building-grpc-server-dotnet-5.png 800w, /static/2d078f119b64e3731370c7f2afce9b0b/c1b63/building-grpc-server-dotnet-5.png 1200w, /static/2d078f119b64e3731370c7f2afce9b0b/29007/building-grpc-server-dotnet-5.png 1600w, /static/2d078f119b64e3731370c7f2afce9b0b/e04e6/building-grpc-server-dotnet-5.png 2152w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We are almost there! Remember we can’t access the service yet through the browser since browsers don’t understand binary protocols. In the next step, we will to test our service 🎉</p> <h2>Common Errors</h2> <p>A common <a href="https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos">error</a> you’d find on macOS systems with .NET is HTTP/2 and TLS issue shown below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABxElEQVR42qVPXWsTQRRdbZLux+xmvz+SzaaNCv4AUdAGX4RaG7ZpJk0VKdjix5/zoetDf4VvfQgEBoPSB0EIBBai6cz1zlbBdwcO59w75945o8wuLqzvXy5Hi3JBV6t1hXK5omzK6JwxymaMfmPziqeXUzpDzFEvf6B/WdJyUUoeX329GrLPrKnsPX324P3bUzg5PRPHJ2cwefMO8QEOX76Gyash0AmFPD+A57u7sL+3D4MXAzjIhzCmR3A0nkgWo8MR5IN83e/3Hyu1Wu2haTXB9Xzu+YFAFrbrCcf1BfYFMS1hNW1hoXb+6cta+pu2w+v1Bmxs1H4pirKDUB7ZtgPd7rbo9e5Cr3cHsu4WtNsptNMOpGkGLdRx0oKk1YYoTvCuA2EYV3WWbYkUfUEQrlVV3akS+n4gh64RHM08SVo8imKOwzxOEi773s0PuB+E3HFcrusGNwyDE0KuTdMUhJg/cdeTKiGaoJN1hUwkE+C3wfsD1/UgCCMIoxjkw8Q0AZeBJqHpUgtN06S+SYjnnuO655ikwFQFsawCTQW+XvFfbRik0JFxCHt6pTdRY32O9SdV1T82CLkvF95G1BGN/4Tcces3fnDLdZD7Z/cAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-6.png" title="" src="/static/649b50c189801c60e912a81c69d4651a/5a190/building-grpc-server-dotnet-6.png" srcset="/static/649b50c189801c60e912a81c69d4651a/772e8/building-grpc-server-dotnet-6.png 200w, /static/649b50c189801c60e912a81c69d4651a/e17e5/building-grpc-server-dotnet-6.png 400w, /static/649b50c189801c60e912a81c69d4651a/5a190/building-grpc-server-dotnet-6.png 800w, /static/649b50c189801c60e912a81c69d4651a/c1b63/building-grpc-server-dotnet-6.png 1200w, /static/649b50c189801c60e912a81c69d4651a/29007/building-grpc-server-dotnet-6.png 1600w, /static/649b50c189801c60e912a81c69d4651a/ade5e/building-grpc-server-dotnet-6.png 2188w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>gRPC template uses TLS by default and Kestrel doesn’t support HTTP/2 with TLS on macOS systems. We need to turn off TLS (ouch!) in order for our demo to work.</p> <blockquote> <p>💡 Please don’t do this in production! This is intended for local development purposes only.</p> </blockquote> <p>On local development</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Turn off TLS</span> builder<span class="token punctuation">.</span>WebHost<span class="token punctuation">.</span><span class="token function">ConfigureKestrel</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Setup a HTTP/2 endpoint without TLS.</span> options<span class="token punctuation">.</span><span class="token function">ListenLocalhost</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">,</span> o <span class="token operator">=></span> o<span class="token punctuation">.</span>Protocols <span class="token operator">=</span> HttpProtocols<span class="token punctuation">.</span>Http2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h2>Testing the service</h2> <p>Usually, when interacting with the HTTP/1.1-like server, we can use cURL to make requests and inspect the responses. However, with gRPC, we can’t do that. (you can make requests to HTTP/2 services, but those won’t be readable). We will be using <a href="https://github.com/fullstorydev/grpcurl">gRPCurl</a> for that.</p> <p>Once you have it up and running, you can now interact with the server we just built.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl <span class="token parameter variable">-plaintext</span> localhost:5000 Inventory/GetBookList</code></pre></div> <aside> 💡 Note: gRPC defaults to TLS for transport. However, to keep things simple, I will be using the `-plaintext` flag with `grpcurl` so that we can see a human-readable response. </aside> <p>How do we figure out the endpoints of the service? There are two ways to do this. One is by providing a path to the proto files, while the other option enables reflection through the code.</p> <p><strong>Using proto files</strong></p> <p>If you don’t want to enable reflection, we can use the Protobuf files to let gRPCurl know which methods are available. Normally, when a team makes a gRPC service they will make the protobuf files available if you are integrating with them. So, without having to ask them or doing trial-and-error you can use these proto files to introspect what kind of endpoints are available for consumption.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl -import-path Proto <span class="token parameter variable">-proto</span> bookshop.proto <span class="token parameter variable">-plaintext</span> localhost:5000 Inventory/GetBookList</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABlklEQVR42p2P20oCURSGdfbk6EyiTrrH0qyLuvR4EUQnJSw6vEMvEXTZhRt8jN5BqBgGEfMiiFDpIDoXRVJelL1AOa6WtYmCkmjDx1rsvdf//8t2sLPvvKgc7Z1f1vLN5l3uymyzerPNdKPCSmWDlUsGq5RKzDjWmXGoMx0p6garnlVZ86LBGvVG7rp2mS8Xy7uFQkG2UUq1TDrdza5vQCa72V9e24bF7BasZDchvboE8wtzkEomIBqNQjyegAQS5ySTKXxLWclECmLRWCcUCoVteIKEiI+iOAJ2u/1VEIQe1h7e/5UXBJB7ZPJdEEW6AaqBP0D7fj8FVR0DWVZAkpzgcjmxl0EgBPDfT1i8dj4FCSFdWRnFYbnvwmGHJMGgiqII3H0YFq+drwmffJjK6/X2PB6PpSiKJUmShatbfGAYr1zw4VPQ4XA8E0yDa/XtRIQPCBBR+AD7YQnR+NvKfk0LngbGJ27U0FTLMzljeiOzpm9i2qRhn6mFVZPSgIn/fqOFhrdut3IyCDcQJLyJcIf/EOEa5A0P6cZ039orygAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-7.png" title="" src="/static/b0011f4a15506033cf4ed87954a32778/5a190/building-grpc-server-dotnet-7.png" srcset="/static/b0011f4a15506033cf4ed87954a32778/772e8/building-grpc-server-dotnet-7.png 200w, /static/b0011f4a15506033cf4ed87954a32778/e17e5/building-grpc-server-dotnet-7.png 400w, /static/b0011f4a15506033cf4ed87954a32778/5a190/building-grpc-server-dotnet-7.png 800w, /static/b0011f4a15506033cf4ed87954a32778/c1b63/building-grpc-server-dotnet-7.png 1200w, /static/b0011f4a15506033cf4ed87954a32778/29007/building-grpc-server-dotnet-7.png 1600w, /static/b0011f4a15506033cf4ed87954a32778/e04e6/building-grpc-server-dotnet-7.png 2152w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now, let’s say we didn’t have reflection enabled and try to call a method on the server.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl <span class="token parameter variable">-plaintext</span> localhost:5000 Inventory/GetBookList</code></pre></div> <p>We can expect that it will error out. Cool!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 26%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABLElEQVR42qXMT07CQBiHYTqTzljE1DYUbIwuvACXACmlFEossZKoGyZ6Cu/jxoUHYEMXGE2TppFoWCsxFptG4wb4HPy3cuckT+a3+PJmzt0zMr2/7k6SyclrOuulbzMWJ+8sCEcsGoUsikI2jvgOAhbeBCy4CtgojFgyiVkapyx9euml0+R0fHvn9fv9lcxOsVhoN+3njueB6x0unIMjaHePwel0wd1vgbNnQ900oVypgFE1PtWMGlimBQ3LBqvemDftFlTKuw+lUmkzw58uK2qcUzWQZGWRVQsg5XVY3dgGSdVB1tZAzudAURXgt3+ZI0EAgtEj31vLoEqpeClSMsSEDjCVfEyz/vIXOULFL4T4giD8Qgj97AHGeEhF8YK3tGUQcTlu/R+U7wb6ADRcgabOOBN/AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-8.png" title="" src="/static/3b54de060442b6ebe32148fd3cea900e/5a190/building-grpc-server-dotnet-8.png" srcset="/static/3b54de060442b6ebe32148fd3cea900e/772e8/building-grpc-server-dotnet-8.png 200w, /static/3b54de060442b6ebe32148fd3cea900e/e17e5/building-grpc-server-dotnet-8.png 400w, /static/3b54de060442b6ebe32148fd3cea900e/5a190/building-grpc-server-dotnet-8.png 800w, /static/3b54de060442b6ebe32148fd3cea900e/c1b63/building-grpc-server-dotnet-8.png 1200w, /static/3b54de060442b6ebe32148fd3cea900e/29007/building-grpc-server-dotnet-8.png 1600w, /static/3b54de060442b6ebe32148fd3cea900e/e04e6/building-grpc-server-dotnet-8.png 2152w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><strong>Enabling reflection</strong></p> <p>While in the <code class="language-text">BookshopServer</code> folder run the following command to install the reflection package.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package Grpc.AspNetCore.Server.Reflection</code></pre></div> <p>Add the following to the Program.cs file. Note that we are using the new Minimal API approach to configure these services</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Register services that enable reflection</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddGrpcReflection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Enable reflection in Debug mode.</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>app<span class="token punctuation">.</span>Environment<span class="token punctuation">.</span><span class="token function">IsDevelopment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> app<span class="token punctuation">.</span><span class="token function">MapGrpcReflectionService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABU0lEQVR42qWRy26CQBSGGS7hNqAiSG1iNGnSVdONTfoW7etoLypqUqHWQjdd9B1cyOM56d8ZFNqlpCf5MjBwPv4zSBIvAH3OLWfIuanJ8Ng7kMrKZtPt51eGeL1h69cE8SpG+pZiGS3x9PCM+WyORbQo1vglQbpJkaUfSFYJomnEsvcUj6PxthKqqpabFoWiKEwErknZk3NIISREyg3DQKvlMdum8Lw2fN8X96COA0odWJYN13VhUwqZyAdkGYqsMHFNCPkVCruuGzjrnrODLODNDVh2kVq8XDSXnJCQcKEOno6JpIZhQnzANC2+Z0PTtHojlwl7vT7z2j6CoIOgE8JtNAqpSFlLqKrqTozCk+75+s3XAv7oFPZH4a4SupTmbqMJ07KZycet+5fFcem6ViUkF4PB/eXV9aTb64/CMBzzvTqMPL85cZrOXSX8e5j/qMLzA9OG8O599Q6qAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-dotnet-9.png" title="" src="/static/1490dc3c85335a827f86af33132fe227/5a190/building-grpc-server-dotnet-9.png" srcset="/static/1490dc3c85335a827f86af33132fe227/772e8/building-grpc-server-dotnet-9.png 200w, /static/1490dc3c85335a827f86af33132fe227/e17e5/building-grpc-server-dotnet-9.png 400w, /static/1490dc3c85335a827f86af33132fe227/5a190/building-grpc-server-dotnet-9.png 800w, /static/1490dc3c85335a827f86af33132fe227/c1b63/building-grpc-server-dotnet-9.png 1200w, /static/1490dc3c85335a827f86af33132fe227/29007/building-grpc-server-dotnet-9.png 1600w, /static/1490dc3c85335a827f86af33132fe227/21e8f/building-grpc-server-dotnet-9.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Conclusion</h2> <p>As we have seen, similar to the Go implementation, we can use the same Protocol buffer files to generate the server implementation in .NET. In my opinion .NET’s new tooling makes it easier to generate the server stubs when a change happens in your Protobufs. However, setting up the local developer environment could be a bit challenging especially for macOS.</p> <p>Feel free to let me know if you have any questions or feedback. Until next time! 👋</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>References</h2> <ul> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&#x26;tabs=visual-studio-code">https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&#x26;tabs=visual-studio-code</a></li> <li><a href="https://grpc.io/docs/languages/csharp/quickstart/">https://grpc.io/docs/languages/csharp/quickstart/</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos">https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60-samples?view=aspnetcore-6.0">https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60-samples?view=aspnetcore-6.0</a></li> </ul><![CDATA[Building a gRPC Server in Go]]>https://www.sahansera.dev/building-grpc-server-go/https://www.sahansera.dev/building-grpc-server-go/Fri, 25 Feb 2022 00:00:00 GMT<h2>Intro</h2> <p>In this article, we will create a simple web service with gRPC in Go. We won’t use any third-party tools and only create a single endpoint to interact with the service to keep things simple.</p> <h2>Motivation</h2> <p>This is the second part of an articles series on gRPC. If you want to jump ahead, please feel free to do so. The links are down below.</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a></li> <li><a href="https://sahansera.dev/building-grpc-server-go/">Building a gRPC server with Go</a> (You are here)</li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC server with .NET</a></li> <li><a href="https://sahansera.dev/building-grpc-client-go">Building a gRPC client with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC client with .NET</a></li> </ul> <p>Please note that this is intended for anyone who’s interested in getting started with gRPC. If you’re not, please feel free to skip this article.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>The plan</h2> <p>So this is what we are trying to achieve.</p> <ol> <li>Generate the <code class="language-text">.proto</code> IDL stubs.</li> <li>Write the business logic for our service methods.</li> <li>Spin up a gRPC server on a given port.</li> </ol> <p>In a nutshell, we will be covering the following items on our initial diagram.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZElEQVR42nVS2W7DIBD0//9Upahvlao+VIriNIlzNIc5jO1gMAZPF3JUqdKVzOJldpnZJcMfG4OnZbz9pdUOHaRl18h4x7auxjD6h/zstgkhQMgK5fEAVp6gVE2xS/Kan/GxEb+F2jNhGLb7JRhnMJ19LBgJOR9gbQ/bOxjrko+xaNODweuiJdyYLundAGN69L2nnIgd7ryzGzvvRzyztlGYzhb4nC0hOX+K8WG8q7kUpJuHfwrWdQ1BspQUkNSSpwUp99b2LLJTSiXwuSlpKD2C1wiDIt8lKVGidTaNJARHZ006C72A0dR3yVGKEgPhMmMMttsNjieB7/wFwTFoOYU+TNCpL3TO4cRK7I8naMJqzaHLN+hmDc8n4Nt3zHcF5kVO7WmRee+x2+2Q5znJaxJtRTIZ44m5EOQriVpV9AokxSpUFI+YaJFQsSqwWq7S/jrlMQ3mP4tvMwzu2cm1hz59sYc/rsG7uTvIp7kAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-go-1.png" title="" src="/static/c1481df614d654fbbcaede50e8cf20db/5a190/building-grpc-server-go-1.png" srcset="/static/c1481df614d654fbbcaede50e8cf20db/772e8/building-grpc-server-go-1.png 200w, /static/c1481df614d654fbbcaede50e8cf20db/e17e5/building-grpc-server-go-1.png 400w, /static/c1481df614d654fbbcaede50e8cf20db/5a190/building-grpc-server-go-1.png 800w, /static/c1481df614d654fbbcaede50e8cf20db/c1b63/building-grpc-server-go-1.png 1200w, /static/c1481df614d654fbbcaede50e8cf20db/29007/building-grpc-server-go-1.png 1600w, /static/c1481df614d654fbbcaede50e8cf20db/27fe5/building-grpc-server-go-1.png 2287w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>💡 As always, all the code samples documentation can be found at: <a href="https://github.com/sahansera/go-grpc">https://github.com/sahansera/go-grpc</a></p> </blockquote> <h2>Prerequisites</h2> <p>This guide targets Go and assumes you have the necessary go tools installed locally. Other than that, we will cover gRPC specific tooling down below. Please note that I’m using some of the commands that are macOS specific. Please follow this <a href="https://grpc.io/docs/languages/go/quickstart/">link</a> to set it up if you are on a different OS.</p> <p>To install Protobuf compiler:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">brew <span class="token function">install</span> protobuf</code></pre></div> <p>To install Go specific gRPC dependencies:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">go <span class="token function">install</span> http://google.golang.org/protobuf/cmd/[email protected] go <span class="token function">install</span> http://google.golang.org/grpc/cmd/[email protected]</code></pre></div> <h2>Project structure</h2> <p>There is no universally agreed-upon project structure per se. We will use Go modules and start by initializing a new project. So our business problem is this - we have a bookstore and we want to expose its inventory via an RPC function.</p> <p>Since this talks about the creation of the server, we will call it <code class="language-text">bookshop/server</code></p> <p>We can create a new folder called <code class="language-text">server</code> for the server and initialize it by so:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">go mod init bookshop/server</code></pre></div> <p>In a later post, we will also work on the client-side of this app and call it <code class="language-text">bookshop/client</code></p> <p>This is what it’s going to look like at the end of this post.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 444px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 98%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACL0lEQVR42p1Uy5LTMBD0Ca7sJo7ixLLlWHb8zMObZBP2wRZQ3CiKLU5cqOLInf/YP256HBI4QB576ChSya2emZ5xlDLwvBhXm3eIkwmCoEAcT9uzXi8kDOSOrNv9IRg4/X6EwcDi/sMnZPk1bNJgnC0g553OEN2O36JDuG5wnFB+Ol2NgWtQPRQI4gLj8RJFueYDqxbpeMH9BmFYwOXdo4RKNoMUphxD6wQRQ45GExhTwtoZ/9eIogq+nx5UqbYhj+D1DZ5+Fvj8/gbNIMdNXGGiU1xKyF1/j0NkCR/zmGtHEi4X59MKq+s7BMznKMgw9EboKdO+ekpRJEq59ztkA1us8PH7D8TpFdHg4sJrVR2v7D9y6HIz4Dp3MxidITAFDHNmTPV8wqGKsFSscDxDStvYZI6Eas8mVH8RrlUFS1UxK5uSTFQe997/FHKtXQsb1VvbEDrIz1Ko9j50Q7xidV8uH2DDDG9tjU1U4pb2ESu4R4i6jCLXY/i0oCMH8kE51PC1JTLEYd4iCU9X2Fdbezninwu23dewS1Nr5NM3qKd3yIo1Urad9HSvdzyPu1w7O1M+jhS7I4COpixI0xZF2u4ZttmGLIQzEoa2QULLpNmStmnOJxQySfwjB8GCLZfIlKEPC4a8t40y5ymUHC35cU5CQ2OLQjF2yEfO96FU59LDi29PcF9/wZw9XU5uMZndo+aaU6l0zGnEO4Wuhpus0PMzjrKoHf9/YCEj7lSFvwDd1Rtdg9s/mwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-go-2.png" title="" src="/static/b6da05025f4b88f31d960e3ccf8ef1fa/9b7bd/building-grpc-server-go-2.png" srcset="/static/b6da05025f4b88f31d960e3ccf8ef1fa/772e8/building-grpc-server-go-2.png 200w, /static/b6da05025f4b88f31d960e3ccf8ef1fa/e17e5/building-grpc-server-go-2.png 400w, /static/b6da05025f4b88f31d960e3ccf8ef1fa/9b7bd/building-grpc-server-go-2.png 444w" sizes="(max-width: 444px) 100vw, 444px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h2>Creating the service definitions with .proto files</h2> <p>In my previous post, we discussed what are Protobufs and how to write one. We will be using the same example which is shown below.</p> <p>A common pattern to note here is to keep your <code class="language-text">.proto</code> files in their separate folder, so that use them to generate the server and client stubs.</p> <p><a href="https://github.com/sahansera/go-grpc/blob/main/proto/bookshop.proto">bookshop.proto</a></p> <div class="gatsby-highlight" data-language="protobuf"><pre class="language-protobuf"><code class="language-protobuf"><span class="token keyword">syntax</span> <span class="token operator">=</span> <span class="token string">"proto3"</span><span class="token punctuation">;</span> <span class="token keyword">option</span> go_package <span class="token operator">=</span> <span class="token string">"bookshop/pb"</span><span class="token punctuation">;</span> <span class="token keyword">message</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span> <span class="token builtin">string</span> title <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token builtin">string</span> author <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token builtin">int32</span> page_count <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token keyword">optional</span> <span class="token builtin">string</span> language <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">message</span> <span class="token class-name">GetBookListRequest</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">message</span> <span class="token class-name">GetBookListResponse</span> <span class="token punctuation">{</span> <span class="token keyword">repeated</span> <span class="token positional-class-name class-name">Book</span> books <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">service</span> <span class="token class-name">Inventory</span> <span class="token punctuation">{</span> <span class="token keyword">rpc</span> <span class="token function">GetBookList</span><span class="token punctuation">(</span><span class="token class-name">GetBookListRequest</span><span class="token punctuation">)</span> <span class="token keyword">returns</span> <span class="token punctuation">(</span><span class="token class-name">GetBookListResponse</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Note how we have used the <a href="https://developers.google.com/protocol-buffers/docs/proto#options">option</a> keyword here. We are essentially saying the Protobuf compiler where we want to put the generated stubs. You can have multiple option statements depending on which languages you are using to generate the stubs for.</p> <blockquote> <p>💡 You can find a full list of allowed values at <a href="https://github.com/protocolbuffers/protobuf/blob/2f91da585e96a7efe43505f714f03c7716a94ecb/src/google/protobuf/descriptor.proto#L44">google/protobuf/descriptor.proto</a></p> </blockquote> <p>Other than that, we have 3 messages to represent a Book entity, a request and a response, respectively. Finally we have a service defined called <code class="language-text">Inventory</code> which has a RPC named <code class="language-text">GetBookList</code> which can be called by the clients.</p> <p>If you need to understand how this is structured, please refer to my previous <a href="https://sahansera.dev/introduction-to-grpc/">post</a> 🙏</p> <h2>Generating the stubs</h2> <p>Now that we have the IDL created we can generate the Go stubs for our server. It is a good practice to put it under the <code class="language-text">make gen</code> command so that we can easily generate them with a single command in the future.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">protoc <span class="token parameter variable">--proto_path</span><span class="token operator">=</span>proto proto/*.proto <span class="token parameter variable">--go_out</span><span class="token operator">=</span>. --go-grpc_out<span class="token operator">=</span>.</code></pre></div> <p>Once this is done, you will see the generated files under the <code class="language-text">server/pb</code> folder.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 403px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVR42o2Sy1LCMBSG+wYIGAq1Sdq06Y1SoNBaQR0dXTjjjONWfQIWbn3935OAzMiKxTcnl+bLOSd1xm6AIVN47nzcNhnGkwRS5uCCxuMQo5Eg5Nk4nhehP9T4/lD42b1AhmsU0w6z6s5KmRHSpefiiGBOkgU4idr7N6TlFuXiEWFcg8sKgvYs9N0ROz9ZO+BItYLSLXR+g/evHbrNKx6ePmm+wZWowOmgQYTL49jg0+H9eg3j+MNx3ZB6peCOAlwMBXpFAy+YQusaadYSDZJ0DRmUiOIldLI6xLWNPs+odwGMx+CYprumftMrT6FfNuBqhoSERqST+khedFZoxqbP5WwLTkLG/MPjCSOU1mwXJgqDjHoXllCqQqyXFhVViKK5zVpR9P0Eg4GHy0tOMv7/lfdCypAJMHrx3mILP66QUnZZ3tosTdl5cW2zM1kFdCFjwpZ6+tv8AqCP4cDnCOboAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-go-3.png" title="" src="/static/71fe5723f87eb928195ad96244feee60/045fd/building-grpc-server-go-3.png" srcset="/static/71fe5723f87eb928195ad96244feee60/772e8/building-grpc-server-go-3.png 200w, /static/71fe5723f87eb928195ad96244feee60/e17e5/building-grpc-server-go-3.png 400w, /static/71fe5723f87eb928195ad96244feee60/045fd/building-grpc-server-go-3.png 403w" sizes="(max-width: 403px) 100vw, 403px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Awesome! 🎉 now we can use these subs in our server to respond to any incoming requests.</p> <h2>Creating the gRPC server</h2> <p>Now, we will create the main.go file to create the server.</p> <p><a href="https://github.com/sahansera/go-grpc/blob/main/server/main.go">main.go</a></p> <div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go"><span class="token operator">...</span> <span class="token keyword">type</span> server <span class="token keyword">struct</span> <span class="token punctuation">{</span> pb<span class="token punctuation">.</span>UnimplementedInventoryServer <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token punctuation">(</span>s <span class="token operator">*</span>server<span class="token punctuation">)</span> <span class="token function">GetBookList</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> in <span class="token operator">*</span>pb<span class="token punctuation">.</span>GetBookListRequest<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>pb<span class="token punctuation">.</span>GetBookListResponse<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator">&amp;</span>pb<span class="token punctuation">.</span>GetBookListResponse<span class="token punctuation">{</span> Books<span class="token punctuation">:</span> <span class="token function">getSampleBooks</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">nil</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> listener<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">Listen</span><span class="token punctuation">(</span><span class="token string">"tcp"</span><span class="token punctuation">,</span> <span class="token string">":8080"</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> s <span class="token operator">:=</span> grpc<span class="token punctuation">.</span><span class="token function">NewServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> pb<span class="token punctuation">.</span><span class="token function">RegisterInventoryServer</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> <span class="token operator">&amp;</span>server<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">:=</span> s<span class="token punctuation">.</span><span class="token function">Serve</span><span class="token punctuation">(</span>listener<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatalf</span><span class="token punctuation">(</span><span class="token string">"failed to serve: %v"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">...</span></code></pre></div> <ol> <li>We first define a struct to represent our server. The reason why we need to <a href="https://go.dev/doc/effective_go#embedding">embed</a> <code class="language-text">pb.UnimplementedInventoryServer</code> is to maintain future compatibility when generating gRPC bindings for Go. You can read more on this in this initial <a href="https://github.com/grpc/grpc-go/issues/3669">proposal</a> and on <a href="https://github.com/grpc/grpc-go/blob/master/cmd/protoc-gen-go-grpc/README.md">README</a>.</li> <li>As we discussed, <code class="language-text">GetBookList</code> can be called by the clients, and this is where that request will be handled. We have access to context (such as auth tokens, headers etc.) and the request object we defined.</li> <li>In the <code class="language-text">main</code> method, we are creating a listening on TCP port 8080 with the <code class="language-text">net.Listen</code> method, initialize a new gRPC server instance, register our Inventory service and then start responding to incoming requests.</li> </ol> <h2>Interacting with the Server</h2> <p>Usually, when interacting with the HTTP/1.1-like server, we can use cURL to make requests and inspect the responses. However, with gRPC, we can’t do that. (you can make requests to HTTP/2 services, but those won’t be readable). We will be using <a href="https://github.com/fullstorydev/grpcurl">gRPCurl</a> for that.</p> <p>Once you have it up and running, you can now interact with the server we just built.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl <span class="token parameter variable">-plaintext</span> localhost:5000 Inventory/GetBookList</code></pre></div> <blockquote> <p>💡 Note: gRPC defaults to TLS for transport. However, to keep things simple, I will be using the <code class="language-text">-plaintext</code> flag with <code class="language-text">grpcurl</code> so that we can see a human-readable response.</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABoklEQVR42q2QXUvjQBSGi5hmkjZN2rRp+pWt9IOidWlXRCGt1aqro/ZevYiW0JKL4pX+LJFF6upPEwZeZ0Yq4s2y4MDDmXPmzDMfsZvg/Od0ejEKr66OgyCg4/GYyhiMaRRFdBbNaDThcTajk8mEBpeBRPSJPAzD4/A6HE2vp+sxMc72/MfD0wNsbvms3+the2sb/X4fvu9jfzjE0e8jnFCKw/0DDPeG2B3sYrAzwMavDaytrqHdbrNez0en0/kjhVY6/eJ5VaRMk6mqimVlGUpcgaIoELmAEIJ4PA6VqNB1Hbqmy7qocZimERim8cx1S7GEpj27rotGo8HK5TKazSZarRZqtRoqlQpKpRKKxSKq1Sry+bwUJpPJzzDDMGDb9l8p1LjQcRx0u10mJPV6XUoKhQJE42LjYp5IJL7CRD2TybwLLct6MniBqOorlzP+vA/4bZjYIOICkX/hVYi5cC6FPzzvxVupoVCuMMfJyVM/3+xf8F7munlks9n3P8zZ9l3Gzs1TVvqeLz6ohDyQ/+M+ZabmpmneSiEfCif5DQhP7A0Ip7epcLQhewAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-go-4.png" title="" src="/static/f9ac764800043e617a828c66ae06f036/5a190/building-grpc-server-go-4.png" srcset="/static/f9ac764800043e617a828c66ae06f036/772e8/building-grpc-server-go-4.png 200w, /static/f9ac764800043e617a828c66ae06f036/e17e5/building-grpc-server-go-4.png 400w, /static/f9ac764800043e617a828c66ae06f036/5a190/building-grpc-server-go-4.png 800w, /static/f9ac764800043e617a828c66ae06f036/d1d24/building-grpc-server-go-4.png 987w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>How do we figure out the endpoints of the service? There are two ways to do this. One is by providing a path to the proto files, while the other option enables reflection through the code.</p> <p><strong>Enabling reflection</strong></p> <p>This is a pretty cool feature, where it will give you introspection capabilities to your API.</p> <div class="gatsby-highlight" data-language="go"><pre class="language-go"><code class="language-go">reflection<span class="token punctuation">.</span><span class="token function">Register</span><span class="token punctuation">(</span>gs<span class="token punctuation">)</span></code></pre></div> <p>Following screenshot displays some of the commands you can use to introspect the API.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABxklEQVR42q2QXWviQBSGpfgRPxJrosbEjxhNMOlGDdWK1lIs7JamYAvdvSxJc6F4Ie3N9meVUoqt/WmFgdM5IxXc6x14eCdnJu9550Tu/d+d27ubaXAbXPi+74Vh6IV+yHS5XHoPiwdvMVuwPTKbzXYIguBiPp9PKe0Iruufp69Xf6YwGJ2Qk/EYBkcDGB+PYTQcwdnkDM5/ncOldwmTyQT6vT44jgPOjw2ddgeGwwFxuy50u90XZqjpjQ/bPoBKpULy+TwoigLlchnUsgqqqkKhUIBEIsGIRqMQi8UgHo9v4ZIcwTOe59fUbi9iGsbaol1rjSY5sG2o1+tQq9WYabVaBU3TQNd1KBaL2JTVM5kMJJNJpnKpSHheAEmS3pmhYTTWttOGal0nVqvFfsBUmBaRZXlb+0YURdYAyWazJJ1OY21jSE3eDvtH0O64n67rkpZlEdRer0fovAg1I4ZhMKVJ2b7ZbBLTNFmNpvtMpVI0obhihrqmfZiWDbphEkUpYUeWDFPQZ4AgCJDL5XBG2xoq3sEnU0hJkfF7M8OCJD3ui9KKF/afBJ5/pgPegeO4Hf33jPIkZIUVDfKXGdIVo2T+A+gT+QLFwsETh7q1xgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-grpc-server-go-5.png" title="" src="/static/883f50db1fc2b463ea65ba7129930a61/5a190/building-grpc-server-go-5.png" srcset="/static/883f50db1fc2b463ea65ba7129930a61/772e8/building-grpc-server-go-5.png 200w, /static/883f50db1fc2b463ea65ba7129930a61/e17e5/building-grpc-server-go-5.png 400w, /static/883f50db1fc2b463ea65ba7129930a61/5a190/building-grpc-server-go-5.png 800w, /static/883f50db1fc2b463ea65ba7129930a61/d1d24/building-grpc-server-go-5.png 987w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl <span class="token parameter variable">-plaintext</span> -msg-template localhost:8080 describe .GetBookListResponse</code></pre></div> <p><strong>Using proto files</strong></p> <p>If you don’t want to to enable it by code we can use the Protobuf files to let gRPCurl know which methods are available. Normally, when a team makes a gRPC service they will make the protobuf files available if you are integrating with them. So, without having to ask them or doing trial-and-error you can use these proto files to introspect what kind of endpoints are available for consumption.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">grpcurl -import-path proto <span class="token parameter variable">-proto</span> bookshop.proto list</code></pre></div> <p>gRPCurl is great if you want to debug your RPC calls if you don’t have your client built yet.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Conclusion</h2> <p>In this article we looked at how we can create a simple gRPC server with Go. In the next one we will learn how to do the same with .NET.</p> <p>Feel free to let me know any feedback or questions. Thanks for reading ✌️</p> <h2>References</h2> <ul> <li><a href="https://medium.com/@nate510/structuring-go-grpc-microservices-dd176fdf28d0">https://medium.com/@nate510/structuring-go-grpc-microservices-dd176fdf28d0</a></li> <li><a href="https://www.youtube.com/watch?v=RHWwMrR8LUs&#x26;ab_channel=NicJackson">https://www.youtube.com/watch?v=RHWwMrR8LUs&#x26;ab_channel=NicJackson</a></li> <li><a href="https://www.oreilly.com/library/view/grpc-up-and/9781492058328/">https://www.oreilly.com/library/view/grpc-up-and/9781492058328/</a></li> </ul><![CDATA[Introduction to gRPC]]>https://www.sahansera.dev/introduction-to-grpc/https://www.sahansera.dev/introduction-to-grpc/Fri, 18 Feb 2022 00:00:00 GMT<h2>Intro</h2> <p>If you have built RESTful or other OpenAPI-like APIs for some time and wondering what’s next for you, then you have come to the right place. This article series discusses leveraging gRPC to build your next API, even multiple services. We will initially look at the main concepts from a high-level view and then move on to the implementation aspects of it.</p> <h2>Motivation</h2> <p>There are many tutorials on getting started there. But the main issue I faced was that they even made me more confused as there was a lot of contexts lost in the process and brought in way too many third-party libraries or explained a bunch of steps without emphasizing how different pieces work together. Therefore, I thought to create a guide for anyone who’s interested in getting started with gRPC from a hands-on perspective.</p> <p>This article is the first part of a series on gRPC. The links are down below. If you want to jump ahead, please feel free to do so.</p> <ul> <li><a href="https://sahansera.dev/introduction-to-grpc/">Introduction to gRPC</a> (You are here)</li> <li><a href="https://sahansera.dev/building-grpc-server-go/">Building a gRPC server with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-server-dotnet/">Building a gRPC server with .NET</a></li> <li><a href="https://sahansera.dev/building-grpc-client-go">Building a gRPC client with Go</a></li> <li><a href="https://sahansera.dev/building-grpc-client-dotnet">Building a gRPC client with .NET</a></li> </ul> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>Background</h2> <p>One of the popular choices for building APIs nowadays is creating a RESTful service. However, before even coming to REST API, we need to look back to see other forms we used to have in the past.</p> <ul> <li><a href="https://en.wikipedia.org/wiki/SOAP">SOAP</a> - Popularized back in the late-90s for building service-oriented architectures (SOA) systems which are known for exchanging <em>bloated</em> XMLs. The benefits were detrimental for distributed applications where the schema was rigid.</li> <li><a href="https://en.wikipedia.org/wiki/Representational_state_transfer">REST</a> - Promoted the resource-oriented architecture (ROA)-style distributed applications. Often bulky with JSONs, and everything that the service provides is represented as resources (Eg: <code class="language-text">/api/v1/users/</code> or <code class="language-text">api/v1/books/1234</code> etc.). Sometimes this could result in exposing too much or too little data at the cost of making multiple HTTP calls.</li> <li><a href="https://en.wikipedia.org/wiki/GraphQL">GraphQL</a> - GraphQL takes a step further and exposes a single endpoint that you can use to query or mutate the data through HTTP verbs. It’s still a request-response model and based on the text-based transport protocol, HTTP 1.x.</li> </ul> <p>What if I want to have some bi-directional communication? None of the above solved that. Then we got technologies like WebSockets and Server Sent Eventing.</p> <ul> <li><a href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a> - Built to support bi-directional communication over a single TCP connection. Known to be a very chatty protocol often sending packets back and forth. If you want to know about WebSockets, here is an <a href="https://sahansera.dev/understanding-websockets-with-aspnetcore-5/">article that I wrote</a>.</li> <li><a href="https://en.wikipedia.org/wiki/Server-sent_events">Server Sent Eventing</a> - Another paradigm where the server sends messages to the client once the initial connection has been set up by the client.</li> </ul> <p>There’s a recurring theme going on in the above technologies. It could be summarized as the messages being (bulky, not strongly typed etc.) and inefficient protocols (text-based such as HTTP 1.x etc.).</p> <h2>Hello gRPC</h2> <p>gRPC was born to address some of the challenges we face in the above approaches. In 2015 Google released gRPC to the open-source world. The idea behind gRPC is to enable developers to use <a href="https://en.wikipedia.org/wiki/Remote_procedure_call">RPC</a>-like communications over HTTP/2 while have a single client library. Whoever is maintaining gRPC will maintain the client libraries. Because HTTP/2 works on binary format, gRPC will also abstract away the HTTP/2 stuff from you. The developers only have to define their service contracts through requests, responses and RPC calls, and the gRPC framework will handle the rest for us.</p> <blockquote> <p>💡 Even before we start learning about gRPC, you must have also thought about what the “g” in gRPC mean? Some say it stands for “Good”; others say it stands for “Google”. You know what? It doesn’t matter as it doesn’t provide more context. You can find all the different variations <a href="https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md">here</a>, which is, by the way, hilarious! 😂</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 552px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 59.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAC6klEQVR42n1S+0uTURj+bHbV7KdlSKSG9IPEkrzNaa7CC0qmTY/Obd+mTre5m5p4I3KKKBPLSyzpIqiZWnO5zW3fdFOnkhn032QFum+9naMWI7IXHg7nhfOc533eh6L+XREECFk5QoPhslClS+Or1Rl8pTIJ9zml4xO0aNQCRT19rPjdIsgcTEjBrILUwVRQFECE0GSKDAeyWjkUQpxMZSMvq6YhXVCni0tVqc5lGAwxSUkZMXkDZn3FYxMUajRsxZt5kC8tszXMGiEWU8cVr6rqGk8muxjeMwGcIGfx02fK8olpKBt/FZQsOH/K7J5gDVYo++ASU1KrIxmPMFw0bDHfGxsfLB573l84+GTq/siLXPI4v7U1StDWdj6nsfGqQNecxk1OvoSmZvX169ug8PhZ2sEQZWzN8vqhQjQzV3m3oxMKjU2hguYWyNMbQsKWViixvNRzudzobLX+RlZDY3qmUskT6HRx5BOsqByPCQbGv2v0rv7QefzfaIcHZPalcqpichYV9vRBXnvnfl57F5vf3rV/p6ML8nv7zLySksRUmr6SitCFsGVR2OOzCrs3oc7mjieQHIH0KdrpRXK3D6oXXXtimytYvejeq7baQTT5liaP+dqWpFyskF/bICQqUxDiIos1utQbSBC51+IlBOGEcu9GpebjF9AGtkPNWztg3NwJqTc/Q9nryW7MF3mwDJPpRH7rUJRA10ZGPlMwMKQv7TdD0aPur5Vztu/Yu11sAxArKJlr5Tpt94w3uFdGNIx/VI1P2slYxAueNF4JSkyv16ZkSKUxhBhH52QsLzbqdm/fwwc4NgVaHYtmFw6WU+vbAMx1fGyOvOIIm5oSbqlUN3PU6uxslTEHt0+jmfcK8bwNRBPT+xLbUkhq9+xjUsDCqg6DHQhE/g3A/T/5wyMfBf4Uucud3mp14BPUun1BhZMBDLZ+JYAVMv9XGL5ZUoT0d2yaAtugZ/ysxuMDLeMPGVe3QLLoFv0CXN127R+PEskAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="introduction-to-grpc" title="" src="/static/9d0c125599cba7de0200e654e16530cf/08c0b/introduction-to-grpc-1.png" srcset="/static/9d0c125599cba7de0200e654e16530cf/772e8/introduction-to-grpc-1.png 200w, /static/9d0c125599cba7de0200e654e16530cf/e17e5/introduction-to-grpc-1.png 400w, /static/9d0c125599cba7de0200e654e16530cf/08c0b/introduction-to-grpc-1.png 552w" sizes="(max-width: 552px) 100vw, 552px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>Source: <a href="https://grpc.io/docs/what-is-grpc/introduction/" target="_blank">https://grpc.io/docs/what-is-grpc/introduction/</a></i></center <p>RPC has been around for some time; however, gRPC approaches RPC in a much cleaner way. Another term that you’d come across when learning gRPC is <a href="https://en.wikipedia.org/wiki/Inter-process_communication">inter-process communication</a> or IPCs. GRPC is mainly built to cater to inter-process communication that lets you connect, invoke, and operate, an excellent choice for micro-services like applications. In distributed computing realm, inter-process communication or, in short, IPC refers to passing messages (synchronously or asynchronously) where any application or a node can act as both client and a server.</p> <h2>Protocol Buffers</h2> <p>They are a language-agnostic way to define what your service does. These are commonly known as “IDLs” or <a href="https://en.wikipedia.org/wiki/Interface_description_language">Interface Definition Language</a>(s).</p> <p>So the steps are,</p> <ol> <li>You write the messages. These messages have statically typed fields.</li> <li>You write your services by defining what comes in, what goes out.</li> <li>Compile the proto files and generate the client libraries for your application.</li> </ol> <p>It’s also worth mentioning that Protocol Buffers are not the only way to define our IDLs. There are other formats like <a href="https://google.github.io/flatbuffers/">FlatBuffers</a>, <a href="https://github.com/microsoft/bond">Bond</a> etc.</p> <p>Let’s say we want to model a book shop which can be queried to get a list of books available. This is what a protocol buffer would like:</p> <div class="gatsby-highlight" data-language="protobuf"><pre class="language-protobuf"><code class="language-protobuf"><span class="token keyword">syntax</span> <span class="token operator">=</span> <span class="token string">"proto3"</span><span class="token punctuation">;</span> <span class="token keyword">message</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span> <span class="token builtin">string</span> title <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token builtin">string</span> author <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token builtin">int32</span> page_count <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token keyword">optional</span> <span class="token builtin">string</span> language <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">message</span> <span class="token class-name">GetBookListRequest</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">message</span> <span class="token class-name">GetBookListResponse</span> <span class="token punctuation">{</span> <span class="token keyword">repeated</span> <span class="token positional-class-name class-name">Book</span> books <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">service</span> <span class="token class-name">Inventory</span> <span class="token punctuation">{</span> <span class="token keyword">rpc</span> <span class="token function">GetBookList</span><span class="token punctuation">(</span><span class="token class-name">GetBookListRequest</span><span class="token punctuation">)</span> <span class="token keyword">returns</span> <span class="token punctuation">(</span><span class="token class-name">GetBookListResponse</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The above is an example that we will use throughout this blog series. Don’t worry if you don’t understand what’s going on here. We will go in-depth in upcoming posts.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 65.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACWElEQVR42qWTS28SURiG+QeauHBTTUtBWgIzMMwMMDMw3MFLwRijS/dQitDI/VKktT/ERNM0rkyqib/ErqR7Sbk0QRry+p2hXVjrQp3kmXPOfOd7z3m/c8Y0n8/B+N/nUsfEBkcfP6HbbqCxt4tq/TWqDaLZQ6HSNShfjK+j3t5D8VULR5+/GMKm2ewHKuUWzKs2mN0Slu95sGwTjTauqkhqGmx2EUsWAXetv7OyJuHmbSvqrV2cn89gGg1PUWt24fBEoOoPoUVS8GpxKGoUbwsZHJYySMcTFA9D8Ebgln9F9MdgdfjQ2nmDyXgM05AEq7U2OG8MWvIp/NHHkNQk3JRs5YOwuXTwog5ODF0Lm2del9GgHY5Ho4VgudLEukeHkngCP+GLpCEHUkYCE3NLITj/RrBSbWHNFYCsP4KgJCGoKfgUsk7IahyCHP6joMcXgcV+RZBZtrtUSk7AQzVhSIToixJh8LR7p0B4glRL1rISLMpgcYVxa0WkQ+lhPGaCp2S5tgPem4BG9VMJfzgNhVolmiHrSYiB+5D0B5A02jktyg5CoMV4Emy/SKPzPIROdx+j0XAhWKt3wElhqFQ7lUT8dNp+LQmZEhleEpGVuIGX8BnjmNHferaB7IaObm8fQyY4It+l7Rru0J1y0DVwyDHYyR7vJmtUV4eLbPIB2DmV0Aw4irFvnDuIVWcQN5Y4+iE6mEzo2rDXu/cHyG2+xGa+iFyuYPSzBkWjb8BiRPYidjlnq7CNfL6Eg8MPODubwDSdTjEYfEe//w3Hx1//iZOTPmkMwLR+AhsbYDkTTXznAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="introduction-to-grpc-2" title="" src="/static/e0537d1ed9e8550e23a6f22864f64f93/5a190/introduction-to-grpc-2.png" srcset="/static/e0537d1ed9e8550e23a6f22864f64f93/772e8/introduction-to-grpc-2.png 200w, /static/e0537d1ed9e8550e23a6f22864f64f93/e17e5/introduction-to-grpc-2.png 400w, /static/e0537d1ed9e8550e23a6f22864f64f93/5a190/introduction-to-grpc-2.png 800w, /static/e0537d1ed9e8550e23a6f22864f64f93/c1b63/introduction-to-grpc-2.png 1200w, /static/e0537d1ed9e8550e23a6f22864f64f93/29007/introduction-to-grpc-2.png 1600w, /static/e0537d1ed9e8550e23a6f22864f64f93/a751b/introduction-to-grpc-2.png 3289w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <ul> <li>The first line specifies the Protobuf version we will be using. It will be set to <code class="language-text">proto2</code> if you don’t specify explicitly.</li> <li>The Book is a <code class="language-text">message</code> definition with some statically typed fields such as title, author etc.</li> <li><code class="language-text">GetBookListRequest</code> and <code class="language-text">GetBookListResponse</code> are also messages composed of the Book type we defined above.</li> <li>Inventory is a <code class="language-text">service</code> that says what methods we expose to the remotely invoked clients.</li> </ul> <p>There are many advantages of using Protobufs compared to something like JSON. Once you write the definitions for your service, you can share them with other teams and use them to generate stubs/code that can interact with your service.</p> <p>Another advantage is that Protobufs are binary encoded. The payload is smaller than JSON, which means it would be efficient to send. This also means that it will use fewer CPU cycles to serialize/deserialize the messages.</p> <h2>gRPC Server &#x26; Client</h2> <p>Now that we have the Protobuf definitions for our service, we could generate the Server-side and Client-side implementations using the <a href="https://grpc.io/docs/protoc-installation/">Protoc compiler</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 45.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABXklEQVR42nWS2VaDMBCG+/6P5L3HC71Q79RSlsoaQiENS0jC7yTUpT0454TALN9s7HAji9H0WK50s5pgnf6v32KxWH0bjt1P0KzBGEOZp/5uW0FRgCX4vhD4KM4XEMjWoSxLsCJHXXNMk7oGLuSllMY4KkxqXu9phrULtFnwGAk8xcLDDH07wOpLMdNExRif+Aqojb1paXVoOMdbEGMfHSGEwJa4xMbaX6DdAH5L0zQ4nbgHu1a3xHjgpUJjDDhvyLmF7HKCK1jdwc4tLWJwY4Si+Rpainu3ZiQ72bSEVRV6ySlhS+cErTV2wzAgDENkWY70/Y4CBCR7hiweMHQJeprRMUuR0gLkOGA4Z+jZC9lCGH6PPHnFZ1YiSWJIKbFTSiGKIhwOB/T94OdRsRplVVG73G+cswq8ZqSvfCWM2u+6tX0HCYKAGDEcy8/Qte3Of+L+zcVu2de5uVh7WcoXB6a7lYepElAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="introduction-to-grpc" title="" src="/static/28b7f79564ae18cd72558ba5a93cde92/5a190/introduction-to-grpc-3.png" srcset="/static/28b7f79564ae18cd72558ba5a93cde92/772e8/introduction-to-grpc-3.png 200w, /static/28b7f79564ae18cd72558ba5a93cde92/e17e5/introduction-to-grpc-3.png 400w, /static/28b7f79564ae18cd72558ba5a93cde92/5a190/introduction-to-grpc-3.png 800w, /static/28b7f79564ae18cd72558ba5a93cde92/c1b63/introduction-to-grpc-3.png 1200w, /static/28b7f79564ae18cd72558ba5a93cde92/29007/introduction-to-grpc-3.png 1600w, /static/28b7f79564ae18cd72558ba5a93cde92/ab387/introduction-to-grpc-3.png 2097w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <ol> <li>We first create the definition of the service with a <code class="language-text">.proto</code> file</li> <li>We then generate the server-side code in our preferred language (Go, C#, Java etc.). This code includes the boilerplate code to serialize, deserialize, functions for receiving and responding to messages.</li> <li>We then generate the client-side code in our preferred language (doesn’t have to be the same language we chose for the server). This includes methods that we can invoke on our server with additional code to serialize, deserialize messages.</li> <li>Depending on which gRPC mode we choose, the client-server communication happens over an HTTP/2 connection. We will discuss more on these modes in the next section.</li> </ol> <h2>gRPC Modes</h2> <p>There are 4 modes of gRPC communication styles. Following are their brief introductions. Feel free to go more in-depth by reading through the official <a href="https://grpc.io/docs/what-is-grpc/core-concepts/">docs</a>.</p> <ol> <li>Unary RPC - More like our traditional APIs where we send a request and receive a single response.</li> <li>Server Streaming RPC - Client sends a request and reads until the server stops sending messages via a stream.</li> <li>Client Streaming - Reverse of the above, the client sends messages through the stream and waits for the server to read and return a response.</li> <li>Bidirectional streaming RPC - Pretty much both (2) &#x26; (3) combined - both client and server streams messages both ways.</li> </ol> <h2>Pros and Cons</h2> <p>Any technology comes with a set of advantages and disadvantages. Whether you choose to use gRPC might depend on some of these factors.</p> <p><strong>Pros</strong></p> <ul> <li>Efficient for inter-process communication with all the good stuff that comes with HTTP/2.</li> <li>Well defined interfaces to be able to communicate also while supporting polyglot development.</li> <li>Code-generation of client and server stubs with strong types.</li> </ul> <p><strong>Cons</strong></p> <ul> <li>It may not be suitable for external-facing services since most web browsers’ support is limited.</li> <li>Changing the service definitions might require rework and regeneration of code.</li> <li>Could be a steeper learning curve compared to other RESTful or GQL like architectural styles.</li> </ul> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h2>Conclusion</h2> <p>In this article, we looked at gRPC from a high level. In the next article, we will look at how we can put these into action and generate a gRPC service with Go. Feel free to let me know any feedback or questions. Thanks for reading ✌️</p> <h2>References</h2> <ol> <li><a href="https://developers.google.com/protocol-buffers/docs/proto3">https://developers.google.com/protocol-buffers/docs/proto3</a></li> <li><a href="https://www.oreilly.com/library/view/grpc-up-and/9781492058328/">https://www.oreilly.com/library/view/grpc-up-and/9781492058328/</a></li> <li><a href="https://github.com/grpc-ecosystem/awesome-grpc">https://github.com/grpc-ecosystem/awesome-grpc</a>you</li> </ol><![CDATA[Automating Raspberry Pi K3s provisioning with Ansible]]>https://www.sahansera.dev/k3s-rapsberrypi-ansible-automation/https://www.sahansera.dev/k3s-rapsberrypi-ansible-automation/Wed, 15 Dec 2021 00:00:00 GMT<p>In this article, we will be looking at how we can automate most of the steps involved in installing a Raspberry Pi+K3s cluster by using Ansible.</p> <p>I explained how to set up a Kubernetes cluster on a Raspberry Pi in my previous article. In case you missed it, you can find it from the following link:</p> <p>🔗 <strong><a href="https://sahansera.dev/building-your-own-private-kubernetes-cluster-on-a-raspberry-pi-4-with-k3s/">Building your own private Kubernetes cluster on a Raspberry PI 4 with K3S</a></strong></p> <p>If you have a fresh bunch of Raspberry Pis, you might want to follow Step 0 through to Step 8 in the above. However, it’s a one-off setup, so please proceed to the following sections if you already have done them.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>In a nutshell, this is what we are trying to automate.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9UlEQVR42oVTQU8TURDe/6DHHkxMvGg0EiUSjf4ADxw8eeHg0RsHz/IDuAjeUGuC8WAoCPGAJk0pB6MYSgK2uglVEtaKdOku3e6+3ffmzXOmS0trA052X+btzPfeN/PNWsYY3/fL5TKt5OuOIeKgTw7lpCuZRW8QBI7jhGEIAKpjfb5iXwMcek3fa1IINDBYStk96WTjhEiEy8W3y6sLe3UHmQRaSkmNBhCB1wFQD8+6/+vBwxuj986tV/K0lUrSzYqzQPJj+tBCiGq16rouV2R05fPm1MjFZ9czxew8gxNpgZK1BLe1+YGmKrTqwKkuAh91DLhC+8vX6eHzL66eWc3maKsIHEv5ck/lJiaXHk1kd6OfMaYlEiSO43/4r70rfJh5k/aeTrdC0Es7/sHIsLh2ufjdqYSAIopE3Gq1iO1A/djlxeAkkTnPPF74OP56ZXYfagLaaUjaEO3+fhvP9fZr9WOwVioCzB7g5B9sJLqT1kcb29+C0F9cmc3ln+/+3uaaQbV15jjBoFfvLphpcNS4Xm3s/pW7t8+WtgrcbclSSU4dEJkQBG40GrZtB80mXV4qrM3cvDB3J/N++tWRVCn4P/Ol+WbH3nlya+jppUxpkYeEJtbqTvLpRjks9ca39fwnjTrdWr1/0ulGR6RqQ3tmjDF/AcP6Q2zRhMawAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="k3s-rapsberrypi-ansible-automation-0.png" title="" src="/static/d39f0323d272251c6acceddf6c3761df/5a190/k3s-rapsberrypi-ansible-automation-0.png" srcset="/static/d39f0323d272251c6acceddf6c3761df/772e8/k3s-rapsberrypi-ansible-automation-0.png 200w, /static/d39f0323d272251c6acceddf6c3761df/e17e5/k3s-rapsberrypi-ansible-automation-0.png 400w, /static/d39f0323d272251c6acceddf6c3761df/5a190/k3s-rapsberrypi-ansible-automation-0.png 800w, /static/d39f0323d272251c6acceddf6c3761df/c1b63/k3s-rapsberrypi-ansible-automation-0.png 1200w, /static/d39f0323d272251c6acceddf6c3761df/d0fa6/k3s-rapsberrypi-ansible-automation-0.png 1263w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Step 1 - Installing Ansible</h3> <p>So what do we need to get started? You need one or more RPis with Raspbian installed (or an OS <a href="https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/#operating-systems">supported by K3s</a>). We can get to our desired state by using an Ansible playbook.</p> <p><a href="https://www.ansible.com/">Ansible</a> is an industry-leading IT automation tool to provision and manage your infrastructure in a declarative form such YAML.</p> <p>The easiest way to get the steps to installing Ansible would be to head over to the <a href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html">official docs</a>. For most OSs, it can be just installed via <code class="language-text">pip</code>.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">python <span class="token parameter variable">-m</span> pip <span class="token function">install</span> <span class="token parameter variable">--user</span> ansible</code></pre></div> <blockquote> <p>💡 Note: Use the latest version of Python installed on your machine. In my case, I have it set to <code class="language-text">python3</code> since Mac OS comes with an older version of Python installed.</p> </blockquote> <p>Depending on your system and the internet connection, it will a bit of take time to install Ansible. Sometimes even if you installed it, it would still say that it can’t find <code class="language-text">ansible</code> command. If that’s the case, feel free to follow this <a href="https://stackoverflow.com/questions/63177609/zsh-command-not-found-ansible-after-pip-installing">link</a>. If you are on a Mac, you could also run a <code class="language-text">brew install ansible</code> to install ansible.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABl0lEQVR42q2P24vaQBTG86ji/bqJRmMwNy/xUhAXwW2qLdZCW2hXwrIi4pv4Itk/es3+CTubr2dmq9D3Dvw4M99888050mHvn3zfDzbEw2Yb7La7YEv1eDi+czwG+91e6Lxy/LUfrH+vr9z/un968B9Pq7vVjTRffsH8x094n5bR98VnfPu6wmK+wHQ6xWw2g/fRw3g8RrfbRbvdRq/XQ9/tw+25cBwHRsuIHNuB6w4wmUzuJKWmvjV0ndlO5/XDcMgsy2SmZbFOp8M0TWP0iNXrdVatVpksy0ytq0xRFNZoNIReKpVeL3vyeJIiy2hqGkzTjNx+H4PBAKqqggwgAygEFCD2vHKda5czEdHH4lwoFCjwr0nXdXFh2zZarZbQLMsSZ8MwBORBrVYD9zWbzUuN+J6aiCqViifx37iJRotGo5EIuXTHO6VxQGMhmUwik8kglUpdSafTnIjfZbNZiEAyn3O5XEjBZwoJSQzL5XKYz+fDYrEooKAwFouFiUQijMfj/0DameoLhT7Tu1uJlkHY/wGeE/sDmKq/EJLuWG8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="k3s-rapsberrypi-ansible-automation-1.png" title="" src="/static/70581b905961f6da018dc7d0db07d5c3/5a190/k3s-rapsberrypi-ansible-automation-1.png" srcset="/static/70581b905961f6da018dc7d0db07d5c3/772e8/k3s-rapsberrypi-ansible-automation-1.png 200w, /static/70581b905961f6da018dc7d0db07d5c3/e17e5/k3s-rapsberrypi-ansible-automation-1.png 400w, /static/70581b905961f6da018dc7d0db07d5c3/5a190/k3s-rapsberrypi-ansible-automation-1.png 800w, /static/70581b905961f6da018dc7d0db07d5c3/c1b63/k3s-rapsberrypi-ansible-automation-1.png 1200w, /static/70581b905961f6da018dc7d0db07d5c3/6aacb/k3s-rapsberrypi-ansible-automation-1.png 1347w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Step 2 - K3s Ansible Playbook</h3> <p>Without writing everything from the ground up, we will be using the <a href="https://github.com/k3s-io/k3s-ansible">k3s-ansible</a> repo.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone [email protected]:k3s-io/k3s-ansible.git</code></pre></div> <p>Once you have cloned the repo, you can run the following command to create a new playbook for your RPi cluster provisioning. Make sure to replace <code class="language-text">my-cluster</code> with the desired name for your cluster. In my case, I called it <code class="language-text">rpi-cluster</code></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">cp</span> <span class="token parameter variable">-R</span> inventory/sample inventory/my-cluster</code></pre></div> <p>Next, we need to map out the nodes and master them in the <code class="language-text">inventory/my-cluster/hosts.ini</code> file. Update the <code class="language-text">[master]</code> and <code class="language-text">[node]</code> attributes accordingly.</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">[master] 10.0.0.100 [node] 10.0.0.101 [k3s_cluster:children] master node</code></pre></div> <p>Under <code class="language-text">[node]</code> attribute you could even specify a range of IPs if they are consecutive like so.</p> <div class="gatsby-highlight" data-language="txt"><pre class="language-txt"><code class="language-txt">[node] 10.0.0.[101:105]</code></pre></div> <p>Check the <code class="language-text">inventory/my-cluster/group_vars/all.yml</code> to set variables such as <code class="language-text">k3s_version</code> and <code class="language-text">ansible_user</code>, depending on your environment.</p> <h3>Step 3 - Running the Ansible Playbook</h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">ansible-playbook site.yml <span class="token parameter variable">-i</span> inventory/rpi-cluster/hosts.ini --ask-pass</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABo0lEQVR42oWQy08iQRDG+zKSkBgIr+GxI8i8ewgwPSJ3V44CE00Mj0wIMe7Gy+7Vjv+snhdi5ODBZsrqiZC4Hjx8+SqVql9/XeTX4vrPdD59mE/n91EU8eViyRfRgt/e3PK733eJZG82mfFoFvHJ9YRfhpc8HIV8dDHi44vx/Xg4fgjDq7/D4fCQnP88Ww8GAzgJTmLmM+j3+xCwAGQtHfvgUQ8s0wLbssGxHaAuTWrTMMHQjdh1KM71njWtqJN2u/3PcRzQdf2t0WgITdNEtVoVqqqKcrks1LIqKpWKyOfzIpfLfdJH761UKkGz2VylUimDMMbWlFLodDoxClqtFpimKQcAQYDQRHJJqlgsfvJCoRDXaj/AsqznBBgEwUoCPc8TCNxi4i0m3WLKLS7thcuJdvXOESh/BbZtrxPgaa+39hCI0Bjh0O12ZJ2kRDDU6/V9QjzDF0dwLIH7hLrrPBpd/9U06Atz3c3RkbbBVzd4m002m937TplM5n9/wVu+IvQJHzgm5OCAKukMSytpP60ovqIQnxCCriS+q78RQ3k4q7wD4ly+adTugGEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="k3s-rapsberrypi-ansible-automation-2.png" title="" src="/static/bb872c572dedb1a2894597c50e238a37/5a190/k3s-rapsberrypi-ansible-automation-2.png" srcset="/static/bb872c572dedb1a2894597c50e238a37/772e8/k3s-rapsberrypi-ansible-automation-2.png 200w, /static/bb872c572dedb1a2894597c50e238a37/e17e5/k3s-rapsberrypi-ansible-automation-2.png 400w, /static/bb872c572dedb1a2894597c50e238a37/5a190/k3s-rapsberrypi-ansible-automation-2.png 800w, /static/bb872c572dedb1a2894597c50e238a37/c1b63/k3s-rapsberrypi-ansible-automation-2.png 1200w, /static/bb872c572dedb1a2894597c50e238a37/bed7a/k3s-rapsberrypi-ansible-automation-2.png 1437w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You can now SSH into the Pi master or nodes and run <code class="language-text">kubectl</code> commands within it.</p> <p>if you want to uninstall everything that got installed as part of the playbook:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">ansible-playbook reset.yml <span class="token parameter variable">-i</span> inventory/rpi-cluster/hosts.ini --ask-pass</code></pre></div> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>This article looked at how we can automate provisioning our Raspberry Pi with Ansible. If you are new to DevOps and want to automate provisioning systems, this is a great start. A special shoutout to <a href="https://github.com/itwars">@itwars</a> for putting this together and doing a lot of heavy lifting creating these scripts.</p> <h3>References</h3> <ul> <li><a href="https://github.com/k3s-io/k3s-ansible">https://github.com/k3s-io/k3s-ansible</a></li> </ul><![CDATA[Go for .NET Developers]]>https://www.sahansera.dev/go-for-dotnet-developers/https://www.sahansera.dev/go-for-dotnet-developers/Fri, 10 Dec 2021 00:00:00 GMT<p>This blog post kind of different from my usual ones since this is based on a recent talk I delivered at the <a href="https://twitter.com/adldnug">Adelaide .NET User Group</a> on the 09 Dec 2021.</p> <p>The slides can be found at my SpeakerDeck <a href="https://speakerdeck.com/sahansera/go-for-net-developers">profile</a>.</p> <iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/0650a28ea55149efaa20cda3a5d66ffb" title="Go for .NET Developers" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 314;" data-ratio="1.78343949044586"></iframe> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>The GitHub repo for the demo code can be found <a href="https://github.com/sahansera/adnug-go">here</a></p> <p>The talk is structured in to 12 sections,</p> <ol> <li><a href="https://github.com/sahansera/adnug-go/tree/main/1-getting-started">Getting Started</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/2-variables">Variables</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/3-modules">Modules and Packages</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/4-slices-loops">Slices and Loops</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/5-conditionals">Conditionals</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/6-functions">Functions</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/7-pointers">Pointers</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/8-structs">Structs</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/9-unit-testing">Unit Testing</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/10-http-service">A Simple HTTP Service</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/11-http-service-json">Adding JSON to our HTTP Service</a></li> <li><a href="https://github.com/sahansera/adnug-go/tree/main/12-multi-arch-deployments">Multi Arch Deployments with GitHub Actions</a></li> </ol> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>Hope this will help if you are planning to learn Go as a .NET developer. Until next time 👋</p><![CDATA[A closer look at commands and args in Kubernetes Pods]]>https://www.sahansera.dev/closer-look-at-kubernetes-pod-commands-args/https://www.sahansera.dev/closer-look-at-kubernetes-pod-commands-args/Mon, 13 Sep 2021 00:00:00 GMT<p>In my previous blog post, we looked at how commands and args work in Docker. If you missed that one, the link is down below.</p> <ul> <li><a href="https://sahansera.dev/closer-look-at-docker-commands-args/">A closer look at commands and args in Docker containers</a></li> </ul> <p>Today we will be looking at the same but in Kubernetes. To get started, we will be building on top of the following Dockerfile.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> debian</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"printf"</span>, <span class="token string">"Hello from %s\n"</span>]</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"Name Printer"</span>]</span></code></pre></div> <p>This would simply print out <code class="language-text">Hello from Name Printer</code> when you run it.</p> <p>As a time-saver, I have already packaged up the image. You can have look at the image layers in <a href="https://hub.docker.com/layers/sahan/name-printer/v1/images/sha256-99d5313101544ceff84d499f7c476bf63ee85f895af4d3b26040048acf6763b6?context=repo">Docker Hub</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 46%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFUlEQVR42pVSi07DMBDr//8cnwAqrGwrfeSdNGm9c6oNNDE0IllNkzuffZem/xrw+tai+zzi/aPDuR8wjAopLQghPgXGVsSIhgdRfrQ2mJWBc6FeOucRY/oOfgDvI4yxGCW3HQIakpWy4nTqceiOmGaFIETjNMs3IufyEMyz1ld3bXfGy8GjYRVeppREUZSKHj4EbNv2NNZ1rcVNzKJQ1JDQWCvSaVvDCel/FgmZX0rZLS9CaK2TYYzVsnUOSusaxHMtPXJyxj4TdHNPyJ5T2M1ylCDaDWEfSqz9y1Is1xakhQMIFVTyc9E27xlfLTNpUkrUzeAzom3uqeY++bfF4TDnRnh9Atzv//sZVS9L/nPSBAVdOS4gm76ip07ToQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-commands-and-args-in-kubernetes-pods-1.png" title="" src="/static/dc0eb3947e71396ac812b0bc6f5e5b26/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-1.png" srcset="/static/dc0eb3947e71396ac812b0bc6f5e5b26/772e8/closer-look-at-commands-and-args-in-kubernetes-pods-1.png 200w, /static/dc0eb3947e71396ac812b0bc6f5e5b26/e17e5/closer-look-at-commands-and-args-in-kubernetes-pods-1.png 400w, /static/dc0eb3947e71396ac812b0bc6f5e5b26/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-1.png 800w, /static/dc0eb3947e71396ac812b0bc6f5e5b26/73b94/closer-look-at-commands-and-args-in-kubernetes-pods-1.png 1004w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You could run this with Docker like so.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run sahan/name-printer:latest</code></pre></div> <p>That should give you <code class="language-text">Hello from Name Printer</code> as the output.</p> <p>Now let’s do the same thing in Kubernetes. I’m a big fan of imperative commands in k8s. You can simple create resources without having to write definition files for simple things.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl run <span class="token builtin class-name">test</span> <span class="token parameter variable">--image</span><span class="token operator">=</span>sahan/name-printer:latest</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 31%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABG0lEQVR42pXOy07CQBQG4E4X2Epv0KZCQ0ojFnqhFnTBoqZxUTKJDV3wSL6PsuIZDNH4PmRsjqcXkbhzki9nTmbOP8NxuIrHh1VG022SpBtKafEjyxqU5rU8L1DenmNPiyJJ0w3artdPK65dl/fh7C2MXHDcaTlf3sFsHkGwWIIXL8D1PfBCH/wgBM8PatFtDCHeiaIYbtzpl22PYTAYHjBLqAKVsTM5jEY2DB3naF1PWM+ymGQYrIskTWWC2GGCICKBEUIYzpxgf8QKWN+xynWgpvU+ZEWBrqqWqmGAZpog6zpI/T7o5hXwPH+Cg3+VVcWzT13X60ARPaM9ekW7c4QjOxxonO9/vaA9qjIuqkCCOu13G81L/1VlkG+fUGLzjack7gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-commands-and-args-in-kubernetes-pods-2.png" title="" src="/static/749c6e7be6c77e62125a99675f1ecaac/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-2.png" srcset="/static/749c6e7be6c77e62125a99675f1ecaac/772e8/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 200w, /static/749c6e7be6c77e62125a99675f1ecaac/e17e5/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 400w, /static/749c6e7be6c77e62125a99675f1ecaac/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 800w, /static/749c6e7be6c77e62125a99675f1ecaac/c1b63/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 1200w, /static/749c6e7be6c77e62125a99675f1ecaac/29007/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 1600w, /static/749c6e7be6c77e62125a99675f1ecaac/fa60d/closer-look-at-commands-and-args-in-kubernetes-pods-2.png 1792w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>💡 You can quickly delete the pod with <code class="language-text">kubectl delete pod test --grace-period=0 --force</code> command.</p> </blockquote> <p>Let’s export the pod definition to a file.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl run <span class="token builtin class-name">test</span> <span class="token parameter variable">--image</span><span class="token operator">=</span>sahan/name-printer:latest --dry-run<span class="token operator">=</span>client <span class="token parameter variable">-o</span> yaml <span class="token operator">></span> pod.yml</code></pre></div> <p>In the pod definition file, we will add a new field called <code class="language-text">args</code> under <code class="language-text">spec.containers.args</code> like so:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">creationTimestamp</span><span class="token punctuation">:</span> <span class="token null important">null</span> <span class="token key atrule">labels</span><span class="token punctuation">:</span> <span class="token key atrule">run</span><span class="token punctuation">:</span> test <span class="token key atrule">name</span><span class="token punctuation">:</span> test <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">containers</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> sahan/name<span class="token punctuation">-</span>printer<span class="token punctuation">:</span>v1 <span class="token key atrule">name</span><span class="token punctuation">:</span> test <span class="token key atrule">args</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"Kubernetes"</span><span class="token punctuation">]</span> <span class="token key atrule">resources</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token key atrule">dnsPolicy</span><span class="token punctuation">:</span> ClusterFirst <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always <span class="token key atrule">status</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre></div> <p>Running and building this gives you <code class="language-text">Hello from Kubernetes</code> on the console</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl create <span class="token parameter variable">-f</span> pod.yml</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABSUlEQVR42q2Rz07CQBDGt5RiCglUClu2pWBEEuVia43KQRMIbUn1CbzoG+hrGP+9GtkjetDngM04u1YxeDNu8ss3883Ol02WEEJ0wqIyY6yMtYLlqmr0GVv1ZG0WRZFC5cgT7x9cX14czQ/jk1kSpzyZZnwcJ6hTnmYZH00mPE7QT1OeSk9qOuVZds5HozEPw3A2HA7nQRBcqUBGnfvBbh+o6yzdbhe29gbg9/vg9XagiX2j7X8qa4HbYUAZBZd54HltsG0bSiVDmKYJyJ0KtOr1R7+7DZZlLWq0KZxeTzT8jtj0PFGhVFQaTbFh28Jp+6JarQlcEZqmKc1ZIIDegwrUdf0ZkcZSKxRAk7XUYhF0w1AUkKJU9OXyGiIPfFKBeOkWm3fkBXn9hpDf/JyvkHtvmHND8oOfSo6R0z9ylu+35Pu+QrV/gnwA7cNzuMIR5qoAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-commands-and-args-in-kubernetes-pods-3.png" title="" src="/static/6b83d53788ba44f569e324043649ccb5/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-3.png" srcset="/static/6b83d53788ba44f569e324043649ccb5/772e8/closer-look-at-commands-and-args-in-kubernetes-pods-3.png 200w, /static/6b83d53788ba44f569e324043649ccb5/e17e5/closer-look-at-commands-and-args-in-kubernetes-pods-3.png 400w, /static/6b83d53788ba44f569e324043649ccb5/5a190/closer-look-at-commands-and-args-in-kubernetes-pods-3.png 800w, /static/6b83d53788ba44f569e324043649ccb5/c1b63/closer-look-at-commands-and-args-in-kubernetes-pods-3.png 1200w, /static/6b83d53788ba44f569e324043649ccb5/e40ed/closer-look-at-commands-and-args-in-kubernetes-pods-3.png 1378w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>This makes sense because we didn’t change/override the <code class="language-text">ENTRYPOINT</code>. Whatever we define as <code class="language-text">args</code> simply gets appended to that. So how can we change the <code class="language-text">ENTRYPOINT</code>’s behaviour?</p> <p>We can use the <code class="language-text">command</code> attribute to define whichever command we want to run in it.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">creationTimestamp</span><span class="token punctuation">:</span> <span class="token null important">null</span> <span class="token key atrule">labels</span><span class="token punctuation">:</span> <span class="token key atrule">run</span><span class="token punctuation">:</span> test <span class="token key atrule">name</span><span class="token punctuation">:</span> test <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">containers</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> sahan/name<span class="token punctuation">-</span>printer<span class="token punctuation">:</span>latest <span class="token key atrule">name</span><span class="token punctuation">:</span> test <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"date"</span><span class="token punctuation">]</span> <span class="token key atrule">args</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"-u"</span><span class="token punctuation">]</span> <span class="token key atrule">resources</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token key atrule">dnsPolicy</span><span class="token punctuation">:</span> ClusterFirst <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always <span class="token key atrule">status</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre></div> <p>Now when we run the new pod definition it should print out the current date-time in UTC format. As you can see it’s easy to override the <code class="language-text">ENTRYPOINT</code>’s behaviour in the container.</p> <p>One thing to take note of is, remember that we had <code class="language-text">bash</code> as <code class="language-text">CMD</code> set in our that why we didn’t have to pass in that. If you have a container that and you want to invoke a command, you can simply prepend <code class="language-text">/bin/bash -c</code> to the command you want to run.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>That’s it for this one - hope it’s useful to understand how commands and args work in pods.</p><![CDATA[Building your own private Kubernetes cluster on a Raspberry PI 4 with K3S]]>https://www.sahansera.dev/building-your-own-private-kubernetes-cluster-on-a-raspberry-pi-4-with-k3s/https://www.sahansera.dev/building-your-own-private-kubernetes-cluster-on-a-raspberry-pi-4-with-k3s/Sat, 14 Aug 2021 00:00:00 GMT<p>In this article, we will look at setting up your own private Kubernetes cluster on a Raspberry Pi using K3S in your home Wi-Fi network! Our setup will be pretty simple - single master and a single worker node. You can always add more nodes if you like.</p> <p>Here’s a diagram to give you an idea of what we will be building today.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 92%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRElEQVR42p1US2sUQRDePyYegr9DvHrR/AEPouYkgiAiiKAnUUwOLgleYoyPgKgwuq8JbvaR7BhdJ7vznn7NZ9VsJmR0d7NYUExPd/XXVV993ZV+v4+joxGM0ZBCQEo51YWQQKbQGITYanlgyzL8YxVFwcoAnd8KZlrEXyYo2IvSmesVrQR+hhnubob40PRhZIwgjBBFZQ/DkLIUE9Dj71RAoyTeDzRub3lodIN8UhtD5WQlNzTHwGxMwewMtUYcJ2jvdWijmRnIoAsB5osUrJUqkZypCJmOpwLOLTmOY2qKgc5BuaMppAaSzgPI/ccF3OIZRmkCcThEa2MHrxvBiRZ+9C0cHnwr0lscMJECX67dwerSJWzcf4kURH4QIIwSpEKWSvZ9/+ySU60wWH+Dj1euw7Ma+aRR+lgmaQnQdd2zM+RTO70edu1dOMMh05Ubl5ckST7mGOb6YOAQmEKapmB1sPMN+uVrOGODUWwmgJZlYfvdW7Qa9ZOTGMBxHIxHI1r/ioePnuDc+SVcXV6G53n5ekI+dMfYtgWqNYMa6bkS0SSTHoUx3NiUADkTLlVRJi9W17Cycgv1ei3PrKCBzfND7La7FC+oKSSV3rMqXl2+iedVO6+Yb0Vx1YpNzF+z2cypKAAL4/+U1ML7qMsS1o17WL9wEfbTKphu7jJneJp8HjOnxeszV4dudx/t6iYC4uv0uzRtI2c8H5BK+L7Xxs7nT9TFQYmb/wIMqLwhycW2bYzHYyg1kQXzx1/mpfDi1Zkn7D+3GWrHuqGrSwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-1" title="" src="/static/d23110ad26edbca2213e84fadd59f2d2/5a190/building-your-own-private-kubernetes-cluster-1.png" srcset="/static/d23110ad26edbca2213e84fadd59f2d2/772e8/building-your-own-private-kubernetes-cluster-1.png 200w, /static/d23110ad26edbca2213e84fadd59f2d2/e17e5/building-your-own-private-kubernetes-cluster-1.png 400w, /static/d23110ad26edbca2213e84fadd59f2d2/5a190/building-your-own-private-kubernetes-cluster-1.png 800w, /static/d23110ad26edbca2213e84fadd59f2d2/eb3fa/building-your-own-private-kubernetes-cluster-1.png 1026w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <center style="margin-bottom: 20px"><i>Topology of what we'll be building today</i></center> <p>Basically, we’ll be tying two Raspberry Pis together with K3S and connecting them to a local WLAN. We can then deploy our workloads using a client (your laptop) which will be talking to the Kubernetes API just as you’d normally with any other K8S cluster.</p> <p>Note that the local addresses shown in this post might be different what you have got in your home network.</p> <hr> <p><strong>Recent updates (15/08/2021)</strong></p> <p>These updates are primarily courtesy of <a href="https://dev.to/chriscarrau/comment/1h819">Chris Carr</a> who suggested this tip!</p> <p>You can enable ssh, set the hostname and wireless network credentials before flashing the micro SD card. Simply press <code class="language-text">Cmd+Shift+X</code> (on Mac) or <code class="language-text">Ctrl+Shift+X</code> (on Windows) to get to the Advanced options window. This will make the steps 1, 3 &#x26; 8 redundant now. I have kept them in case you’d want to do them manually.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 133%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEbUlEQVR42qWUXWgcVRTH724t9FHwwYcIBbeCbz74BUKsGg3YWKoBkz7UFAKSl2IJBjEiSLGxNX0KbtEFE0MsTdkHLSR58CuPxtQ2yRpis00234ndyczuzM731z2ec2e2zYa0QTxwOMPMvb85/3POvexmJnOwcOOX5+/8PXVUyufrpUKhxgu5XP3q3Fz9ai7y3d83cc9WPv/yP4XCC9ls9gAb/fbKYa24IvmBA75lc0c3wDEMcA0TowlysQjrKyvCy9sy2LoOtMaqVIR7uAcCTmsr3d3dj7PM4GDKtG0Z0EzT5IZpgm3bYFoWWBjJiwgtShK+M8EPAvB8X8SyqtI6TntxndHZ2VnHMplMytB1AdR1neuGLjYiXLiAYzQw6wpm5PsI9DxaDhsbG6BpWgS0LP0+0DDuAQUEpar490pFRyCWAjPinNc42dra2j5AQ+eWqoM3twnFbUlkRlnZjoNZuULqTtsfSBlidlyqgHFXAcuxhTySHWDNdtu+QIyc6ge4xNUMkBUZXQFFlsF1XQHZKXl9fZ0UPBho2RanbAQc5SoILJfLoJQUygQ817sHrQJJFT3jPr2jo6OO9fX1pVCOPD8/D+3t7Xx5aQnA4+Bi3ah2rqidh9EDB39mx6NENj4+Dj09PVzCkcLS6G1tbfeB+Xweurq6eG5qBkCxIF4EcUoAIUoNQ5FdiJGMkhgaGuK5XE60QACjOUTJuKGsKJyk0SYVo++4YCs4exo2ynCAm25NU7Y2N6GCTQlxNm3TiiQP4kmxHCdqiu9yGyVyhNMcqpqKo2REjQDRq9oub+FgO9FJcVCyaErvq22p5fcvy9Lpy1Bo6eXq1ALQgJSwu6VSGRysY4gjU22EsDB6Xvr5D1g63cflMwOw1vGN3vHMa3Xsc3YkNX2oSZbPDsDGuWGuzi2DE/qglVXqoDizBKM5JA+plvFMzvb/AL+xF/lU8g2YTjTqbewJAj6byh1qlTcbz0O+8TNe/n1OLLZMSxw5supwk9Nz6EXvb/ePwAR7k98+2AKzj7yLwKdiYKJZXkiegFusgcsjExHQiC4GGuhqV6szSE0gy/ePwjR7ixeSJ2E20VwLXDxwEmbYcb49OgkmhNjxkpjDcIfcqvvxgBNwijXxxWTrHkB8OYN/2x6ZBBv7qWFDdLpI8V4MAl9kWfUglvxwYKJVpC9fnwC3pMN2UQIZz3DNgFdlB+F/AP44AWGxAmVZoaEVGe2+C3fW8KGSCaiM3RCLA+pmLPFBwIWBMQQe2xtYiIGlsT9rZO1lOyXf2g28wJ57Mpd4R1pItiCwKcAMaXWIp0PEvbz6bfXqr+E0OxbcSbTAX4nmigD2spdSWD+5mDwFC+xtro3djLKIZXHHA+3Md+D8lIvSQ1Y1Q/X6JCyyZn43cQqWEq2GAL7Hnn7sGms4n2UNX19hr3x1rbkznf3kUjrb+UU6++FFjBfT3x//ID3c/mk6+9GX8Tv89vGl9PCJs+mrtAf3ZtnrF46yw48ytETsyf/pgvMvg2qZ0+JKtiIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-3.png" title="" src="/static/478b5e0911a66e4dfbc2c242b3b316a1/5a190/building-your-own-private-kubernetes-cluster-3.png" srcset="/static/478b5e0911a66e4dfbc2c242b3b316a1/772e8/building-your-own-private-kubernetes-cluster-3.png 200w, /static/478b5e0911a66e4dfbc2c242b3b316a1/e17e5/building-your-own-private-kubernetes-cluster-3.png 400w, /static/478b5e0911a66e4dfbc2c242b3b316a1/5a190/building-your-own-private-kubernetes-cluster-3.png 800w, /static/478b5e0911a66e4dfbc2c242b3b316a1/e2310/building-your-own-private-kubernetes-cluster-3.png 968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <hr> <h3>Step 0 - The initial setup</h3> <p>First, we need to set up the Raspberry Pis to have an OS, enable SSH, little bit of configuration to be able to use K3S.</p> <blockquote> <p>💡 If you haven’t done a setup on Raspberry Pi in headless mode before, I have a post describing just that. <em>Note that this is for Desktop version if you want to use them for other stuff as well.</em> That post can be found here: <a href="https://sahansera.dev/setting-up-raspberry-pi-4-headless-mode/">https://sahansera.dev/setting-up-raspberry-pi-4-headless-mode/</a></p> </blockquote> <p>I have listed down the specs and the OS versions I used for my kit.</p> <ul> <li>2 x Raspberry Pi 4 Model B - 4GB RAM</li> <li>2 x 128GB Micro SD Cards</li> <li>Raspbian OS Lite edition (based on Debian Buster)</li> <li>Stackable case</li> </ul> <p>Here’s a sneak peek of my build ✌️</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQCA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAGrF6Dor//EABkQAQEBAQEBAAAAAAAAAAAAAAIDAQQAEv/aAAgBAQABBQJ9KLXRX0GmNhPd2Q+Zgg//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAaEAACAwEBAAAAAAAAAAAAAAABEQACIRJh/9oACAEBAAY/ArDMM6AAEdk/IzWcrIqhT//EABoQAAMAAwEAAAAAAAAAAAAAAAABESExQYH/2gAIAQEAAT8hSRRDJDRdN9OhQO9Le8sg+eswALT/2gAMAwEAAgADAAAAEDfP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFREBAQAAAAAAAAAAAAAAAAAAECH/2gAIAQIBAT8Qp//EAB0QAQACAgIDAAAAAAAAAAAAAAEAESFRMWGBkcH/2gAIAQEAAT8Qs6wLNpcp31Q1Nqzsl3s8TgYO4e5armfce7D924qMqoK58z//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-2.jpg" title="" src="/static/2c6712e6ebb99649b71f1bc60dc2a24b/4b190/building-your-own-private-kubernetes-cluster-2.jpg" srcset="/static/2c6712e6ebb99649b71f1bc60dc2a24b/e07e9/building-your-own-private-kubernetes-cluster-2.jpg 200w, /static/2c6712e6ebb99649b71f1bc60dc2a24b/066f9/building-your-own-private-kubernetes-cluster-2.jpg 400w, /static/2c6712e6ebb99649b71f1bc60dc2a24b/4b190/building-your-own-private-kubernetes-cluster-2.jpg 800w, /static/2c6712e6ebb99649b71f1bc60dc2a24b/e5166/building-your-own-private-kubernetes-cluster-2.jpg 1200w, /static/2c6712e6ebb99649b71f1bc60dc2a24b/b17f8/building-your-own-private-kubernetes-cluster-2.jpg 1600w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You can now use <a href="https://www.raspberrypi.org/software/">Raspberry Pi Imager</a> without using Balena Etcher to flash the micro SD card. This will make selecting the OS version and flashing process easier.</p> <p>In summary, we will be looking at doing the following steps.</p> <blockquote> <p>💡 You need to do the following configs to both (or more) of your Raspberry PIs</p> </blockquote> <ol> <li>Enable <code class="language-text">ssh</code> on both RPIs (manual)</li> <li>Enable cgroups</li> <li>Set up wireless (manual)</li> <li>Enable 64-bit mode at the kernel level</li> <li>Booting up</li> <li>Set up IP tables</li> <li>Assign static IPs</li> <li>Change the hostnames (manual)</li> <li>Install K3S Server on Master</li> <li>Install K3S Agent on Worker</li> </ol> <p>Before booting up, we need to do a couple of configurations as mentioned below.</p> <h3>Step 1 - Enable SSH (Manual)</h3> <p>Open up the root of the micro SD card (this would be mounted as <code class="language-text">boot</code>) you just flashed. Create a blank file named <code class="language-text">ssh</code> at the root of that folder.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">touch</span> <span class="token function">ssh</span></code></pre></div> <h3>Step 2 - Enable cgroups</h3> <p><code class="language-text">cgroups</code> is an essential kernel level feature which underpins the containerisation technology. This allows the processes to run in isolation with a specific set of resource assigned to it.</p> <p>Let’s open up the root of the micro SD volume (this would be mounted as <code class="language-text">boot</code>) you just flashed and edit the <code class="language-text">cmdline.txt</code> file.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 68%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB70lEQVR42rVTy6oaQRB1mX+4l+SXkiwFF1nmAS4EQTf+T37CfRaKRAdcBMkVHHWcVzvTM0473TmncvuC4IVsUlB0TU/VqXOqZjrT6fTNYnt6fJo/Pczn84f1ei3O+F99sVg8EqdD2253Padjl9RHZUyr67rSWZbp6/Uq3ratPp/PumkaXVWVVkohp9a0siyZU1wuF7fZbD4JYPAz+OJgdVMZnkhwKGborLXi/t4/G2Ne3iNuGa9Wq74ALpfLr7woi1KymsY4NJcCMHFgJTHvfCMwc2AugEVR3AJCvwAej0fDYsh1p9NJiuM4dnmeC6MoilgsTQ6Hg5wE3u/39xkiybAjZiSANIJjfhITkA0JHoahNCToq4Cl/iuZBUykcdh0mgfz8imZDpb3JaOTYXGSJCKVoBiDPPMe718kkyFPsoey+wzTNDW+O2U/jwGAqbNgEh1CV6hMWBKcW2eMuvuAADFeJpnQ4jhxCktp8GX82kYujDJstpV5vgoYBIEA7nY7WYrfMmNu8VJXLtet+/6jdLPf/Gy4uLPMj7JB5BZwNpt95gXm0SDBAkScscqVzbPcosiqLMVzbrEccSixGI9F3ZX1+AW/CWCv13s7mUzej0ajj8PhUNzHg8FAvP98Dvr9m/c+Zzwef+h2u+86/8P+AAvRZY2rjYe2AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-4.png" title="" src="/static/7def6e44cf7ff3124d15cba8d059fd18/5a190/building-your-own-private-kubernetes-cluster-4.png" srcset="/static/7def6e44cf7ff3124d15cba8d059fd18/772e8/building-your-own-private-kubernetes-cluster-4.png 200w, /static/7def6e44cf7ff3124d15cba8d059fd18/e17e5/building-your-own-private-kubernetes-cluster-4.png 400w, /static/7def6e44cf7ff3124d15cba8d059fd18/5a190/building-your-own-private-kubernetes-cluster-4.png 800w, /static/7def6e44cf7ff3124d15cba8d059fd18/c1b63/building-your-own-private-kubernetes-cluster-4.png 1200w, /static/7def6e44cf7ff3124d15cba8d059fd18/29007/building-your-own-private-kubernetes-cluster-4.png 1600w, /static/7def6e44cf7ff3124d15cba8d059fd18/6e29b/building-your-own-private-kubernetes-cluster-4.png 1610w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Add the following line to the end of the file.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">cgroup_memory</span><span class="token operator">=</span><span class="token number">1</span> <span class="token assign-left variable">cgroup_enable</span><span class="token operator">=</span>memory</code></pre></div> <p>You might be wondering why are we doing this. Here’s an excerpt from the K3S <a href="https://rancher.com/docs/k3s/latest/en/advanced/">docs</a>:</p> <blockquote> <p>💡 Standard Raspbian Buster installations do not start with <code class="language-text">cgroups</code> enabled. K3S needs <code class="language-text">cgroups</code> to start the <code class="language-text">systemd</code> service. <code class="language-text">cgroups</code> can be enabled by appending <code class="language-text">cgroup_memory=1 cgroup_enable=memory</code> to <code class="language-text">/boot/cmdline.txt</code></p> </blockquote> <h3>Step 3 - Setting up wireless mode (Manual)</h3> <p>I don’t carry around my Raspberry Pi setup 😆, so I opted in to use my home Wi-Fi network instead of ethernet. Here’s how I did it.</p> <p>While at the root of the <code class="language-text">/boot/</code> volume, create a new file called <code class="language-text">wpa_supplicant.conf</code> file and add the following lines to it.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">country</span><span class="token operator">=</span>AU <span class="token assign-left variable">ctrl_interface</span><span class="token operator">=</span>DIR<span class="token operator">=</span>/var/run/wpa_supplicant <span class="token assign-left variable">GROUP</span><span class="token operator">=</span>netdev <span class="token assign-left variable">update_config</span><span class="token operator">=</span><span class="token number">1</span> <span class="token assign-left variable">network</span><span class="token operator">=</span><span class="token punctuation">{</span> <span class="token assign-left variable">ssid</span><span class="token operator">=</span><span class="token string">"your-networks-SSID"</span> <span class="token assign-left variable">psk</span><span class="token operator">=</span><span class="token string">"your-networks-password"</span> <span class="token punctuation">}</span></code></pre></div> <p>Take a note of the <code class="language-text">country</code> field (you can find them in <a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements">here</a>), and make sure to replace that with the corresponding country code that suits you. <code class="language-text">ssid</code> and <code class="language-text">psk</code> would be the name of your wireless network and the password, respectively.</p> <h3>Step 4 - Run the kernel in 64-bit mode</h3> <p>Next up, we will tell the Raspbian OS to run the kernel in 64-bit mode. This is required for K3S.</p> <p>Open the <code class="language-text">config.txt</code> file and add the following line to the bottom.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">arm_64bit</span><span class="token operator">=</span><span class="token number">1</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 59.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABqUlEQVR42q1RS07DMBDtx0kbOx/HdlJK2wOAWAIH6AaBBAcpiy5AZVFYAQlF4kaskLKBAyFVqYYZl1KKoEgIS08zyYzfvOepVPAAQA+xg9hCbP8BdLdXeT/1h/HZ43U2gizPX/Pbm2mGyLPbD1xdjqeXiPvJZDq5y209W/a9YoSL0fnTgrCppVsYrUGbtDRJCn4QgowVKJ1gHoDjuOC6DWCOA/U6AzL1CbP3+ELiLGFL8aKVGtDalGnaQrLYwiChUhqazSbUajUkdiyIvNGYA2szitVqdUnYNgIVxhArU4ZhBFIqiGSMkJDQAMw9jwPVAlQvhG+V+34AURTNKF9R2Da82Ggl1rLSBht9q2SuwEOb9a8211smwm530xIGYWiVRJEEzgWSeuBxDlwIoBqBlNFQgRGtImF1lTC1b5hAe7Nbdro9jB1rmZTNwWxkjFnYb8YWyr9ZiuZFYhQpKl3XtVulZpy+zurPllMtin6/DweHR+Xu3j4+ukA7PtTWvB0NpO0TIeUo5HlByPDjuNPpDaWUA1zEKf77FdQntaZ8gPeHQRCcLAj/9bwB3R4LCFL2fuQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-5.png" title="" src="/static/5ef2c1635a4fdedf317a65171fcf3b2f/5a190/building-your-own-private-kubernetes-cluster-5.png" srcset="/static/5ef2c1635a4fdedf317a65171fcf3b2f/772e8/building-your-own-private-kubernetes-cluster-5.png 200w, /static/5ef2c1635a4fdedf317a65171fcf3b2f/e17e5/building-your-own-private-kubernetes-cluster-5.png 400w, /static/5ef2c1635a4fdedf317a65171fcf3b2f/5a190/building-your-own-private-kubernetes-cluster-5.png 800w, /static/5ef2c1635a4fdedf317a65171fcf3b2f/c1b63/building-your-own-private-kubernetes-cluster-5.png 1200w, /static/5ef2c1635a4fdedf317a65171fcf3b2f/29007/building-your-own-private-kubernetes-cluster-5.png 1600w, /static/5ef2c1635a4fdedf317a65171fcf3b2f/b88bf/building-your-own-private-kubernetes-cluster-5.png 1702w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>That’s it! Now insert the micro SD card to the Raspberry PI and boot it up.</p> <h3>Step 5 - Booting up</h3> <p>When you boot up the RPIs for the first time it will take a couple of minutes to appear on your home network.</p> <p>If you log in to the router dashboard, you’ll be able to see the RPIs with their IP addresses.</p> <p>You can log in to them via <code class="language-text">ssh</code> like so.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> [email protected]</code></pre></div> <p>Note that <code class="language-text">pi</code> is the default user and <code class="language-text">raspberry</code> would be the default password.</p> <blockquote> <p>💡 Make sure to change the default password with <code class="language-text">passwd</code> command when you log in for the first time to both nodes</p> </blockquote> <h3>Step 6 - Enabling static-IP configuration</h3> <p>When we boot up the RPIs K3S connects to the worker nodes by using their IPs. Since we will be using WLAN, if we restart our nodes, the IPs would be different and this setup would not work. So we need to add a piece of config to assign static IPs to them.</p> <p>There are a couple of ways you can do this. Best option would be to use your router’s DHCP server capabilities and make an address reservation. You’d need the device name and MAC address of the RPIs.</p> <p>If not, you can edit the <code class="language-text">/etc/dhcpcd.conf</code> file and let the RPI know which IP to assign itself. I would recommend to add this just below “Example static IP configuration” section so that it’d be easier to find it in the future.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">interface wlan0 static <span class="token assign-left variable">ip_address</span><span class="token operator">=</span><span class="token number">10.0</span>.0.100 static <span class="token assign-left variable">routers</span><span class="token operator">=</span><span class="token number">10.0</span>.0.1 static <span class="token assign-left variable">domain_name_servers</span><span class="token operator">=</span><span class="token number">8.8</span>.8.8</code></pre></div> <p>Once done, it would look like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 54%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlElEQVR42q2RwU7CQBBAW0qBCqEtkAppgZZCIOIBPoUACTd+QCMnJDEegNAS1A/AhIv/pCfrd5CGdpxpADFiosZNXqa73Xk7O8swOADAQM6Rsz9CuQaqWKbfv5Ie59PnyfQaFpa9Xsxm7r1tBzzM5641Gbs3w6F7Oxq59nTi0v9D7ixrbY3H8LRavaBUpgIVvVRyNFUDUZK8VDoNYZ4HQRAgkUhALBaDUCgEdJFj4D+PYrfbfUPhaSAsm6ZTLpdB13WvXq9DLpeDfD5PcygWiyBJUiCOx+MQ5jhgWBbYLUeFjUbDqVarUKvVvGazCTvoEFVVQdM0SKVSoChKII1Go8EBFHEeCNvt9ocwm806tBmjRzGZTIIoikGk6ggS0Zosy5DJZCCNraFvyvkiLGEPqRrTNIMr7xKoKrrWd/077OEnoWEYr4VCASqVygbx8eo+zn2s1kepz/O8j/t8lO/jDo7jNiTsdDrOXogJTiQSAcTbRsC1fa9+8sqtVmtfoYD0kAFygVz+EsoZ4CP2lsvlCfPPg30HeJ0hCr1kctYAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-6.png" title="" src="/static/bdb8c5ee5dcd6e06e9c8416e31ef2e21/5a190/building-your-own-private-kubernetes-cluster-6.png" srcset="/static/bdb8c5ee5dcd6e06e9c8416e31ef2e21/772e8/building-your-own-private-kubernetes-cluster-6.png 200w, /static/bdb8c5ee5dcd6e06e9c8416e31ef2e21/e17e5/building-your-own-private-kubernetes-cluster-6.png 400w, /static/bdb8c5ee5dcd6e06e9c8416e31ef2e21/5a190/building-your-own-private-kubernetes-cluster-6.png 800w, /static/bdb8c5ee5dcd6e06e9c8416e31ef2e21/264eb/building-your-own-private-kubernetes-cluster-6.png 867w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Make sure to update the IP addresses for each RPI.</p> <h3>Step 7 - Setup IP Tables</h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> iptables <span class="token parameter variable">-F</span> <span class="token function">sudo</span> update-alternatives <span class="token parameter variable">--set</span> ip6tables /usr/sbin/ip6tables-legacy</code></pre></div> <h3>Step 8 - Change the hostname to something sensible (Manual)</h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">vi</span> /etc/hostname</code></pre></div> <p>This file will only contain a single line, so you can name it something that makes sense to you. I renamed mine as <code class="language-text">controlplane</code> for the master node and <code class="language-text">node01</code> for the worker node.</p> <p>Next, change the /etc/hosts file where it says <code class="language-text">raspberrypi</code> to the name of your node.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">vi</span> /etc/hosts</code></pre></div> <p>Here’s an example for the <code class="language-text">node01</code> node.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 717px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 58.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABnUlEQVR42q2Sy0sCURTGx3loM06T71S0iVFBwZ20lv46d7VNwYV7pwhxVQsjsDRNzYKgEC4IbaKFS5vu6dywCQ0MqwM/5nXP933nzuU4LACQEBVx/xK10+lI3Gc9Nhp7vebx82DYGd21u+Sm3Sb3/T55GA5J6+yMnNRq5LReJ5eNBhm0WqR3cfHBdbNJhldXo/b5+cttt7tvC+7m81XD2IZYLGapbjcosgypZBJyuRzoug5erxc2QyGIRqMQCgYhEg5DJBKBrXgctPV1a0PToFKpHNqCcV2vplIpcLlcr/hIGel0mmYyGZrNZmkikaCyolBN06gsy1TBe1VVqcfjoTzPv0qSBMVi8UswEAiYzB0XW2xLGSFMFMQ07Orz+cDhcMDntwUsp9MJpVLpyBbEkUy/P8AaLXSca2DjGoYBgiDMvWcGM5PvgqK0ZvLYgAusxQTMYFHsx4SipFQ57sNtirytyJQJ4h6atiDPu8yZoLUkydKE5XL5K6EgSAc47gQZI0+rgO1j/MuTQqFQtAOykyOK4s5fwFMSn2n9b70Dv6QXxPwI8sMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-7.png" title="" src="/static/bbdf3a59de66ddfe51cd33798ea37db1/0ad97/building-your-own-private-kubernetes-cluster-7.png" srcset="/static/bbdf3a59de66ddfe51cd33798ea37db1/772e8/building-your-own-private-kubernetes-cluster-7.png 200w, /static/bbdf3a59de66ddfe51cd33798ea37db1/e17e5/building-your-own-private-kubernetes-cluster-7.png 400w, /static/bbdf3a59de66ddfe51cd33798ea37db1/0ad97/building-your-own-private-kubernetes-cluster-7.png 717w" sizes="(max-width: 717px) 100vw, 717px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>💡 Make sure to reboot the RPIs once you have done all these changes.</p> </blockquote> <h3>Step 9 - Installing K3S on the Master node</h3> <p>To install K3S on your master node, run the following command. It will do all the bootstrapping it needs to do under the hood.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-sfL</span> https://get.k3s.io <span class="token operator">|</span> <span class="token function">sh</span> -</code></pre></div> <p>As a side note, I always run a <code class="language-text">sudo apt update &amp;&amp; sudo apt upgrade</code> before installing anything.</p> <h3>Step 10 - The worker node setup &#x26; agent registration</h3> <p>Before set up the worker node, we need to take the token from the server. You need to run the following commands on the master node in order to get this token.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">cat</span> /var/lib/rancher/k3s/server/token</code></pre></div> <p>Then you need run the following command on the worker node. Make sure to update them according to your environment.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-sfL</span> https://get.k3s.io <span class="token operator">|</span> <span class="token assign-left variable">K3S_NODE_NAME</span><span class="token operator">=</span><span class="token string">"node01"</span> <span class="token assign-left variable">K3S_URL</span><span class="token operator">=</span><span class="token string">"https://10.0.0.100:6443"</span> <span class="token assign-left variable">K3S_TOKEN</span><span class="token operator">=</span><span class="token string">"token from above step"</span> <span class="token function">sh</span> -</code></pre></div> <p><strong>Explanation of the variables:</strong></p> <ul> <li><code class="language-text">K3S_NODE_NAME</code> - name of the worker node you are configuring. Remember that we set up the hostnames in step 7.</li> <li><code class="language-text">K3S_URL</code> - the IP address of your master node. The default K3S server port is 6443, so keep it unchanged.</li> <li><code class="language-text">K3S_TOKEN</code> - Token that we received from the from the K3S server. Eg: <code class="language-text">K10141483xxxxxxxxxx::server:xxxxxxxxxxxx</code></li> </ul> <p>You’d see <code class="language-text">[INFO] systemd: Starting k3s-agent</code> message and it’s ready to go!</p> <p>That’s it! you can now run commands and see it in action. You will need to <code class="language-text">sudo su</code> in order to run commands on the master node.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl get nodes</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 30.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABOklEQVR42q2PX0vCYBSHneY0dAMbTPzTHChjg4q696aB30P8BNFHcTd+mfAqxDQ3UxGsFkhB4MUuuhghh19nywLptgMPB875nYf3TSS4ELycI3xvAbhk7D3C0A6DwP7YbOw/u2+im9ZnEFwkdiVOb2+Gr29PWD/7NHddLKZTPC6XWPs+/NUK7miEh8kkZsb70WAAbzzG3POiLM14fj8cTtiVjISHJ6dnd5qmQ8xktpVKhXRdp2NNi3s+nydJkkiWZSoUCpTNZulIUUgtFimZSpEgCNvok+122+WXpmOhaRhj0zQhSzLVajWwCKqqwrIs5HI5sAiiKKJUKsUoioJGo4FqtQoWUiTsdDrejzDNoat6vd7jUJeFTrlcdljqGIbhsOgXPnY4vwfPutx7zWbzut/vH0RCYcd/lPAFPSC6yfiv36UAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="building-your-own-private-kubernetes-cluster-8.png" title="" src="/static/cab5fd69fe625b104f91bc499ef952fb/5a190/building-your-own-private-kubernetes-cluster-8.png" srcset="/static/cab5fd69fe625b104f91bc499ef952fb/772e8/building-your-own-private-kubernetes-cluster-8.png 200w, /static/cab5fd69fe625b104f91bc499ef952fb/e17e5/building-your-own-private-kubernetes-cluster-8.png 400w, /static/cab5fd69fe625b104f91bc499ef952fb/5a190/building-your-own-private-kubernetes-cluster-8.png 800w, /static/cab5fd69fe625b104f91bc499ef952fb/e4374/building-your-own-private-kubernetes-cluster-8.png 927w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>By the end of this article, I hope you have your k8s cluster up and running. If you ran into issues, let me know in the comments below. In the next article, we will look at deploying a sample application and see the cluster in action.</p> <p>I’m hoping to publish a script to automate most if not all of these steps pretty soon. Until next time 👋</p> <h3>References</h3> <ul> <li><a href="https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/">https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/</a></li> <li><a href="https://rancher.com/docs/k3s/latest/en/installation/install-options/agent-config/">https://rancher.com/docs/k3s/latest/en/installation/install-options/agent-config/</a></li> <li><a href="https://www.electrondust.com/2017/11/25/setting-raspberry-pi-wifi-static-ip-raspbian-stretch-lite/">https://www.electrondust.com/2017/11/25/setting-raspberry-pi-wifi-static-ip-raspbian-stretch-lite/</a></li> <li><a href="https://thepihut.com/blogs/raspberry-pi-tutorials/19668676-renaming-your-raspberry-pi-the-hostname">https://thepihut.com/blogs/raspberry-pi-tutorials/19668676-renaming-your-raspberry-pi-the-hostname</a></li> <li><a href="https://www.tomshardware.com/how-to/fix-cannot-currently-show-desktop-error-raspberry-pi">https://www.tomshardware.com/how-to/fix-cannot-currently-show-desktop-error-raspberry-pi</a></li> </ul><![CDATA[Tips and Tricks to Ace the Certified Kubernetes Application Developer]]>https://www.sahansera.dev/tips-and-tricks-for-acing-ckad/https://www.sahansera.dev/tips-and-tricks-for-acing-ckad/Mon, 02 Aug 2021 00:00:00 GMT<p>I recently passed the <a href="https://www.cncf.io/certification/ckad/">Certified Kubernetes Application Developer</a> exam and thought to share some tips and tricks that might come in handy if you are also planning to take the exam in the future.</p> <h3>📔 Background</h3> <p>About a month ago, I decided to learn more about Kubernetes as it would be really useful for the stuff I’m working at GitHub daily. Prior to that, I was always fascinated by Kubernetes but never got the chance to work on an actual system that used it. I knew how it worked from a 10,000 feet view, but didn’t have an idea of core components, basic constructs and literally to be able to do anything with it.</p> <p>Having taken the exam, I’m quite comfortable navigating through Kubernetes and now it makes sense when I’m doing something with it, rather than merely following some commands.</p> <p>CKAD is a hands-on exam and managing your time is absolutely crucial. I hope you find the following tips useful✌️</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>🗒️ Summary of the exam</h3> <p>To summarize the key facts about the CKAD exam,</p> <ul> <li>Passing score is 66%</li> <li>2 hours duration, comprised of 19 questions</li> <li>Questions will have varying weights (from 2% - 13%)</li> <li>You can also open only one tab to browse Kubernetes documentation</li> <li>Remotely proctored</li> </ul> <h3>💻 Aliases and bash tricks</h3> <p>This is a really important first that I can’t recommend enough. I was using the full <code class="language-text">kubectl</code> command during the study phase but later started using just <code class="language-text">k</code> by setting up an alias when I was practising simply to cut down the time when typing commands.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">alias</span> <span class="token assign-left variable">k</span><span class="token operator">=</span>kubectl</code></pre></div> <p>Initially, it will take a few seconds to type this out but it will pay dividends throughout the exam. Here are a few more if you are interested. You don’t need to use everything in here though. In fact, I only used the above alias.</p> <p>Feel free to mix and match the commands you are comfortable with 👍</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">alias</span> <span class="token assign-left variable">kd</span><span class="token operator">=</span><span class="token string">'kubectl describe'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">kr</span><span class="token operator">=</span><span class="token string">'kubectl run'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">kc</span><span class="token operator">=</span><span class="token string">'kubectl create'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">ke</span><span class="token operator">=</span><span class="token string">'kubectl explain'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">kgp</span><span class="token operator">=</span><span class="token string">'kubectl get pods'</span> <span class="token builtin class-name">alias</span> <span class="token assign-left variable">kgs</span><span class="token operator">=</span><span class="token string">'kubectl get svc'</span></code></pre></div> <p>You don’t need to be a Linux guru to take the exam, but, remember you will do it in some Linux env. (potentially Ubuntu). So it helps to know a few basic Bash commands if you are coming from Windows.</p> <ul> <li><code class="language-text">cp</code> - Copy files</li> <li><code class="language-text">mv</code> - Move/Rename files</li> <li><code class="language-text">mkdir</code> - Create new folder</li> <li><code class="language-text">ls</code> - List files</li> <li><code class="language-text">rm</code> - Remove/Delete files</li> <li><code class="language-text">grep</code> - Search through text. Useful when you want to filter a list of pods. Eg: <code class="language-text">kubectl get pods | grep -i status:</code></li> <li><code class="language-text">Ctrl+R</code> - To do a reverse search to find a command you have previously run</li> </ul> <p>Extra tip: Use short names of resources whenever possible.</p> <p>Not sure what are the short names? You can check it with <code class="language-text">kubectl api-resources</code> command.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB7ElEQVR42lVSXW/aQBA0YIOxjcF2THP+BCOqSgnNj2jr/DdHisRDlOaNBvpihBC/LlaDvJ09HJI8rNa3Ozs7c2fl7/19tv7z+64st8V+vy+221Pe7XbF4XAoVqtV8fjwUDw+PRWb5+figF5ZlsVms5H5Ldbr9d1yuZwpV9+u8u83N5SkKQVBUPu+T2EYyojjmDzXpdFoRMPhkIQQNJ1mlGUZzedfibGu69Su69aO45Bt27cKDr+ECKnT6RwVRXltt9vHbrd7bLc7R1VVuXaOXk+XYQ+HR+/Cf6u/NkGIXPGFyL9cCjJNk1RNq0FI/X6fF5CqatQAZfR0nQzgBrZNnndBOs7A1+jVDeZWESLO4yQlbKSu1pWEUAhClTTtM6GORYZhwv6IoigmyxpQq9X6TIh7yCeTTIJAUsMmwZZUx5kXcA2DUnnfMHCnDkVxin5PzjSkJ0LcYZ6kEwASgvXaNC1pl+2w8hBKLkUgybhmWZa0nM3mFIQRO6uZ9INCkadQaA1sKf+kxJDDTMQKeYG0jBr3TgoTqbyx+07oeeMf02z2D5Yr3NkLhisAK9ipwjCqrMGgwkNUAFe63q8Mw6gYm6RTieEZ9F6al/6JUPDA5rVpOgvkBc7nwL+1GI8j5PG5BgIZ3OMZ/m5618z1H64M3mEktYZkAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-tricks-for-acing-ckad-1.png" title="" src="/static/bb71424ae729b8cfd1dfcf1dd870eafc/5a190/tips-and-tricks-for-acing-ckad-1.png" srcset="/static/bb71424ae729b8cfd1dfcf1dd870eafc/772e8/tips-and-tricks-for-acing-ckad-1.png 200w, /static/bb71424ae729b8cfd1dfcf1dd870eafc/e17e5/tips-and-tricks-for-acing-ckad-1.png 400w, /static/bb71424ae729b8cfd1dfcf1dd870eafc/5a190/tips-and-tricks-for-acing-ckad-1.png 800w, /static/bb71424ae729b8cfd1dfcf1dd870eafc/c1b63/tips-and-tricks-for-acing-ckad-1.png 1200w, /static/bb71424ae729b8cfd1dfcf1dd870eafc/29007/tips-and-tricks-for-acing-ckad-1.png 1600w, /static/bb71424ae729b8cfd1dfcf1dd870eafc/20f38/tips-and-tricks-for-acing-ckad-1.png 2818w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>⌨️ Get a good grasp of VIM</h3> <p>I found having previous experience in VIM came in handy. However, you don’t need to be a master at it. Using nano would be fine too if you are good.</p> <p>Take the time to set the following to your VIM profile before attempting any questions.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">vi</span> ~/.vimrc</code></pre></div> <p>Add the following lines and save it.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">set</span> expandtab <span class="token builtin class-name">set</span> <span class="token assign-left variable">tabstop</span><span class="token operator">=</span><span class="token number">2</span> <span class="token builtin class-name">set</span> <span class="token assign-left variable">shiftwidth</span><span class="token operator">=</span><span class="token number">2</span></code></pre></div> <p>These commands will save you from having indentation issues and weird syntax issues while working with YAML files during the exam.</p> <p>Here are some other commands that may be of help if you are not familiar with VIM.</p> <ul> <li><code class="language-text">/</code> - Search through text. Also, use <code class="language-text">n</code> to go to the next result.</li> <li><code class="language-text">dd</code> - Delete a line</li> <li><code class="language-text">u</code> - Undo</li> <li><code class="language-text">Shift+A</code> - Go to the end of the line and enter the INSERT mode</li> <li><code class="language-text">gg</code> - Go to the beginning of the file</li> <li><code class="language-text">G</code> - Go to the end of the file</li> <li><code class="language-text">o</code> - Go to the next line and enter INSERT mode</li> <li><code class="language-text">v</code> - Enter VISUAL mode. You can select a block of lines with arrow keys or <code class="language-text">j</code> and <code class="language-text">k</code> keys. You can copy with <code class="language-text">y</code> and paste with <code class="language-text">p</code> . Also, you can indent a block with <code class="language-text">Shift + ></code> to right and <code class="language-text">Shift + &lt;</code> to indent to the left</li> </ul> <p>And finally, while you are in NORMAL mode you can type <code class="language-text">ZZ</code> to quickly save and go back to the terminal without having to type <code class="language-text">:wq</code> How cool is that? ⚡</p> <h3>☄️ Mastering the imperative commands</h3> <p>You would come across many questions where you would have to create pods, deployments, services etc. In such cases, don’t bother writing up YAML definitions from scratch - or even finding the relevant reference in the k8s docs.</p> <p>You can save a lot of time by using imperative commands. For instance, if you are tasked to create a pod with <code class="language-text">nginx</code> as the image, <code class="language-text">tier:frontend</code> as labels with the port <code class="language-text">80</code> exposed:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl run tmp <span class="token parameter variable">--image</span><span class="token operator">=</span>nginx <span class="token parameter variable">--labels</span> <span class="token assign-left variable">tier</span><span class="token operator">=</span>frontend <span class="token parameter variable">--port</span> <span class="token number">80</span></code></pre></div> <p>Say you are asked to expose a deployment <code class="language-text">nginx</code> with a <code class="language-text">NodePort</code> service called <code class="language-text">nginx-svc</code>,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl expose deploy nginx <span class="token parameter variable">--name</span><span class="token operator">=</span>nginx-svc <span class="token parameter variable">--port</span><span class="token operator">=</span><span class="token number">80</span> --target-port<span class="token operator">=</span><span class="token number">80</span> <span class="token parameter variable">--type</span><span class="token operator">=</span>NodePort</code></pre></div> <p>But what if you can’t get everything included in a single command you can use the <code class="language-text">--dry-run=client -o yaml > tmp.yaml</code> to export it to a file before creating the resource.</p> <p>Oh btw, if you need to delete a pod quickly you can use the <code class="language-text">--grace-period=0 --force</code> command to quickly delete them without waiting.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl delete po <span class="token operator">&lt;</span>pod name<span class="token operator">></span> --grace-period<span class="token operator">=</span><span class="token number">0</span> <span class="token parameter variable">--force</span></code></pre></div> <h3>🤔 When in trouble</h3> <p>Pay attention to the weightage of the question and a rough idea of how long it will take you to solve it. I can remember, I was looking at a question that was quite long and had a fair bit of configuration to be done. But the weightage was only 2% 😆 I marked it down on the provided <strong>notepad</strong> and skipped it (you can also <strong>Flag</strong> a question). The next question was 4% and was really really easy! I hope you get the point.</p> <blockquote> <p>💡 Don’t be afraid to skip and revisit questions.</p> </blockquote> <p>If you forgot how something is placed in a resource definition, you can use <code class="language-text">kubectl explain &lt;resource name> --recursive | less</code> to find what you are looking for.</p> <p>Another useful tip I can give you is, the <code class="language-text">kubectl &lt;resource name> -h</code> command. You can use it like so.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">k run <span class="token parameter variable">-h</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACG0lEQVR42o1SS24TQRRsj+PMt+fTM/bEjiKCkFhYSJYT7EQWIlsrEkIsWGUEh0TiEpkF3AGyiDkC003Vs/nKCBal9+ZNd3VVV6vX68tXy+fLN4vF5c18/rSZzWbAvJk9mTXPrq6a9fWLZr2+FlxcrBqu+RVnZ4ub8/PF2+Vq9XI6nR6qx48e3k9Ojt14MrHjybEbjWo3qo/ccDhypix/whiX5blLs+xPdLv6KQiCE5XlxSbLchcEYef7ge33+9bzPKuU+l90gOv1ene+75+qNM02FdRU1chSCfsMJ4ZR5OIk2dY4cVjsuHEPfifUIDSmoh2bF4XYKmCvqoauBDjjIcaUzg8CIWaN4hgHxeLsELODg4M7EJ8qbNgURemiKLZchCrKqCqhwjDCLPpxZ/FuTZ7jcFyVTtMuDEPnQaEQxlpvyrKCmspSkUFfIxSqqusx547/JSgERiKuCUACm87z+l2v56FXO8s6FcK8MBYBSbq0S4skhQJRmCRaSBCaAMH95Q6FcEgVlqdTRVEYgdkeJD2vgWHRJslpnQr3EOp72uJ7IimStqwEiCzsSwWhxb1a3KnFe5OnRUDtV9gm8WfMH6gkTb/QIjfR5neFvAZ5Qkwd3xqqGBBSdYPBAPaZeMBZx9RBuFWIBe8TrT9gwy1qqzPdQkmrpc9aJC1gDwUtNrawJ2AP3AIf8f0OtQaUAcbA0b+AcAR7ZuMdj/cNyKb+10novHwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-tricks-for-acing-ckad-2.png" title="" src="/static/a14f5b74fe1046df67075fe6b67a0834/5a190/tips-and-tricks-for-acing-ckad-2.png" srcset="/static/a14f5b74fe1046df67075fe6b67a0834/772e8/tips-and-tricks-for-acing-ckad-2.png 200w, /static/a14f5b74fe1046df67075fe6b67a0834/e17e5/tips-and-tricks-for-acing-ckad-2.png 400w, /static/a14f5b74fe1046df67075fe6b67a0834/5a190/tips-and-tricks-for-acing-ckad-2.png 800w, /static/a14f5b74fe1046df67075fe6b67a0834/c1b63/tips-and-tricks-for-acing-ckad-2.png 1200w, /static/a14f5b74fe1046df67075fe6b67a0834/29007/tips-and-tricks-for-acing-ckad-2.png 1600w, /static/a14f5b74fe1046df67075fe6b67a0834/56dab/tips-and-tricks-for-acing-ckad-2.png 2296w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>☝️ A note on clusters &#x26; namespaces</h3> <p>This is also a very important point you should pay attention to. At the top of each question, if you will be given a command to set the current context. <strong>Make sure to run it for each question</strong> as different questions will be in different clusters.</p> <p>Another point is, <strong>pay attention to any namespaces</strong> in the given question text. Sometimes it will be worded within the question. Sometimes it will be at the bottom of the question as a separate note!</p> <p>In a question where you will have to <code class="language-text">ssh</code> into servers please make sure to remember (or note it down) which cluster and server you are in. And remember to <code class="language-text">exit</code> out of it before going to the next question.</p> <h3>📄 Leverage the docs</h3> <p>In certain cases, it’s better to visit the docs than to spend time to figure out what needs to be done. For instance, if there’s a question on setting up a Persistent Volume, the question will also have a section to create a Persistent Volume Claim and to create a Pod to use that.</p> <p>Go to the docs, type <code class="language-text">pv</code> at the search bar and click on the link that says “Configure a Pod to Use a PersistentVolume for Storage”. And yes, you need to know where things are at within the K8S docs!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 33%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA9klEQVR42pWR6Y6CMBRGff/3m81RlmIpRaBAWUShZ4pmNM6PSbzNydfkJqe37WaeHfMCi3MeuMwPprPz+OYLtYligco0sUg4SMl5NfnyfpZlTfea0NqGrmuxbU3f2efuVeau0v94Eko1kmYjeXkiLya64cLs18JrV70LQxEjU0XkMxaCJE2QmaA85rSloa1rrDF0Te33BvuHpqoYhv4h3L7viIOE8Dti9xWw/QjYb0M+3/bIvUaFBW1ecKorxqpkWDG3HBvDVOQs/e9TOTaln6jOFZWSlCrxHK5oEVH5T+qKI63W2KOm0ZlH3chSf5BmMgWcp/uEP+gaHZN9Wnp+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-tricks-for-acing-ckad-3.png" title="" src="/static/9b97538a8cd17fe8d8d482d20d5c327a/5a190/tips-and-tricks-for-acing-ckad-3.png" srcset="/static/9b97538a8cd17fe8d8d482d20d5c327a/772e8/tips-and-tricks-for-acing-ckad-3.png 200w, /static/9b97538a8cd17fe8d8d482d20d5c327a/e17e5/tips-and-tricks-for-acing-ckad-3.png 400w, /static/9b97538a8cd17fe8d8d482d20d5c327a/5a190/tips-and-tricks-for-acing-ckad-3.png 800w, /static/9b97538a8cd17fe8d8d482d20d5c327a/c1b63/tips-and-tricks-for-acing-ckad-3.png 1200w, /static/9b97538a8cd17fe8d8d482d20d5c327a/29007/tips-and-tricks-for-acing-ckad-3.png 1600w, /static/9b97538a8cd17fe8d8d482d20d5c327a/c8252/tips-and-tricks-for-acing-ckad-3.png 2178w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>👟 Practice, practice, practice</h3> <p>Speed is key to the exam. Although you get 2 hours, it will just fly! 🦅</p> <p>When you pay for the exam you will get 2 free mock exam sessions before sitting the real exam.</p> <img src="https://i.pinimg.com/originals/98/16/67/981667bac32a91436e9dd3fa78afa4ab.jpg"> <center style="margin-bottom: 20px"><i>As Jeremy Clarkson would say, "SPEEEEEEEEED!!!!" 😂</i></center> <p>Here are some more exercises I used.</p> <ul> <li><a href="https://github.com/dgkanatsios/CKAD-exercises">https://github.com/dgkanatsios/CKAD-exercises</a> [Free]</li> <li><a href="https://github.com/bmuschko/ckad-prep">https://github.com/bmuschko/ckad-prep</a> [Free]</li> <li><a href="https://kodekloud.com/courses/certified-kubernetes-application-developer-ckad/">https://kodekloud.com/courses/certified-kubernetes-application-developer-ckad/</a> [Paid]</li> <li><a href="https://medium.com/bb-tutorials-and-thoughts/practice-enough-with-these-questions-for-the-ckad-exam-2f42d1228552">https://medium.com/bb-tutorials-and-thoughts/practice-enough-with-these-questions-for-the-ckad-exam-2f42d1228552</a> [Free]</li> </ul> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>👋 Conclusion</h3> <p>Do you know what’s the hardest thing to do after the exam? waiting for the results! 🤣 It might take up to 24 - 36 hours to get your result. Here’s my certificate if you are interested.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 77.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC5klEQVR42n1SSU9UQRCe/+J6GIRhBAEFlTWIBj0ZjWskHoyHcRlmhh1h2ASXADG4JhgPmgghBj2oIdHowRg9EA9yksQtkmACzOvuN/P6s6p7ZIvYSadev6r66vuqyqe1xloXsPfnbw8pz37/Lz6ZTMKHNU7KA77PAhOfNBofajyfBL7R2+CulZNK/RvQ86wdfafR9ljj7oRG0yONsfd6hX/1SSbTgKyOGbHV6Tef+IjG9WcaD15rXB3XaB/Riz69Ks8WIsAFubLKX+c8/X/6UaN7zMMdYtgx6mH8g8aCWAJcfhwFTE678FV0CoSGXQy9cPHms4eZOU2V9GIfh19Zhiw7lZbK7tkFjbdTGjdfujh/38WeLolANAGfPyywMZTAupCAP+xgR4vCyRsCvU+S1LMUvvyyKNMzHjH2cGXcRc0tF4UtEv5agfWUt4Hy/WGFzNp5+AIxieyYQG6DQEGTRG69QFZUYtM5BxkRie1NDqq6FAEISnCw+YJERjhB8RL5jcLEB+sUsqMOsiIOAUYZQGBrncCuVoHdbRLlcYGSuEJpu30XECjbRV+7QkmbQHG7RA4TiAgEYgpZtQuWYYDRCTSTHP6LDlUUqOyyYFVkSwm8ooP/0btbIhhzSK7EllrHqGFShlgkwQwdLIIS4DaSspOZXhIoi1swZlreoVBGtowsF2JFNi+RtgxoGFr9wRgHOUYCT+xAn0B1r8K+HoG9PQrVlwX291lb3sl9lyYvEF0CNYDcyJx6iSMDCqdvp3CCJnx0wEHNkMKxQYVD/QqH+wXO3nNxfFDSBrg4eE2Y+FMmhohQfhaDG8lpuXz9Z6aQGfpKjnkjKZ+mzo0vbrMT5cI5pILbwkMpjUsTY9vGQ5mDLzPCU3RR2TGHkqYf1KeE6VdOvWXO/SpqFUYar01GRCGPVqyoVZk+B+vksh46dihBs4e0c82KLtnGBPKIUV4DsxG0iwKFzc7SbUm/W5hxwmyIAQzP4Q8RveasoQ+wWwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tips-and-tricks-for-acing-ckad-4.png" title="" src="/static/7f318d8b9269ad918fa442ed8eddaa57/5a190/tips-and-tricks-for-acing-ckad-4.png" srcset="/static/7f318d8b9269ad918fa442ed8eddaa57/772e8/tips-and-tricks-for-acing-ckad-4.png 200w, /static/7f318d8b9269ad918fa442ed8eddaa57/e17e5/tips-and-tricks-for-acing-ckad-4.png 400w, /static/7f318d8b9269ad918fa442ed8eddaa57/5a190/tips-and-tricks-for-acing-ckad-4.png 800w, /static/7f318d8b9269ad918fa442ed8eddaa57/c1b63/tips-and-tricks-for-acing-ckad-4.png 1200w, /static/7f318d8b9269ad918fa442ed8eddaa57/29007/tips-and-tricks-for-acing-ckad-4.png 1600w, /static/7f318d8b9269ad918fa442ed8eddaa57/c658e/tips-and-tricks-for-acing-ckad-4.png 2202w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>I hope you found these tips helpful. Feel free to comment below if you have got any tips and tricks too! Good luck with your exam!!! 🎉</p><![CDATA[Setting up Elastic Workplace Search with Docker]]>https://www.sahansera.dev/setting-up-elastic-workplace-search-docker/https://www.sahansera.dev/setting-up-elastic-workplace-search-docker/Fri, 23 Jul 2021 00:00:00 GMT<p>Recently I started looking into Elastic Enterprise Search’s <a href="https://www.elastic.co/workplace-search/">Elastic Workplace Search</a> offering. The configuration was not quite obvious when I first started off with the <a href="https://www.elastic.co/guide/en/enterprise-search/current/docker.html">Docker version</a> of it. The goal of this blog post is to walk you through the process of setting it up with Docker for local development.</p> <blockquote> <p>💡 Note: This is only meant for local development. Consider their <a href="https://www.elastic.co/enterprise-search">cloud offering</a> for production use.</p> </blockquote> <h3>Prerequisites</h3> <ul> <li><a href="https://www.docker.com/products/docker-desktop">Docker Desktop</a></li> <li><a href="https://docs.docker.com/compose/install/">Docker Compose</a></li> <li>Allocate docker memory to more than 4GB (really important to check this)</li> </ul> <h3>tl;dr</h3> <p>If you are looking for something to get started quickly, all you have to do is the following.</p> <ol> <li>Clone this <a href="https://github.com/sahansera/ElasticWorkplaceSearchWithDocker/">repo</a>.</li> <li>Update the environment variables in the <code class="language-text">.env</code> file. You can run <code class="language-text">openssl rand -hex 32</code> to generate values for <code class="language-text">ENCRYPTION_KEYS</code> variable.</li> <li>Run <code class="language-text">docker-compose up</code></li> <li>Go to <code class="language-text">http://localhost:3002</code> and login as <code class="language-text">enterprise_search</code> and the password you set in the above step.</li> <li>[Optional] You can jump ahead to the “Indexing sample data” section to ingest some dummy data into the index.</li> </ol> <p>But, if you are feeling adventurous, read on! 🤘</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Creating the docker-compose file</h3> <p>After a bit of trial and error the I came up with the following docker-compose file. The only difference between the above is that this version doesn’t use a .env file and we need to go through some steps to set up each service.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">elasticsearch</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/elasticsearch/elasticsearch<span class="token punctuation">:</span>7.13.4 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"node.name=es-node"</span> <span class="token punctuation">-</span> <span class="token string">"discovery.type=single-node"</span> <span class="token punctuation">-</span> <span class="token string">"cluster.name=enterprise-search-docker-cluster"</span> <span class="token punctuation">-</span> <span class="token string">"bootstrap.memory_lock=true"</span> <span class="token punctuation">-</span> <span class="token string">"xpack.security.enabled=true"</span> <span class="token punctuation">-</span> <span class="token string">"xpack.security.authc.api_key.enabled=true"</span> <span class="token punctuation">-</span> <span class="token string">"ES_JAVA_OPTS=-Xms512m -Xmx512m"</span> <span class="token key atrule">ulimits</span><span class="token punctuation">:</span> <span class="token key atrule">memlock</span><span class="token punctuation">:</span> <span class="token key atrule">soft</span><span class="token punctuation">:</span> <span class="token number">-1</span> <span class="token key atrule">hard</span><span class="token punctuation">:</span> <span class="token number">-1</span> <span class="token key atrule">enterprisesearch</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/enterprise<span class="token punctuation">-</span>search/enterprise<span class="token punctuation">-</span>search<span class="token punctuation">:</span>7.13.4 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"elasticsearch.host=http://elasticsearch:9200"</span> <span class="token punctuation">-</span> <span class="token string">"ent_search.auth.default.source=standard"</span> <span class="token punctuation">-</span> <span class="token string">"elasticsearch.username=elastic"</span> <span class="token punctuation">-</span> <span class="token string">"elasticsearch.password=password"</span> <span class="token punctuation">-</span> <span class="token string">"secret_management.encryption_keys=[changeme Eg: ddc8XXXXXXXXXXXXXXXXXXXXc1157]"</span> <span class="token punctuation">-</span> <span class="token string">"allow_es_settings_modification=true"</span> <span class="token punctuation">-</span> <span class="token string">"JAVA_OPTS=-Xms2g -Xmx2g"</span> <span class="token punctuation">-</span> <span class="token string">"ENT_SEARCH_DEFAULT_PASSWORD=changeme"</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> 3002<span class="token punctuation">:</span><span class="token number">3002</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'elasticsearch'</span><span class="token punctuation">]</span></code></pre></div> <p>Note that I haven’t exposed the <code class="language-text">elasticsearch</code> service to the host, but feel free to do so by adding <code class="language-text">ports</code> section under the service definition.</p> <p>First, set the <code class="language-text">ENT_SEARCH_DEFAULT_PASSWORD</code> to something you can remember. This will be the default password to be able to login in to the Enterprise Search instance.</p> <p>For the <code class="language-text">secret_management.encryption_keys</code> use the following command to generate a secure key. Note that should be at least one value in that list.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">openssl rand <span class="token parameter variable">-hex</span> <span class="token number">32</span></code></pre></div> <p>Don’t worry about setting up <code class="language-text">elasticsearch.password</code> for now as we will be setting it up in a later step.</p> <h3>Configuring Elasticsearch</h3> <p>If you check the logs for <code class="language-text">enterprisesearch</code> container now, you’ll see that it’s failing to authenticate against our <code class="language-text">elasticsearch</code> instance. Don’t worry about it for now. We are going to set it in the below step.</p> <p>To fix the auth, let’s connect to our <code class="language-text">elasticsearch</code> container. The easiest way would be to do that from the Docker Desktop interface, or you could use your shell-fu as well 🤺</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA3klEQVR42n2QyW7DMAxE/f9/FxQ9dEMLtDUsL6FE7ZLjKaU0QA5FD08iJXIw5MDsMGrCOE7QZOB9hHMBzvqO/Yf272+4az60Q0syEcEYBrMVGFZE2WVYnxHjXySkvMOGijMnbJxhpL4LOh+wBId12XAmLcJGhBLIXxtqqSh35FxwXA68fiqc3jd80IEnVfFFFUMbkcXhtyFM0wylln6vy4pNByhKMDYiSJ3/pa2klB2nh0c8v7zhsu+IYiqGiMEYC60Zszib5xWzuGyQ7JO06xht0eruaT1t9BBSj2/vP9lWf16E8+KPAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-elastic-workplace-search-docker-1.png" title="" src="/static/9c98475aed3888b9cc0e0c34124cc772/5a190/setting-up-elastic-workplace-search-docker-1.png" srcset="/static/9c98475aed3888b9cc0e0c34124cc772/772e8/setting-up-elastic-workplace-search-docker-1.png 200w, /static/9c98475aed3888b9cc0e0c34124cc772/e17e5/setting-up-elastic-workplace-search-docker-1.png 400w, /static/9c98475aed3888b9cc0e0c34124cc772/5a190/setting-up-elastic-workplace-search-docker-1.png 800w, /static/9c98475aed3888b9cc0e0c34124cc772/f6f7a/setting-up-elastic-workplace-search-docker-1.png 1009w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once logged in, run the following command.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bin/elasticsearch-setup-passwords auto</code></pre></div> <p>Once you get the auto-generated passwords for each user, you will be able to see the following.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Changed password <span class="token keyword">for</span> user apm_system PASSWORD apm_system <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user kibana_system PASSWORD kibana_system <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user kibana PASSWORD kibana <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user logstash_system PASSWORD logstash_system <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user beats_system PASSWORD beats_system <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user remote_monitoring_user PASSWORD remote_monitoring_user <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX Changed password <span class="token keyword">for</span> user elastic PASSWORD elastic <span class="token operator">=</span> XXXXXXXXXXXXXXXXXXXX</code></pre></div> <p>Make note of the password for <code class="language-text">elastic</code> as we will be using that to configure the <code class="language-text">enterprisesearch</code> instance.</p> <p>Or you could run the following command to quickly generate and grab the password for the <code class="language-text">elastic</code> user.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bin/elasticsearch-setup-passwords auto <span class="token parameter variable">-b</span> <span class="token operator">|</span> <span class="token function">grep</span> <span class="token string">"PASSWORD elastic ="</span> <span class="token operator">|</span> <span class="token function">cut</span> <span class="token parameter variable">-d</span> <span class="token string">'='</span> <span class="token parameter variable">-f2</span> <span class="token operator">|</span> <span class="token function">xargs</span></code></pre></div> <h3>Configuring Elastic Enterprise Search</h3> <p>Since we have already set up the ES instance in the above step, setting up EES will be easy.</p> <p>This time we need to connect to the <code class="language-text">enterprisesearch</code> container and update the <code class="language-text">elasticsearch.password</code> environment variable.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">vi</span> config/enterprise-search.yml</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 31.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABTElEQVR42p3OMU/CQBgG4AuUwkQLlIa0Urj2WgkTA+CigCP+KRcdCoMjA4MJgwMDcRITBiANaSBoHPwBxgH0B5gIxX5eL2HQwcHhyffmvst7h67Pr8Tx8ObiwXVaS2dmLx3HfnRnzPNyYT+5rn03GNj3twN7Nhqx/WI6/WE+mbTm0/Flv98XkK7rZu3keNOo14Hy67UaBE4bDahWKmDoOiiKwqiqCge/aNmsL6VScNZs7nq9HkGSJJkY6x+5fP7LMIwtxtjTcjkvmUx6sVjMQwj9KRQKbenclcvlz263ywotYpCNZVlgmqZPCIEg72maBplMBgRBAHqXSSQSjCiKkE6nffowHFWru06nY9JyhGVZfqGLd47jVpFIZB2gmc1wOLzen0Wj0TXP8yzvJy1b0Y63Uqn02m63cVDIF4tFQn9ToPnwnwrxeJzQHv4bB1CdQcWC7DoAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-elastic-workplace-search-docker-2.png" title="" src="/static/c75d88c97d741b99cf920e9891b5a42a/5a190/setting-up-elastic-workplace-search-docker-2.png" srcset="/static/c75d88c97d741b99cf920e9891b5a42a/772e8/setting-up-elastic-workplace-search-docker-2.png 200w, /static/c75d88c97d741b99cf920e9891b5a42a/e17e5/setting-up-elastic-workplace-search-docker-2.png 400w, /static/c75d88c97d741b99cf920e9891b5a42a/5a190/setting-up-elastic-workplace-search-docker-2.png 800w, /static/c75d88c97d741b99cf920e9891b5a42a/cec12/setting-up-elastic-workplace-search-docker-2.png 1187w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Update <code class="language-text">CHANGE_THIS</code> with the password you received for <code class="language-text">elastic</code> user in the above step. Make sure to restart the container once that’s done and navigate to <code class="language-text">localhost:3002</code> You should be able to see the following welcome screen.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 76.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWElEQVR42q2TTU7DMBCFc3Wu0DPAGdiCxAaBxAYJsUCiLWpL06ZJbMfjn/gxNlkgNQ4U1dKTktj58mYyr2hkg3JX4nNbYl9V0FrDGPNnaU2QLGIJqVDITqPjG00GFA/Q6aJBkVVY62DOJKUNiqkDZOyRps5rsuPA+KJ1Hn3o0ffhh3p438N6PwrPAjX30jNg1ayw2mxwqBu0QkKqDnH5HNBkgTa52ZYl1usNynLHk7DDmuFCiOQ2ujmqjDI9jH8uBGA+X2CxWCbIoa7ZpUij4vljYw6JKAe0yUXdtqiG2VRKJddxuUzJWSANJZPp0NQVnHOMCbF7Qw//4TAw8OVD4O61wf1bi+elxNNc4X3v0954DyeAQI/LB42L6xazG4nZreRrgavHLu11dGLJsSwEnkWnWYb7FuDio5Afm0lgzLWKwed8fstAdCbFK+6NAjXlozcWu98iGIFffxaQK/bU+goAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-elastic-workplace-search-docker-3.png" title="" src="/static/ed28b9df061f4dae8a835fad4d56b7d2/5a190/setting-up-elastic-workplace-search-docker-3.png" srcset="/static/ed28b9df061f4dae8a835fad4d56b7d2/772e8/setting-up-elastic-workplace-search-docker-3.png 200w, /static/ed28b9df061f4dae8a835fad4d56b7d2/e17e5/setting-up-elastic-workplace-search-docker-3.png 400w, /static/ed28b9df061f4dae8a835fad4d56b7d2/5a190/setting-up-elastic-workplace-search-docker-3.png 800w, /static/ed28b9df061f4dae8a835fad4d56b7d2/c1b63/setting-up-elastic-workplace-search-docker-3.png 1200w, /static/ed28b9df061f4dae8a835fad4d56b7d2/77f8f/setting-up-elastic-workplace-search-docker-3.png 1353w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Log in with the <code class="language-text">enterprise_search</code> and the password that you set for <code class="language-text">ENT_SEARCH_DEFAULT_PASSWORD</code> in the <code class="language-text">docker-compose-yml</code> file.</p> <p>Also, if you have a look at the container logs you will be able to see the following as well. If you can see the below, that means you are good to go!</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token comment">#########################################################</span> <span class="token important">***</span> Default user credentials have been setup. These are only printed once<span class="token punctuation">,</span> so please ensure they are recorded. <span class="token important">***</span> <span class="token key atrule">username</span><span class="token punctuation">:</span> enterprise_search <span class="token key atrule">password</span><span class="token punctuation">:</span> XXXXXXXXXXXXXXXXXXXX <span class="token comment">#########################################################</span></code></pre></div> <p>Once logged in, you will be able to see the following dashboard! 🙌</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 83%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACfUlEQVR42o1U224TMRDNP/AJfAb0M8oPIPGEkLgUFRAUPiSCJ+CpD7wjVSqqFCm8VAIRlbRpQzbZbDbZ7K7tXd+GM04CpGpRrJy1PR4fnxmP0zjtnb5NkqSZptPmIIqb5/0IGAVEESMO/eAa9AdR8/Q8anbPLt59abdvNj5+eE+y1j7LC/q3eU8kLZEACkOkMXew20vwS3f+dDqdrcaDR7ukKmNG44nN8tLWWgdfY709vpD265mw335J2x1J+3Mo7AnQGaqAH0A0C/6GCbvd7u3GzvPXBJMfjlNK0oxAGI601lF3mNPJIKfz0ZwmmaCyciSxLCFXAWLZrxSCcKvxFIRsyAsBpTUZYwOhQ8xVpaiuK5JS4gCDNLgA3u/9Cm6dcOfZXjAoVUOd+UPITdWa0umMZtOUsnlBNdYYc+R7ksSUFyWis+uET3ZfBYNU1RohX0oFxf14RsdnU5pmOWmsWxBMc0nf+znloiJzmfDx7ssrCTlk8pb6U0OfjkVQZpBXco7GOdsklcpguiEhN85rls1J15IKjItSINySpFI40FEmDIlqQ0JOuIJNCEkaNgtlfPNu2aOsiAVb5zcMGRuTZILLyEPejDEhsVxWfBjnrqqqMN6IkPNVyDr4FkoHAgbfPBNLqUI5bUzoEArnSkFFCNctwl1hVYc8vorQCal8hTeN0uBFjzD9LMs9ai6MQeihMCCMkcQVaPHMF2/54c4LNtRQqKFQw4HfXgAuAz+rQazj8UQP40SPk1THQF4KDQFhD+9nwl6vd6uxt/eGlorouuaXhf6f9vff5v69u9utdnv788HB9sHhYcDR0dEaWq3WAmG8mF/2Ae7s7+/f+A37meSQOC5BzAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-elastic-workplace-search-docker-4.png" title="" src="/static/e86900c9e46b29be3401ebb7f12033fc/5a190/setting-up-elastic-workplace-search-docker-4.png" srcset="/static/e86900c9e46b29be3401ebb7f12033fc/772e8/setting-up-elastic-workplace-search-docker-4.png 200w, /static/e86900c9e46b29be3401ebb7f12033fc/e17e5/setting-up-elastic-workplace-search-docker-4.png 400w, /static/e86900c9e46b29be3401ebb7f12033fc/5a190/setting-up-elastic-workplace-search-docker-4.png 800w, /static/e86900c9e46b29be3401ebb7f12033fc/c1b63/setting-up-elastic-workplace-search-docker-4.png 1200w, /static/e86900c9e46b29be3401ebb7f12033fc/29007/setting-up-elastic-workplace-search-docker-4.png 1600w, /static/e86900c9e46b29be3401ebb7f12033fc/b29d9/setting-up-elastic-workplace-search-docker-4.png 1989w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Indexing sample data</h3> <p>Once you log in there will be many options available for you to import from. To keep things simple, I will be using a <a href="https://www.elastic.co/guide/en/workplace-search/7.13/workplace-search-custom-api-sources.html">custom source</a>. Once you have set it up, you can do a curl request to index some sample data.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml">curl <span class="token punctuation">-</span>X POST http<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3002/api/ws/v1/sources/&lt;put the source id here<span class="token punctuation">></span>/documents/bulk_create \ <span class="token key atrule">-H "Authorization</span><span class="token punctuation">:</span> Bearer &lt;put the access token here<span class="token punctuation">></span>" \ <span class="token key atrule">-H "Content-Type</span><span class="token punctuation">:</span> application/json" \ <span class="token punctuation">-</span>d '<span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token key atrule">"id"</span> <span class="token punctuation">:</span> <span class="token number">1234</span><span class="token punctuation">,</span> <span class="token key atrule">"title"</span> <span class="token punctuation">:</span> <span class="token string">"The Meaning of Time"</span><span class="token punctuation">,</span> <span class="token key atrule">"body"</span> <span class="token punctuation">:</span> <span class="token string">"Not much. It is a made up thing."</span><span class="token punctuation">,</span> <span class="token key atrule">"url"</span> <span class="token punctuation">:</span> <span class="token string">"https://example.com/meaning/of/time"</span><span class="token punctuation">,</span> <span class="token key atrule">"created_at"</span><span class="token punctuation">:</span> <span class="token string">"2019-06-01T12:00:00+00:00"</span><span class="token punctuation">,</span> <span class="token key atrule">"type"</span><span class="token punctuation">:</span> <span class="token string">"list"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token key atrule">"id"</span> <span class="token punctuation">:</span> <span class="token number">1235</span><span class="token punctuation">,</span> <span class="token key atrule">"title"</span> <span class="token punctuation">:</span> <span class="token string">"The Meaning of Sleep"</span><span class="token punctuation">,</span> <span class="token key atrule">"body"</span> <span class="token punctuation">:</span> <span class="token string">"Rest, recharge, and connect to the Ether."</span><span class="token punctuation">,</span> <span class="token key atrule">"url"</span> <span class="token punctuation">:</span> <span class="token string">"https://example.com/meaning/of/sleep"</span><span class="token punctuation">,</span> <span class="token key atrule">"created_at"</span><span class="token punctuation">:</span> <span class="token string">"2019-06-01T12:00:00+00:00"</span><span class="token punctuation">,</span> <span class="token key atrule">"type"</span><span class="token punctuation">:</span> <span class="token string">"list"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token key atrule">"id"</span> <span class="token punctuation">:</span> <span class="token number">1236</span><span class="token punctuation">,</span> <span class="token key atrule">"title"</span> <span class="token punctuation">:</span> <span class="token string">"The Meaning of Life"</span><span class="token punctuation">,</span> <span class="token key atrule">"body"</span> <span class="token punctuation">:</span> <span class="token string">"Be excellent to each other."</span><span class="token punctuation">,</span> <span class="token key atrule">"url"</span> <span class="token punctuation">:</span> <span class="token string">"https://example.com/meaning/of/life"</span><span class="token punctuation">,</span> <span class="token key atrule">"created_at"</span><span class="token punctuation">:</span> <span class="token string">"2019-06-01T12:00:00+00:00"</span><span class="token punctuation">,</span> <span class="token key atrule">"type"</span><span class="token punctuation">:</span> <span class="token string">"list"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span>'</code></pre></div> <p>Make sure to change <code class="language-text">&lt;put the source id here></code> and <code class="language-text">&lt;put the access token here></code> in the above. You can get those values from the Credentials section of the custom source.</p> <p>You should be able to see the content once the request has been made. That’s it! 🎉</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwElEQVR42p1RTUvcUBTNShAGClXBwYXbLgT/gqiLLuzCgIgOaEFR/BuzmJ/gQuyDWfRHtBRcuRVKMXRmyKCkOuZjEjPJJC95L+/2vhenTKUM2guH9+7l5LxzbjTbspZdt9e8v7M+3d5axHFcYnQ65Or7NflhtMjPVpeYN3fE7Fr/RMu0SKdrnSPv88XF5ZJWr9f1b1++AgcQAsqSp2x4UUCBmFSSWwjB5b3dbn/QpqemN3e2a+D4g/zml8N6tsf6/iNDMRbFQxYOBizPc4b8SaBPghtaZf6dvrt/DPdOIHq2C7brwYPjKWeclyh9THA57rDydlGv7R9BGFMRJ7m0DznjwDgCTymMH/y5l305k5B9Sum44Lxe+3gMw5SKMhk8ueMQPIbg9X1gOI+iGFzPVxgmqeLJRySn7wdK0DRNKbiAgkcQRJmwA6oijuJSSiFJU+UE9wgJCsmeMTaKipwM0vSZw929Q3SYi35IIcNXeTHa3cvqrx1OvZnTt2oHUKg57gqFculSiP8TXKxWN08ODyCMEvwpCcTDBCIExlAxX4Isy5SgYRgbWrVamX2/vrJ2ekZWz8ZAyOvQbDbXG43GzG9bBfPCqBl8OgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="setting-up-elastic-workplace-search-docker-5.png" title="" src="/static/649802a93da7dfec7661f9f2c96beaa6/5a190/setting-up-elastic-workplace-search-docker-5.png" srcset="/static/649802a93da7dfec7661f9f2c96beaa6/772e8/setting-up-elastic-workplace-search-docker-5.png 200w, /static/649802a93da7dfec7661f9f2c96beaa6/e17e5/setting-up-elastic-workplace-search-docker-5.png 400w, /static/649802a93da7dfec7661f9f2c96beaa6/5a190/setting-up-elastic-workplace-search-docker-5.png 800w, /static/649802a93da7dfec7661f9f2c96beaa6/c1b63/setting-up-elastic-workplace-search-docker-5.png 1200w, /static/649802a93da7dfec7661f9f2c96beaa6/6f92b/setting-up-elastic-workplace-search-docker-5.png 1501w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>In this article, we looked at setting up Elastic Workplace Search with Docker containers to improve the developer experience without having to use a cloud option for dev work. We looked at two options a shorted version and a longer version where we dived deeped into how to set it up manually as well.</p> <h3>References</h3> <ol> <li><a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-install.html">https://www.elastic.co/guide/en/workplace-search/current/workplace-search-install.html</a></li> <li><a href="https://docs.docker.com/compose/">https://docs.docker.com/compose/</a></li> </ol><![CDATA[A closer look at commands and args in Docker containers]]>https://www.sahansera.dev/closer-look-at-docker-commands-args/https://www.sahansera.dev/closer-look-at-docker-commands-args/Tue, 22 Jun 2021 00:00:00 GMT<p>Today we will be looking at how we can make use of commands and args in Docker to run our own processes when we spin up containers. This could be useful if you want to run one-off tasks without having to create your own images.</p> <p>Let’s imagine that we want run a bash command. What’s the easiest way to do this? Just by spinning up a container using a lightweight image like busybox, ubuntu, debian? For instance, let’s say we use Debian as our base image.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run debian <span class="token builtin class-name">echo</span> hello</code></pre></div> <p>We will get back “hello”, which is expected. What would happen if we check if the container is running?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJElEQVR42qXMUUuDUBQHcL3q3DJIM9C7qTPHdLtqw2y5CEEYA2EbSD4EPXTr2/bQ40Z9j+ilwNN1D/sCXfhxLv//4XAbnJ48bO6fsjx7WcwXNC8KWiyXtK5rut1WzJau12u6WpW0LMtDVlU1zfOCpumcuX3OsrvXolg+EkJOOV3XgzgOf/zpFPwobmaza7hJEgivIpiwjJAQSBhBMCEw9gNwhi5ceh44zhBMEwPGuDFxHwaW86VpWswpihLajvurGwac9weNqupgs0VV06Db68GZqoLc7cLIn4LljgDbLlwYGCRRhE6n0/ZNO2VZ/pYkKeHY8wRBeOcR+kCCsGP/vYiEfTtZt+d5/ggdcvGA54/dDiH0ybyx/XF7UGQMxmasf2hviH/7KVhPzdYYsQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-1.png" title="" src="/static/f95d3de0aaad7b5732e819a67d288262/5a190/closer-look-at-docker-commands-args-1.png" srcset="/static/f95d3de0aaad7b5732e819a67d288262/772e8/closer-look-at-docker-commands-args-1.png 200w, /static/f95d3de0aaad7b5732e819a67d288262/e17e5/closer-look-at-docker-commands-args-1.png 400w, /static/f95d3de0aaad7b5732e819a67d288262/5a190/closer-look-at-docker-commands-args-1.png 800w, /static/f95d3de0aaad7b5732e819a67d288262/c1b63/closer-look-at-docker-commands-args-1.png 1200w, /static/f95d3de0aaad7b5732e819a67d288262/29007/closer-look-at-docker-commands-args-1.png 1600w, /static/f95d3de0aaad7b5732e819a67d288262/fbae3/closer-look-at-docker-commands-args-1.png 2260w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The container has stopped running. Containers are not meant to host an operating system compared to a VM. When you run a basic container like Debian or Ubuntu, it will exit once you perform a <code class="language-text">docker run debian</code></p> <p>However, in cases like Nginx, or Redis they will keep running unless you shut it down or it crashes for some reason. How does this happen?</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Under the hood</h3> <p>Let’s have a look at their <code class="language-text">Dockerfile</code>s. Note that these are oversimplified versions of these files (except for Debian).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAME/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB3AoD/8QAGhAAAQUBAAAAAAAAAAAAAAAAEgECAxEUE//aAAgBAQABBQJuqk1BH3H/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAcEAABAwUAAAAAAAAAAAAAAAASAAIRISMxQnH/2gAIAQEABj8CcQYp1OkS1VwZX//EABsQAAICAwEAAAAAAAAAAAAAAAAhETEBQVGh/9oACAEBAAE/Ic0EtIqdSej0GKOH/9oADAMBAAIAAwAAABDzz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EAB0QAAICAQUAAAAAAAAAAAAAAAERADEhQVGBocH/2gAIAQEAAT8QNXu8C80nD0tUE3biHiDbVgPXP//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-2.jpg" title="" src="/static/a58c3692b8f52023dda40fc11ff10a99/4b190/closer-look-at-docker-commands-args-2.jpg" srcset="/static/a58c3692b8f52023dda40fc11ff10a99/e07e9/closer-look-at-docker-commands-args-2.jpg 200w, /static/a58c3692b8f52023dda40fc11ff10a99/066f9/closer-look-at-docker-commands-args-2.jpg 400w, /static/a58c3692b8f52023dda40fc11ff10a99/4b190/closer-look-at-docker-commands-args-2.jpg 800w, /static/a58c3692b8f52023dda40fc11ff10a99/e5166/closer-look-at-docker-commands-args-2.jpg 1200w, /static/a58c3692b8f52023dda40fc11ff10a99/b17f8/closer-look-at-docker-commands-args-2.jpg 1600w, /static/a58c3692b8f52023dda40fc11ff10a99/f6bb7/closer-look-at-docker-commands-args-2.jpg 2705w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Each of these files defines a <code class="language-text">CMD</code> instruction at the bottom which tells them to run some command. In the case of Debian, it issues just <code class="language-text">bash</code> which is just a shell, not a process. If you already spotted, both <code class="language-text">Redis</code> and <code class="language-text">nginx</code> Dockerfiles have definitions to spin up their processes - the Redis server and Nginx web server, respectively.</p> <h3>The <code class="language-text">CMD</code> instruction</h3> <p>You would be wondering how would one go about customising which commands to run when you spin up a container. One way to specify commands when you start a container is by issuing the name of the executable when we run the container. For example,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run debian <span class="token builtin class-name">echo</span> hello-world</code></pre></div> <p>If you check the container status, you will see that your container has stopped. Can we make this permanent? Yes, that’s why we need to have a Dockerfile to specify what’s our desired state when the container runs.</p> <p>Say we use the <code class="language-text">CMD</code> instruction in our Dockerfile, there are 3 ways to specify this according to <a href="https://docs.docker.com/engine/reference/builder/#cmd">Docker docs</a>.</p> <ul> <li><code class="language-text">CMD ["executable","param1","param2"]</code> (<em>exec</em> form, this is the preferred form)</li> <li><code class="language-text">CMD ["param1","param2"]</code> (as <em>default parameters to ENTRYPOINT</em>)</li> <li><code class="language-text">CMD command param1 param2</code> (<em>shell</em> form)</li> </ul> <blockquote> <p>💡 There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.</p> </blockquote> <p><a href="https://github.com/sahansera/DockerCommandsAndArgs/blob/main/Test1.Dockerfile">Test1.Dockerfile</a></p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> debian</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"printf"</span>, <span class="token string">"Hello from Test1"</span>]</span></code></pre></div> <p>How can we dynamically define what gets printed? One caveat here is that if we are to pass in command line args it <em>will not get appended</em> to the existing CMD instruction we have set. Docker will try to execute as a command and our CMD instruction will get replaced. Let’s give this a test.</p> <p>If you run <code class="language-text">docker run test1 hello</code>, it will result in a “starting container process caused: exec: “hello”: executable file not found in $PATH: unknown.” error.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 28.500000000000004%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABPUlEQVR42q3PT07CQBQG8OkCSosRhGJacGMof1XQJTbcgqXhCHoMXbDkKjZuXFSgJFBaE2OKusM1LBrTRcnL55ToAUx8yS/zvcm8yQzD5rMFRF0Al5wRC8LQCANuvTYinqMwMIIgMEKeEUXG77kf8Vz3a7NpM8YE5tmPr8uPF/j+khaei6k9xdiyMHdmcJw5Fo7D92xMJhOMrCc8mCZM8x6jkQXPdeF5LjmLZ9hj2x8Oh3vsrH3h1/QajhvNrd46J73SpJZepfppnfRqhaq1BjWaJ1QuV0jTiqRyxdIRz6XdqqrFrShKuOr33/hLs0xRCstsJot9JU8HqoZc7hCFTAa5fB6JRBKimIIkyUgm4yxCkuVdL6ZSkNPpuCf+VfR6vffdhbyuuQF3y939lSAI8dyg0+ncrFYrif1jCbFvRDPA2ncdrt0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-3.png" title="" src="/static/53ecb5794a150e415c6de7f322750391/5a190/closer-look-at-docker-commands-args-3.png" srcset="/static/53ecb5794a150e415c6de7f322750391/772e8/closer-look-at-docker-commands-args-3.png 200w, /static/53ecb5794a150e415c6de7f322750391/e17e5/closer-look-at-docker-commands-args-3.png 400w, /static/53ecb5794a150e415c6de7f322750391/5a190/closer-look-at-docker-commands-args-3.png 800w, /static/53ecb5794a150e415c6de7f322750391/91f10/closer-look-at-docker-commands-args-3.png 992w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We can still pass in a command like so: <code class="language-text">docker run test1 printf hello</code> but that beats the purpose of having a Dockerfile in the first place.</p> <h3>The <code class="language-text">ENTRYPOINT</code> instruction</h3> <p>How can we specify something to run always so that we only need to specify its arguments. Let’s create another file called Test2.Dockerfile.</p> <p><a href="https://github.com/sahansera/DockerCommandsAndArgs/blob/main/Test2.Dockerfile">Test2.Dockerfile</a></p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> debian</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"printf"</span>, <span class="token string">"Hello from %s\n"</span>]</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"Test2"</span>]</span></code></pre></div> <p>The <code class="language-text">ENTRYPOINT</code> instruction says Docker that whatever we have specified there, must be run when the container starts. In other words, if we are to pass in arguments, they will get <em>appended</em> to what we have specified there as the ENTRYPOINT.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-t</span> <span class="token builtin class-name">test</span> <span class="token parameter variable">-f</span> Test2.Dockerfile <span class="token builtin class-name">.</span></code></pre></div> <p>If we run this now, we get the following.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABYElEQVR42q3RvWrCUBQH8JvErxodqpjFwbQoWlyiIKXkLXT2CdpX6Nfk4Cp9IkGXihVjk9QqmoiIQ0AzVOz19CTaUAotFBr4cS435xz+IQRugX1bPmcte1aEzUZyLBYLyURj05RMZFkW3u2rbdseAPhUWK2WOUIIQ+r1B2GkPI6m8xEY5mw7UFXa6XTooN+nmq7R4esLVfFOURTa6z3RVqtFm80mbbfbVNd1OpmMt6o2hG63O63VasckFoslzy9kQyoUIV8q0QxW8SQDefEUcmcZSGfTkEqJIIoiCIIA8XgccMatjkQisQuGjqBarc7X67WAKUmS5yNGIBgAfzhMA5Eo+PwhCHEc8DwPHOcD7PkRy7I7p5bL5Tl++n4hmhwatoh+xTCM5/s7By58PyyceQux2UBwGASyP7t+S+fgOM5NWKlUvIRRdInu0TW6+QtM6MzcybJ81Wg0IsT91f/4fABJ6uScCYdEewAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-4.png" title="" src="/static/dbb90d816be54985423774870e4ba81f/5a190/closer-look-at-docker-commands-args-4.png" srcset="/static/dbb90d816be54985423774870e4ba81f/772e8/closer-look-at-docker-commands-args-4.png 200w, /static/dbb90d816be54985423774870e4ba81f/e17e5/closer-look-at-docker-commands-args-4.png 400w, /static/dbb90d816be54985423774870e4ba81f/5a190/closer-look-at-docker-commands-args-4.png 800w, /static/dbb90d816be54985423774870e4ba81f/636c2/closer-look-at-docker-commands-args-4.png 911w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Cool! If we pass in a parameter now, it gets picked up and <em>replaces</em> the <code class="language-text">CMD</code> instruction we had at line no. 3; but it will still show the output as we have defined in our ENTRYPOINT. This makes sense since there can be only one CMD instruction per container.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZklEQVR42q3Ry07CQBQG4GnLTQsLymVDAoI0YIxJYSExfQvY4hPoK3hbsWBLfCIWspCwkBaoVrQl2LBoIm4aHI6HlhA10cTESb7MSebMyT8ZAhfAwkwt2M6kDAASgCPZtiVZyDRNyUK2bUuW5dWOY6M3l9fvKr3OZkVCCEOazeukrtzq5osOhmku1KFKu90uVft3dKQN6YOu0cFgQBWlT1VVpZ3ODW232+6uaSM6Hj8uhqN76PV6z41GI0oEQUhVjmRDKpXh4LBCRakMO1kR9jM5KO6JkC/kIZ3OQDabg928CPF4HAQhCrFYzJVIJJbB0BbU68fT+XyexJQkxfNhIxAMgJ/fpv5wBHz+EIQ4DnieB47zAfb8iGXZ5WqvVqtTfLo3ED2tGxaIfsYwzBffz3Hg+3rgZDMQGw0E60tAvNr1W7oVjuPchLVabZMwgk7QFTpD53+BCVd3LmVZPm21WmHifvU/rg9sGOUnlcBfYAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-5.png" title="" src="/static/f0d79600acb0ae4c0db45455f0332fe7/5a190/closer-look-at-docker-commands-args-5.png" srcset="/static/f0d79600acb0ae4c0db45455f0332fe7/772e8/closer-look-at-docker-commands-args-5.png 200w, /static/f0d79600acb0ae4c0db45455f0332fe7/e17e5/closer-look-at-docker-commands-args-5.png 400w, /static/f0d79600acb0ae4c0db45455f0332fe7/5a190/closer-look-at-docker-commands-args-5.png 800w, /static/f0d79600acb0ae4c0db45455f0332fe7/636c2/closer-look-at-docker-commands-args-5.png 911w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You would be thinking what’s the purpose of having CMD if it gets replaced? It’s really handy when you want to specify a default value.</p> <p>What would happen if we specify multiple arguments?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZ0lEQVR42q3RXUvCYBQH8GcvUIKDWkxclpuzt5tq9kr4LfTaT1BfobcrL7yVPpSIeaPbbChrExteDLSLxB5PZ8O8siBo8GPscM7Z/+EhcA/sx9DYD977JzABHSYT3fd93UM9z9M9FAQB1rw5PxLWAOBbbjQaHhBCGFKpPCV67UbXfeuC4/WnbcuijcYzNVst+mJ3qN21qWmatIXfhmHQer1Oa7VapNOxqOP0pqZlQ7PZfC2Xy+tEFMXU2eWVm8udwtH5Bd07PIa0okF2Kw2qpoC2q4GiqJDJYC27A6qaAU3LQjIpA86CJEmzldUYlEqlwXg8TmDKWCopb7rixgaIskyl7TTEhTWIsRwIQhx4ngds+hHLsrPwXSgUBnj0cCFJMQzjICAsMyUMQ7GJ8ojjWIr1pXAugr2f84X9xULkzv9If0uzDMdxUcJisbhIKKBr9Ihu0d1fYMJw5iGfz99Uq9U4ia76H58v6WTlwkxzbFgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="closer-look-at-docker-commands-args-6.png" title="" src="/static/83ffc7d92c4cf7e05b3c11f080e299a2/5a190/closer-look-at-docker-commands-args-6.png" srcset="/static/83ffc7d92c4cf7e05b3c11f080e299a2/772e8/closer-look-at-docker-commands-args-6.png 200w, /static/83ffc7d92c4cf7e05b3c11f080e299a2/e17e5/closer-look-at-docker-commands-args-6.png 400w, /static/83ffc7d92c4cf7e05b3c11f080e299a2/5a190/closer-look-at-docker-commands-args-6.png 800w, /static/83ffc7d92c4cf7e05b3c11f080e299a2/636c2/closer-look-at-docker-commands-args-6.png 911w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>What happend here? Let me know in the comments below what are your thoughts on this 😊</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>In this article we looked at passing in commands and args to container when we spin them up. In the next article, we will compare this to a Kubernetes Pod definition and figure out how we can do the same.</p> <h3>References</h3> <p><a href="https://docs.docker.com/engine/reference/builder/">https://docs.docker.com/engine/reference/builder/</a></p><![CDATA[ASP.NET Core Health Checks]]>https://www.sahansera.dev/aspdotnet-core-health-checks/https://www.sahansera.dev/aspdotnet-core-health-checks/Thu, 25 Mar 2021 00:00:00 GMT<p>When you are developing a project where you have multiple services talking to each other, it would be hard to see their service health instantly.</p> <p>This article will look at how we can configure ASP.NET Core health checks and look into what kind of different metrics we can gather from it.</p> <blockquote> <p>💡 Follow along with the code samples for this blog post from this <a href="https://github.com/sahansera/AspNetCoreHealthChecks">repo</a>.</p> </blockquote> <h3>The setup</h3> <p>I will be using <a href="http://asp.NET">ASP.NET</a>’s MVC and template, as most of you are familiar with it. However, you can also use other project types such as API, Console or even Blazor.</p> <p>Let’s scaffold an MVC app.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new mvc <span class="token parameter variable">-n</span> Monitor dotnet new sln dotnet sln <span class="token function">add</span> Monitor</code></pre></div> <p>First, we need to install the HealthChecks package.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> Monitor/ dotnet <span class="token function">add</span> package AspNetCore.HealthChecks.UI</code></pre></div> <p>Now we are ready to hook it up to the middleware pipeline.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Application Configuration</h3> <p>Navigate to the <code class="language-text">Startup.cs</code> and, let’s add the following in the ConfigureServices method.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">services.AddHealthChecks<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>For starters, we will add an endpoint to spit out a JSON to show the app’s current status.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">UseEndpoints</span><span class="token punctuation">(</span>endpoints <span class="token operator">=></span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapControllerRoute</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">name</span><span class="token punctuation">:</span> <span class="token string">"default"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">pattern</span><span class="token punctuation">:</span> <span class="token string">"{controller=Home}/{action=Index}/{id?}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Mapping a new endpoint to see the health</span> endpoints<span class="token punctuation">.</span><span class="token function">MapHealthChecks</span><span class="token punctuation">(</span><span class="token string">"/health"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 780px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABAklEQVR42s2QSW+DMBCF/f//WaVeemgEAZKwGWKDAWP25XVwF7Xn9JCRnjwej56/GaZqjbPno64qSClR1w3KsvxUUUIKiYLq4i6gtca+79CtRsIFXl7fENzSP2JdP0CpCtM0wRiDcRzRU63ve3Tdoe4r76hntobzPENVGreYg+cF0kz+iA1kcDRv24Z13VA3jTUolIJL5NcwxPvJwck5ww8ucFwPrh8guN7g+RdkuUBRKshjGhJb1hXzstifD1NjOktb0yriJAXPcoRRjChOiCCje2bPLL/bt5JWVRHEt9hANG3bwrSGCFc8GkwKgYgIeMrt6A8b4p+DHbv7recjfHrDD4HXWd+5iYsVAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-1.png" title="" src="/static/052657e3d2f468b7ee11e42d031ac667/a1792/aspdotnet-core-health-checks-1.png" srcset="/static/052657e3d2f468b7ee11e42d031ac667/772e8/aspdotnet-core-health-checks-1.png 200w, /static/052657e3d2f468b7ee11e42d031ac667/e17e5/aspdotnet-core-health-checks-1.png 400w, /static/052657e3d2f468b7ee11e42d031ac667/a1792/aspdotnet-core-health-checks-1.png 780w" sizes="(max-width: 780px) 100vw, 780px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Well, that isn’t particularly useful. This health checks package we are using provides many different output formats. They have made it extensible so that you can even use a custom output format. If you have many services talking to each other, it will make sense to use a format like JSON.</p> <blockquote> <p>💡 Feel free to skip this section and jump ahead to “Registering health checks” section to see the final result.</p> </blockquote> <h3>Health checks UI dashboard</h3> <p>We are interested in seeing is a Dashboard that comes out-of-the-box. We will use our current project as a monitoring app to probe in and check the health status of some other applications that are running. The following diagram explains our desired state.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 70.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHcKhGMX//EABoQAAICAwAAAAAAAAAAAAAAAAIRASEAEiL/2gAIAQEAAQUCgrvYWsXY3H//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAaEAABBQEAAAAAAAAAAAAAAAARAAEQImFR/9oACAEBAAY/AnbixWByCY//xAAaEAACAwEBAAAAAAAAAAAAAAAAAREhMVFh/9oACAEBAAE/IZIjHmqir1gkkdTMGsP/2gAMAwEAAgADAAAAELzP/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EFuv/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGRAAAgMBAAAAAAAAAAAAAAAAAREAITFB/9oACAEBAAE/EAqokmRRgvNqp64epYyQFzexone3ALA0qeIFB6dn/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-2.jpg" title="" src="/static/3b38887a21920c8226bf32bca797cbe6/4b190/aspdotnet-core-health-checks-2.jpg" srcset="/static/3b38887a21920c8226bf32bca797cbe6/e07e9/aspdotnet-core-health-checks-2.jpg 200w, /static/3b38887a21920c8226bf32bca797cbe6/066f9/aspdotnet-core-health-checks-2.jpg 400w, /static/3b38887a21920c8226bf32bca797cbe6/4b190/aspdotnet-core-health-checks-2.jpg 800w, /static/3b38887a21920c8226bf32bca797cbe6/75474/aspdotnet-core-health-checks-2.jpg 822w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s get to a point we can see something visually and start extending our solution on top of that. To be able to use the dashboard, it needs to have a backing store. We need to bring in a package called <code class="language-text">AspNetCore.HealthChecks.UI.InMemory.Storage</code></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package AspNetCore.HealthChecks.UI.InMemory.Storage</code></pre></div> <p>Let’s change the code in our Startup/ConfigureServices class. Replace the previous code with the following.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// ..</span> services<span class="token punctuation">.</span><span class="token function">AddHealthChecksUI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddInMemoryStorage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ..</span></code></pre></div> <p>Next, let’s map a new endpoint to be able to see the dashboard.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// ...</span> app<span class="token punctuation">.</span><span class="token function">UseEndpoints</span><span class="token punctuation">(</span>endpoints <span class="token operator">=></span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapControllerRoute</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">name</span><span class="token punctuation">:</span> <span class="token string">"default"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">pattern</span><span class="token punctuation">:</span> <span class="token string">"{controller=Home}/{action=Index}/{id?}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> endpoints<span class="token punctuation">.</span><span class="token function">MapHealthChecksUI</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//...</span></code></pre></div> <p>Now we are ready to run the application and see what we get.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">dotnet <span class="token keyword">new</span> webapi <span class="token operator">-</span>n Service1 dotnet <span class="token keyword">new</span> webapi <span class="token operator">-</span>n Service2 dotnet sln <span class="token keyword">add</span> Service1 Service2</code></pre></div> <p>We need to add the health checks middleware to the two projects we created in the above step.</p> <p>Startup.cs</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// ...</span> services<span class="token punctuation">.</span><span class="token function">AddHealthChecks</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ...</span> <span class="token punctuation">}</span> app<span class="token punctuation">.</span><span class="token function">UseEndpoints</span><span class="token punctuation">(</span>endpoints <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// ...</span> endpoints<span class="token punctuation">.</span><span class="token function">MapHealthChecks</span><span class="token punctuation">(</span><span class="token string">"/health"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HealthCheckOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ResponseWriter <span class="token operator">=</span> UIResponseWriter<span class="token punctuation">.</span>WriteHealthCheckUIResponse <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ...</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Just copy and add the following lines to the <code class="language-text">.csproj</code> files of the two services we created.</p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PackageReference</span> <span class="token attr-name">Include</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>AspNetCore.HealthChecks.UI<span class="token punctuation">"</span></span> <span class="token attr-name">Version</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>5.0.1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PackageReference</span> <span class="token attr-name">Include</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>AspNetCore.HealthChecks.UI.Client<span class="token punctuation">"</span></span> <span class="token attr-name">Version</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>5.0.1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span></code></pre></div> <p>Finally, you need to add the endpoints of the services to our Monitor project. Let’s add the following bit to the <code class="language-text">appsettings.json</code> file of the Monitor project. Head over to the <a href="https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks#sample-2-configuration-using-appsettingsjson">official docs</a> to learn more about the configuration.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token property">"HealthChecksUI"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"HealthChecks"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"Name"</span><span class="token operator">:</span> <span class="token string">"Service 1"</span><span class="token punctuation">,</span> <span class="token property">"Uri"</span><span class="token operator">:</span> <span class="token string">"https://localhost:5011/health"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"Name"</span><span class="token operator">:</span> <span class="token string">"Service 2"</span><span class="token punctuation">,</span> <span class="token property">"Uri"</span><span class="token operator">:</span> <span class="token string">"https://localhost:5021/health"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"EvaluationTimeInSeconds"</span><span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre></div> <blockquote> <p>💡 Don’t forget to take the port numbers your servers are running on from the corresponding launchSettings.json files. This could change depending on whether you are using IIS Express or Kestrel for development.</p> </blockquote> <p>If you fire up the 3 projects now, you will be able to see the health checks dashboard.</p> <p>The default URI for the dashboard UI is <code class="language-text">https://localhost:&lt;app port>/healthchecks-ui</code></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 41%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABtUlEQVR42n2Sy2oUQRSG53E0iqAobl24kTBRQdSd4NKn0IWowQzmGURBEJEZRE1PZzBjmAyo26zUhelJpulr3bq6a6p/T5WjG8WCnypOnfOdS1XnwsUubt2+g/k8Rp4XkFKBCwnBOL4lR3g2eIn1xw/Q6/Xw/MVTBOEWwu0h7t6/h0cbD7G5+QSv+6+wNXzv7Z1Tp8/j0upVZGmKgjMURYEkSSGEQFYW+DD+iFk0h1ACC6vQ2hZuheEI8RH5qRK21YAFTNOgs3LyHLpr1wkgoesaUikoVflK7cJidzLFdH+GKC4poQaTGlVtMHg7xJevCeK0BFeNF5MVOsdPnEX38g1wLlBV2oN+A41ZYG9viu8/DsHJmUsJRokrXWO0HWIWZ2RTyzuSUEsgVfgvYEPAT9MJDmcHKKl9ITj5MQJWmIwCJPEcxdLuJKX4P9BQy+OdHWRZ6udmjEFDc2rbFsPwDflXsNZ6u5PW+tcM167chCKYa7GuGwoyfndrTI+S57k/O5ADePvnd5RY/kni5IHHVs5gtXvNv2xRlFRN7r+P212VQRAgiiIKpllxDsaYP/cHfbr/G/gTQT00hTLixlEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-3.png" title="" src="/static/111c9c4611bf8f2d508c6b1ff6316c82/5a190/aspdotnet-core-health-checks-3.png" srcset="/static/111c9c4611bf8f2d508c6b1ff6316c82/772e8/aspdotnet-core-health-checks-3.png 200w, /static/111c9c4611bf8f2d508c6b1ff6316c82/e17e5/aspdotnet-core-health-checks-3.png 400w, /static/111c9c4611bf8f2d508c6b1ff6316c82/5a190/aspdotnet-core-health-checks-3.png 800w, /static/111c9c4611bf8f2d508c6b1ff6316c82/c1b63/aspdotnet-core-health-checks-3.png 1200w, /static/111c9c4611bf8f2d508c6b1ff6316c82/29007/aspdotnet-core-health-checks-3.png 1600w, /static/111c9c4611bf8f2d508c6b1ff6316c82/3a7f3/aspdotnet-core-health-checks-3.png 3076w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Under the hood, this small SPA dashboard polls the <code class="language-text">/healthchecks-api</code> URI which returns a JSON.</p> <h3>Registering health checks</h3> <p>Without bringing in any other dependencies let’s simulate the 3 states that it will report to us:</p> <p>Head over <a href="https://github.com/xabaril/AspNetCore.Diagnostics.HealthChecks#health-checks">here</a> to see a complete list of supported health checks. Let’s add some dummy health checks to see them in action in the dashboard.</p> <p>We will replace the code in <code class="language-text">Service1/Startup.cs</code> with the following.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddHealthChecks</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddCheck</span><span class="token punctuation">(</span><span class="token string">"Foo"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> HealthCheckResult<span class="token punctuation">.</span><span class="token function">Healthy</span><span class="token punctuation">(</span><span class="token string">"Foo is OK!"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">tags</span><span class="token punctuation">:</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token string">"foo_tag"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddCheck</span><span class="token punctuation">(</span><span class="token string">"Bar"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> HealthCheckResult<span class="token punctuation">.</span><span class="token function">Unhealthy</span><span class="token punctuation">(</span><span class="token string">"Bar is unhealthy!"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">tags</span><span class="token punctuation">:</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token string">"bar_tag"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddCheck</span><span class="token punctuation">(</span><span class="token string">"Baz"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> HealthCheckResult<span class="token punctuation">.</span><span class="token function">Degraded</span><span class="token punctuation">(</span><span class="token string">"Baz is degraded!"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">tags</span><span class="token punctuation">:</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token string">"baz_tag"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 19.499999999999996%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAqUlEQVR42k1PWw7DIAzj/ofaHXaA/XcftIQ3BerhTJWKZNkQJw7G2h2EiEetDcEHpJSRc1EupSKECOdE9RP0x5iwbV84Ebw+b5hSCrz3Cmpxh2q7/4M4LOeM4zhWqKgm7j5r7QpzyxfQS4NhyhgDow/kVvGNDv08dfC5OARuXtF71zuZYA8DiDmnelo7YW7DdV0YqyAtYy5z9IKk305a56HnCQZw2zuQbz8gKTYgfb+RegAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-4.png" title="" src="/static/b6c654a610a6ccdb4663c3a8dc90dc16/5a190/aspdotnet-core-health-checks-4.png" srcset="/static/b6c654a610a6ccdb4663c3a8dc90dc16/772e8/aspdotnet-core-health-checks-4.png 200w, /static/b6c654a610a6ccdb4663c3a8dc90dc16/e17e5/aspdotnet-core-health-checks-4.png 400w, /static/b6c654a610a6ccdb4663c3a8dc90dc16/5a190/aspdotnet-core-health-checks-4.png 800w, /static/b6c654a610a6ccdb4663c3a8dc90dc16/c1b63/aspdotnet-core-health-checks-4.png 1200w, /static/b6c654a610a6ccdb4663c3a8dc90dc16/29007/aspdotnet-core-health-checks-4.png 1600w, /static/b6c654a610a6ccdb4663c3a8dc90dc16/577cd/aspdotnet-core-health-checks-4.png 2609w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In my <a href="https://sahansera.dev/distributed-caching-aspnet-core-redis/">previous</a> blog post, I showed you how we could easily integrate with the Redis. I will be using <code class="language-text">AspNetCore.HealthChecks.Redis</code> package to configure the health checks.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package AspNetCore.HealthChecks.Redis</code></pre></div> <p>We will also add this configuration bit to its <code class="language-text">appsettings.json</code> file.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token property">"Redis"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"ConnectionString"</span><span class="token operator">:</span> <span class="token string">"localhost:5002"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre></div> <p>Let’s spin up a Redis docker container on port <code class="language-text">5002</code>.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">--name</span> redis-cache <span class="token parameter variable">-p</span> <span class="token number">5002</span>:6379 <span class="token parameter variable">-d</span> redis</code></pre></div> <p>Dashboard:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 23%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0UlEQVR42m2Q6w3CMAyEu/8QLMIMTAG06iNJE+cJbQ/bEkiV+PGpqc9nX9KN4wRhMRY5F6SU9XuCaymlv5q1Do6ZjcHldkUnTc6tCIHUsLBYSv0ZYoyY5wWGF9baTrT2wrp6DMPAgQxiKeiISA05Z5CYrQGFwLWZhxhOYNm08lKnSN8XST1Nk/ZI0uC9JEzwbMglc7LCSYMOvve9nqUmyFDPhu9/rVUHPZ4PvYUEk4Vda03F4zjwer9RGKkFIk1AFPUJRBf2ff+xbZs+U4xJEf0D6D2AdoGGVF4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-5.png" title="" src="/static/a7172bbc0ff4a7da49dc46f873d46e6a/5a190/aspdotnet-core-health-checks-5.png" srcset="/static/a7172bbc0ff4a7da49dc46f873d46e6a/772e8/aspdotnet-core-health-checks-5.png 200w, /static/a7172bbc0ff4a7da49dc46f873d46e6a/e17e5/aspdotnet-core-health-checks-5.png 400w, /static/a7172bbc0ff4a7da49dc46f873d46e6a/5a190/aspdotnet-core-health-checks-5.png 800w, /static/a7172bbc0ff4a7da49dc46f873d46e6a/c1b63/aspdotnet-core-health-checks-5.png 1200w, /static/a7172bbc0ff4a7da49dc46f873d46e6a/29007/aspdotnet-core-health-checks-5.png 1600w, /static/a7172bbc0ff4a7da49dc46f873d46e6a/80cfc/aspdotnet-core-health-checks-5.png 1844w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Feel free to stop the docker container and see how the errors get displayed in the dashboard.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABMUlEQVR42n2RaW7CMBCFc/+z9BSVegP+AKKiESUhOIuzeMn2Om+A/GnVkT7ZnuXN2E7udwNS1w28j0KAc36DZx9++4m1rVJbi7fdB94/d0i6rkdpSrRth05oVDhgGJxS1zWK4q7x8BTmGuOovus10/h3Y3Cz1UOwqipJ9Og7ESxL2LaFkambplG0meRR6CHqlVJyjTGyVgjSPEqzhAEWvZJaEXPOyVWsUhSFTHHFfr9HmqYqwrxhGHSQoBMH3dOX4A9b51lXXvd0OuF8Pm/Qt66rsizLtqdN04RkFSeejpfNwWtXFh8OBxyPx034crlojGKciCKzDEBRFcQ/xoL0K0We50qWZbjlN4zj+Ge+CsYY9M0W6RIl0UV5dDnzEzr5JL7pOE7bFQknIvqRfa/wzEY/2Jhoq0DC4vAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-6.png" title="" src="/static/6178dc1e51a900010053f055db8235d0/5a190/aspdotnet-core-health-checks-6.png" srcset="/static/6178dc1e51a900010053f055db8235d0/772e8/aspdotnet-core-health-checks-6.png 200w, /static/6178dc1e51a900010053f055db8235d0/e17e5/aspdotnet-core-health-checks-6.png 400w, /static/6178dc1e51a900010053f055db8235d0/5a190/aspdotnet-core-health-checks-6.png 800w, /static/6178dc1e51a900010053f055db8235d0/c1b63/aspdotnet-core-health-checks-6.png 1200w, /static/6178dc1e51a900010053f055db8235d0/29007/aspdotnet-core-health-checks-6.png 1600w, /static/6178dc1e51a900010053f055db8235d0/476e1/aspdotnet-core-health-checks-6.png 1841w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Adding custom health checks</h3> <p>Now let’s switch to our <code class="language-text">Service2</code> project and do something interesting with it as well. We are going to be adding our custom health check.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RemoteHealthCheck</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IHealthCheck</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>HealthCheckResult<span class="token punctuation">></span></span> <span class="token function">CheckHealthAsync</span><span class="token punctuation">(</span><span class="token class-name">HealthCheckContext</span> context<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CancellationToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> isHealthy <span class="token operator">=</span> <span class="token function">CheckRemoteEndpointHealth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span><span class="token return-type class-name">isHealthy <span class="token punctuation">?</span></span> HealthCheckResult<span class="token punctuation">.</span><span class="token function">Healthy</span><span class="token punctuation">(</span><span class="token string">"Remote endpoint is healthy."</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> HealthCheckResult<span class="token punctuation">.</span><span class="token function">Unhealthy</span><span class="token punctuation">(</span><span class="token string">"Remote endpoint is unhealthy"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">CheckRemoteEndpointHealth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Just stubbing it out for demo</span> <span class="token class-name"><span class="token keyword">var</span></span> rnd <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> rnd <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We can now register RemoteHealthCheck in the Startup class like so.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddHealthChecks</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCheck</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RemoteHealthCheck<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>RemoteHealthCheck<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now the dashboard will show you the status of both services.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 27%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0UlEQVR42mVQOW7DMBDU/5+VPpW7FCkCyJRJmod4ig4w4WwgNS4GWmo4x3LR2oDYtgdyKSi5ClrrF3IuiDGh1vYGYyy8dfj8+cLH9w1LrRX7vgtx9AEXI0Lc5Z9zDiFEeO8nb8T4OAZ6PwStNSi1YV3vSDmjzvOS50BxnCa9dyH2lKD0Q0LIM5TGIQSZT6zrKkGcGZ5SxkKTMlel8Pf1QpkpTPJTzGZxNmaTE2OMC9TwDguR4xMs/+saIWh+NjbPp6TSkF+t9VtDpRSstVdDbvkHq5V+/yAuDbgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-check-7.png" title="" src="/static/6c28969b4d3c1fe34bee9624f600e2c1/5a190/aspdotnet-core-health-checks-7.png" srcset="/static/6c28969b4d3c1fe34bee9624f600e2c1/772e8/aspdotnet-core-health-checks-7.png 200w, /static/6c28969b4d3c1fe34bee9624f600e2c1/e17e5/aspdotnet-core-health-checks-7.png 400w, /static/6c28969b4d3c1fe34bee9624f600e2c1/5a190/aspdotnet-core-health-checks-7.png 800w, /static/6c28969b4d3c1fe34bee9624f600e2c1/c1b63/aspdotnet-core-health-checks-7.png 1200w, /static/6c28969b4d3c1fe34bee9624f600e2c1/29007/aspdotnet-core-health-checks-7.png 1600w, /static/6c28969b4d3c1fe34bee9624f600e2c1/757fd/aspdotnet-core-health-checks-7.png 2800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You’ll see that the RemoteHealthCheck will be going down from time to time because we have set it to return an unhealthy randomly. You can also set the <code class="language-text">EvaluationTimeInSeconds</code> setting to like 2s to see the result quickly.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 27%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA1klEQVR42lWQzU7EMAyE+/7vxZErB04IqU13m38nabsgDR6LLeLwSU5sz9iets2DrOsNvQ+01o19Py5EGkqpGGP/B3ObDwh3j9fPd7x8vGEaY6DWCu89zvNEyBm5FFQlhIis7xiTmm5q1HAcxwV7nVuxLItqiJlMrYm6FxNlURNBlQp3v5mJiFgjhQnjJ/M8mxHjGKMZTiaiAXl8fWPvDaN3xJSsiEZ/aw7d4nHBU3B6DsQcTzXxQZeUsjWxiCJR/4IKlt/VeWNOyDs/cc5pLlhM4ZwLfgCB4n6o28ZkmQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="aspdotnet-core-health-checks-8.png" title="" src="/static/a4262a07b195e6f62492163238eed3ee/5a190/aspdotnet-core-health-checks-8.png" srcset="/static/a4262a07b195e6f62492163238eed3ee/772e8/aspdotnet-core-health-checks-8.png 200w, /static/a4262a07b195e6f62492163238eed3ee/e17e5/aspdotnet-core-health-checks-8.png 400w, /static/a4262a07b195e6f62492163238eed3ee/5a190/aspdotnet-core-health-checks-8.png 800w, /static/a4262a07b195e6f62492163238eed3ee/c1b63/aspdotnet-core-health-checks-8.png 1200w, /static/a4262a07b195e6f62492163238eed3ee/29007/aspdotnet-core-health-checks-8.png 1600w, /static/a4262a07b195e6f62492163238eed3ee/d10b5/aspdotnet-core-health-checks-8.png 2794w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>Today we looked at how you can improve your developer experience by leveraging ASP.NET’s health checks. There are so many providers and configurations to play around with. Feel free to head over to the <a href="https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks">docs</a> to learn more.</p> <h3>References</h3> <ol> <li><a href="https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks">https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-5.0">https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-5.0</a></li> </ol><![CDATA[Distributed Caching in ASP.NET Core with Redis]]>https://www.sahansera.dev/distributed-caching-aspnet-core-redis/https://www.sahansera.dev/distributed-caching-aspnet-core-redis/Sun, 28 Feb 2021 00:00:00 GMT<p>About a year ago, I wrote a blog post on <a href="https://sahansera.dev/in-memory-caching-aspcore-dotnet/">simple In-Memory Caching in ASP.NET Core with IMemoryCache</a>. This article mainly introduced the concept of caching and how we can store stuff in the server’s memory for simple tasks. Today’s objective is to leverage the IDistributedCache to do some distributed caching so that we can horizontally scale out our web app.</p> <blockquote> <p>💡 <strong>UPDATE on Aug 2022:</strong> I have recently migrated this project to .NET 6 also with its minimal hosting model and also added a docker-compose.yaml file for better developer experience 🎉. You can still access the .NET Core 5 (works with .NET Core 3.1+ too) version from <a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/tree/dotnet5">this branch</a>.</p> </blockquote> <p>For this specific tutorial, I will be using Redis as my cache provider. Redis is a battle-tested, fast memory cache that can store many types of objects. Redis is being used by giants such as Twitter, Github, Instagram, Stackoverflow, Medium, Airbnb etc.</p> <blockquote> <p>💡 You can find the accompanying code for this blog post from <a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis">here</a>.</p> </blockquote> <p>Here’s a snapshot of what we are going to be building.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABz0lEQVR42p2S3WoTURSF5w0EL/UVRJ9B1Hu98CnslZeCD6B4X1AkImppFRSFEQVREYRaAk3BWE1S2yYzbWaSzF/m/8x8npl0IkTSUA8M+5zZ+6y91l5HYcESImOj0WBgWeU5z/Nj65XjksXluLvP4Ys1tNevEIFfJU4GWLHwDw9QL11k/fo1Hp8+xfajWvk/S9P/Awxtm5dXLvPw7BmenT/H7/fvJnkhTi65Au13u7RUlb2tLbIF81s4w1RKs6QZbhCUpqRzpBZ9ql5KEatvlp3runR7PfSexk67TRRF/4LNeKQskhDHMcZggCXBZ92tTq4kbiUTWsowyNm3M8IkP3p3AtM0pyyLsNYWvG0lUyDf9xkOR5O9BFtupNQ24gnDpiH4aQoJKsrCsefRNwwCuT8wh7zZhZtPDO4+3UWkSVlTzNUwB4SezaYeceNBn+Xauuzuo7SGGV0Pmj0b17EZj8flhQLY9XxWt+HCUpNb9+p4EsBxXBk9LNshCx0+7sHV23We37/Dl88qihcmaKaDyP5K1nX9SHJO30lY+aCh6fZUcgFoSBXFMgNQvxnUv67wqdFCieIIazSaa0qn0+bH903J3Jv3YNnp/JIktHKGfwBJsN6A1SXUEgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="distributed-caching-in-aspdotnet-core-with-redis-1.png" title="" src="/static/f5cf079e725b11c30eb666b3ff414626/5a190/distributed-caching-in-aspdotnet-core-with-redis-1.png" srcset="/static/f5cf079e725b11c30eb666b3ff414626/772e8/distributed-caching-in-aspdotnet-core-with-redis-1.png 200w, /static/f5cf079e725b11c30eb666b3ff414626/e17e5/distributed-caching-in-aspdotnet-core-with-redis-1.png 400w, /static/f5cf079e725b11c30eb666b3ff414626/5a190/distributed-caching-in-aspdotnet-core-with-redis-1.png 800w, /static/f5cf079e725b11c30eb666b3ff414626/c1b63/distributed-caching-in-aspdotnet-core-with-redis-1.png 1200w, /static/f5cf079e725b11c30eb666b3ff414626/d7ceb/distributed-caching-in-aspdotnet-core-with-redis-1.png 1446w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <ol> <li>User requests a <code class="language-text">user</code> object.</li> <li>App server checks if we already have a user in the cache and return the object if present.</li> <li>App server makes a HTTP call to retrieve the list of users.</li> <li>Users service returns the users list to the app server.</li> <li>App server sends the users list to the distributed (Redis) cache.</li> <li>App server gets the cached version until it expires (TTL).</li> <li>User gets the cached user object.</li> </ol> <p>The main reason why we call this a distributed cache is that it lives outside of our application server (as opposed to traditional in-memory caching) and we have the flexibility of scaling it horizontally (when operating in the cloud), if need be. Head over <a href="https://azure.microsoft.com/en-us/services/cache/#what-you-can-build">here</a> to have a look at how this could be useful in enterprise applications.</p> <p>The <code class="language-text">IDistributedCache</code> interface provides us with a bunch of methods to manipulate your cache. And the actual implementation is specific to the technology we want to use. Here’s a summary of different ways you can do this.</p> <table> <thead> <tr> <th>Technology</th> <th>NuGet package</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td>Distributed Memory Cache</td> <td>-</td> <td>This is only recommended for dev and testing purposes. This is not an actual distributed cache.</td> </tr> <tr> <td>Distributed SQL Server Cache</td> <td>Microsoft.Extensions.Caching.SqlServer</td> <td>Use SQL Server instance as a cache (locally or in cloud with Azure SQL Server).</td> </tr> <tr> <td>Distributed Redis Cache</td> <td>Microsoft.Extensions.Caching.StackExchangeRedis</td> <td>Use Redis as a backing store (locally or in cloud with Azure Redis Cache)client package is Developed by peeps at StackExchange.</td> </tr> <tr> <td>Distributed NCache Cache</td> <td>NCache.Microsoft.Extensions.Caching.OpenSource</td> <td>Wrapper around the NCache Distributed Cache</td> </tr> </tbody> </table> <h3>Scaffolding a sample app</h3> <p>We will create a Web MVC app in <a href="http://asp.NET">ASP.NET</a> Core 6.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new mvc <span class="token parameter variable">-n</span> DistributedCache dotnet new sln dotnet sln <span class="token function">add</span> DistributedCache</code></pre></div> <p>Let’s go ahead and add the Redis client package from NuGet.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package Microsoft.Extensions.Caching.StackExchangeRedis <span class="token parameter variable">--version</span> <span class="token number">6.0</span>.0</code></pre></div> <h3>Creating a Redis docker container</h3> <p>For this step, I assume that you have already installed Docker on your machine. It’s handy to have this so that you can spin up your own Redis container whenever you want for development purposes.</p> <blockquote> <p>💡 For this section you can run <code class="language-text">docker-compose.yaml</code> at the root of the project to spin up a local Redis container.</p> </blockquote> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">--name</span> redis-cache <span class="token parameter variable">-p</span> <span class="token number">5002</span>:6379 <span class="token parameter variable">-d</span> redis</code></pre></div> <p>We are telling docker to use the official <code class="language-text">redis</code> image and spin up a container with the name <code class="language-text">redis-cache</code> and bind port <code class="language-text">6379</code> of the container to the port <code class="language-text">5002</code> of your host machine. Why I chose port 5002 is that it might be open as it’s a less obvious port number.</p> <p>If you haven’t got the Redis image locally, it will fetch that from the DockerHub and spin up a new container under the name <code class="language-text">redis-cache</code>. Next let’s verify that our docker instance is up and running. You could do so with,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token function">ps</span> <span class="token parameter variable">-a</span></code></pre></div> <p>or alternatively with <code class="language-text">docker ps -a | grep redis-cache</code> to filter our the output, if you have a bunch of containers running in the background like I do 😅</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 16%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoElEQVR42q3KwQqCQBQF0FkHQkrzdFQypFq0aF+/0K5fcdUqKEQXls0gMzyUsP+MXqPQH3ThwOVy2SM/b3WrO+x7bLBF0z0RLakNysZgWdV4yQu8FiXepMJa6XEf2Dr+lNKmustXlp02LE3S43K1pvki+XCAAUVxTCKMiHNO4PsE8APkeh4FQlDgCwrtZ+ghh7eYcZo4zoHZuNbuD/bW9AvfW0l1RKiiFwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="distributed-caching-in-aspdotnet-core-with-redis-2.png" title="" src="/static/4c45c34c2393e7a219d86c62c9d807c4/5a190/distributed-caching-in-aspdotnet-core-with-redis-2.png" srcset="/static/4c45c34c2393e7a219d86c62c9d807c4/772e8/distributed-caching-in-aspdotnet-core-with-redis-2.png 200w, /static/4c45c34c2393e7a219d86c62c9d807c4/e17e5/distributed-caching-in-aspdotnet-core-with-redis-2.png 400w, /static/4c45c34c2393e7a219d86c62c9d807c4/5a190/distributed-caching-in-aspdotnet-core-with-redis-2.png 800w, /static/4c45c34c2393e7a219d86c62c9d807c4/c1b63/distributed-caching-in-aspdotnet-core-with-redis-2.png 1200w, /static/4c45c34c2393e7a219d86c62c9d807c4/52c43/distributed-caching-in-aspdotnet-core-with-redis-2.png 1499w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now that we have the Redis container up and running let’s configure our web app to use it.</p> <h3>Application Configuration</h3> <ul> <li>For .NET Core 3.1 - 5 with generic host model:</li> </ul> <p><a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Startup.cs">Startup.cs</a></p> <ul> <li>For .NET 6+ with minimal hosting model:</li> </ul> <p><a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/blob/dotnet6/DistributedCache/Program.cs">Program.cs</a></p> <p>Since we have already added the required NuGet package, we only need to register its service in our app’s DI container and tell it where to find our Redis instance.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token comment">// Register the RedisCache service</span> services<span class="token punctuation">.</span><span class="token function">AddStackExchangeRedisCache</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Configuration <span class="token operator">=</span> Configuration<span class="token punctuation">.</span><span class="token function">GetSection</span><span class="token punctuation">(</span><span class="token string">"Redis"</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">"ConnectionString"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>When we call <code class="language-text">AddStackExchangeRedisCache</code> on the services object, it registers a singleton of RedisCache class against the IDistributedCache interface under the covers. This is what it looks like in the <a href="https://github.com/dotnet/aspnetcore/blob/main/src/Caching/StackExchangeRedis/src/StackExchangeRedisCacheServiceCollectionExtensions.cs">source</a>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token comment">// ...</span> services<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>ServiceDescriptor<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Singleton</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IDistributedCache<span class="token punctuation">,</span> RedisCache<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ..</span></code></pre></div> <p><a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/appsettings.json">appsettings.json</a></p> <p>Since we have the Docker instance up and running at port <code class="language-text">5002</code>, we can mention that for development settings.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token string">"Redis"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">"ConnectionString"</span><span class="token punctuation">:</span> <span class="token string">"localhost:5002"</span> <span class="token punctuation">}</span></code></pre></div> <p>I have brought across the service from my previous tutorial and added them to this project. You can find them under the <code class="language-text">Services</code> folder. In fact, I have made the code to look more bit simpler as well.</p> <h3>Implementation</h3> <p>The functionality is pretty simple, and here’s what we going to do:</p> <ol> <li>Get the cached user (if any) and display its email address</li> <li>A button to invoke a HTTP call and cache a list of users</li> <li>A button to clear the cache</li> </ol> <p>The UI would look something like the following.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 24%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAnklEQVR42qWPwQrCMBBE8//3/oYH0Zu9iYpXQaHWpmrEiOYYrMkmYxIQNNSDOPBgYXdnZ5mUEjeloLXGwxhE+Tdyee8/+jnMWAtLlCDn4MJChBzhKAR29RaHfRvqExrO0XVdWnzN5bCeEHDB+BySN7xFudygXKwwnc1RVTXiR/Gbb+o1DIfgQ0J9NygmAsV4jcFwhMtVpT6R+83wHz0BjM+FPz/Moo0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="distributed-caching-in-aspdotnet-core-with-redis-3.png" title="" src="/static/7cf059b76040187a7ae21a9ac5b895e2/5a190/distributed-caching-in-aspdotnet-core-with-redis-3.png" srcset="/static/7cf059b76040187a7ae21a9ac5b895e2/772e8/distributed-caching-in-aspdotnet-core-with-redis-3.png 200w, /static/7cf059b76040187a7ae21a9ac5b895e2/e17e5/distributed-caching-in-aspdotnet-core-with-redis-3.png 400w, /static/7cf059b76040187a7ae21a9ac5b895e2/5a190/distributed-caching-in-aspdotnet-core-with-redis-3.png 800w, /static/7cf059b76040187a7ae21a9ac5b895e2/c1b63/distributed-caching-in-aspdotnet-core-with-redis-3.png 1200w, /static/7cf059b76040187a7ae21a9ac5b895e2/29007/distributed-caching-in-aspdotnet-core-with-redis-3.png 1600w, /static/7cf059b76040187a7ae21a9ac5b895e2/49853/distributed-caching-in-aspdotnet-core-with-redis-3.png 2224w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s look at the main entry point of the actions, the HomeController class.</p> <p><a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Controllers/HomeController.cs">HomeController.cs</a></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IActionResult<span class="token punctuation">></span></span> <span class="token function">Index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> users <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">await</span> _cacheService<span class="token punctuation">.</span><span class="token function">GetCachedUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">?.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">View</span><span class="token punctuation">(</span>users<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IActionResult<span class="token punctuation">></span></span> <span class="token function">CacheUserAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> users <span class="token operator">=</span> <span class="token keyword">await</span> _usersService<span class="token punctuation">.</span><span class="token function">GetUsersAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cacheEntry <span class="token operator">=</span> users<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">View</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>Index<span class="token punctuation">)</span><span class="token punctuation">,</span> cacheEntry<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IActionResult</span> <span class="token function">CacheRemoveAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> _cacheService<span class="token punctuation">.</span><span class="token function">ClearCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">RedirectToAction</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>Index<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The code here is pretty self-explanatory, and we implement the 3 features we discussed in the Index, CacheUserAsync and CacheRemoveAsync actions.</p> <blockquote> <p>💡 Tip: You would ideally want to decorate the UsersService class with CacheService by using a DI container such as <a href="https://github.com/khellang/Scrutor">Scrutor</a>. You don’t want to write the plumbing code we have written here to emulate a similar thing as the default DI container doesn’t support the behaviour. Refer to Andrew Lock’s <a href="https://andrewlock.net/adding-decorated-classes-to-the-asp.net-core-di-container-using-scrutor/#manually-creating-decorators-with-the-asp-net-core-di-container">excellent article</a> on this topic.</p> </blockquote> <p>I’m going to skip all the other plumbing code and show you how we Get and Set values with the Redis cache. The real magic happens in the ICacheProvider class.</p> <p>The code itself it pretty self-explanatory. In the GetFromCache method, we call the <code class="language-text">GetStringAsync</code> with a given key (<code class="language-text">_Users</code> in this case). It’s worth noting that we need to deserialise it to the type we want before returning it to the caller. Similarly, we serialise our users list and save it as a string in the Redis cache under the <code class="language-text">_Users</code> key.</p> <p><a href="https://github.com/sahansera/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Infrastructure/CacheProvider.cs">CacheProvider.cs</a></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CacheProvider</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICacheProvider</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IDistributedCache</span> _cache<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">CacheProvider</span><span class="token punctuation">(</span><span class="token class-name">IDistributedCache</span> cache<span class="token punctuation">)</span> <span class="token punctuation">{</span> _cache <span class="token operator">=</span> cache<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">GetFromCache</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cachedResponse <span class="token operator">=</span> <span class="token keyword">await</span> _cache<span class="token punctuation">.</span><span class="token function">GetStringAsync</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> cachedResponse <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token keyword">null</span> <span class="token punctuation">:</span> JsonSerializer<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Deserialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cachedResponse<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">SetCache</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">,</span> <span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token class-name">DistributedCacheEntryOptions</span> options<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> response <span class="token operator">=</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> _cache<span class="token punctuation">.</span><span class="token function">SetStringAsync</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> response <span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ClearCache</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> _cache<span class="token punctuation">.</span><span class="token function">RemoveAsync</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <h3>So what gets saved under the covers?</h3> <p>We can connect to the container and open up the <code class="language-text">redis-cli</code> to see what’s inside. To do that, you could run the following command.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> <span class="token builtin class-name">exec</span> <span class="token parameter variable">-it</span> redis-cache redis-cli</code></pre></div> <p>Once you are in, you could issue a <code class="language-text">hgetall _Users</code> command to inspect what’s inside the hash that got saved in our request.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 61%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB5ElEQVR42qWR227aQBCGAZ8Paxubg0lLOIS1ARNETkrVqtzUKheRemGJC9+h3iTPkOQV+gJ5AfcZa2unswRMLpJKbVf6NOPdmd9zqFTw/Fh/db/frcLr+IbG10skpku0aZLQ280tTZKUbtCm6YYmNyldLuMDGLdafQvX67Vb2Z/PUfT08dMCBifBLzqZMzo5ZSNkOr9gZ+cf2NX8kp0tLtj55RWbzRYsoFMWBhGbjE9ZGEb5LFrAcBg8lYJus5V1u31w627uNFtAXG+LRizQDAt0QQRRFECURZBkGSQRkSRQFBVRChnvBEH4WQp6npd1OkcQhJO83fJB5YEYJEvPiRgC1Wp1a1+h4BYFs1KwiRX6/hEguSwrbyX+URB/mL2osJm1/Q64XiPn5RNCwEQUVQViWWCaBO8ssJ06aJoGuq6DYZro66AbRkGIzVs/CNq2s53hu/fd/Lg3ABqMuQ8+jqE/GMJoFMBxrw+9/hD4aLjPCcdTbgvu8y5LQcdxM6/RgDoupdX2oY4LMQyzrIyDb6CqGliWDbaNOA5gZ/yt4HHY0UEQW8h4+ZiQY7XgouA/zPCw5d1A+WP+Mphvdr/dvf8Kxc6WFVZx5bEoio/IPfJQq9X+lkfU+IJatVJ09yH8B1yj8hvpetHD7G+RQAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="distributed-caching-in-aspdotnet-core-with-redis-5.png" title="" src="/static/17c68c3e7d0fc28660e1387f0f444d6b/5a190/distributed-caching-in-aspdotnet-core-with-redis-5.png" srcset="/static/17c68c3e7d0fc28660e1387f0f444d6b/772e8/distributed-caching-in-aspdotnet-core-with-redis-5.png 200w, /static/17c68c3e7d0fc28660e1387f0f444d6b/e17e5/distributed-caching-in-aspdotnet-core-with-redis-5.png 400w, /static/17c68c3e7d0fc28660e1387f0f444d6b/5a190/distributed-caching-in-aspdotnet-core-with-redis-5.png 800w, /static/17c68c3e7d0fc28660e1387f0f444d6b/c1b63/distributed-caching-in-aspdotnet-core-with-redis-5.png 1200w, /static/17c68c3e7d0fc28660e1387f0f444d6b/2b608/distributed-caching-in-aspdotnet-core-with-redis-5.png 1540w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you like to use a GUI, here’s a nice representation of what our web app saved under the hood. I used <a href="https://redislabs.com/redis-enterprise/redis-insight/">RedisInsight</a> tool for this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 26.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAsUlEQVR42oWQSRJDIQhEvf8pk1VWf3BGRTvgHyqbJFS9QhS7UeN9gLMe67IhpgQndamM2sakseZ+1984+gaMTwRHBWvIWF2AFfHaGjp3aPTeMcbAr9DznAkpF5jXHvDYAp42YQmELAZ6/9KgUqSR8C9aY2QZzPQP92tzALcoy6QhZkQhiZmek5CpylRnnojxFOzjEBBYnqeoyUVlxi7fsFkHK/8bk4jP59GxPtFhqDa8AXXOiIEvRTw3AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="distributed-caching-in-aspdotnet-core-with-redis-4.png" title="" src="/static/710a2fc71779bba994d84d293a3bcb25/5a190/distributed-caching-in-aspdotnet-core-with-redis-4.png" srcset="/static/710a2fc71779bba994d84d293a3bcb25/772e8/distributed-caching-in-aspdotnet-core-with-redis-4.png 200w, /static/710a2fc71779bba994d84d293a3bcb25/e17e5/distributed-caching-in-aspdotnet-core-with-redis-4.png 400w, /static/710a2fc71779bba994d84d293a3bcb25/5a190/distributed-caching-in-aspdotnet-core-with-redis-4.png 800w, /static/710a2fc71779bba994d84d293a3bcb25/c1b63/distributed-caching-in-aspdotnet-core-with-redis-4.png 1200w, /static/710a2fc71779bba994d84d293a3bcb25/29007/distributed-caching-in-aspdotnet-core-with-redis-4.png 1600w, /static/710a2fc71779bba994d84d293a3bcb25/56fb6/distributed-caching-in-aspdotnet-core-with-redis-4.png 2752w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Demo</h3> <p>Here’s a working demo when you run the code from my repo:</p> <img src="/16f0846dfd8ec8afdd90a7e39d029bd5/distributed-caching-in-aspdotnet-core-with-redis-6.gif" style="width: 100%;height: 100%;margin: 0;"> <p>As you can see, it will only fetch the users list only the first time we click the “Cache It” button. Every subsequent requests will fetch the users list from the Redis cache and serve to our app. The cache expiry can be configured by setting a sliding window or an absolute expiry by passing in configuration. In this demo I have set a sliding expiry for 2 minutes.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <h3>Conclusion</h3> <p>In this article, we converted our previous In-Memory example to use the IDistributedCache interface provided by <a href="http://asp.NET">ASP.NET</a> Core and used Redis as a backing store. This approach can be utilised to leverage cloud service such as Azure Redis Cache for use-cases such as response caching, session storage etc.</p> <p>Hope you enjoyed this article and feel free to share your thoughts and feedback. Until next time 👋</p> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0">https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0</a></li> <li><a href="https://redis.io/commands">https://redis.io/commands</a></li> <li><a href="https://aspnetcore.readthedocs.io/en/stable/performance/caching/distributed.html#using-a-redis-distributed-cache">https://aspnetcore.readthedocs.io/en/stable/performance/caching/distributed.html#using-a-redis-distributed-cache</a></li> </ol><![CDATA[Publishing Android Apps to Microsoft App Center from Azure DevOps]]>https://www.sahansera.dev/publishing-android-apps-to-microsoft-appcenter/https://www.sahansera.dev/publishing-android-apps-to-microsoft-appcenter/Tue, 16 Feb 2021 00:00:00 GMT<p>In my previous article, we looked at how we can set up multi-stage builds for an Android app with Azure DevOps. In this article, we will be looking at distributing this app to App Center for internal testing.</p> <blockquote> <p>💡 Note: that this a continuation of my previous blog article, <a href="https://sahansera.dev/multi-stage-builds-with-azure-pipelines-ionic/">Multi-stage builds for Ionic Apps with Azure Pipeline Templates</a>. I suggest you go through it if you haven’t already.</p> </blockquote> <p>Let’s look at what we are going to be building today. As always, you can find the complete source code in <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/tree/main/infrastructure">here</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 31.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAGABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAd2ACf/EABcQAAMBAAAAAAAAAAAAAAAAAAABEBL/2gAIAQEAAQUCj0f/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAXEAEBAQEAAAAAAAAAAAAAAAAAASGR/9oACAEBAAY/AkZZx//EABoQAAMAAwEAAAAAAAAAAAAAAAABESExYYH/2gAIAQEAAT8hdq9FZexLwF0f/9oADAMBAAIAAwAAABAEP//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/ECf/xAAWEQEBAQAAAAAAAAAAAAAAAAABEDH/2gAIAQIBAT8QcJ//xAAcEAEAAgEFAAAAAAAAAAAAAAABACERMUFRcbH/2gAIAQEAAT8QwopnhXUVk5U3rUU3boi+z//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-0-1.jpg" title="" src="/static/cf8523b7f8c99e1590a8c36b89f7da7a/4b190/publishing-android-apps-to-microsoft-appcenter-0-1.jpg" srcset="/static/cf8523b7f8c99e1590a8c36b89f7da7a/e07e9/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 200w, /static/cf8523b7f8c99e1590a8c36b89f7da7a/066f9/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 400w, /static/cf8523b7f8c99e1590a8c36b89f7da7a/4b190/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 800w, /static/cf8523b7f8c99e1590a8c36b89f7da7a/e5166/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 1200w, /static/cf8523b7f8c99e1590a8c36b89f7da7a/b17f8/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 1600w, /static/cf8523b7f8c99e1590a8c36b89f7da7a/cb9ea/publishing-android-apps-to-microsoft-appcenter-0-1.jpg 2351w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>What is Microsoft (Visual Studio) App Center?</h3> <p>Microsoft App Center is a platform where you can build, test, distribute and monitor your mobile apps. The beauty of this is that you can use it as a place to distribute your apps for a target user group for beta testing your app - similar to TestFlight but you can do it for multiple apps.</p> <h3>Creating a new app in App Center</h3> <p>To get started, head over to <a href="https://appcenter.ms/apps">https://appcenter.ms/apps</a>. If you haven’t already got an app created in App Center, we first need to do that. You could either create it under an Organization or a User. In my case, I have created it under my user account. The details look like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 736px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 131.50000000000003%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB70lEQVR42q2VS27bMBCGdcB0020XOVCBXqENsgm6bc7QXbtruivg2NHLih6UKZGUqD8zVOxIhu1KjgcgaEvEx5l/HvKM0QijCM9JgjzPkNDeti3mWNd1qOva/fa00vD9ABFB4zjGcrk8A2ihtsCmaSArCSkrdxObtXbS4vNVrVBrtYM7IHsWBL7bhRDu4LG1g/FOAPXnFzY3X2CzpAeyu3meu5DDIHAabnXZB40iIFlYGPPwG9XXz7DpugcaYyAKgXJTolbqaMgMa/isKMBRjS61b5p7muKPwhBplqGqqqN6MUTRhRyBpHNaa0qE2sF3QM5oWZbuxVCj/cWRMITPuiVKZK9OjIBDjRh+KqtTzONbt0WJV9E5gxWVEns117zeMzvk4TFv8JQIFBTSvkb/BR56KFWL5zRH6Pvkaf1+oLV9org2jbmAhy7z1DHcNbM1PAVcr5PJ2R2VzbCt2ExLXUFQTaNt7uTxeB7GceSmzdb+ZQ38dANZFpScdJaXBz1k73hgcJtxYuaUzlENpdwgXicOvFqt3or/7KS4solhqJM6240imF+H1DlFkSOjb8xFymY46y4GnGKWhy4VAFUZeBzwf+/U92PK2o/qLA9b24N+LiQ+3Czw6W6Jq28L/HgQ7wd+vF3g+vuK9kfc/xV4AV+t/Fyk2mCTAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-0.png" title="" src="/static/a95b1151a762b81518dbb9366f37c6d4/f941f/publishing-android-apps-to-microsoft-appcenter-0.png" srcset="/static/a95b1151a762b81518dbb9366f37c6d4/772e8/publishing-android-apps-to-microsoft-appcenter-0.png 200w, /static/a95b1151a762b81518dbb9366f37c6d4/e17e5/publishing-android-apps-to-microsoft-appcenter-0.png 400w, /static/a95b1151a762b81518dbb9366f37c6d4/f941f/publishing-android-apps-to-microsoft-appcenter-0.png 736w" sizes="(max-width: 736px) 100vw, 736px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Creating a Service Connection</h3> <p>Before pushing our signed APK to the App Center, we need to create a link between the Azure DevOps instance and the App Center tenant. For that, we will be creating a service connection so that the build agent has necessary credentials to make this link work and where to put the latest build.</p> <p>There are two parts to getting a service connection.</p> <p>First, we need to generate an API token which will be used by the <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/app-center-distribute?view=azure-devops">AppCenterDistribute</a> task. Let’s do the following steps in the App Center.</p> <ol> <li>Click on Profile Icon (top-right corner)→ Account Settings → User API Tokens.</li> <li>Click on New API token button.</li> <li>Give it a memorable description</li> <li>Select Full Access as for Access option.</li> </ol> <blockquote> <p>💡 Tip: Make sure to copy the API token as we will need it for our next step.</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 87.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAABS0lEQVR42r2TS07DMBCGczCExAlYcRhgjbgCiB1S7wAbOAIrJKhE27QNebmN83CT2LF/bFetQPRBAmKkz3Em0afx2HaUUqCUIk0zJEli50XB0CaMg3Nun44ZPG+KIPDh++8Yuy6iKGwtLMvSzq0wZyXSnKEWEkonpR6EHgy8kTa/D/OvFdZC4WkQ4WXkoz+JMQrokpDCDVN4c4a4EDshTGBKOWjZaGGj8OxlePMTvE4I+t7sCy4p4Gd8J0HO4c5r0IUWwi5R6T586skKtfz2U+yS1wIr/U7bsJtijstql0wQJuHRCnXJbL6N2FYohICUcp3MdHODWYYoDEAIaVfhlpOFRnAwxlBV1V8Iu4ez7wa03ZjOFaoN/Fq46f1/e7itKnNdT3pjHFwNcHQzxOH1EMe3YxS17CbkWnj5GOPsPsS55vQuxMVDjAVX+ACBUTKcrmMHuAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-1.png" title="" src="/static/9c9b0e0a7797131de2c091f86dcfff47/5a190/publishing-android-apps-to-microsoft-appcenter-1.png" srcset="/static/9c9b0e0a7797131de2c091f86dcfff47/772e8/publishing-android-apps-to-microsoft-appcenter-1.png 200w, /static/9c9b0e0a7797131de2c091f86dcfff47/e17e5/publishing-android-apps-to-microsoft-appcenter-1.png 400w, /static/9c9b0e0a7797131de2c091f86dcfff47/5a190/publishing-android-apps-to-microsoft-appcenter-1.png 800w, /static/9c9b0e0a7797131de2c091f86dcfff47/95e27/publishing-android-apps-to-microsoft-appcenter-1.png 1199w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once that’s done, let create the service connection in Azure DevOps.</p> <ol> <li>Navigate to your project’s settings → Scroll down until you find Service connections. At the time of writing, it could be found under <code class="language-text">https://dev.azure.com/{your directory}/{your project name}/_settings/adminservices</code></li> <li>Click on Create service connection.</li> <li>Type in “App Center” in the search box and select Visual Studio App Center from the list.</li> <li>Full in the details of the service connection pop up, as shown below. Make sure to keep the Server URL as it is.</li> </ol> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 593px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 166%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAhCAYAAADZPosTAAAACXBIWXMAAAsTAAALEwEAmpwYAAADZElEQVR42pVWaXraMBDVHfp9/ZGVJYANBBtjFhuzhdhA0iakQEOaZu0Jep0eoxec6o2R6xC2/HhosTSaefM0QrhOgwaBT91Oh877fTrrdXk8CALyz/sU+OfyW1t+O+O5TrvFfczXqjaVTYNRr1XJrlgkYOjX6ytNpxN6fXmh56cnupvf0sPPe/p2M6anxweazaY0nUx47vrqK83k2tvvM/KabmSw3fKoUa+RcF2HDdzN53R//4MXWmWTioU8lU6LdFoskFE6fdcHYAhrAdMo8VjgRy1QiypWeScoY4AaC7g5HAQ0kAB/OCm+EAA326D2CQy8ZpP5cBr1KAy1ACfndW0rWl4zNAhO4CWyB2KRwV63LQ9weA6HZE5SlM2kN6IpcxFxiAQUCzoV8jr34V0cu/AJxyIOx9dX9OXygq6kJKCv5cQsc7oJAoJEUiBiCLrX7byRw0chEPtoOGAvG/WqlE5hrVfbMo19nJS85E4JFwCP8RYAv8nEMaWSiXfAfDqVZB5F1a5wuAg7kCH7ksPA95nL8E77rFGMkXEF1ADQBSBKq2yEBvFhPL5m7qDFN5Daai2w/A1zMASgj0IR6VDLZVfKZVfAhkqkaMrT5rI4ILRV1+7DWYarKlxkCZwCKmvxi7+TQQgbhKMwIDGXFyMeo49vCMc0JBDWDhEwh8hUPIOe50ZEqyw23QZToiSyCpCewCK1EW3VtqXRBhsO52tRNjGOa3MZXBxcx+HwLkbDSI8APMSJnEXZGoviu+mmcMj4QT0My1dLtm1+sFDKkBx4Dq3CaxjMZTMss1XA4QJehMSXItetRZVBH/Mq4wj38GCfjo8OVwLruR7qmiaR4/u6CfGHay2HDurh5UhyOIqexFVQt0Lpct2jJcpVh4zgloZSez2pRYYUev+sx/j/yPtvHve1OjQtmwr1Hnlu+IZ0Om02gj5aZBt9GMdBWw2i7FTMYnTJVasQH6vHfONNCTvb7+qu91lEBYA3mTv/awCUnGzLoKpl8H4BQeq6tpCOtla0q3CSTnGbyeUpndVJk9ITyUSCyvU2mVWPShWHksmkFOnBWvEmjo/oYH+PLOndbHJDesmiz89/6Oj3X/pkBtKgfGRKlvwLUmuRIQ1mslq0cR3wPSc9s2W4J9kc7blTOuw90n7OpH/vcB49VrIlUAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-2.png" title="" src="/static/025d1bf8c932d953e40ce6ff04bc82e5/0b5b1/publishing-android-apps-to-microsoft-appcenter-2.png" srcset="/static/025d1bf8c932d953e40ce6ff04bc82e5/772e8/publishing-android-apps-to-microsoft-appcenter-2.png 200w, /static/025d1bf8c932d953e40ce6ff04bc82e5/e17e5/publishing-android-apps-to-microsoft-appcenter-2.png 400w, /static/025d1bf8c932d953e40ce6ff04bc82e5/0b5b1/publishing-android-apps-to-microsoft-appcenter-2.png 593w" sizes="(max-width: 593px) 100vw, 593px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now we can use this service connection to deploy our app from Azure DevOps to the App Center.</p> <h3>Configuring the deploy task</h3> <p>We will first look at configuring the deploy task for the Debug build. We will add a new “Deploy” stage and continue from there.</p> <p>The latest additions to the <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/blob/main/infrastructure/azure-pipelines.yml">azure-piplines.yml</a> will look like follows.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">...</span> <span class="token punctuation">-</span> <span class="token key atrule">stage</span><span class="token punctuation">:</span> Deploy <span class="token key atrule">displayName</span><span class="token punctuation">:</span> Deploy Debug and Release Apps to App Center <span class="token key atrule">dependsOn</span><span class="token punctuation">:</span> Build <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">deployment</span><span class="token punctuation">:</span> Deploy_Android_Debug <span class="token key atrule">variables</span><span class="token punctuation">:</span> <span class="token key atrule">buildConfiguration</span><span class="token punctuation">:</span> Debug <span class="token key atrule">displayName</span><span class="token punctuation">:</span> Deploy Android <span class="token punctuation">-</span> Debug App <span class="token key atrule">pool</span><span class="token punctuation">:</span> <span class="token key atrule">vmImage</span><span class="token punctuation">:</span> $(vmImageName) <span class="token key atrule">environment</span><span class="token punctuation">:</span> Staging <span class="token key atrule">strategy</span><span class="token punctuation">:</span> <span class="token key atrule">runOnce</span><span class="token punctuation">:</span> <span class="token key atrule">deploy</span><span class="token punctuation">:</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">template</span><span class="token punctuation">:</span> ionic<span class="token punctuation">-</span>android<span class="token punctuation">-</span>debug<span class="token punctuation">-</span>deploy.yml <span class="token key atrule">parameters</span><span class="token punctuation">:</span> <span class="token key atrule">appCenterServiceConnection</span><span class="token punctuation">:</span> <span class="token string">'App Center Integration for Sample App'</span> <span class="token key atrule">appSlug</span><span class="token punctuation">:</span> <span class="token string">'{username}/{appname in App Center}'</span> <span class="token key atrule">appFile</span><span class="token punctuation">:</span> <span class="token string">'$(Pipeline.Workspace)/$(projectName)/app-$(buildConfiguration).apk'</span> <span class="token key atrule">releaseNotes</span><span class="token punctuation">:</span> <span class="token string">'Debug App'</span></code></pre></div> <p>I’m not going to do this for release build as it will be the same configuration but a different app slug.</p> <blockquote> <p>💡 How do I find the app slug? Go to your app’s overview page in App Center and take note of the URL; the app slug would be {your org or user name}/{app name}</p> </blockquote> <p>Let’s create a new file for the <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/blob/main/infrastructure/ionic-android-debug-deploy.yml">ionic-android-release-deploy.yml</a> for the App Center deployment.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">parameters</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> appCenterServiceConnection <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Name of the service connection with App Center'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> appSlug <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Path to application in AppCenter'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> appFile <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Path of the apk to deploy'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> releaseNotes <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Release notes'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> deployMobileAssets <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Deploy Mobile Assets"</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> AppCenterDistribute@3 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">serverEndpoint</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.appCenterServiceConnection }}'</span> <span class="token key atrule">appSlug</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.appSlug }}'</span> <span class="token key atrule">appFile</span><span class="token punctuation">:</span> <span class="token string">'${{parameters.appFile}}'</span> <span class="token key atrule">symbolsOption</span><span class="token punctuation">:</span> <span class="token string">'Android'</span> <span class="token key atrule">destinationType</span><span class="token punctuation">:</span> <span class="token string">'groups'</span> <span class="token key atrule">releaseNotesOption</span><span class="token punctuation">:</span> <span class="token string">'input'</span> <span class="token key atrule">releaseNotesInput</span><span class="token punctuation">:</span> <span class="token string">'${{parameters.releaseNotes}}'</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Distribute to App Center'</span></code></pre></div> <p>The configuration for the release app will be the same in our case. So I am going to skip to keep things simple.</p> <p>Once the pipeline runs, you should be able to see your brand new app release in the App Center 😎 Pat yourself on the back! Rest is easy.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAZ0lEQVR42p2Q2wrAIAxD/f9/lTlx0JtmxtexMQyc0oeSpk1mBlWBiID9GGMLd18kVUU5K3LOaK1hVwxDkpqjSl9mhNt2FRFILBSNeu+PIdp7DFjwtB+GjMnmDfdAuRTHxOaPvmZ58g0B/ouyh5mUNAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-3.png" title="" src="/static/921a3d6e1089f48dbc8e2859a7a33f12/5a190/publishing-android-apps-to-microsoft-appcenter-3.png" srcset="/static/921a3d6e1089f48dbc8e2859a7a33f12/772e8/publishing-android-apps-to-microsoft-appcenter-3.png 200w, /static/921a3d6e1089f48dbc8e2859a7a33f12/e17e5/publishing-android-apps-to-microsoft-appcenter-3.png 400w, /static/921a3d6e1089f48dbc8e2859a7a33f12/5a190/publishing-android-apps-to-microsoft-appcenter-3.png 800w, /static/921a3d6e1089f48dbc8e2859a7a33f12/c1b63/publishing-android-apps-to-microsoft-appcenter-3.png 1200w, /static/921a3d6e1089f48dbc8e2859a7a33f12/91b67/publishing-android-apps-to-microsoft-appcenter-3.png 1527w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Deploying our app for internal testing</h3> <p>App Center has the concept of adding <a href="https://docs.microsoft.com/en-us/appcenter/dashboard/creating-and-managing-apps#adding-collaborators-to-apps">collaborators</a> to your app. This means that you can share your app with others by inviting them via email addresses. The official docs explains the concepts behind <a href="https://docs.microsoft.com/en-us/appcenter/dashboard/creating-and-managing-teams">creating teams</a> for your app.</p> <p>When you have a release under your app, you could then distribute it to your team members by going to the “Distribute” on the left sidebar.</p> <p>You can either create a new release from an existing release artifact or decide to distribute the release you have already got there.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAyElEQVR42qWRTQrCMBCFe/+tR3Dh1hO4dSGCdGs3IsUitBTznzR5ZkIrbVVQ+sELmQl5ycxkgnNwzmCtRdd1f8nGu7JtcbkzNEyj1RJZXTfgUsI5B601jDEvzeOJlAarKpTnAuv9DcfrA+gMMqME8t0GZZGDCMFjQAiRTOeQofd+lAlppV9n1mgctitUxenNkNpAmhNCSBrw/TYZylgui71wMZhDZ58Mv5EMqSS6OC2hfznmxj/5yVApFafMU7CUiaHsJ71ENKwn8lBzwrwvaG8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-4.png" title="" src="/static/a27206e53db13e280f40de758be1d722/5a190/publishing-android-apps-to-microsoft-appcenter-4.png" srcset="/static/a27206e53db13e280f40de758be1d722/772e8/publishing-android-apps-to-microsoft-appcenter-4.png 200w, /static/a27206e53db13e280f40de758be1d722/e17e5/publishing-android-apps-to-microsoft-appcenter-4.png 400w, /static/a27206e53db13e280f40de758be1d722/5a190/publishing-android-apps-to-microsoft-appcenter-4.png 800w, /static/a27206e53db13e280f40de758be1d722/c1b63/publishing-android-apps-to-microsoft-appcenter-4.png 1200w, /static/a27206e53db13e280f40de758be1d722/29007/publishing-android-apps-to-microsoft-appcenter-4.png 1600w, /static/a27206e53db13e280f40de758be1d722/963fa/publishing-android-apps-to-microsoft-appcenter-4.png 2666w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABAElEQVR42pWQbW+EIBCE/f+/sP1Qeye+RI30RAUBdeqQeK0mvSrJZGGB3Z0n8t6Dcs7BbXGVP+xfyVob4r2pEWVZhjRN8RnHuMUfMMbg6mJBqu97RF3XoX18oZA93guFVns8zITROnSdgj7RYFmWEJVSiOxqaTQacnB4K0fcpcWtsRhGB6P1c2J+eiWutm0RTdO042QDNw/mN460w/OpCauqQpIkyPMc3ItEIFuZ1nUdckKIcM/Hpwr+ThytHT/9pXmefwrSSlmWgdUwDNArt6trx5AHMtrsNU0TxEvyY/f/tPF9WmaSUxZFEYqRpZQyNLoy4Y4hizKy29b1yPFMwW93tl4lwkbCdgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="publishing-android-apps-to-microsoft-appcenter-5.png" title="" src="/static/073af347e2baa8d5fcaa1782c47b1ed0/5a190/publishing-android-apps-to-microsoft-appcenter-5.png" srcset="/static/073af347e2baa8d5fcaa1782c47b1ed0/772e8/publishing-android-apps-to-microsoft-appcenter-5.png 200w, /static/073af347e2baa8d5fcaa1782c47b1ed0/e17e5/publishing-android-apps-to-microsoft-appcenter-5.png 400w, /static/073af347e2baa8d5fcaa1782c47b1ed0/5a190/publishing-android-apps-to-microsoft-appcenter-5.png 800w, /static/073af347e2baa8d5fcaa1782c47b1ed0/c1b63/publishing-android-apps-to-microsoft-appcenter-5.png 1200w, /static/073af347e2baa8d5fcaa1782c47b1ed0/85053/publishing-android-apps-to-microsoft-appcenter-5.png 1225w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>When adding user groups for testing, you can add the teams you have already created for your app or directly share it with their email address. Once you hit Submit, they will receive an email to directly download the APK to their phone, install and run the app.</p> <h3>Conclusion</h3> <p>This article continued building upon our previous azure-pipelines.yml file and looked at how we can deploy our app to App Center for distribution. I hope this was helpful. Until next time 👋</p> <h3>References</h3> <ol> <li> <p><a href="https://docs.microsoft.com/en-us/appcenter/">https://docs.microsoft.com/en-us/appcenter/</a></p> </li> <li> <p><a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&#x26;tabs=schema%2Cparameter-schema">https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&#x26;tabs=schema%2Cparameter-schema</a></p> </li> </ol><![CDATA[Multi-stage builds for Ionic Apps with Azure Pipeline Templates]]>https://www.sahansera.dev/multi-stage-builds-with-azure-pipelines-ionic/https://www.sahansera.dev/multi-stage-builds-with-azure-pipelines-ionic/Tue, 26 Jan 2021 00:00:00 GMT<p>In this article, we will create an Azure Pipeline for an Ionic app using the new Azure pipelines YAML templates. We will also cover signing a release build of the published artifacts (APK signing). I have summarised the goals of this blog post in the diagram below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 36%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABNElEQVR42n1Ry06EMBTlm/0GTdTEvboyk7gaXboxcTULExUdjRJChCE6wASGx+AAHWh7bMtj1IU3OW3v7enpfWj4xzjnYIwNEBFM3RxOWKLc1ChIIzgMnHHFldCYWNTjDtIERZ2lCO/vu/2rovBjgjhvhhdFlCBzfeVpkteD8d8Z9oK0aZRPKRXZNNg9+8TkOZcRFX88vYR+eN4Kju99PHlr+EuGK70GE5zbaAp79aHIsfUC/WAHZbJETVsBb5FgXVTDx77lYG68qwS069cIZkgQZRwTQwiKVlmBjSBZKPJqZuLtZA8kjUBVXxh0M4ftkaHk0JjBubnAps6hbQfQ4u9QZJlFVQ0tkOXvj1zcGduSH47H0I9GwmPtUHg3Ff5DqBfo+9hDWpqlqAgZPo7DCMHcV+dv0aQZP41jJs0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-0.png" title="" src="/static/bae2960ee9ab6d889a814e6099c8aa99/5a190/multi-stage-builds-with-azure-pipelines-ionic-0.png" srcset="/static/bae2960ee9ab6d889a814e6099c8aa99/772e8/multi-stage-builds-with-azure-pipelines-ionic-0.png 200w, /static/bae2960ee9ab6d889a814e6099c8aa99/e17e5/multi-stage-builds-with-azure-pipelines-ionic-0.png 400w, /static/bae2960ee9ab6d889a814e6099c8aa99/5a190/multi-stage-builds-with-azure-pipelines-ionic-0.png 800w, /static/bae2960ee9ab6d889a814e6099c8aa99/c1b63/multi-stage-builds-with-azure-pipelines-ionic-0.png 1200w, /static/bae2960ee9ab6d889a814e6099c8aa99/29007/multi-stage-builds-with-azure-pipelines-ionic-0.png 1600w, /static/bae2960ee9ab6d889a814e6099c8aa99/f5aa5/multi-stage-builds-with-azure-pipelines-ionic-0.png 2312w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><em>A picture is worth a thousand words. Any points for my diagramming skills? 💯😅</em></p> <p>Our main goals are to;</p> <ol> <li>Scaffold an Ionic app</li> <li>Generate the Android app</li> <li>Set up Azure pipelines</li> <li>Sign the release APKs</li> </ol> <blockquote> <p>💡 You can follow along with my completed project repository in <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic">here</a>.</p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Scaffold an Ionic App</h3> <p>Let’s get the initial setup out of the way! Ionic docs already have got a great <a href="https://ionicframework.com/docs">getting started guide</a>. In this step, we are not going to do anything fancy in Ionic’s side of things. We will just scaffold a basic app and use Capacitor to generate an Android app. If you have already got an existing Ionic app, you can skip ahead to the “Create the Azure Pipeline” section.</p> <p><strong>1. Creating a blank Ionic app</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">ionic start sampleapp blank <span class="token parameter variable">--capacitor</span> <span class="token parameter variable">--type</span><span class="token operator">=</span>ionic-angular</code></pre></div> <p>As you can see, there’s nothing fancy here. Just a blank Ionic+Angular project with Capacitor integration.</p> <p><strong>2. Set Project ID (optional)</strong> - You don’t need to change the appId for this tutorial, but I like to set it anyway.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> ... <span class="token property">"appId"</span><span class="token operator">:</span> <span class="token string">"com.sahan.sampleapp"</span><span class="token punctuation">,</span> ... <span class="token punctuation">}</span></code></pre></div> <p>Open up <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/blob/main/sampleapp/capacitor.config.json">capacitor.config.json</a> file and change the <code class="language-text">appId</code> property to whatever suits you.</p> <p><strong>3. Add Capacitor - Android Project</strong></p> <p>If you haven’t set up your environment for Android development, please follow this <a href="https://ionicframework.com/docs/developing/android">guide</a> to do so.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">ionic build <span class="token operator">&amp;&amp;</span> ionic cap <span class="token function">add</span> android</code></pre></div> <p>Note the <code class="language-text">ionic build</code> command here. We first need to issue the <code class="language-text">ionic build</code> command before adding the Android project for the first time. With the initial <code class="language-text">ionic cap add android</code> command it will copy the required web assets into the native project.</p> <blockquote> <p>💡 It’s now <a href="https://capacitorjs.com/docs/cordova#native-project-management">recommended to keep your native projects</a> in source control.</p> </blockquote> <p>If you make any changes to your Ionic project, you can use the <code class="language-text">ionic cap copy</code> command to copy any changed web assets or <code class="language-text">ionic cap sync</code> if you are using/updated any native dependencies.</p> <p>After these steps, your project structure should look like the following.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 496px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACC0lEQVR42pWUW2+bQBCF8d31JU5iczMOJtheWGBZjOM4rlupN1VRlac85P//k9PZRa2qvgQeVqBldfjOnNkxDCuGV3zFqrzA3ee4Px/oWcAwI7TsuNZSGv1VgsFdCqNlRbjePSK6vKC7zDD0JUb3e/TovWUxqO9NltEm9clGgH+/YB5LOEWBofqT2UCEzvaWHAOiJEGO6Vbg9PoN07DAjElCz+iAQFtZakDXtqNKcBwKsB9nTEJJm6m23vfqCyrCrhsTBNWzQxuDzQHmzxdMAqqbSZvzHYzFrj4dCXacWIuSIEMveMDN+Q2jdY7hWlBiVI873qiOyq4SNQyTYUpCfP8RlhTwDgWW1DZWlhMlq2mZUSCcgFTbUJ1GDsM2YHAPewRPJRYJ1U/boD87cX3BIFWhMHRXEuPyF1wpNd2cBG16X59K2EJqO+8J9j2OD+s/gsscU/YFZiZwG+dYlgU8WrNtpgteJ+VKUPWhXaVzk2TU1BIrsm3l1dMj2p4XvxvOf4QkSP1zKzJt1z+WcIsqFJV0p3ENibDnRnDiBIusIvOPe1yzjPqR1W5sTej/tcyx4hyWEGQ7hyOlpmuZUe22Ub2rLavRY7shgi2DVRyJ8BFXYVKb7t+rpygNdc386AQ/+Yyr6EIpf0KfBkPH4c1Gl1ktEgzRT58xzp8x25SYbh5o4jxRuqm20nQe/gb/LdJ8q/aFsAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-1.png" title="" src="/static/c8b7b9b1d611ef3e3684302eaee26af8/bb630/multi-stage-builds-with-azure-pipelines-ionic-1.png" srcset="/static/c8b7b9b1d611ef3e3684302eaee26af8/772e8/multi-stage-builds-with-azure-pipelines-ionic-1.png 200w, /static/c8b7b9b1d611ef3e3684302eaee26af8/e17e5/multi-stage-builds-with-azure-pipelines-ionic-1.png 400w, /static/c8b7b9b1d611ef3e3684302eaee26af8/bb630/multi-stage-builds-with-azure-pipelines-ionic-1.png 496w" sizes="(max-width: 496px) 100vw, 496px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Create the Azure Pipeline</h3> <p>For this tutorial, I will create a single <code class="language-text">azure-pipelines.yml</code> file and build-publish two Android apps, one of Debug configuration and another for Release.</p> <p><strong>1. Creating the initial structure of the <code class="language-text">azure-pipelines.yml</code></strong></p> <p>Let’s first create a folder called <code class="language-text">infrastructure</code> at the root of our solution structure. We will use this folder to keep all our Azure Pipeline line YAML files. We will then add two additional YAML files called <code class="language-text">ionic-android-debug-build.yml</code> and <code class="language-text">ionic-android-release-build.yml</code>. I will explain why we do this in a second. Your file structure should look like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 328px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVR42nWRS2/aQBSFTRuaRAoigI3B2AQn4WX8xs8QEaVJpa7SCpK0m66qbipVVdn0J/Rnf7046yw+3bkzmjPnzlHe6DMOLQ9n/YDqRPQjDzMM6PoeXc/DiANORnMUdUytO6lQtPHrHNspJ5c5zvsn+uk9epxhpQVGUtCLcwZ5SXOW8NYMOTAD6lbEu2H8Kkqv3KIlD7TW39E2/3A//iDf/GH56SdXX3asnnfcfftL9viL6687yqffNOPPdNIt7WQj9QUt31YojcuCo/OM1mRFRzDiG+zyHs1fc776wKi8E8e3DJa3XEjfC29oTAta7hVqsKI5Lzh1So5F48jOUOq9mcw+oaOPsfoTuosFo9zndOJIDbASj2Hqo3tu1dtliLX0RTzi4jriLAsxXJfa/v/kn5VDY4Giz2kbc0zTEaceui9B2K7UUIIJKtozv6r7PdUN6Mw9CTBCXfhowkFfgtPEXF0WNX1KQ5yq/Vl1OEwjcejJ6xFmEgovF40oZJjFqCJsxiFnRSwThAyiQAT3k075D70vzQxGsiuhAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-2.png" title="" src="/static/eed8069d53f12410b7077fc11f68bebe/d5c60/multi-stage-builds-with-azure-pipelines-ionic-2.png" srcset="/static/eed8069d53f12410b7077fc11f68bebe/772e8/multi-stage-builds-with-azure-pipelines-ionic-2.png 200w, /static/eed8069d53f12410b7077fc11f68bebe/d5c60/multi-stage-builds-with-azure-pipelines-ionic-2.png 328w" sizes="(max-width: 328px) 100vw, 328px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>For our initial step, we will create the barebones structure of our azure-pipelines.yml file</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">trigger</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> main <span class="token key atrule">variables</span><span class="token punctuation">:</span> <span class="token key atrule">vmImageName</span><span class="token punctuation">:</span> <span class="token string">'windows-latest'</span> <span class="token key atrule">projectName</span><span class="token punctuation">:</span> <span class="token string">'SampleApp'</span> <span class="token key atrule">stages</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">stage</span><span class="token punctuation">:</span> Build <span class="token key atrule">displayName</span><span class="token punctuation">:</span> Build Ionic <span class="token punctuation">-</span> Android projects <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token comment"># Debug build</span> <span class="token punctuation">-</span> <span class="token key atrule">job</span><span class="token punctuation">:</span> Build_Ionic_Android_Debug <span class="token key atrule">variables</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> buildConfiguration <span class="token key atrule">value</span><span class="token punctuation">:</span> Debug <span class="token key atrule">displayName</span><span class="token punctuation">:</span> Build Debug <span class="token key atrule">pool</span><span class="token punctuation">:</span> <span class="token key atrule">vmImage</span><span class="token punctuation">:</span> $(vmImageName) <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">template</span><span class="token punctuation">:</span> ionic<span class="token punctuation">-</span>android<span class="token punctuation">-</span>debug<span class="token punctuation">-</span>build.yml <span class="token comment"># Release build</span> <span class="token punctuation">-</span> <span class="token key atrule">job</span><span class="token punctuation">:</span> Build_Ionic_Android_Release <span class="token key atrule">variables</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> buildConfiguration <span class="token key atrule">value</span><span class="token punctuation">:</span> Release <span class="token key atrule">displayName</span><span class="token punctuation">:</span> Build Release <span class="token key atrule">pool</span><span class="token punctuation">:</span> <span class="token key atrule">vmImage</span><span class="token punctuation">:</span> $(vmImageName) <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">template</span><span class="token punctuation">:</span> ionic<span class="token punctuation">-</span>android<span class="token punctuation">-</span>release<span class="token punctuation">-</span>build.yml</code></pre></div> <p>Here’s a rough explanation of the structure of the above file.</p> <ol> <li><code class="language-text">trigger</code>: We trigger our pipeline whenever a change happens to the <code class="language-text">main</code> branch</li> <li><code class="language-text">variables</code>: We have defined two global variables which can be reused in our stages</li> <li><code class="language-text">stages</code>: This is where we define our stages. Initially, we have added <code class="language-text">Build</code> stage. Later we will add another stage called <code class="language-text">Deploy</code> <ul> <li><code class="language-text">jobs</code>: We have two jobs called <code class="language-text">Debug</code> and <code class="language-text">Release</code> .</li> <li><code class="language-text">jobs:steps</code>: Note how we give our template path with a <code class="language-text">template</code> attribute. This way, we don’t need to keep everything in one YAML file.</li> </ul> </li> </ol> <blockquote> <p>💡 Tip: If you are using the <code class="language-text">script</code> task you will sometimes find that any multiline commands won’t run. Only the first one would get picked up. You can append your commands with <code class="language-text">&amp;&amp;</code> or use a PowerShell task as shown in this tutorial.</p> </blockquote> <p><strong>2. Create ionic-android-debug-build.yml</strong></p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">script</span><span class="token punctuation">:</span> npm install <span class="token punctuation">-</span>g @ionic/cli <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Install Ionic CLI'</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> Npm@1 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">workingDir</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)'</span> <span class="token key atrule">command</span><span class="token punctuation">:</span> install <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'NPM Install'</span> <span class="token punctuation">-</span> <span class="token key atrule">powershell</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string"> ionic cap build android --no-open npx cap sync cd android ./gradlew assemble$(buildConfiguration)</span> <span class="token key atrule">workingDirectory</span><span class="token punctuation">:</span> $(Build.SourcesDirectory)/$(projectName) <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Build Android Project'</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> CopyFiles@2 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">SourceFolder</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)'</span> <span class="token key atrule">contents</span><span class="token punctuation">:</span> <span class="token string">"**/app-$(buildConfiguration).apk"</span> <span class="token key atrule">targetFolder</span><span class="token punctuation">:</span> <span class="token string">"$(Build.ArtifactStagingDirectory)/$(projectName)"</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Copy unsigned APK to staging directory"</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> PublishBuildArtifacts@1 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">PathtoPublish</span><span class="token punctuation">:</span> <span class="token string">"$(Build.ArtifactStagingDirectory)/$(projectName)"</span> <span class="token key atrule">ArtifactName</span><span class="token punctuation">:</span> <span class="token string">"$(projectName)"</span> <span class="token key atrule">publishLocation</span><span class="token punctuation">:</span> <span class="token string">"Container"</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Publish artifacts"</span></code></pre></div> <p>We use the <code class="language-text">gradelw</code> utility that comes with the android project to build it. This is a wrapper around Gradle which invokes it with any parameters we have given (in our case <code class="language-text">assembleDebug</code> or <code class="language-text">assembleRelease</code>). If Gradle is not found in your system (or in a build agent) it will also download it from a distribution server.</p> <p>The <code class="language-text">ionic-android-release-build.yml</code> is pretty much the same configuration except for some extra flags to do the release build. We will go through this file in the Signing step.</p> <p>Now, let go ahead and commit the changes. You might want to <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/create-first-pipeline?view=azure-devops&#x26;tabs=java%2Ctfs-2018-2%2Cbrowser#create-your-first-java-pipeline">create a new Azure Pipeline</a> if you haven’t already. Once the pipeline is run, you will be able to see all green (hopefully). 😀</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 691px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 135.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD10lEQVR42nVV2XbiWAzkRzpgFm94xSsYzL4EkszDzJl0P/X/f0W1SgYCNHnQsX3vtVRXKpVarmNjt99iu1njsNshzzPU0yleDzu87vdYzuc4vR4xm1bo97oY9HtPjXu2ZaJlDvpwbAu2acIxLVjmAFzjpiVrDBh4Q31+56jXNdTUoW2bGFo2ommE8m2MOIxQz6aYzyos5rV8B3r4GTquDR0H6ShGHIUKrKWI+n24kYth6uqBRA6EgYcsTeALOjr8Dt3QdZGnKXzfg0uHvB43jXYb2ShBksTqZFZVyLNMjE5d9OUMz15scP7u9wwYnTa8odsgvGwyWuT58N0hAok2LgopUCoOU81r++WH/mh0XvTZNTpixtVxGPqa58bhoIde20C0iBFMQ4RegCwZCdoRRnGkV59MxijzHJPxWJmQyl4cBVe0WthLlbnQk2hRGSLIA6mqLz+WavyxEifzeobFbCbFqlFV4rxonFpmX2/Hogxd+95hWAbwMx+e4yKOxbEUJgwCuHKQV+y0X9SM9te1L/zjdb8Q8sodA6N6hHASnakzkWuWgqRAWRbi1Lnm69bokM6Ylq8csiiC0E89DEdDeFKYVKrtiZMw8O+q+ow+2hyW9YCw3UG6zhDVsRA1wXo1x1iQLRcLPfgduXtCG1KGzXCPsCvQIwdu6GgbkrAkORNNGtH4Iyl065jfDMj3O4Tdlw7yfYF4maDMCux3W9RC7s16KRWeapXXqyUcQUGH/I+oGaieziSHaVPlixiY5JJvw/Fsjcogt3kjz3gl7vH9FtnFHhC2UR7HSLc58lEq8rXHtJrgdDphJ9JGdCuxWrh4Oh5l/yD5nV+DKcLbHA7IJ1ckyzEVLVXIshoUF3pc7JK7xwL9hXD8ViHdFSjSHIf9BsvVQgV2KuJKHt4W4+5drOkU5xahRLUHisq8yd2jwly594SL9wh/CMKPCtmhVIQfHyL7IrKb7Ro7GQ1EwKpSO5+ROwq/QWgS5WBwHgvNk3Yrc5fnPR85Sh4RvksOD5LDJNOKLha1Vnq7WmG1nGNGLi4XMtA2yoJEpI2iYktAqrx2CgdRw8O+VljzqBz7qjA7xBPV5pWo5jT2OFXIvOmWRrHP8ClHqtKi0PEokg5ZK7qZjNSBODVEqphDqnSj1s03e5kOIxlm6tALQkXA3uUiW4kISFSqdTOw/CuyRyNqGosWyRhoVesDNsK17b8y2H+fcDoc8evzEz///w+/fn7in/f3M9LqL+P6Rrro/e2E4+tBFaeVlJXQJEFRFRjXpUoWN2qhDAWhkllC8XxmHAEcEyT/Qs4WeYY/28o232DLz+8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-3.png" title="" src="/static/951693eb5b9c5757645e06620dc4d5be/e185b/multi-stage-builds-with-azure-pipelines-ionic-3.png" srcset="/static/951693eb5b9c5757645e06620dc4d5be/772e8/multi-stage-builds-with-azure-pipelines-ionic-3.png 200w, /static/951693eb5b9c5757645e06620dc4d5be/e17e5/multi-stage-builds-with-azure-pipelines-ionic-3.png 400w, /static/951693eb5b9c5757645e06620dc4d5be/e185b/multi-stage-builds-with-azure-pipelines-ionic-3.png 691w" sizes="(max-width: 691px) 100vw, 691px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you have a look at the build artifacts, you will find the debug and release build APKs all in one place now. Next, we will have a look at signing the APK files.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 46%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABCUlEQVR42pVSV3bCMBDUUYK7CdiWi1wwbhT7Annc/yQTjd4jj5AAycfsrrZJs1rhuy4272ss84zL5QO7poa1eoPnOnAd+98Q6zBApRRUnoF24HvPi15cJMIwhGo6pLmCY63g2NbTAvtFjqBIZQKpEW03iKPtQzBeaCZSxj9yeeaYBEXXtRj7zjTOsxRZKn8F4/t2h7oqjX0f8z0XgoK39rrpfD5jWWaUKodM4q/EK+ir6wrdvjX2fcw05CfQwSbTOGCaRpxOR/MK0rgtpN3umr81rCqF42HEMPQYNQY9gl6Da3SlR93oF5L2Q8qcIdeFu1jkqaHPBOqqLExiGPgmhzqJo2++W/CXPwErXRTbaIRF2wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-4.png" title="" src="/static/a1024958d33dc7f70a8901a235b4ba74/1e043/multi-stage-builds-with-azure-pipelines-ionic-4.png" srcset="/static/a1024958d33dc7f70a8901a235b4ba74/772e8/multi-stage-builds-with-azure-pipelines-ionic-4.png 200w, /static/a1024958d33dc7f70a8901a235b4ba74/e17e5/multi-stage-builds-with-azure-pipelines-ionic-4.png 400w, /static/a1024958d33dc7f70a8901a235b4ba74/1e043/multi-stage-builds-with-azure-pipelines-ionic-4.png 690w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Signing the APK</h3> <p>If you are publishing your app to the Play Store, we need to consider signing our APKs. Ionic’s <a href="https://ionicframework.com/docs/deployment/play-store">official documentation</a> already covers this step, so we will look at how we can automate this step.</p> <p><strong>1. Generating the .keystore file</strong></p> <p>If you are doing this for the first time, you need to create the private <code class="language-text">.keystore</code> file used for signing the APK. You can use the <code class="language-text">keytool</code> file that comes with the Android SDK:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">keytool <span class="token parameter variable">-genkey</span> <span class="token parameter variable">-v</span> <span class="token parameter variable">-keystore</span> my-release-key.keystore <span class="token parameter variable">-alias</span> alias_name <span class="token parameter variable">-keyalg</span> RSA <span class="token parameter variable">-keysize</span> <span class="token number">2048</span> <span class="token parameter variable">-validity</span> <span class="token number">10000</span></code></pre></div> <p>Remember to take note of the following when you are generating this file.</p> <ul> <li>Keystore Password</li> <li>Keystore Alias</li> <li>Key Password</li> </ul> <p>We will be putting these into pipeline variables as our next step.</p> <p><strong>2. Creating the pipeline variables</strong></p> <p>Once you have generated the keystore file, you can navigate to the Library section of your project in Azure DevOps.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABNklEQVR42pWT6WrDMBCE8xqliQ/J1uUrliwnpe//XNPdDYFQUir/GLAFHs23Oz7d8oacIlLasG0JOSdY00O1DbRqoUhtU6OpqyKdYtphjIF1AcF7dLolIwXvHJw1cM6LcV1dygzzvsOR4fnzQz56VXU5i0rTieH31x2M7Z0htIpQaxFj9r0WlaYTw9uesS4TvO3FSKtGxM+Ozhj7mGFcMHhHspjGAfM0YJlHXGe6JFi0ZH4IuZsSAich5DE4mE4TLiUk6U6RNJojCc2yIy0j4npFjiuWaZTE4xAE90g6MUzrLDV5bpYPeSEs7uKR+T16GKNg/lWPXkbwf7F5iQ9kqgUvIhDmszJPNVQj3jSX/TX5OzFNy4ZD8LJR/ht+38q4fNFMc+WOFiF7Z2UR7+bF76bvRKUz/AG4BGtZiO8CIgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-5.png" title="" src="/static/2518476b152b1902061e06a2efd9f3ee/5a190/multi-stage-builds-with-azure-pipelines-ionic-5.png" srcset="/static/2518476b152b1902061e06a2efd9f3ee/772e8/multi-stage-builds-with-azure-pipelines-ionic-5.png 200w, /static/2518476b152b1902061e06a2efd9f3ee/e17e5/multi-stage-builds-with-azure-pipelines-ionic-5.png 400w, /static/2518476b152b1902061e06a2efd9f3ee/5a190/multi-stage-builds-with-azure-pipelines-ionic-5.png 800w, /static/2518476b152b1902061e06a2efd9f3ee/97bfd/multi-stage-builds-with-azure-pipelines-ionic-5.png 1078w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Make sure to put in the <code class="language-text">keystorePassword</code>, <code class="language-text">keyAlias</code> and <code class="language-text">keyPassword</code> variables which we created in the above step.</p> <p>After that, you need to upload your keystore file to Secure files section. We will later access this file in the Android signing task.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 418px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABVklEQVR42qVSzXqCMBDMY1gEAiggKD8SP20RDKhIvfv+rzLdRLF6sB56mG+STXZ3ZhOWJSmSeYzAn4LbFhxu/wusKreQVYVimcO2TLgOvx8+rp/xojEJYoYxgvExgmmOdUGbWB0otZZJe2LbumJoMDC/3RsKeq4L1ndHfJ97SFmj3dU47g9k34esK5z7Dvu2weVyQZEl96RZKrASAruqRvm5wWEv4ZHqKFuDhYEPhWgWIo5mWMQxJTnw4xRxsUGQCIT5Gn4YkTKlztEKJ56r8/zpREPFPM8DEzS7LFlgOnHJ1tW2S0ps7sC0Hc0WgXP+NFNtl+4+2lbMilwVnOuAfbswzFLD+oU6U+r/AgtI9jLPsFoJiGJ5XYtCr9XLK9agWEpO3hbMsxT9qUPbSpy6DnW1RUWQ9EBl+aUfpW0aNI3Ujd79VabsjY0PmGPjzq9gqS/15mP/AJElH2FsQU04AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-6.png" title="" src="/static/849c39384bf3de44ddb6290ff23e34e5/d7398/multi-stage-builds-with-azure-pipelines-ionic-6.png" srcset="/static/849c39384bf3de44ddb6290ff23e34e5/772e8/multi-stage-builds-with-azure-pipelines-ionic-6.png 200w, /static/849c39384bf3de44ddb6290ff23e34e5/e17e5/multi-stage-builds-with-azure-pipelines-ionic-6.png 400w, /static/849c39384bf3de44ddb6290ff23e34e5/d7398/multi-stage-builds-with-azure-pipelines-ionic-6.png 418w" sizes="(max-width: 418px) 100vw, 418px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><strong>3. Add the signing task</strong></p> <p>We will be using the <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/android-signing?view=azure-devops">AndroidSigning@3</a> task to sign our release build. It’s pretty straight forward task to use and the parameters we need to send are as follows.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> AndroidSigning@3 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">apkFiles</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)/*.apk'</span> <span class="token key atrule">apksign</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">apksignerKeystoreFile</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keystoreFileName }}'</span> <span class="token key atrule">apksignerKeystorePassword</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keystorePassword }}'</span> <span class="token key atrule">apksignerKeystoreAlias</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keyAlias }}'</span> <span class="token key atrule">apksignerKeyPassword</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keyPassword }}'</span> <span class="token key atrule">apksignerArguments</span><span class="token punctuation">:</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>out $(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)/$(projectName)<span class="token punctuation">-</span>$(buildConfiguration).apk <span class="token punctuation">-</span><span class="token punctuation">-</span>verbose <span class="token key atrule">zipalign</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Sign the APK'</span></code></pre></div> <p>Notice how we are linking the variables we created in the Library section. Now, our final <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/blob/main/infrastructure/ionic-android-release-build.yml">ionic-android-release-build.yml</a> file would look like the following.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">parameters</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> keystoreFileName <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"The keystore file name for signing the apk"</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> string <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> keystorePassword <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Password for the keystore"</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> string <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> keyAlias <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Key alias"</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> string <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> keyPassword <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Key password"</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">script</span><span class="token punctuation">:</span> npm install <span class="token punctuation">-</span>g @ionic/cli <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Install Ionic CLI'</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> Npm@1 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">workingDir</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)'</span> <span class="token key atrule">command</span><span class="token punctuation">:</span> install <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'NPM Install'</span> <span class="token punctuation">-</span> <span class="token key atrule">powershell</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string"> ionic cap build android --prod --no-open npx cap sync cd android ./gradlew assemble$(buildConfiguration)</span> <span class="token key atrule">workingDirectory</span><span class="token punctuation">:</span> $(Build.SourcesDirectory)/$(projectName) <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Build Android Project'</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> AndroidSigning@3 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">apkFiles</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)/*.apk'</span> <span class="token key atrule">apksign</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">apksignerKeystoreFile</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keystoreFileName }}'</span> <span class="token key atrule">apksignerKeystorePassword</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keystorePassword }}'</span> <span class="token key atrule">apksignerKeystoreAlias</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keyAlias }}'</span> <span class="token key atrule">apksignerKeyPassword</span><span class="token punctuation">:</span> <span class="token string">'${{ parameters.keyPassword }}'</span> <span class="token key atrule">apksignerArguments</span><span class="token punctuation">:</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>out $(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)/$(projectName)<span class="token punctuation">-</span>$(buildConfiguration).apk <span class="token punctuation">-</span><span class="token punctuation">-</span>verbose <span class="token key atrule">zipalign</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">'Sign the APK'</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> CopyFiles@2 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">SourceFolder</span><span class="token punctuation">:</span> <span class="token string">'$(Build.SourcesDirectory)/$(projectName)/android/app/build/outputs/apk/$(buildConfiguration)'</span> <span class="token key atrule">contents</span><span class="token punctuation">:</span> <span class="token string">"**/$(projectName)-$(buildConfiguration).apk"</span> <span class="token key atrule">targetFolder</span><span class="token punctuation">:</span> <span class="token string">"$(Build.ArtifactStagingDirectory)/$(projectName)"</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Copy unsigned APK to staging directory"</span> <span class="token punctuation">-</span> <span class="token key atrule">task</span><span class="token punctuation">:</span> PublishBuildArtifacts@1 <span class="token key atrule">inputs</span><span class="token punctuation">:</span> <span class="token key atrule">PathtoPublish</span><span class="token punctuation">:</span> <span class="token string">"$(Build.ArtifactStagingDirectory)/$(projectName)"</span> <span class="token key atrule">ArtifactName</span><span class="token punctuation">:</span> <span class="token string">"$(projectName)"</span> <span class="token key atrule">publishLocation</span><span class="token punctuation">:</span> <span class="token string">"Container"</span> <span class="token key atrule">displayName</span><span class="token punctuation">:</span> <span class="token string">"Publish artifacts"</span></code></pre></div> <blockquote> <p>💡 Tip: I found that I can’t make the file name coming from the variable group. Which means you need to have it hardcoded in the azure-pipelines.yml file</p> </blockquote> <p>First, we need to tell the azure-pipeline.yml which variable group to use. Here’s a small excerpt of that.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token comment"># other tasks removed for brevity</span> <span class="token punctuation">-</span> <span class="token key atrule">job</span><span class="token punctuation">:</span> Build_Ionic_Android_Release <span class="token key atrule">variables</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">group</span><span class="token punctuation">:</span> SampleApp<span class="token punctuation">-</span>Release <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> buildConfiguration <span class="token key atrule">value</span><span class="token punctuation">:</span> Release</code></pre></div> <blockquote> <p>💡 Note that the notation for <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&#x26;tabs=yaml%2Cbatch">defining a variable</a> can take two variants. If you are not using a group you could simply write it as <code class="language-text">buildConfiguration: Release</code>. If you are using the <code class="language-text">group</code> attribute, you need to use the notation I have shown in the above snippet. I had to learn it the hard way 😂</p> </blockquote> <p>You would have also noticed that we are using <code class="language-text">parameters</code> here. These will be injected in the azure-pipeline.yml file.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token comment"># other tasks removed for brevity</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">template</span><span class="token punctuation">:</span> ionic<span class="token punctuation">-</span>android<span class="token punctuation">-</span>release<span class="token punctuation">-</span>build.yml <span class="token key atrule">parameters</span><span class="token punctuation">:</span> <span class="token key atrule">keystoreFileName</span><span class="token punctuation">:</span> <span class="token string">'sampleapp-release-key.keystore'</span> <span class="token key atrule">keystorePassword</span><span class="token punctuation">:</span> $(keystorePassword) <span class="token key atrule">keyAlias</span><span class="token punctuation">:</span> $(keyAlias) <span class="token key atrule">keyPassword</span><span class="token punctuation">:</span> $(keyPassword)</code></pre></div> <p>Once you push your changes to the repo, Azure DevOps would ask you to permit your pipeline to access the secure file which we added in the above step. Click on View and permit it. Alternatively, you could also use Azure KeyVault for storing secrets.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 30%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7UlEQVR42n2Oy07DMBBF/RfQBYumMagk8SN+JSRuElCLVMqC//+Yi8eKeEjQxdFcz/GMzRa7wWUqcZl5qhxvB45z3KVKlOu5yJk8uXNy78s9Tv0dZnODxd7i47jHYLZgThQIukzwTEdZlWvvN12+s+aWw6sCTmwzg3mArndgwQcE66ClhpTqOuI7CyFzVUpnZJqvqxosBo/JOXit0F5BK4kuODhroKTAODzBO5v75G2rIeoKTPYzXk6vmOIIk4RJ4i9oiJbEcciZFlvTfnnKokk/rEyPeXlGTJep8R9Net1bi867nImfXooG1eMen77ZvgCYKZt6AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-7.png" title="" src="/static/0aebaf65b3e7841c3073aec94cb06e75/5a190/multi-stage-builds-with-azure-pipelines-ionic-7.png" srcset="/static/0aebaf65b3e7841c3073aec94cb06e75/772e8/multi-stage-builds-with-azure-pipelines-ionic-7.png 200w, /static/0aebaf65b3e7841c3073aec94cb06e75/e17e5/multi-stage-builds-with-azure-pipelines-ionic-7.png 400w, /static/0aebaf65b3e7841c3073aec94cb06e75/5a190/multi-stage-builds-with-azure-pipelines-ionic-7.png 800w, /static/0aebaf65b3e7841c3073aec94cb06e75/66465/multi-stage-builds-with-azure-pipelines-ionic-7.png 1097w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The completed YAML files can be found in my repo over <a href="https://github.com/sahansera/AzurePipelinesTemplateForIonic/tree/main/infrastructure">here</a>. Once the pipeline is completed, you can have a look at the artifacts. Voilà! We have the signed APKs</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 599px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQklEQVR42qVS2W7CMBD0f7R9KLnsVIlzkZA7JEGBpBT+/2emXj9QVCFEy8No7T3Gu95hrnCwThIcPxfMhwlxFMBYvcMyjX+BuYIjkD66tkHfdwgD+Rxh328x7/cIfQ9vry9PkWlC2/VgCA82F3BsS8O2zIdhmSuYxupyZzRuq8aNwgBSdUn3R0H5abpGvskutWydxBjU2JssBZ3/giSOUOQ56qrUZ/KxTBFN04jlsMfp+IXz+YS6LPQDZZHfRZFv1DJbLPOMStWQj9FWibltKt3pbhz0tq9fJXsLcRQiV6Tj0F/y9B/S/1Vljm3XaPkMKqFRhNLztIwofgsUy9IUXddq/ZKPSd9XgQB1XWBU3S1KQmSn3YiDEjqRB1LqnN8gf6oIx2FAHIbax64l4HKODyHguhyCOyDRk70nG5IZd35yvgEb/1ZmzulBXwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="multi-stage-builds-with-azure-pipelines-ionic-8.png" title="" src="/static/4788c14fe715d74c8dfc8ed000153e10/43142/multi-stage-builds-with-azure-pipelines-ionic-8.png" srcset="/static/4788c14fe715d74c8dfc8ed000153e10/772e8/multi-stage-builds-with-azure-pipelines-ionic-8.png 200w, /static/4788c14fe715d74c8dfc8ed000153e10/e17e5/multi-stage-builds-with-azure-pipelines-ionic-8.png 400w, /static/4788c14fe715d74c8dfc8ed000153e10/43142/multi-stage-builds-with-azure-pipelines-ionic-8.png 599w" sizes="(max-width: 599px) 100vw, 599px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Conclusion</h3> <p>In summary, we created a blank Ionic app, set up Azure DevOps pipeline YAML files and published the signed artifacts. You can use the same structure to add an iOS project as well. However, the build steps could be more involved and cumbersome to get right initially. In my next article, we will look at how we can integrate with Microsft App Center for distribution.</p> <p>If you have any feedback or questions, let me know in the comments below. Until next time 👋</p> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops">https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&#x26;tabs=yaml%2Cbatch">https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&#x26;tabs=yaml%2Cbatch</a></li> <li><a href="https://ionicframework.com/docs/developing/android">https://ionicframework.com/docs/developing/android</a></li> </ol><![CDATA[Understanding WebSockets with ASP.NET]]>https://www.sahansera.dev/understanding-websockets-with-aspnetcore-5/https://www.sahansera.dev/understanding-websockets-with-aspnetcore-5/Fri, 01 Jan 2021 00:00:00 GMT<p>In this article, we will go through RFC 6455 WebSockets specification and configure a generic <a href="http://asp.NET">ASP.NET</a> (Core) 7 application to communicate over WebSockets connection with SignalR. We will dive into the underlying concepts to understand what happens under the covers.</p> <blockquote> <p>💡 <strong>UPDATE on March 2023</strong>: I have recently migrated this project to .NET 7 🎉. You can still access the .NET 5 version from this <a href="https://github.com/sahansera/WebSocketsTutorial/tree/dotnet-5">branch</a>.</p> </blockquote> <h3>Walkthrough video</h3> <p>If you like to watch a video walkthrough instead of this article, you can follow along on my Youtube channel too 😊</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/8KlbUkdqgz8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <h3>A bit about WebSockets</h3> <p>WebSockets was introduced to enable two-way communication between a client and a server. One of the pain points with HTTP 1.0 was creating and closing a connection each time we send a request to the server. With HTTP 1.1 however, <em><a href="https://tools.ietf.org/html/rfc2616#page-44">persistent connections</a> (RFC 2616)</em> were introduced by making using of a keep-alive mechanism. With this, connections could be reused for more than one request - which will reduce latency as the server knows about the client and they do not need to start over the handshake process per request.</p> <blockquote> <p>💡 When you are learning about protocols, a good place to start is to read its corresponding RFC specification.</p> </blockquote> <p>WebSockets is built on top of HTTP 1.1 spec as it allows persistent connections. So, when you are making a WebSocket connection for the first time, it is essentially an HTTP 1.1 request (more on this later). This enables real-time communication between a client and a server. In a nutshell, the following diagram depicts what happens during the initiation (handshake), data transfer and closing of a WS connection. We will dive deeper into these concepts later.</p> <div data-ea-publisher="sahanseradev" data-ea-type="image"></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 107.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB7OsQmbYVgluK/8QAGRAAAgMBAAAAAAAAAAAAAAAAAAECEBES/9oACAEBAAEFAodGic6VaaI//8QAFhEBAQEAAAAAAAAAAAAAAAAAAREg/9oACAEDAQE/AQmP/8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQIBAT8BY//EABoQAAEFAQAAAAAAAAAAAAAAABABAhIgITH/2gAIAQEABj8CWQ1teD//xAAcEAACAgIDAAAAAAAAAAAAAAAAAREhMUEQgZH/2gAIAQEAAT8hZZL4KQtMrvTNMyzPQiCmr4rI/9oADAMBAAIAAwAAABB418D/xAAXEQEBAQEAAAAAAAAAAAAAAAARAAEQ/9oACAEDAQE/ECC2ZHP/xAAYEQEBAAMAAAAAAAAAAAAAAAABEQAQIf/aAAgBAgEBPxBVsx67/8QAHRABAQEBAAEFAAAAAAAAAAAAAREAIUExUYGRsf/aAAgBAQABPxAIACSDr5xXBJ740s2iEDPEUO+suQwfNnIk8uZPQcV4feZUIK/u/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding-websockets-with-aspnetcore-1.jpg" title="" src="/static/3ca403c56afd23af37206f10fe96ff47/4b190/understanding-websockets-with-aspnetcore-1.jpg" srcset="/static/3ca403c56afd23af37206f10fe96ff47/e07e9/understanding-websockets-with-aspnetcore-1.jpg 200w, /static/3ca403c56afd23af37206f10fe96ff47/066f9/understanding-websockets-with-aspnetcore-1.jpg 400w, /static/3ca403c56afd23af37206f10fe96ff47/4b190/understanding-websockets-with-aspnetcore-1.jpg 800w, /static/3ca403c56afd23af37206f10fe96ff47/61d5f/understanding-websockets-with-aspnetcore-1.jpg 1162w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The protocol has two parts to it; <strong>Handshake</strong> and <strong>Data Transfer</strong>.</p> <p><strong>Handshake</strong></p> <p>Let’s talk about the opening handshake first. From the spec,</p> <blockquote> <p>The opening handshake is intended to be compatible with HTTP-based</p> </blockquote> <p>server-side software and intermediaries, so that a single port can be used by both HTTP clients talking to that server and WebSocket clients talking to that server.</p> <p>Simply put, a WebSocket connection is based on HTTP (and TCP as transport) over a single port. Here’s the summary of steps.</p> <ol> <li>A server must be listening for incoming TCP socket connections. This could be any port you have assigned - normally this would be <code class="language-text">80</code> or <code class="language-text">443</code>.</li> <li>The client initiates the opening handshake (otherwise the server wouldn’t know who to talk to) with an HTTP GET request - This is the “Web” part in “WebSockets”. In the headers, the client will ask the server to <em><strong>Upgrade</strong></em> the connection to a WebSocket.</li> <li>The server sends a handshake response telling the client that it will be changing the protocol from HTTP to WebSocket.</li> <li>Both client and server negotiate the connection details. Either of the parties can back out if the terms are unfavourable.</li> </ol> <p>Here’s what a typical opening (client) handshake request looks like.</p> <div class="gatsby-highlight" data-language="http"><pre class="language-http"><code class="language-http"><span class="token request-line"><span class="token method property">GET</span> <span class="token request-target url">/ws-endpoint</span> <span class="token http-version property">HTTP/1.1</span></span> <span class="token header"><span class="token header-name keyword">Host</span><span class="token punctuation">:</span> <span class="token header-value">example.com:80</span></span> <span class="token header"><span class="token header-name keyword">Upgrade</span><span class="token punctuation">:</span> <span class="token header-value">websocket</span></span> <span class="token header"><span class="token header-name keyword">Connection</span><span class="token punctuation">:</span> <span class="token header-value">Upgrade</span></span> <span class="token header"><span class="token header-name keyword">Sec-WebSocket-Key</span><span class="token punctuation">:</span> <span class="token header-value">L4kHN+1Bx7zKbxsDbqgzHw==</span></span> <span class="token header"><span class="token header-name keyword">Sec-WebSocket-Version</span><span class="token punctuation">:</span> <span class="token header-value">13</span></span></code></pre></div> <p>Note how the client sends out <code class="language-text">Connection: Upgrade</code> and <code class="language-text">Upgrade: websocket</code> headers in the request.</p> <p>And, the server handshake response,</p> <div class="gatsby-highlight" data-language="http"><pre class="language-http"><code class="language-http"><span class="token response-status"><span class="token http-version property">HTTP/1.1</span> <span class="token status-code number">101</span> <span class="token reason-phrase string">Switching Protocols</span></span> <span class="token header"><span class="token header-name keyword">Upgrade</span><span class="token punctuation">:</span> <span class="token header-value">websocket</span></span> <span class="token header"><span class="token header-name keyword">Connection</span><span class="token punctuation">:</span> <span class="token header-value">Upgrade</span></span> <span class="token header"><span class="token header-name keyword">Sec-WebSocket-Accept</span><span class="token punctuation">:</span> <span class="token header-value">CTPN8jCb3BUjBjBtdjwSQCytuBo=</span></span></code></pre></div> <p>Note how the server sends out <code class="language-text">HTTP/1.1 101 Switching Protocols</code> in the response headers. Anything other than a 101 indicates that the opening handshake was not completed.</p> <p>The closing handshake is pretty simple. Either the client or server can send out a closing handshake request. From the spec,</p> <blockquote> <p>It is safe for both peers to initiate this handshake simultaneously.</p> </blockquote> <p>The closing handshake is intended to complement the TCP closing handshake (FIN/ACK), on the basis that the TCP closing handshake is not always reliable end-to-end, especially in the presence of intercepting proxies and other intermediaries.</p> <p>We will talk about these in action when we jump over to the demo section.</p> <p><strong>Data Transfer</strong></p> <p>The next key concept we need to understand is Data Transfer. Either of the parties can send messages at any given time - as it is a Full Duplex communication protocol.</p> <p>The messages are composed of one or more <em>frames</em>. A frame can be of type text (UTF-8), binary, and control frames (such as <code class="language-text">0x8 (Close)</code>, <code class="language-text">0x9 (Ping)</code>, and <code class="language-text">0xA (Pong)</code>).</p> <p>If you are interested, you can read the full RFC spec from <a href="https://tools.ietf.org/html/rfc6455">here</a>.</p> <h3>Setup</h3> <p>Let’s put this into action and see how it works.</p> <blockquote> <p>💡 Follow along with the completed code from my repository <a href="https://github.com/sahansera/WebSocketsTutorial">here</a></p> </blockquote> <p>First create a new <a href="http://asp.NET">ASP.NET</a> 5 WebAPI app.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new webapi <span class="token parameter variable">-n</span> WebSocketsTutorial dotnet new sln dotnet sln <span class="token function">add</span> WebSocketsTutorial</code></pre></div> <p>Now we will add SignalR to our app.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> WebSocketsTutorial/ package Microsoft.AspNet.SignalR</code></pre></div> <h3>Sample code explanation</h3> <p>We will start by adding the WebSockets middleware to our WebAPI app. Head over to the <code class="language-text">Startup.cs</code> file and add the following line inside the <code class="language-text">Configure</code> method.</p> <p>I like to keep things simple for this tutorial. Therefore, I’m not going to talk about SignalR (Hubs and stuff). It would be purely based on WebSocket communication. You can also achieve the same with raw WebSockets, you don’t have to use SignalR if you want to keep things even more simpler.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token range operator">..</span><span class="token punctuation">.</span> app<span class="token punctuation">.</span><span class="token function">UseWebSockets</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token range operator">..</span><span class="token punctuation">.</span></code></pre></div> <p>Next, we will delete the default WeatherForecastController and add a new controller called WebSocketsController. Note that we will be just using a controller action instead of intercepting the request pipeline</p> <p>The full code for this controller will look like this. This code is based on Microsoft’s official docs’ <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-5.0#send-and-receive-messages">example</a>.</p> <p><a href="https://github.com/sahansera/WebSocketsTutorial/blob/main/WebSocketsTutorial/Controllers/WebSocketsController.cs">WebSocketsController.cs</a></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">System</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>WebSockets</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Text</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Threading</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Mvc</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Logging</span><span class="token punctuation">;</span> <span class="token keyword">namespace</span> <span class="token namespace">WebSocketsTutorial<span class="token punctuation">.</span>Controllers</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">ApiController</span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Route</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"[controller]"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WebSocketsController</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ControllerBase</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ILogger<span class="token punctuation">&lt;</span>WebSocketsController<span class="token punctuation">></span></span> _logger<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">WebSocketsController</span><span class="token punctuation">(</span><span class="token class-name">ILogger<span class="token punctuation">&lt;</span>WebSocketsController<span class="token punctuation">></span></span> logger<span class="token punctuation">)</span> <span class="token punctuation">{</span> _logger <span class="token operator">=</span> logger<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpGet</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"/ws"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>HttpContext<span class="token punctuation">.</span>WebSockets<span class="token punctuation">.</span>IsWebSocketRequest<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> webSocket <span class="token operator">=</span> <span class="token keyword">await</span> HttpContext<span class="token punctuation">.</span>WebSockets<span class="token punctuation">.</span><span class="token function">AcceptWebSocketAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _logger<span class="token punctuation">.</span><span class="token function">Log</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Information<span class="token punctuation">,</span> <span class="token string">"WebSocket connection established"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">Echo</span><span class="token punctuation">(</span>webSocket<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> HttpContext<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">=</span> <span class="token number">400</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Echo</span><span class="token punctuation">(</span><span class="token class-name">WebSocket</span> webSocket<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">byte</span></span><span class="token punctuation">[</span><span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> webSocket<span class="token punctuation">.</span><span class="token function">ReceiveAsync</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArraySegment<span class="token punctuation">&lt;</span><span class="token keyword">byte</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span> _logger<span class="token punctuation">.</span><span class="token function">Log</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Information<span class="token punctuation">,</span> <span class="token string">"Message received from Client"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>result<span class="token punctuation">.</span>CloseStatus<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> serverMsg <span class="token operator">=</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">.</span><span class="token function">GetBytes</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Server: Hello. You said: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> webSocket<span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArraySegment<span class="token punctuation">&lt;</span><span class="token keyword">byte</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>serverMsg<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> serverMsg<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>MessageType<span class="token punctuation">,</span> result<span class="token punctuation">.</span>EndOfMessage<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span> _logger<span class="token punctuation">.</span><span class="token function">Log</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Information<span class="token punctuation">,</span> <span class="token string">"Message sent to Client"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">byte</span></span><span class="token punctuation">[</span><span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">;</span> result <span class="token operator">=</span> <span class="token keyword">await</span> webSocket<span class="token punctuation">.</span><span class="token function">ReceiveAsync</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArraySegment<span class="token punctuation">&lt;</span><span class="token keyword">byte</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span> _logger<span class="token punctuation">.</span><span class="token function">Log</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Information<span class="token punctuation">,</span> <span class="token string">"Message received from Client"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> webSocket<span class="token punctuation">.</span><span class="token function">CloseAsync</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>CloseStatus<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> result<span class="token punctuation">.</span>CloseStatusDescription<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span> _logger<span class="token punctuation">.</span><span class="token function">Log</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Information<span class="token punctuation">,</span> <span class="token string">"WebSocket connection closed"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Here’s what we did,</p> <ol> <li>Add a new route called <code class="language-text">ws/</code></li> <li>Check if the current request is via WebSockets otherwise throw a 400.</li> <li>Wait until client initiates a request. <a href="https://github.com/sahansera/WebSocketsTutorial/blob/d03e001de0534f26aff8f597ab05ce45886288d6/WebSocketsTutorial/Controllers/WebSocketsController.cs#L40">L:40</a></li> <li>Going into a loop until the client closes the connection. <a href="https://github.com/sahansera/WebSocketsTutorial/blob/d03e001de0534f26aff8f597ab05ce45886288d6/WebSocketsTutorial/Controllers/WebSocketsController.cs#L43">L:43</a></li> <li>Within the loop, we will prepend “Server: Hello. You said: &#x3C;client’s message>” to the message and send it back to the client.</li> <li>Wait until the client send another request.</li> </ol> <p>Note that the server does not need to wait until the client sends a request to push messages to the client, after the initial handshake. Let’s run the application and see whether it works.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run <span class="token parameter variable">--project</span> WebSocketsTutorial</code></pre></div> <p>Once you run the application, head over to <a href="https://localhost:5001/swagger/index.html"><code class="language-text">https://localhost:5001/swagger/index.html</code></a>. You should see the Swagger UI.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 78.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAABYlAAAWJQFJUiTwAAACn0lEQVR42q2SXW/SUBjHiZoY75ddL7tzTC9MvDHxdd/CaOI38RVmuPFCN83MPoBuxBsjkAkdm1BWgbL3jI3NZUBfaAsIrG+n/D3tJizRxC3xSX59es7p82v7nOPz0YiFw4Ozs9PDnyKRIZ7nPViW7cEwzFCErvVwx8e4NZ/nFofD4dig7zjOV4olRhT2IHdqbbVV09utQx1Aj5pS18tVWRdljVLXJVHRqxRBVN25jqIo2C8LSeo65wov8cs5riIegOcLJMWyWFhMIU1zms0glWbBZpawtr5OWcPGzg628ll8yy5h6XsOKyurzn4ph3w+l6Oui57w8r1B7sb9YVy7ep34/X4MDAxgZMQPv38Uo6NXcPvuTTx/+QJPAo/xLPgUoVchTL5/g4mp13g7NeG8m57EnbFbfeHHmQ/cYioJ2ivCMAlEIl8Qi0URjUZpjmFuLgYmydD1ecQTccSZr3Sc8EjMx510JoWZ2Zm+UBBErtls46BcJaJUQ03RIEkKJFmBotYhyxoOKgJqeh6W08RRdL2rQ8PNzWazL9ws7nIZfgvz6QJZyBTAZQvgC6vY2CyisLyG5ZV1VKoClJ97aHca0HWDont0Oh1PKElSX6hpGudOEschDn1xt9t13+zRvydw7C5sy4ZlWT0Mw/CEqqr2hbIkcg1Fwo/SNimVdqFqKmzbgmkaFNPLtmUeQedPQtf+FBb3ZS6+bSLCS2RrtwpBaaHetqC1zH+iNnXn0AYESTkhLKtcWgBSe4TU2ybqHRuaKzwFastwdNqm6kmhpqleDx1iENvrjft71qkw/9ZDOvCEhmnRZ6zuWaA9Jm4t3dhsT0jPkCekO0pwxvh9DhuNRu8LLwQCgbFQKPSI5gfBYPDhWXBrxsfH3dox1+X73/ELsmaWoV5cedUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding-websockets-with-aspnetcore-2.png" title="" src="/static/1e88118b633de157a8edd5fdb0eebcdb/5a190/understanding-websockets-with-aspnetcore-2.png" srcset="/static/1e88118b633de157a8edd5fdb0eebcdb/772e8/understanding-websockets-with-aspnetcore-2.png 200w, /static/1e88118b633de157a8edd5fdb0eebcdb/e17e5/understanding-websockets-with-aspnetcore-2.png 400w, /static/1e88118b633de157a8edd5fdb0eebcdb/5a190/understanding-websockets-with-aspnetcore-2.png 800w, /static/1e88118b633de157a8edd5fdb0eebcdb/c1b63/understanding-websockets-with-aspnetcore-2.png 1200w, /static/1e88118b633de157a8edd5fdb0eebcdb/29007/understanding-websockets-with-aspnetcore-2.png 1600w, /static/1e88118b633de157a8edd5fdb0eebcdb/b54cd/understanding-websockets-with-aspnetcore-2.png 1662w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We will now see how we can get the client and server to talk to each other. For the purpose of this demo, I will be using Chrome’s DevTools (Open new tab → Inspect or press F12 → Console tab). But, you can use any client of your choice.</p> <p>First, we will create a WebSocket connection to our server endpoint.</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> webSocket <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WebSocket</span><span class="token punctuation">(</span><span class="token string">'wss://localhost:5001/ws'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>What this does is, it initiates a connection between the client and the server. <code class="language-text">wss://</code> is the WebSockets Secure protocol since our WebAPI app is served via TLS.</p> <p>You can then send messages by calling <code class="language-text">webSocket.send()</code> method. Your console should look similar to the one below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACC0lEQVR42qWSsW6bQBjH6QtYqsekjjEHHBg4wEAAO7jvEPU5OnRq1fgZqkrp4FaqnTj10sRKlyhS8gwZrAxePHnp5MVTYo6vH7ZiJ22lRukn/fTd6e5+/A8QBCy4vi7C9Mr+eTNhAMBmsymbTCZsOp2y8XjMRsMhG49Gy47zIfbZbLbYi1iIgzwX7urrh49vzgaHcPC9Nz8/O8+Oj0+yT/v72ed2O+t0OtnR0Tfs3ax32Mu63YOs3+9ng5NBdnr6I+f28uIS2u0vr1dCqht7bi2Eqm7det42rxqMb2y84JubJW5aDg/jBreYy23H45pu8O2ozuN6wt1awHWD3diOD6pWfbsWUn3P9yLwGy9TO6yDRg0I/BiiuAGhH0DkMtjxHfAMCrFdhcC1IQhC8DwUUW1uWgw0TXu3Eoqi2IqiEHaSJHVcC1xLB6eqgWtqYBs6yDIBBaGqAlRRcCyDJFVAqlRAJmSuUhVKpdJaKElSy/d9CMMwtW1zKdIpMMQxdaCUolRZoShrZFmeYzool8sPhfU4hqTZTL0aJmQ6mJq6BKULIZHzw39AMGG+/kCIC604FybN1HVMsDAVwSQkP4DkV7yf6p8JRVFqqYtrkZSQ/P2Qv6Z5dEJxa6ulqiouyqmiyHdPfroQJ+9zIW5IHyv6XYhfef0fFgoFUiwWd5FXT2Q3dwj36pnw/7Vw/AKq7lWVZ5aBWQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding-websockets-with-aspnetcore-3.png" title="" src="/static/ba338f1f5b40bdf36ef69268ecaf1988/5a190/understanding-websockets-with-aspnetcore-3.png" srcset="/static/ba338f1f5b40bdf36ef69268ecaf1988/772e8/understanding-websockets-with-aspnetcore-3.png 200w, /static/ba338f1f5b40bdf36ef69268ecaf1988/e17e5/understanding-websockets-with-aspnetcore-3.png 400w, /static/ba338f1f5b40bdf36ef69268ecaf1988/5a190/understanding-websockets-with-aspnetcore-3.png 800w, /static/ba338f1f5b40bdf36ef69268ecaf1988/c1b63/understanding-websockets-with-aspnetcore-3.png 1200w, /static/ba338f1f5b40bdf36ef69268ecaf1988/67a79/understanding-websockets-with-aspnetcore-3.png 1408w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>A closer look at the WebSocket connection</h3> <p>if you go to the <em>Network</em> tab, filter out the requests by the <code class="language-text">WS</code> tab and click on the last request called <code class="language-text">ws</code>.</p> <p>Click on the <em>Messages</em> tab and examine the message passed back and forth. During this time, if you invoke the following command, you will be able to see “This was sent from the Client!” appearing in this box. Give it a try!</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">webSocket<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Client: Hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 86.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAADHklEQVR42qWUXW8bRRSGt+IKBERRlZvitfdr9sve712v196kblwElZCKSCMhAQKpiWkS4oTwa+CHNBftbaUoUsUFVJG3riP+RuvRy9m1cS7oBxIXj94zZ8+8M2d2doVtYfu9i/NHW89e/LE3nU53J+PJcDoeDy/+vBiePXky/P3p0+HZ2dnwvOK8Gk8nk+Ff02ml5ZzLy8u9F0WxJQjCNcG4bny0/8M3459GBzg5PuG/nJxgNDrG9999i/v3d7C/f4i9vQPs7Ayxu/tjpUdHP2N0eIzDw6Myx0ejIzx4cPCcDD8QVgVhJWr5Y9d2YDD9lW1bPO30+MZ6zjukfpDwMGpz1wsrtVseD+OUO25YxXVJfSU2ZMgKe762tvahsLq6uuIEYZFmPWz0N7njhch66+j3BzBNG34QImmnsJstJEmKIIjQajno9nJEUQzH8XiX6h3Xn9AO54ZyvVZokgiCl6pKNSiKDFVVoGkqDNOgWKWcUuVUUsu20GzasEyT280mdNOcG1LRSmfwRWGkA6jhTa7FfWjRBjw/RhgmYEyHbTWRtjO0CZ86sEwy0C3ozKLnFjcMG4a+MIzI0B1sFfX1LYjdu7zW+xJSfhf5rT6Cdgw38hCmIaJOjKTbRpwlaAYt+O0IUZbSOOV+EsF0W3ND6nlFEmuFLtfBqGXWEKGrdfS/yrD5dY7gjgX/cxPOpwxK90aFltfAchHhHRv9exnf2O5A6XxydYaaphVM16ExxgkwnSHbjJF/liHu+xXRTQ96qIAFEjRfguo14GY2skHCs9sJFLdxZUgHXjAyImNOoIyZqkOVNWikbIGmsNdAc0hVmb3dUC93THH5lpcw7d/QnLIjlan/1fCd8LKePN5uaBjGvPV5/o3Qc14uTnplSHfxomyJ9CUxowVmVDCjCTMqnlmWNTNNc0aLLON/oJqXix0WS0MqLGzbRvkdWxbdfoKKqx04joM8z9Hr9SrtdrtLLXNUxxfdLO4h/SFc1//V96PH9J0+DKP4tOV4p7Isn9br9SWSJL02Jh42Go3Hoij+Rl7vC9U/bB58/D8pPa79DXMc0VL2jGpKAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding-websockets-with-aspnetcore-4.png" title="" src="/static/cca65ff1bb1c91ad84454a72add54b1d/5a190/understanding-websockets-with-aspnetcore-4.png" srcset="/static/cca65ff1bb1c91ad84454a72add54b1d/772e8/understanding-websockets-with-aspnetcore-4.png 200w, /static/cca65ff1bb1c91ad84454a72add54b1d/e17e5/understanding-websockets-with-aspnetcore-4.png 400w, /static/cca65ff1bb1c91ad84454a72add54b1d/5a190/understanding-websockets-with-aspnetcore-4.png 800w, /static/cca65ff1bb1c91ad84454a72add54b1d/c1b63/understanding-websockets-with-aspnetcore-4.png 1200w, /static/cca65ff1bb1c91ad84454a72add54b1d/29007/understanding-websockets-with-aspnetcore-4.png 1600w, /static/cca65ff1bb1c91ad84454a72add54b1d/ca98b/understanding-websockets-with-aspnetcore-4.png 1968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>As you can see, the server does need to wait for the client to send a response (that is, after the initial handshake), and the client can send the messages without being blocked. This is <em><a href="https://en.wikipedia.org/wiki/Duplex_(telecommunications)#Full_duplex">Full Duplex</a></em> communication. We have covered the Data Transfer aspect of WebSocket communication. As an exercise you could run a loop to push messages to the client to see it in action.</p> <p>In addition to this, the server and client will have <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Pings_and_Pongs_The_Heartbeat_of_WebSockets">ping-pongs</a> to see if the client is still alive. This is an actual feature in WebSockets! If you really want to have a look at these packets, you can use a tool like WireShark to get an idea.</p> <p>How does it do the Handshake? Well, if you jump over to the Headers tab, you will be able to see the request-response headers we talked about in the first section of this post 🙌</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 93.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAADYklEQVR42q1Ty04bSRTtbLJiCQODedju6rb77X64/cR2AvaYgJhECsoHJIJNDCFEYIKGfELyBbGU5XwPq9llwYKMM45GkSzhbp+5VYYZZTZJpLR0dLu7bp177rlVEn4/v/33hz/qn4af2sPhsDUcfm6NePw8bF1eXLT6l5eti/fvW5cUOfg3z8NoNAHQHI1G7aurqxrOz29Lj+q/JA87O38dn3Zx9tur8dnZKxwddbG78wR7e/t4/vyFQIfeO5097O0/w8nJqUD3+AQHB4dxt/sSx8fdPzc2NtKSvLDA8mZuYGQMZBVlbNsWiqUqqpUSSuUV+EFBIOcGIhpWDvmwDMvxYJg5LC3L8dJyGrKS6U/Pz6vSgiwz1wsGlcoKavU7Y8dx0bhzF3dXm9B0k0jyCMMiTMNCWCiJb55TpvwgTwVMOy6Wq1TA7ScSCUVaIIWpVGogyzIUUijLVI2gsDSYPIGuZcDSKSj0rjIZ6dQyDC1LRXToGTW2LBNqJtNPTE8rkq67LCwUB5pOLWv6WNMoidTIWRNLTIMdUvWgLGD6JahWAMX0wQzvBrFiUtSdicJiscjK5fJANwwiMsa6riHr+Mg97MB4eAD1wTOwXztQ7u9D3nqK+eZjJFpP/kPzcZxo7yLZeNTXEqRwZmaGJZPJAWOMWpXHvHWygDyqYP3ePVQaVfh+ANf1aDAuGFP+j5jHDLWsadrEQ3oG5B8tsjEnVgikHLVajYbgwbZtGoTNPeZFBZTrPELM4xeElPAFId9QrVbRbDZRr9VRKBQEHMcBbUI2m+W5N4jZ1wjT6TQq1DIn5NGlVj3PQz6fF+DE30XIFdbrdayvr4vWecsGDY28FuAe85zvVri5uYm1tTXxvsL99Gn6uZwoYNsOVFX9dsJSqYR2u41GoyFI+D/aJMCJeLze822EdDbFlHnk/vFh3LTM129AewUhFZkQ0jmUaeEj94TiiK5dlEolI2ozIoUReRmRj1EYhgJEHlmWFdFgIlIeEckVJ6TJf/hXIRk+oCRYlj3m/vDrx1vd3t7G1tYWVldXr49OEUEQCLW6rgsQUXxtxUcalipNzU3NOK7/xsl573w/fEvoaYbZI+IeKRKR2uzNzc0J/EwgET26twKLi4tv6fsd4fXs7OxPEj23pB/33PoHZ3MoFhvoQvUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="understanding-websockets-with-aspnetcore-5.png" title="" src="/static/f9ac0c49f3b5382f6a0c64e7d212f82a/5a190/understanding-websockets-with-aspnetcore-5.png" srcset="/static/f9ac0c49f3b5382f6a0c64e7d212f82a/772e8/understanding-websockets-with-aspnetcore-5.png 200w, /static/f9ac0c49f3b5382f6a0c64e7d212f82a/e17e5/understanding-websockets-with-aspnetcore-5.png 400w, /static/f9ac0c49f3b5382f6a0c64e7d212f82a/5a190/understanding-websockets-with-aspnetcore-5.png 800w, /static/f9ac0c49f3b5382f6a0c64e7d212f82a/c1b63/understanding-websockets-with-aspnetcore-5.png 1200w, /static/f9ac0c49f3b5382f6a0c64e7d212f82a/29007/understanding-websockets-with-aspnetcore-5.png 1600w, /static/f9ac0c49f3b5382f6a0c64e7d212f82a/cf0be/understanding-websockets-with-aspnetcore-5.png 1994w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Have a play around with <code class="language-text">webSocket.close()</code> too so that we can fully cover the open-data-close loop.</p> <h3>Conclusion</h3> <p>If you are interested in having a look at the RFC for WebSockets, head over to <a href="https://tools.ietf.org/html/rfc6455">RFC 6455</a> and have a read. This post only scratches the surface of WebSockets, and there are many other things that we could discuss such as Security, Load Balancing, Proxies etc.</p> <p>Don’t forget to let me know any feedback or comments. Until next time ✌️</p> <h3>References</h3> <ol> <li><a href="https://tools.ietf.org/html/rfc6455">https://tools.ietf.org/html/rfc6455</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-5.0">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-5.0</a></li> <li><a href="https://www.meziantou.net/using-web-sockets-with-asp-net-core.htm">https://www.meziantou.net/using-web-sockets-with-asp-net-core.htm</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers">https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers</a></li> </ol><![CDATA[How to ramp up in a new codebase with NDepend]]>https://www.sahansera.dev/ramping-up-in-new-codebase-with-ndepend/https://www.sahansera.dev/ramping-up-in-new-codebase-with-ndepend/Wed, 21 Oct 2020 00:00:00 GMT<p>Reading code is good. But, visualising code is even better. I recently wrapped up a gig where we had to perform a .NET Core upgrade (from .NET Framework 4.5 to .NET Core 3.1) which also had quite a bit of tech debt. It could be a bit challenging to ramp up quickly in a codebase that is entirely new to you and get the preferred outcomes of it.</p> <p>In this post, we will look at how we can use static analysis and dependency graphs with <a href="https://www.ndepend.com/">NDepend</a> to quickly ramp up and even identify issues in a real project.</p> <h3>Installation</h3> <p>You can download a free trial from here if you haven’t already got <a href="https://www.ndepend.com/download">NDepend</a>. Once you have downloaded it, there’s nothing there to do in terms of installation. Just extract it to a folder of your choice.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 555px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB2UlEQVR42pVTyW7bMBTUXxje5CUGEkuRZIsite+OigDpoWh76aGBD0V76sf0h6fvPcNGELhAfRhQ5jKchba+fc4QqhhNUyPPCxR5Dtd1YNs2lsvlzbBeBgXPD4ksR6QigsLD/T0WiwVWq9XthF8/xgjDGJu7NUajEcbjMabTKWaz2QXz+Vzwdu5fDqwvRBjplKyydQWjDZIkoUtCxMYgiiKBUqHMaR3B0Lz3+HiV1Ho+GCIyqOsKaZoKCRNmWQYdnQ6HoaK5GBmta61pzBAE/nVCU2akrkDXdsiLUjYbOhTHMQr6XZalfHMck8lE7HIkHMFVyzW1q6gMVmToIKvSpIqVsRoGK9zvdthsNlLUer2W8T2E8OlpQEYNN01DpDnSJJXGU7Kd0AUcgzEnu3dExMrYKuP8fR6F8PvrK2oiq6uKckqJJLmQ9n0vMbR1IxdWtIf3FUUhjg6HAX3XUSwVFBVm23NYx+ORbGpa6NHRIiviVvf7vQTv+wE8z6O36p9Gatd1XbiOc1nzaW3rbMW29ePnL8mprmtRwbf7RMTvkcFF8CN/i3MB7+c4W0v//kOEBjnlNwwf0LatWGPwBayYN/4PROFD8wnOdosdtchg+WyZxyA4Wbrlr/cXzkyLoBLvks4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--0.png" title="" src="/static/cdbf084f5e23442210b6ca26ed1746c8/cd039/ramping-up-in-new-codebase-with-ndepend--0.png" srcset="/static/cdbf084f5e23442210b6ca26ed1746c8/772e8/ramping-up-in-new-codebase-with-ndepend--0.png 200w, /static/cdbf084f5e23442210b6ca26ed1746c8/e17e5/ramping-up-in-new-codebase-with-ndepend--0.png 400w, /static/cdbf084f5e23442210b6ca26ed1746c8/cd039/ramping-up-in-new-codebase-with-ndepend--0.png 555w" sizes="(max-width: 555px) 100vw, 555px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><code class="language-text">VisualNDepend.exe</code> is the standalone application and you can install the VS extension with <code class="language-text">NDepend.VisualStudioExtension.Installer.exe</code>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/klEQVR42pWTWW/aQBSF+f/PfehjmwKNqj70oVIXtWpVlSgKYAJJwQsYLxAINvZ4Ga/Yp9dOIkFLJTrS0Xiuxp/PXdx481ZF8/UUZ+0Z7Sra53O0SM1zirfV+rlF8X1V985a9E5TwYtXIu0SXjZFis/QWBvfYRodLBYdrJYXWK8uYW26cKw+XLsP5gzgboU9DR71cHbsw1jj+Zcebm8u8bk7xAdBwY+hCMteg4c+tsxDXiQA0pPV+DnSMJ4o6I1kDCYabmUDG8tBmORYBTukeYmSrh6oxN+xRzUqiCCZEGcmVM2ENNVrYJoRbFfgfxcBdQhjDZOpAXVegcmh7YL7IayVB+eew2MBQs4RRTF4kiHiKQLKYFdZ/RN4NVJxNZqhO9ZxLZuQxzJccQI20/Hx2QS993cQKdYXBAIz+HEGjzJgPEN5zOHXoY5vQw2fhDkufumQ5DmYbUOzArzrUAkGEjXtBoosYe0GcOICAXPrGh9NWZQ0KKoBmaSolDLV0HEZNn6Ga4PB2VowzQUsa0Pp5oiIE6YZeLo7Dqwg+6qastrY8AIXkecjDCKqWYKcmuR7Hu6WS8Rx/O+mTOeLA2Dl8N52UJQF8nyHoqCxKR+UJAlccp9lWX0+GViNDXMYySOHnBwlNeRpPX3gNKCi05/iIgj8elRicsV5RG7zk+bwNzeYzHOYQF4PAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--1.png" title="" src="/static/741f9e8c82a0d6e972122988d00653f4/5a190/ramping-up-in-new-codebase-with-ndepend--1.png" srcset="/static/741f9e8c82a0d6e972122988d00653f4/772e8/ramping-up-in-new-codebase-with-ndepend--1.png 200w, /static/741f9e8c82a0d6e972122988d00653f4/e17e5/ramping-up-in-new-codebase-with-ndepend--1.png 400w, /static/741f9e8c82a0d6e972122988d00653f4/5a190/ramping-up-in-new-codebase-with-ndepend--1.png 800w, /static/741f9e8c82a0d6e972122988d00653f4/c1b63/ramping-up-in-new-codebase-with-ndepend--1.png 1200w, /static/741f9e8c82a0d6e972122988d00653f4/29007/ramping-up-in-new-codebase-with-ndepend--1.png 1600w, /static/741f9e8c82a0d6e972122988d00653f4/e8f1b/ramping-up-in-new-codebase-with-ndepend--1.png 2246w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We will be using <a href="https://github.com/dotnet-architecture/eShopOnWeb">eShopOnWeb</a> project for this demo.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/dotnet-architecture/eShopOnWeb</code></pre></div> <p>Once you have cloned the repo, you should have the following folder structure.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 463px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 72%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgklEQVR42p2UTW+CQBRF/RsmRZAPEYFAid+YAUWgEW1stRoXNd216aLbLrrqoov+6ts3k7RJa2LRxWQmMDlz3psLFcYiMMYwHA6hqSpkRYFyZNTr9aPvK3qjCavlwHUd1OmBLMt/xvEDDoBJ5KPfsSDVFBiGAU3ToOsGGo0Gzdq/RgfAp/0E60UXg2GEPM8QxzGyLMN8vsBsNoPjtFCr1coD764HyMc+2TTR7bTheR7ZmQIiK6eVK4D36xHmWYBwNEY6TRBFMRzbFsBTyxXA/YZhkbehGxaCICDLLlzHPqnMX8Dbooc08tC0XIRhSIYRLn3/fOAjXcp2OYDnt2GaBqrVKiTpPJgAFlkHE+aToS1iw7N3Tu9+gM8PEXY3PXT7DFd5CsuiTF5IAvxdNl+XBm7WS+y2a/pabNE7Hmweam5rmiZUstV1vTzw9eUD72+fGLER8iynQM8pPimKosBqtcKU1tMkEeAyuazomkGbNahkxu0MsuM/Cd5HlWZuqqrle/oFjq2ZDOf4INEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--2.png" title="" src="/static/44641784a5672003f369db6fd5bb12d4/71ce0/ramping-up-in-new-codebase-with-ndepend--2.png" srcset="/static/44641784a5672003f369db6fd5bb12d4/772e8/ramping-up-in-new-codebase-with-ndepend--2.png 200w, /static/44641784a5672003f369db6fd5bb12d4/e17e5/ramping-up-in-new-codebase-with-ndepend--2.png 400w, /static/44641784a5672003f369db6fd5bb12d4/71ce0/ramping-up-in-new-codebase-with-ndepend--2.png 463w" sizes="(max-width: 463px) 100vw, 463px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Before we begin, we need to build the application so that NDepend can analyse the assemblies.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet build .<span class="token punctuation">\</span>eShopOnWeb.sln</code></pre></div> <p>Now we are ready to use NDepend on our solution. You can either use NDepend standalone app or use VS extension.</p> <h3>Analysing the assemblies</h3> <p>If you choose to do a one-off analysis you will get to select which assemblies you would want to analyse. You can select the <code class="language-text">.sln</code> file of your solution and NDepend will pick up the assemblies.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 63.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACTklEQVR42o3TbU/aUBgG4P7imfkBjMs+LUEXBLXEmUx/hSjqpiMoZM75MnSjlLanb9JXCgXLJupy72kRs2TR7CRXnvuc07QnfVqOX1aQzzEsLzHwZGlRwds5CfNzMuYzLSL9N35ZAnela3BNE46kgokCzo+PITd+oNfWYSgK6qdnsGk/CkNc93pP+jnow7Y64OrMxjcjRN3o40zr4lQN8FW2cULOtQBfBBMtNwILbh6p3Un+RXkEyYtQ+65BUi1whX0Fr4syFqsd8J9D8Ed9LFYc5MsWlg59LOzIyH9UCUPuw4NdJan5Sd6V8WZDwOGFCU7XFASOifu7ESZjGA0w6PdwO7qBqSowVAk6kymPqVITutKCwR5QNqmaxhU4UZYhKwy9cIDbu3vc3f9Gt9uD1wlwHUUQWy0ioSlKSY01hCaEppjUeB7nmKrSCRWmwnUc/D2Gw4hOOMBoNIJhGPRkHbquJzmmqio0TaNM6xN6rA2uWCxiZ2cbBwcHqFarqNVqKJfL+LS/j0qlglKplNja2iLjvLm5mdTt7fG8ROvF4gYu6g1w09MvMTv7CqlU6lE6nU7EeWZm5l+0N8nJ9ak0pqZeYG+vAi6TySCbzT5rIbuQ1Fwuh5WVFfA8j0KhkFhbe4/19XWsrr7D0dEJOIfeX7vdhmVZT4r3JyzLhm1bD9VBGPbR74/ZNn3YEXXyOcPhEKLZhOe70KgZDfqLBKGBy8tLMMYQBAHdyKZGMXqIB873Azyn0+lCow42RZE6q8N1PTiOm4iz53USvu8nN/wDl61wNKwtFt0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--3.png" title="" src="/static/c715e70a788e3fffe57fa68b7fdf34a5/5a190/ramping-up-in-new-codebase-with-ndepend--3.png" srcset="/static/c715e70a788e3fffe57fa68b7fdf34a5/772e8/ramping-up-in-new-codebase-with-ndepend--3.png 200w, /static/c715e70a788e3fffe57fa68b7fdf34a5/e17e5/ramping-up-in-new-codebase-with-ndepend--3.png 400w, /static/c715e70a788e3fffe57fa68b7fdf34a5/5a190/ramping-up-in-new-codebase-with-ndepend--3.png 800w, /static/c715e70a788e3fffe57fa68b7fdf34a5/c1b63/ramping-up-in-new-codebase-with-ndepend--3.png 1200w, /static/c715e70a788e3fffe57fa68b7fdf34a5/a2792/ramping-up-in-new-codebase-with-ndepend--3.png 1462w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once you have analyzed your solution, you will get the following options.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/klEQVR42pWTiY7bIBiE8/5P1O1e7WtU1TbO4Qsw+AaDPR1wut3dVpVqaTSxAh/zT8jh7muJx3vqocTDfZH87lOJp0eFL88az09NUnxPelBcJ//Q/ee4vsJB6xaXyxHXa4Y8PyEvzijLM7SuKQFjJNpWYvEWITis64KPz7Ztya11EdgRdMXpfMKVfrlckWUnSKkI1GiaKIO+HwjuMAwTods7hbAm4DwTaK1HaxbKMY2D9xv+93mXsCgEjqczBreg66h+YQqffHYrAhP4pLgRUJNDpZhaKaib4iTjOO5AIQxOeQEzWSi9QCgH1TjU0mKYAgIhSwAcFSdrhhnXomTPJYqiYF3c27awzu3A2QZMwaPpJ560oBYzGr1D5Q0cgcsr0OJMyA7LUVZVcmMMHKc8mL5FUWUwIztsCWafhqPrNrrn2NuHhBaXokJNUFmVqOo6wbuu2xOOfYdOvKAZF/bBVEzUMJmgR7nlBvQ70HDkvKwg6iqNHRUT+hD2hPPsMS4zVD9CRiClmTJ296u/twlFPyO75JBC8EDJ9Qp1LXhl5h3oeG3CujIBT7AbY68smL9uBNwg7+Q9LDev3PNWr9dGqYaXlmN3Ld1gmgaeNib/m8aJ383T7X1E2/dM2STX2uCQZTn/GeekHy8Z40t2qf8pKX9/LkqJb9+P7FWgqgR+AtJf35TbQI9YAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--4.png" title="" src="/static/7e5a66df95949093e77d199c442ae687/5a190/ramping-up-in-new-codebase-with-ndepend--4.png" srcset="/static/7e5a66df95949093e77d199c442ae687/772e8/ramping-up-in-new-codebase-with-ndepend--4.png 200w, /static/7e5a66df95949093e77d199c442ae687/e17e5/ramping-up-in-new-codebase-with-ndepend--4.png 400w, /static/7e5a66df95949093e77d199c442ae687/5a190/ramping-up-in-new-codebase-with-ndepend--4.png 800w, /static/7e5a66df95949093e77d199c442ae687/c1b63/ramping-up-in-new-codebase-with-ndepend--4.png 1200w, /static/7e5a66df95949093e77d199c442ae687/ddc6c/ramping-up-in-new-codebase-with-ndepend--4.png 1534w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The dashboard gives an excellent breakdown of insights like LoC, tech debt, types, quality gates etc.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD5UlEQVR42o2SS29bVRSF7xgkJvwAfgK/gAGTSgiExIQBMGSGUBXREJo2acgDkrR5qLRIkSCtOmhUQA1KKaiUpEKhTWKSkEdjx44fie34df2IH9f2fZ2Pfa+TzpA40vY5Pvfstddae2snlQqHh0fU63XOlm3btM02pmn64SrHv1deqNPDfyzNdV0sy8Lbleq8PD7eIZ38jXxmgVj4Lo82H7CxPMHmzx/w0+rHvBd9l98TcyB1HNeSH6cTZ4DVahVd1ykWdVqtmjAuEgqmBTRNMBjl4YMQz+6/zfysxpUnr6CFNW5tXBY8l7ydBD0OmeMOIKdiPHZKuXhlo1GDQKDOQbTJ1naVp3/mGFv7kHeWXuXNrdd4aetlbieHod0m4xxR9kBrad8KbX7hVy70DvL1xDf0D19lfPImE9e/59PPBukbnKKrZ4S+gSk+mjrP693nONf/Pm90vUX3tR6+m7lF35eD9I6N0jswxNyP82g/3F/gk66LDI1O0n1piOHxaaZuznD+88sMjFzlwsUrjE5e58b0LH09Y4yN3KDv0jgTkzOMTX/LF/1fMTB8zSc1e2cOrVark82Lf+UKmVyeesPg/yzPIsdVtNomzVYb2+k0VbOqWYzMDq38PmYxilsXcw2Jdhm7Ekc1svJfonF638yhmkXMQhBTD+GcHOGKf6qeglYRzS7sUd+9h3HwCCv9FDf7NyqzCicxjNhjUtEFbLkjvw452Yu72IXnVLfvYh0t4WZWUN59TnIqYTTVyOHkt+XRLk5xH6sYQZUjwiiLld9kOXwbM78FpecSuwK4hyNszVIQqnHccljeh6G8D/U0mlNN0U6vyceEtD4mu0QlKvJyNA5XMCJPOIksYiSWaR4uo/QdVPWIdiog7xM+qJ8rihBymq3vY4R/oZUOiLQNKPzTkVc7EksOpOgm7cI+7klCIu77aVdC1IL3ROqG2LOG8izJSYFKRBiK8UbkobB5hiHJSt/ugApgO71KKy4+iWQ7tynWyH1pD7ORpulJFGZ25QDlsTuJ+jZpjlRuxRdFzl80U2tYuW0BkMR6Uhgd4pTC2CXxVg+Kx0EfRIlXVmbdb4Lvnb+HJCclTWmVcSsx8eXQZ6Vqyc65qWNmRa5Y0Uqt4ui7HRbimyolaEceowqbuNJhld/o2CTea1YxTG1v3k90PcMl8GQLsCWSjdgSRnJFEtc7VnjdLhxghv5AFeWdFFXeFBQkxGPNk2WnlrEl2c4EcMQrvIpe57zRkLPStzpgwggp6DWnlVgUoHWZw4DfnBcMven2pVTPRsAbmwN/bDwL/HF4EafvjILMbRC3GPJBlDdmXk4jw7+I3QgfX544LgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--5.png" title="" src="/static/f2751896fcd265d88fc7dbde18bbc17b/5a190/ramping-up-in-new-codebase-with-ndepend--5.png" srcset="/static/f2751896fcd265d88fc7dbde18bbc17b/772e8/ramping-up-in-new-codebase-with-ndepend--5.png 200w, /static/f2751896fcd265d88fc7dbde18bbc17b/e17e5/ramping-up-in-new-codebase-with-ndepend--5.png 400w, /static/f2751896fcd265d88fc7dbde18bbc17b/5a190/ramping-up-in-new-codebase-with-ndepend--5.png 800w, /static/f2751896fcd265d88fc7dbde18bbc17b/c1b63/ramping-up-in-new-codebase-with-ndepend--5.png 1200w, /static/f2751896fcd265d88fc7dbde18bbc17b/29007/ramping-up-in-new-codebase-with-ndepend--5.png 1600w, /static/f2751896fcd265d88fc7dbde18bbc17b/6e52c/ramping-up-in-new-codebase-with-ndepend--5.png 1948w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>This is quite useful since it gives you a birds-eye view of the solution of what you are working with.</p> <h3>Interactive Dependency Graph</h3> <p>The most exciting feature for me was the dependency graph. For our .NET Core upgrade, it was quite easy to understand which projects should be migrated first with NDepend. So, I believe if you are new to our codebase, this is the best option to start with to get a feel for it.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 81.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC+UlEQVR42o2TW2/jRBiG87P4BfwBBOKaK6SFInHPDUgrLrgCrSrBXdGK5QxbllUFdLuHZNuUHlLHOTjxIU6c+BTbSetsTs7DjNsAu6pYRvo8tmf0fu/3zDcFVhlypK0v6Pz4Cu7Oq8wP32Ry+i4z/fN8bb3nurFarZ77Lqw3j6ub9O+9gbe3gbP/KZb6EE+9i2EldDoJbTOmpifUzREt+0LEOZqZMJ0uyYTmQjwyIf634Ei5RXf7NdwHG3T2NzFqB8T6DvPF5XrJGvHO9xVu3Dnk7dtlbmwb3Nx10EWiax0u+r8zPfmAqfIxz9TPSM1fmDoPSGeXGw+sIV8XFb7ZK/Pdk2NKusu9qsNxM+RhM6DUHWPGMyH4kpGJUpbzjCd1l2A45Dz0CN0+z+IA1fbZ0wJu/nrMJ/eP2Dp2/i0onK6W/0T+neVc5Dg0PFTdIo4TFLWGUlV4pFqcWiHVep1mXUVzgkvBLMvyuNbhleCR5dNq64TeANMwSPwBRdXkxPQxLQtL12na7vMlyxZYt8H6fXmV6LE2YGu/xeOGzaNKkztPa3x10BZlR+hWF7vToWZ7FKIoQlWFXU1jPB7nEccxSZIwGo3wg5CLeMgfTY/Xv/yTn/cV+rbFez+d8eFvTTpewolwX+kGtP0xhV6vR6lUolgsYpqm6LkOnucxGAzy6Ir12HfZ1Tze+rbC7VKDg4bF+3cVPtqpiaTnLBZLSRxZW2EoTs5xnFxI2q4LwJZgYtt2/s8QvPpOj34Ys3vSYKdcZfdU46koXTH7zOcLZrPllZxobOlCOpLh+34uqgvAhoiWwNBut5FVTNILDK1Bo3qWz7J14jBgKTh3XYHmYsFU2MwZrnnJWfKT4kEQ5AnkLP+Nx+fY3W5+ojJBt+eQCMGiEbLxQ4Wtsslmuf/fjS1Ped1Ok8kkL7/RaOSsh1FMmkTcV2xu7amcnilsH2n/56ZkuXCapjmKNSLJPRqGuElKxx0ShBFRMn654NqpdCjFJAaJJgzDHMWLF+Ivofaeb23F/qsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--6.png" title="" src="/static/9399b393f8585b65acb2c07dd2465e24/5a190/ramping-up-in-new-codebase-with-ndepend--6.png" srcset="/static/9399b393f8585b65acb2c07dd2465e24/772e8/ramping-up-in-new-codebase-with-ndepend--6.png 200w, /static/9399b393f8585b65acb2c07dd2465e24/e17e5/ramping-up-in-new-codebase-with-ndepend--6.png 400w, /static/9399b393f8585b65acb2c07dd2465e24/5a190/ramping-up-in-new-codebase-with-ndepend--6.png 800w, /static/9399b393f8585b65acb2c07dd2465e24/c1b63/ramping-up-in-new-codebase-with-ndepend--6.png 1200w, /static/9399b393f8585b65acb2c07dd2465e24/29007/ramping-up-in-new-codebase-with-ndepend--6.png 1600w, /static/9399b393f8585b65acb2c07dd2465e24/4146a/ramping-up-in-new-codebase-with-ndepend--6.png 2043w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>At a glance, it will give you a nice visualisation of the dependencies of your solution. You can zoom in/out of any dependency in your solution structure. If you have got a big monitor, you are in for a treat! 😊 For any migration effort, it’s necessary to identify which projects depend on which.</p> <blockquote> <p>💡 This is just a demo of what you can quickly achieve when you make use of dependency graphs in practice. When you are doing an actual migration though, you need to take small steps carefully and focus on the migration with the least amount of code changes. Keep other refactorings once you are through that phase.</p> </blockquote> <p>Let’s have some fun with the <code class="language-text">eShopOnWeb</code> project now. Looking closer at the <code class="language-text">ApplicationCore</code> project, you will find the following (unless they have changed it while at the time you are reading this post)</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 143.49999999999997%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAYAAACqhkzFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADZElEQVR42pWWSU/jQBCF+fH8BjhwgwMXlgMSAqEgsYjDZJkkLNnjOE4giRP2favpr4YydpLRzEQq3O6ufl316lWbGYn9giCQdrstr6+v8vj4KM/Pz/L09BQ942PW7+/v1XhnD78ZA7u9vZVmsynpdFry+bzUajU5Pj6WUqkkp6encnZ2ppbNZnXN930ZDAbS7/fV3/M8+fj4+Aa8urqSarUqBwcHsrW1JbOzszI3NyeLi4syPz8vCwsLsrS0JOvr6/p+eHgoNzc3Eoah7uOAl5eXb8DhcCjn5+cyGo2k1+upQ6fT0bmLiws1ooEW5rBut6tm8wr4+fkZAbZaLQWCxyDoSNcB+m7s+22dY53NgPEeN9YmAAHjJCtO33EUBL7kf+Ykm8tJJpPRiK6vryM/syjlt7e3CcC2RuVLFwrCnpwUf0rmR1pyriD1el2L8UdA/sRTBoyUel+pwV3o1gYu/dDNWWVZi1MUAaIrflSL0gOMNHZ2dlQOVB1L7e7K/v6+7Lrn5uamSgotcgByizgcB0SonFgoFKRSqSgoOsQYYxxoDYChjEajoeMEICcRIafyRJsI/vLyUjehO8bMsY6s4A+ZTQUkQhxY3NjYkO3tbVlbW1Ohr6ysyOrqqiwvL0sqlVJg+KVIVJ5gpkaIE5Ht7e3pZkAwDqBLSBlfsgCUSBOAbKa5qRwRskgqVI2WYg7+OAjeWKfCADImI/wjQJq/WCxKuVzWShmYtR1PKDB5cEDBRUnB4r4RIFV9f3/X0HE2ou10QODJ+2q7o6MjVQCHc7uYyCNA0jVhG4ekmnOtRmSA0M8NBxq6zUQEGDw+PDxohROAiHMcsO23dEzPsgEAwGvuoEajLiV3L5Iy9yJ+iaKgbi4IA9SbxPfkxHUCQDjbjUK0WLVaURqI0iiKALkcMAM0osebH3ATevnrFmdMRn8F/H0fJgFNMhjpmnyYT1TZAGktA7RbZ9xMOkjNVGBSiwApPTxapyCDyQg7kYDHDwQ40ctU2ACZvLu7Sxg8eV5TryuAoMbmMaRDEFMBidAcMTZSiI77DNBJVJZo4j40xgQgHCIHALmi4ht4Hw3D6KtnlbY1AJlPAMIjXED6NItzasUZt+jGNkCqTErat66n/9coVuI/B8LnM0n4UGD/x/yL4Wtfz1+Mila5RsmDOQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--7.png" title="" src="/static/aea845f228ae1d50fce6df35694c6dea/5a190/ramping-up-in-new-codebase-with-ndepend--7.png" srcset="/static/aea845f228ae1d50fce6df35694c6dea/772e8/ramping-up-in-new-codebase-with-ndepend--7.png 200w, /static/aea845f228ae1d50fce6df35694c6dea/e17e5/ramping-up-in-new-codebase-with-ndepend--7.png 400w, /static/aea845f228ae1d50fce6df35694c6dea/5a190/ramping-up-in-new-codebase-with-ndepend--7.png 800w, /static/aea845f228ae1d50fce6df35694c6dea/ee3fb/ramping-up-in-new-codebase-with-ndepend--7.png 1144w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Can you spot there’s a weird-looking namespace in there? <code class="language-text">Ardalis.GuardClauses</code> doesn’t sound like it’s supposed to be there. But let’s have a look in the class.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 795px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 16%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAqUlEQVR42k2NzQqCUBBGfQ8RDFPT7tVcGUSgqfdXCzQMgt7/Mb5G3bg4zJlZnHFOvIAfJuDFBff6gVvVomoEyvIKxjg4z4lsN/dstyzLEQQBPM+DE4Uh4nO2MrxGTJ8f3vMX4zRDSgOlLOSCtOQ9NPmC0Zur1QekSQrXdSkYxfCDLVo3Eto810DfW1hr0FGoUwpKK3RCoBWadktToaWHkmKNkIgZw+EY4Q+vrmVVOk6bYgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--8.png" title="" src="/static/2616f08cc71921cfef71f688f475a94e/65c7b/ramping-up-in-new-codebase-with-ndepend--8.png" srcset="/static/2616f08cc71921cfef71f688f475a94e/772e8/ramping-up-in-new-codebase-with-ndepend--8.png 200w, /static/2616f08cc71921cfef71f688f475a94e/e17e5/ramping-up-in-new-codebase-with-ndepend--8.png 400w, /static/2616f08cc71921cfef71f688f475a94e/65c7b/ramping-up-in-new-codebase-with-ndepend--8.png 795w" sizes="(max-width: 795px) 100vw, 795px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you have a look at that class, it’s, presumably, in a wrong folder and also a different namespace that to other classes we have in that project. If you shift the file to <code class="language-text">Extensions</code> folder, fix the namespace (in VS) and press <code class="language-text">F5</code> in NDepend. This will do another run, and the latest changes will be available. Press <code class="language-text">Alt+G</code> to quickly go to the dependency graph.</p> <blockquote> <p>💡 Don’t forget to install the VS Extension that comes with NDepend so that you can quickly dive into code from the dependency graph!</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 89.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADxUlEQVR42nWUS3MbRRDH9bWgKL4FxYErR245UgV8AKrCKeFmygTbkFByTPzUI7IdObJlSXYkr2SttNrVPvSWV9LKetn+MTMmJFTCVP1rerpn/tPd0z2hfr9Pq9UiFouxsrLC8fExR0dHRKNRNjY2SKfTSi/tBwcHSg6Hw2qfHPP5XM21Wg3LsgiNx2NGoxGFQoFIJEIulyOZTCpZEsv1W7KdnR12d3eJx+NKL8disVC4vb3l7u6OkBTk6Ha7LC8vs76+ztramiJZXV1lc3OT7e1ttra2lFwsFrm5uVGH5XwjzksOJQuEZrOZctv3fbU5k8ko93Vdp1qtUqlUcF0X0zTVWl68mM+4Wcy5FVjMpkiOtwgNh0MkgiBgOr03ynk6nTCZjJkKXI8DIU+4nkxZiPXLcpvvN9/w45HLriHODnz8wUDx/EsoMRDKdxgyCuYMgwWD4UTYhU7suRYIn1k83Mnw077Ok6zLdTBiKN7hA8J3EMZBj45zQt9NEvQ1+v0B7U6fgT8imrdI5DTC6TK/pAzl4dXV1T3hQN48uHoP4lAwIWifYcW/xn39gJn2La2GQ1mviDxrZPMasVSOeCpDPHlMq9lUJdNoNAiNhiJHIiyJyVjMoznBcE69YRA/ShA5vUAzyzQ8TxH+trLKs6dPebHxnM0Xf5HLZoTnHToCsp5Dbb/KpRfh0o1QrEfQWxEsP0al22e91CeqdzlqzWl2evTaTV4lYhzuJ4THHv1el5EIt9fr0W63FUKlzgpLyU94vPsZP+99ytLB50SML7hs20SacFxxSNXaWKZNSjNI6Q45q8P2aZGdU42TWgfLbWLXLTwRRShd/INo6iHxk8eE9x+wtPUlv25/RdkxydgDzismRauBXvf4YbvAd5t5nuS77GRLPMtU2TNHaBWLcqlIS3qY1p6SvVjnvPCaUuWUl+lHrO59g24UOEjskxWF3u92KBkWS4kzfk9kWM055E2XE6PBvtlTlxkVXTxKU3r4J8dvnrD/Okz01Rrn2iEvs4+4rOYoX1apio2GYWDV65yVKpyXdE50l9h5hbhmc1DrYdgedcvElSF3eh5W4wK7URQvW8TyCjjNS5rNhmq5usiNbEXbtnEdm47n8Mbuihos8TwpPo4LYRc5lISeJ8tmFIjWmv0H42CqOkX2t+8PVFvKhBuC2LFqpOtdXtV9kvkyCaNLWYRsiiikA6H7NvM/wPudI783+Sk4jnNfj2adw2yeXKmKZrewXU/ZZC3+T+t9HJI4EP9nWxTwWfZUlFINX9RiIPSjf/A3HL0Jl08cdNwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--9.png" title="" src="/static/eaac6b09b69df4d49563017d11b69e0c/5a190/ramping-up-in-new-codebase-with-ndepend--9.png" srcset="/static/eaac6b09b69df4d49563017d11b69e0c/772e8/ramping-up-in-new-codebase-with-ndepend--9.png 200w, /static/eaac6b09b69df4d49563017d11b69e0c/e17e5/ramping-up-in-new-codebase-with-ndepend--9.png 400w, /static/eaac6b09b69df4d49563017d11b69e0c/5a190/ramping-up-in-new-codebase-with-ndepend--9.png 800w, /static/eaac6b09b69df4d49563017d11b69e0c/c1b63/ramping-up-in-new-codebase-with-ndepend--9.png 1200w, /static/eaac6b09b69df4d49563017d11b69e0c/a655e/ramping-up-in-new-codebase-with-ndepend--9.png 1563w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We found our first issue within a matter of seconds! 👊</p> <p>Now the file seems to be in the correct place. If you scroll down a little bit in that dependency graph, you would quickly realize that there’s another file called <code class="language-text">JsonExtensions.cs</code> which happens to be also some extensions but in a namespace that is not related to what it represents.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 773px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+klEQVR42o2TTY/SUBSG+WPGj617V65d+C90I6PuhxEmYExckZCgMTHoAgIkuNJFRUampV/cftPbFnBG6+u5V0tkpnFs8uSce3v63HPatGKaJq5isVhI/qe2UiSGYUh0Xd9D7M1mM0yn0939oragVCiwbRuu68JxnL3IGJMUe6KuwLKscqHv+/A8D51OB5PJBIPBAP1+X8Zut4vRaIR2u43xeAxx5XkuY5IkUirkO6FoOwxDcM6hqhp0w6Soyn3x7kQ+n8+haZpESLIsk2w2G8RxvBt9JwyCAEmaIvAYAofGXy7liEXnS1qLsTmPKbehKAo1ESClZ0QzJUIfPEnBzFMYyhC9d+9xVDtCs/Ucz+p1SbPZwuFhDcPhiA7z6LArhGIUzw/hhxE94FOXjLphdN+EQwKBqi7AHJckEVYrTsIM0WWhDj+IkHEXZ9pTnGtVnOtV/LQeY3v6CFta5+YBvhtV/LCeyP31yUPwzw+wjhcIo0Q6LghXJLTwTbmHzcf7+PT6Nrq1a3h7fANv6tfxinIRe82b6LVuIfhwB/mXu8jCKcJVViYMkfIAW+sltmYL0dcG3Gmd4jH8WR3hSQPBrEE5RcrXRgtn9guksU0dxheFv+cXX9ZiKwlzOVyfg3kcTgn2nzrxnv92VC79j4YmMXT1nxR1prn/6/0CGmqjq7MJl7UAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--10.png" title="" src="/static/605a6003a5df9ec60f738fb5187a0065/612f7/ramping-up-in-new-codebase-with-ndepend--10.png" srcset="/static/605a6003a5df9ec60f738fb5187a0065/772e8/ramping-up-in-new-codebase-with-ndepend--10.png 200w, /static/605a6003a5df9ec60f738fb5187a0065/e17e5/ramping-up-in-new-codebase-with-ndepend--10.png 400w, /static/605a6003a5df9ec60f738fb5187a0065/612f7/ramping-up-in-new-codebase-with-ndepend--10.png 773w" sizes="(max-width: 773px) 100vw, 773px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you have a quick look at the folder structure it will tell you a different story,</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 622px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQklEQVR42o1Sy27TUBD1lodAiBYEpaKKHb/iV+wmrhO/Y8dJu0WteIouQJUQFaAiVvwAogtWCEoRtBK/eZi5DRELIroYz73Xc889c85I68E6/HyEfLNCnuWoRmOMRhWKvEBG+34/RKulQFU1tNvqf0Pij+8HcBwHWU6gBFTOANM0Q1mOYJodKEr7fIAyFTqOC7cXYvdegXgQEWhJLGskcQzbdqBp+rnABCC/7Loe/KTA/uMSaTxEXTcoipLAbGpXPjfYHNC2bARxhp2tDZQ5tVmMEA9j6LopiriGQ5aV+XqRBAKQNeoNMzRFiGHCDMmYkvXMRcu6bsAwTHQ6lmif15z/ZZQwRZbb6BgGvCiDEQZIshSTyRRN02A63RRGjccNxnVN+y36N6GJKOB5XcH0b42lPy1Z9LofkgmDHsIoFMbwGGXkdEqZR4mN4lHic54Afoi1TpIMw8FQdCoAW6SNQfTdfgK930UUbQgANmcqmE4waaZCCgbgXFcVzexZDYO7NClMTJLbGizbRT+vMakGiJMUeUFsKioklpbjwbRIR9OC0bFhu92zoHPX8+drlXSWVR2SvnoT5t1bCCwNqasgCm3kkY889FFR+31bQ2AoWDcVdNU1aHeWoa4szeL6LC/T+Q0wlnTl/kdc3vmApUeHWHlyiNvbr9F6/gnBu1/wD07QfXsK7+AUzpufkF98w9UHh+A7i0K6tHuEixTX9k6wtvcZKw/fY3XvC4xXP6DuH0Pf/472S45jLD07woWnX8F3FsVvoZ+sShgAAvcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--11.png" title="" src="/static/2bf7f2d3158a649db23dc77a6f627b3f/604ec/ramping-up-in-new-codebase-with-ndepend--11.png" srcset="/static/2bf7f2d3158a649db23dc77a6f627b3f/772e8/ramping-up-in-new-codebase-with-ndepend--11.png 200w, /static/2bf7f2d3158a649db23dc77a6f627b3f/e17e5/ramping-up-in-new-codebase-with-ndepend--11.png 400w, /static/2bf7f2d3158a649db23dc77a6f627b3f/604ec/ramping-up-in-new-codebase-with-ndepend--11.png 622w" sizes="(max-width: 622px) 100vw, 622px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We won’t refactor this now, but you get the idea. This is just a simple example but you would find these types of issues even in code that’s in production, that could sometimes give you a lot of headaches. But the point I wanted to make was that by using static analysis and dependency graphs you could get a better understanding of what’s going on in your code rather than merely going through folders and files.</p> <blockquote> <p>💡 If you are not sure of what those arrows mean, you can use the Context-Sensitive Help which can be enabled by clicking on the <code class="language-text">?</code> mark.</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 20%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA4ElEQVR42lXO70+CUBTGcf7/v6cXzZaV60UL0yEICYiIjAiwcavLj2vz68Wtli8+e3bO2c4eIyuXdB8zvjU5qC2UWl/o+5C282kan7b16brg7HCIkMJGlFOaLwdVOxiqsmker5DNiqVzw6s3JvDvcd1bPG8wZrEYYTsjsrepZlIUc6rKArbkYo2XrwjLAKkSjJ+9jTKvaXWTwH8gDCcEweScu+SJNDW1Z97zF/J8Rlla1LqJ+HT1w5hUbJhnEV4RsZcb/VB4tNadPiYXjschd//87pK/eWgo1Za6jRGa7GNOr+4nVNSIEHkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--12.png" title="" src="/static/5812348ca6e4cd08e817b62b31d322c3/5a190/ramping-up-in-new-codebase-with-ndepend--12.png" srcset="/static/5812348ca6e4cd08e817b62b31d322c3/772e8/ramping-up-in-new-codebase-with-ndepend--12.png 200w, /static/5812348ca6e4cd08e817b62b31d322c3/e17e5/ramping-up-in-new-codebase-with-ndepend--12.png 400w, /static/5812348ca6e4cd08e817b62b31d322c3/5a190/ramping-up-in-new-codebase-with-ndepend--12.png 800w, /static/5812348ca6e4cd08e817b62b31d322c3/c1b63/ramping-up-in-new-codebase-with-ndepend--12.png 1200w, /static/5812348ca6e4cd08e817b62b31d322c3/07d7d/ramping-up-in-new-codebase-with-ndepend--12.png 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You can also do other cool stuff like finding out who are the callers, callees for a given type. You can even write LINQ queries and drill down further.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC4klEQVR42kWTS28TVxiGRwKB2m7Y8CNokdiQSoRdVEElNqAGWJCQgITY9Qpb2BCBqGgXSCjYGTsJ0ApcQKQJrhNim1wGmsrkQsGXOBl7ZmyPM3F8xWPXcZ4eEiQWj95P5xy9es/3nSMpisrUxDKKkuTFtE7weYLffTrycAKX4L3Kf6pCVdxPk/R7dQaealuIevAv44OmuDeWRjJMnSU9jp7V0Fc0tKxOWDMIpywWcyWiK3lignDG4t+0xYJhirUckdUi8XwZrVzFKNskV/MspkykbCaHoaexVrLkVtcoFIqYeopCucSi28nst+eZv3iR2Qs/MffjD8x+/x1/n+7kZUcH/widbP+GUM8VyuK8oalItt2gXqtTE/xXb1CpVph6HcSqlBntOIVr12d49u3jj7178XzxOQ/27KH/00/o37ED986d3JIkHhw8SKFUYnYuivSuagsTm1K5gm3XeFetshCdo1Cz8Rw/jrvtK0IjPnx9TgKDg/gFkx4PYwMDzAcCPOruxtXaSqlSIRKJI4VfvyWd0MhnTcqFgjC1MZI6xXodz9GjPBFXs6xVvI8f4xsawu/1klpaIhGJ0ACCly4ht7RQFIaalkBaEZvq5DRrkRjCjfVmk6SqioQ1Hp44QV/Llyi9DgI3bmzy7Pp1ZpxOpnp6kPfv55fdu7nT1kahWCSREIZ5M0NMeUkmHKVZrdHcNExsJrwvEnrPnWPJsvBNTDD96hVqOk1a9CupKFwTPby6fTu/HTr00dBMmmTjSaqZDE0Re329ibqskhdDunv4a+4fOcKM388TWWZ+3M+CqN8qL5hxuenZto3LYijygVbWxOtYFkEkNkQjBBsb7wuhImHOTNFo1nkjOxhuP4avu3OTZ2e7tjjTha/rFKNnOhk52U7o52s0GzWsjIHkGI/hCKo4A3Hk58v0jkX4dSjEzZF5bgeiW4yHcQh1iR/VF1wUxJAn4rinxe+Z2qJ39A29wyH+B9/kwB5JZrV6AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--13.png" title="" src="/static/52f1dd7ca2752081c72358bc54648d79/5a190/ramping-up-in-new-codebase-with-ndepend--13.png" srcset="/static/52f1dd7ca2752081c72358bc54648d79/772e8/ramping-up-in-new-codebase-with-ndepend--13.png 200w, /static/52f1dd7ca2752081c72358bc54648d79/e17e5/ramping-up-in-new-codebase-with-ndepend--13.png 400w, /static/52f1dd7ca2752081c72358bc54648d79/5a190/ramping-up-in-new-codebase-with-ndepend--13.png 800w, /static/52f1dd7ca2752081c72358bc54648d79/c1b63/ramping-up-in-new-codebase-with-ndepend--13.png 1200w, /static/52f1dd7ca2752081c72358bc54648d79/29007/ramping-up-in-new-codebase-with-ndepend--13.png 1600w, /static/52f1dd7ca2752081c72358bc54648d79/7e508/ramping-up-in-new-codebase-with-ndepend--13.png 3836w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>How does it stack up against Visual Studio 2019’s tools?</h3> <p>I have used the inbuilt dependency graph that comes with VS 2019 for some time now and it’s good for basic stuff. Since I’m using VS Professional edition, I can’t explain its full capabilities. You can have a look <a href="https://docs.microsoft.com/en-us/visualstudio/modeling/what-s-new-for-design-in-visual-studio?view=vs-2017#edition-support-for-architecture-and-modeling-tools">here</a> to see which editions support dependency diagrams.</p> <p>Here’s a screenshot from VS’s dependency diagram feature.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 72.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACDUlEQVR42o2TWW/TUBCFA6HNYsdZaOLEuxNvcWySNJuaFQcpEZC2AhXekHiEd5D67w9zrygibRB+GMke+X73zJzjTNiL4Ps+PM/j5bouZFlGSZIglkoQRBEtVYVmmFA0HXKrBUEQYBjG0TnPc/nZTL/fRxAER8BKpYJ8LodCPo8X2Sx21zf4/vMe337c4+7L1z/Av889VMb3j5sMWK1Wodkd2H6XKkA0msC0bbS7MVTDRrFYPKHwN/BxgwFrtRpG6wTb2zskt58xTXZclSDVUCgUSXkuPZBVuSzBjV5hsX+P+e6AeDxFj1Su3t5guT/ACWNomnZ65H8BbT/EYLHBcJkgvnqN4HKKIb0PrpYwHQ96GiAbodPpQDMtUvYOm8MHuPEA+YJIwBmN/wmKaSP7/BlM00y5Q8eB3GzSWBG6wwmHSyURRttFNF3Qu00pKMMik1IB2UeWZaHZbFH+dDTqdaiKSi47aMgKmi2NehdQFIUb+H8g7YUtvELRqbxscHdFUUCdYB2KTTAYU6Ta1BP5eh6rfAJki2a3s3iUpCpyZ2fIn5+jeiFjst1jc/0R8WwOkS5yaD2pgCr9aswMlr/xeovh6g03ZbRKuNPD+Rqa1YZNq0mt0OnFBEt4RZMZjdvDhOCXyzUPvUpmWdZTpzNRFCEMw6Mm242ua9BIqaoqlDkVhq7z54eeSX/KqQz/Ak4QkV5eHlBEAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ramping-up-in-new-codebase-with-ndepend--14.png" title="" src="/static/09a27aa3f3533344bb71e6f4dd612c4b/5a190/ramping-up-in-new-codebase-with-ndepend--14.png" srcset="/static/09a27aa3f3533344bb71e6f4dd612c4b/772e8/ramping-up-in-new-codebase-with-ndepend--14.png 200w, /static/09a27aa3f3533344bb71e6f4dd612c4b/e17e5/ramping-up-in-new-codebase-with-ndepend--14.png 400w, /static/09a27aa3f3533344bb71e6f4dd612c4b/5a190/ramping-up-in-new-codebase-with-ndepend--14.png 800w, /static/09a27aa3f3533344bb71e6f4dd612c4b/c1b63/ramping-up-in-new-codebase-with-ndepend--14.png 1200w, /static/09a27aa3f3533344bb71e6f4dd612c4b/29007/ramping-up-in-new-codebase-with-ndepend--14.png 1600w, /static/09a27aa3f3533344bb71e6f4dd612c4b/5ab15/ramping-up-in-new-codebase-with-ndepend--14.png 2446w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You can dive into your projects, but you will soon end up with a huge mess of classes, and you will find yourself turning the level of detail up/down and settings rather than going through the code. Another thing that you need to be aware of is that it could become quite slow or sometimes not even render the dependency graph when you throw a large project at it.</p> <p>When compared with NDepend, I found that NDepend provides an intuitive user experience with excellent breadth and depth into your solution. Best of all, it works all the time with lightning speed! ⚡️ This is why NDepend now has become my go-to tool for working with large projects.</p> <h3>Conclusion</h3> <p>As we have seen in this post, it is quite beneficial to have an overall view of the solution you are dealing with by visualising it. This post only scratches the surface of what NDepend is capable of and there are many other features of it that I haven’t covered.</p> <p>Let me know in the comments below what other tools that you use to quickly ramp up on a codebase. If you want to learn more, you can checkout the links in the below section. Until then 👋</p> <h3>References</h3> <ol> <li><a href="https://www.ndepend.com/docs/ramp-up-in-a-new-code-base">https://www.ndepend.com/docs/ramp-up-in-a-new-code-base</a></li> <li><a href="https://www.ndepend.com/docs/getting-started-with-ndepend">https://www.ndepend.com/docs/getting-started-with-ndepend</a></li> <li><a href="https://www.ndepend.com/docs/visual-studio-dependency-graph">https://www.ndepend.com/docs/visual-studio-dependency-graph</a></li> </ol><![CDATA[Having Fun with Microsoft IoC Container for .NET Core]]>https://www.sahansera.dev/dotnet-core-ioc-container/https://www.sahansera.dev/dotnet-core-ioc-container/Fri, 21 Aug 2020 00:00:00 GMT<p>The objective of this post is to configure and use Microsoft’s default dependency injection container from scratch to understand how it all hangs together when in action. There are many other great articles explaining Dependency Injection and Inversion of Control (DI &#x26; IoC from now on) in ASP.NET Core out there. This article assumes that you understand those principles. So I will not be covering those.</p> <p>We will start with a simple console application, configure an IoC container, and have some fun with it by diving into the .NET Core DI Extensions’ source code.</p> <blockquote> <p>💡 Follow along with the code from my <a href="https://github.com/sahansera/dotnet-ioc-example">repository</a></p> </blockquote> <h3>Microsoft’s IoC Container in .NET Core</h3> <p>The .NET Core IoC container is located in <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection?view=dotnet-plat-ext-3.1">Microsoft.Extensions.DependencyInjection</a> namespace. Let’s look at what are the steps involved in order to use this in our application.</p> <ol> <li>We need to add this assembly via NuGet.</li> <li>Types must be registered with <code class="language-text">ServiceCollection</code></li> <li>Types are retrieved from <code class="language-text">ServiceProvider</code></li> </ol> <p>We will start off with a simple console application and see how we can achieve the above steps.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Setup</h3> <p>Let’s start by creating a console application.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new console <span class="token parameter variable">-n</span> IoCTutorial dotnet new sln dotnet sln <span class="token function">add</span> IoCTutorial</code></pre></div> <p>Open it up in your favourite IDE and you are ready to follow along. I won’t be using any other dependency (such as logging) just to keep this tutorial simple.</p> <p>Let’s go ahead and add the DI assembly from NuGet.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package Microsoft.Extensions.DependencyInjection</code></pre></div> <p>Your project structure should now look like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 45.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABXklEQVR42o2STU/CQBCG+T0mwH603ZZSKO32Y1v6gSfkYIwmno0cxJMe9He/ThdINDGBw9OZ2d0+6XR2NJuFKBODojTIshzGVCgpj+MVXNdFFEUIwxBCiKsYOdKBCnykdQFTVqiqGkVRIggCOI4DxtjVMiscHlxweJ6LruvR97cwJK3rBkmSQCllxVcLpZCUSHDOkaQam6ZEmWfIC4P1uiVpSkLXnrmM/UKOM44UuNH0D+saXdPa9odDk8mYWp9ehHN2bPmMJFiUItzs0LY9DarCkoYzDCvVGeJVgpS60FRrqofcojVWtOf7AUZcP4OVe7DiBcK8YtHco3r4RPf4jf7pC9nuA15zgOrej7F9t7lqD/BPcVjzmjd6f09CFYOpBCJIkTdbmM0d6m6LRdpCBsf1qbeysFP8j2Fv8NC1YfBdCammWOo5arqDBQ0lms/A2dgi+ASSOEf5q/4DG+MHmK4yUNPYlLEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-2.png" title="" src="/static/bd5adfb1aaa6a58e14d9a6d45525a500/5a190/dotnet-core-ioc-container-2.png" srcset="/static/bd5adfb1aaa6a58e14d9a6d45525a500/772e8/dotnet-core-ioc-container-2.png 200w, /static/bd5adfb1aaa6a58e14d9a6d45525a500/e17e5/dotnet-core-ioc-container-2.png 400w, /static/bd5adfb1aaa6a58e14d9a6d45525a500/5a190/dotnet-core-ioc-container-2.png 800w, /static/bd5adfb1aaa6a58e14d9a6d45525a500/84cc5/dotnet-core-ioc-container-2.png 898w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s add two classes into our project; <a href="https://github.com/sahansera/dotnet-ioc-example/blob/master/IoCTutorial/MyService.cs">MyService</a> and <a href="https://github.com/sahansera/dotnet-ioc-example/blob/master/IoCTutorial/MyDependency.cs">MyDependency</a>. Here’s what’s gonna go into those two classes.</p> <p><code class="language-text">MyService.cs</code></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyService</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">MyDependency</span> _myDependency<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MyService</span><span class="token punctuation">(</span><span class="token class-name">MyDependency</span> myDependency<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Constructed MyService"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _myDependency <span class="token operator">=</span> myDependency<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> _myDependency<span class="token punctuation">.</span><span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><code class="language-text">MyDependency.cs</code></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyDependency</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">MyDependency</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Constructed MyDependency"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Doing some work in MyDependency"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As we can see, <code class="language-text">MyService</code> has an (aptly named 😅 ) dependency on <code class="language-text">MyDependency</code>. In order to invoke our service, we need to pass an instance of the required dependency into its constructor. In our driver code (Program.cs) without any IoC stuff, we could do something like this.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> myService <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MyService</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">MyDependency</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> myService<span class="token punctuation">.</span><span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This is baaaaad 💀. Problem with this is, now we are responsible for managing the dependencies, their lifetimes (and also violates SOLID principles) etc. Let’s fix this by using the IoC container. We will simply give them a scoped lifetime.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Register our types in the container</span> <span class="token class-name"><span class="token keyword">var</span></span> container <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ServiceCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>MyDependency<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>MyService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Build the IoC and get a provider</span> <span class="token class-name"><span class="token keyword">var</span></span> provider <span class="token operator">=</span> container<span class="token punctuation">.</span><span class="token function">BuildServiceProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If you aren’t familiar with service lifetimes, it’s best if you refer to the <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetimes">official documentation</a> before continuing on.</p> <blockquote> <p>💡 Note that you don’t have to instantiate ServiceCollection in an ASP.NET Core Web or Worker Service application. You would ideally register your services using the IServiceCollection in the Startup class.</p> </blockquote> <h3>The quick &#x26; dirty service registration</h3> <p>Once we have set this up, we need to call <code class="language-text">BuildServiceProvider</code> method on the container. This returns us a <code class="language-text">ServiceProvider</code> as a result. Remember that we first need to register our types in the container (<code class="language-text">ServiceCollection</code>) and then retrieve them using the provider. Bear with me on this one as we are using the concrete implementations rather than interfaces for this initial cut.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Get our service and call DoSomething()</span> <span class="token class-name"><span class="token keyword">var</span></span> myService <span class="token operator">=</span> provider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>MyService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> myService<span class="token punctuation">.</span><span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now when we run this, it should create an instance of <code class="language-text">MyDependency</code>, construct an instance of <code class="language-text">MyService</code> and run the <code class="language-text">DoSomething()</code> method.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 49%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABw0lEQVR42rWO30rbcBTHk/xSSRrNqtY1Nra2SUyt3RhslvUN+gjbxdQb3eafqk2lYsE7g75H73wC9wq7kFFEQghFHJRdDCsM5o/G5Xha6tZ5sYuNfeHDgcP5fjgM8z9yfHIydnv6sXbZ+nB0+vnTQdNx7bNGw3Zdx256rt1wzu0z17W9pmdfXFzazrnTm+12+6DVah16nrdfr9fln8JisZirLry5Ku0uw0rNCt6XLXi7vQUrayV4t2FBdXUV9tc3YGezDJZVgnK1DJVdC/b2akGlUoHFpaXvhULBGHzyhSxHvo4rU0F0Uu1MqFN+VIn7Cc30H6vT/rSi+qNhycc7n2F/wbJsB/mB+2/Ik0Fh3jRnrzNmBnRNDwzNgFQyBdnsHGhaGpK6DoIsQ1iSQBBEQEkPjuMCBAghN+h4OuDj87FJ5TqOxeRcLkjMZiFuGKAk0jChpuGZbsLLmQzMpDSYz89DLKYAlrrSoDuRB8IQ85wj7BcyNOTzonhDRJHyokBDokR5QaLDfIjKhKeE5Wgk8ogOj4xQbNG+qINcIbl7HYtECUOKOF8hr/8M93DX7XS70b6r/yPDhBH5Lwn3Hb+F+0d6uQOpX67zd0S7VQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-3.png" title="" src="/static/52d70b184e7aa00a5b603b35eeb953b4/5a190/dotnet-core-ioc-container-3.png" srcset="/static/52d70b184e7aa00a5b603b35eeb953b4/772e8/dotnet-core-ioc-container-3.png 200w, /static/52d70b184e7aa00a5b603b35eeb953b4/e17e5/dotnet-core-ioc-container-3.png 400w, /static/52d70b184e7aa00a5b603b35eeb953b4/5a190/dotnet-core-ioc-container-3.png 800w, /static/52d70b184e7aa00a5b603b35eeb953b4/0d1a4/dotnet-core-ioc-container-3.png 1036w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Notice how we never constructed <code class="language-text">MyDependency</code> because the DI framework resolved it and did the constructor injection for us. Nice!</p> <h3>Internals of ServiceCollection and ServiceProvider</h3> <p>Looking at our code, the <code class="language-text">ServiceCollection</code> holds a bunch of <code class="language-text">ServiceDescriptor</code>s and provides some utility methods to manipulate it. <code class="language-text">ServiceDescriptor</code>s are really the objects that describe (type, implementation, lifetime etc.) our service registrations.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServiceCollection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IServiceCollection</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>ServiceDescriptor<span class="token punctuation">></span></span> _descriptors <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>ServiceDescriptor<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Count <span class="token operator">=></span> _descriptors<span class="token punctuation">.</span>Count<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsReadOnly <span class="token operator">=></span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ServiceDescriptor</span> <span class="token keyword">this</span><span class="token punctuation">[</span><span class="token class-name"><span class="token keyword">int</span></span> index<span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> _descriptors<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">set</span> <span class="token punctuation">{</span> _descriptors<span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">Contains</span><span class="token punctuation">(</span><span class="token class-name">ServiceDescriptor</span> item<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">Remove</span><span class="token punctuation">(</span><span class="token class-name">ServiceDescriptor</span> item<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> ICollection<span class="token operator">&lt;</span>ServiceDescriptor<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token class-name">ServiceDescriptor</span> item<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token range operator">..</span><span class="token punctuation">}</span> <span class="token comment">// Other methods removed for brevity</span> <span class="token punctuation">}</span></code></pre></div> <p>In the subsequent sections, we will look at how it gets utilised.</p> <p>The <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectionserviceextensions.addscoped?view=dotnet-plat-ext-3.1"><code class="language-text">AddScoped</code></a> (and other service registration extension methods) method just adds our service registration (as a ServiceDescriptor) into the above <code class="language-text">_descriptors</code> list.</p> <p>Looking at <a href="https://github.com/dotnet/runtime/blob/60eff3f3766631bd4e7ee256ca17619fec90e9e6/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs#L49">the internals</a> of the <code class="language-text">BuildServiceProvider</code> extension method, we can see that it actually instantiates a new service provider with our given service registrations. As we saw earlier these service registrations are passed in as a <code class="language-text">ServiceCollection</code> and if you put debug this, you will be able to see the following:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABXklEQVR42o2Ry3bjIBBE9Ueynsg0IBBIsYk9PvZs8ljMZv7/FypNO84qkzOLq65GUF1ClfMLyHqQC1wD7LwIE3lM2kGbgBCPWFLm9wvU3vH6/E8q2l6x3f4inv/AHN5hnt5A2wsac8OOaewNtbkKRTf29w9cUXWDQnrKWILHXmsoTVBqBOk9iDT6vhOGocfIDJ+ILuvM+Nn3XcuGXQfyCfn5jNPphHzMWNcVcd0Q04YQAvwSYRxfCxE0DyyDiYxULZqkV2pC1TYNRrI45jMb3Q0Cpw1sUjZZa6WWwXVdY7fbfUONhn0K94T8E2JK8D7AOYeFzWJMMiClFcY6kLEwxkhC86W11NIPw3A3LAkV/+VflysulwtyfmazCM+Gdp4xM8bO6PpRUhb6vv/SD9q2FaqOH3s2PByypCv34dzMn71IXxKVPWXw49BPVEopOVzuquhxHIWip2kS/T9GD8MPL4YvpYZZY7wAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-4.png" title="" src="/static/09fda0a1f94a3578a819ea52d1ce0fdd/5a190/dotnet-core-ioc-container-4.png" srcset="/static/09fda0a1f94a3578a819ea52d1ce0fdd/772e8/dotnet-core-ioc-container-4.png 200w, /static/09fda0a1f94a3578a819ea52d1ce0fdd/e17e5/dotnet-core-ioc-container-4.png 400w, /static/09fda0a1f94a3578a819ea52d1ce0fdd/5a190/dotnet-core-ioc-container-4.png 800w, /static/09fda0a1f94a3578a819ea52d1ce0fdd/c1b63/dotnet-core-ioc-container-4.png 1200w, /static/09fda0a1f94a3578a819ea52d1ce0fdd/29007/dotnet-core-ioc-container-4.png 1600w, /static/09fda0a1f94a3578a819ea52d1ce0fdd/89557/dotnet-core-ioc-container-4.png 1928w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you are interested, one observation I made was to get an idea of <em>when</em> it actually instantiates our types. In our Program.cs if you update it as below,</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> provider <span class="token operator">=</span> container<span class="token punctuation">.</span><span class="token function">BuildServiceProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"-----------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> myService <span class="token operator">=</span> provider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>MyService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"-----------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> myService<span class="token punctuation">.</span><span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Should give you,</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 53.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB4UlEQVR42rWSS2/aQBCAWbu2692lgDHmZRrAgGseoVRtlD6U5pIoFAs5Qhx6yCm3KP03bZBP7T9pD4j8paY2ms5WJoL02q70aWYf83lGcir1Hxa5Pj65+nTx7uvpx9eL6SQI57N5OJvNwiAIwgBzH3N/Og0n/jT0/SA8P5+H4/EkPDo6XozHH76dnJ1dCREAEBHTbq36oz9sg/vmxbp7cAjewSvoDEbQ7r+Et719eO/1oO964PU6MDocQf/5AAaDIVSqdlwqlQH5jh6GSEJo6YwueSYH6azx60nOiFWdxhrlCIvTmFNFifHTsSSRmCRIkhQTQu5EY5gvOecFMa0QFssV+9bFDppNZ91qtaHecGB/OIKO+wwaTgso4yAKH4JCIRXC2x1hNmusLKsIRs5YFwoWMBRwngZKKTzWdSCSBKqmgSw/eiiNE/FqR2ha1tLeq0O50byrNp3IsmtRsVqLDKsSdepO1G040VM887rdiFIWYc2Gn4lwZ2RL05SVyijQfH5NTRP0vAE0Y4CeMaHE0lDDjpmqgm1XgHG+GXd7ZNGhuRHm8OASLz5jfoMsBERAyOJ+j7lAluVtbpAviqJc4pvsRqgkGzuV0uoYt9nTMN6j4b321xs7qVe3f27pH/Bn/QZd0KGpsycefAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-5.png" title="" src="/static/3f558d6bb41db8e1f1001e14345b938f/5a190/dotnet-core-ioc-container-5.png" srcset="/static/3f558d6bb41db8e1f1001e14345b938f/772e8/dotnet-core-ioc-container-5.png 200w, /static/3f558d6bb41db8e1f1001e14345b938f/e17e5/dotnet-core-ioc-container-5.png 400w, /static/3f558d6bb41db8e1f1001e14345b938f/5a190/dotnet-core-ioc-container-5.png 800w, /static/3f558d6bb41db8e1f1001e14345b938f/525d3/dotnet-core-ioc-container-5.png 1090w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>See the dotted line before “Doing some work in MyDependency”? I initially thought that these types would have been constructed when we built the service provider. However, it seems that they are instantiated when we call <code class="language-text">provider.GetService&lt;MyService>();</code> instead. This should, however, not to be confused with service lifetimes in an ASP.NET Core web application where service lifetimes play a major role in the request pipeline.</p> <blockquote> <p>💡 The IoC container will instantiate our implementation types when the GetService<T>() method is called, not when the IoC container is built.</p> </blockquote> <p>Looking at the implementation of the <code class="language-text">ServiceProviderEngine</code>’s code you will see that the data structure that holds all our type registrations is really a <code class="language-text">ConcurrentDictionary</code>. If you drill down in the call chain of <code class="language-text">GetService</code> far enough, you would come across the following class in the <a href="https://github.com/dotnet/runtime/blob/907f7da59b40c80941b02ac2a46650adf3f606bc/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteValidator.cs#L14">source of DI extensions</a>.</p> <p>So, we have an idea of how we can register services in the IoC container. Nevertheless, this can still be improved by mapping interfaces rather than using concrete classes. We will look at it in the next section.</p> <h3>Replacing Concrete Classes with Interfaces</h3> <p>Let’s extract interfaces for <code class="language-text">MyService</code> and <code class="language-text">MyDependency</code></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMyService</span> <span class="token punctuation">{</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMyDependency</span> <span class="token punctuation">{</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing interesting here. Let’s just implement these two interfaces in their corresponding classes.</p> <p>Our <code class="language-text">MyService</code> will now use the interface instead of the concrete class.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyService</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMyService</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IMyDependency</span> _myDependency<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MyService</span><span class="token punctuation">(</span><span class="token class-name">IMyDependency</span> myDependency<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Constructed MyService"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _myDependency <span class="token operator">=</span> myDependency<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> _myDependency<span class="token punctuation">.</span><span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>In our Program.cs, we will update the following lines to use the abstractions instead of concrete classes,</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IMyDependency<span class="token punctuation">,</span> MyDependency<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IMyService<span class="token punctuation">,</span> MyService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now when we run the application, we will get the same result. However, to make things interesting, let’s add another <code class="language-text">MyDependency</code> class that implements <code class="language-text">IMyDependency</code> interface. Without spending too much time on a name, let’s name that <code class="language-text">MyDependency2</code> and register it in the IoC container.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IMyDependency<span class="token punctuation">,</span> MyDependency<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IMyDependency<span class="token punctuation">,</span> MyDependency2<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// New implementation</span> container<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IMyService<span class="token punctuation">,</span> MyService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>What do we get now?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 49%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAByklEQVR42rWOTUsbURSGZ+6dDJkkjrGNNR8mmplhTExLoSVt/kFWXetGpVDrZ9RMimLAnUP7P7LrL7B/oQspQWQYhiAWggsxQkEvuXZOTyQt0UUXlr7wcOBw3+ceQfgf+Xxw8Ojm8Gv9tP3l0+H3b/stx7WPmk3bdR275bl20zm2j1zX9lqefXJyajvHzu3sdDr77Xb7o+d5e41GQ/0jLL8pF6y3cxeVnXewWLf85aoF77c2YXG1AkvrFmyvrMDe2jp82KiCZVWgul2F2o4Fu7t1v1arwfzCwlWpVDIGj3wZiY6cP46P+7FEqjuaGuexeJKnNZM/SU3wzFiSJ4ajXCKU41suEEQUuCiKXeQn7n4gTweFRdPMXU6ZOdA13Tc0AyYzk5DPT4OmZSGj66CoKoTCYQgqIZBlGVAEhBAfAUrpNTqeDfik4lgifpnEYma64KdzeUgaBsTTWRhNZeG5bsJrYwqMiSwUXxUhjZ9hqSf1exO5JwwILwgVz6gsc0lRrqmiMEkJsoASZlIwzCJSgKlUYlQkLBodZpGhIYYt1hd1kQuk8FsnIjEq0DLOGWT275D7u16n1431Xf0bBSGEqA8k1HfcCflHbvMLKcKvOnRchg0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-6.png" title="" src="/static/054d62c81104ef8c756fefbacd9cee15/5a190/dotnet-core-ioc-container-6.png" srcset="/static/054d62c81104ef8c756fefbacd9cee15/772e8/dotnet-core-ioc-container-6.png 200w, /static/054d62c81104ef8c756fefbacd9cee15/e17e5/dotnet-core-ioc-container-6.png 400w, /static/054d62c81104ef8c756fefbacd9cee15/5a190/dotnet-core-ioc-container-6.png 800w, /static/054d62c81104ef8c756fefbacd9cee15/0d1a4/dotnet-core-ioc-container-6.png 1036w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>It’s no longer using <code class="language-text">MyDependency</code>, but using <code class="language-text">MyDependency2</code> instead.</p> <blockquote> <p>💡 Remember, if you add multiple concrete implementations for the same interface, it will always pick up the last one that got registered, by default.</p> </blockquote> <h3>Unravelling why we get the last registered implementation type</h3> <p>So, how do we register multiple implementations for the same service type? It’s not supported as you might already know or have come across this <a href="https://github.com/aspnet/DependencyInjection/issues/360">discussion on Github</a>. Looking at the internals of the <code class="language-text">GetService()</code> method we can uncover why this happens.</p> <p>If you debug the <code class="language-text">ServiceCollection</code>, you will be able to see that our two implementation types for <code class="language-text">IMyDependency</code> are still there.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 49.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABxklEQVR42nWSWW+bUBCF+TtNY7MauCvX7IvBMXYSZ2uiqJWq/v/3k4GiKrLSh0/nzL1XaGYOljFbCMHBGEfMGClblOqYIYxCaJ1gehdF0QXxwlLHMSylJPI8h1YcUjAkShAS6dbAJBqSx2D0ULAIiu4F1dO72dPZhJRUS44w8GA5rISq79Dun9HsX5C39zDVEbI4gm97yHQAS2/gixoer77EX3DjApYvW2TDE4bTK8r+jKK7J/+G7vCCcveAeniAKQ4I9Q6B6v4iP+niN3Tv8xpWEATQJkOepsjSLbYmofElEmOQ0MiTZ4Jg8TwujyPwadR5FSGdh0vNsAl8GlkfkQzv6O/+oD39RnHzE9X4C6Z/h6qekLTPkKSevsWKj1jLI9bikhG2PMGRI6xNGCEtajR1iZQ6nLpsmhotUVUlyoIC05qWLuYuJr7yvu/CdR1YLsuhyiNq2l+9f0TWnKDzPWR+gDAdhbKb9+dReB4rZnzyn5nO3DiHG2UUiuqR7X9gN74h787I2jMF8oqif6S0b6GLERtavMtaSrT7P2LShj7o2bRY+ik3PjxnBde+njXwHAS+A3v9HevrK7ik9urbjLO++ucv+QChalmJuosDOAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-7.png" title="" src="/static/05de134123144e9df1171d4e14229b00/5a190/dotnet-core-ioc-container-7.png" srcset="/static/05de134123144e9df1171d4e14229b00/772e8/dotnet-core-ioc-container-7.png 200w, /static/05de134123144e9df1171d4e14229b00/e17e5/dotnet-core-ioc-container-7.png 400w, /static/05de134123144e9df1171d4e14229b00/5a190/dotnet-core-ioc-container-7.png 800w, /static/05de134123144e9df1171d4e14229b00/c1b63/dotnet-core-ioc-container-7.png 1200w, /static/05de134123144e9df1171d4e14229b00/29007/dotnet-core-ioc-container-7.png 1600w, /static/05de134123144e9df1171d4e14229b00/89557/dotnet-core-ioc-container-7.png 1928w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>So there has to be something in the call stack which hands over the last registered implementation type to the service provider. If you start off from <code class="language-text">GetService()</code> method in <code class="language-text">ServiceProviderServiceExtensions</code> class, you would end up in <code class="language-text">ServiceProviderEngine</code>’s <code class="language-text">GetService()</code> method’s <a href="https://github.com/dotnet/runtime/blob/60eff3f3766631bd4e7ee256ca17619fec90e9e6/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs#L82">implementation</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token return-type class-name"><span class="token keyword">object</span></span> <span class="token function">GetService</span><span class="token punctuation">(</span><span class="token class-name">Type</span> serviceType<span class="token punctuation">,</span> <span class="token class-name">ServiceProviderEngineScope</span> serviceProviderEngineScope<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>_disposed<span class="token punctuation">)</span> <span class="token punctuation">{</span> ThrowHelper<span class="token punctuation">.</span><span class="token function">ThrowObjectDisposedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name"><span class="token keyword">var</span></span> realizedService <span class="token operator">=</span> RealizedServices<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">,</span> _createServiceAccessor<span class="token punctuation">)</span><span class="token punctuation">;</span> _callback<span class="token punctuation">?.</span><span class="token function">OnResolve</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">,</span> serviceProviderEngineScope<span class="token punctuation">)</span><span class="token punctuation">;</span> DependencyInjectionEventSource<span class="token punctuation">.</span>Log<span class="token punctuation">.</span><span class="token function">ServiceResolved</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> realizedService<span class="token punctuation">.</span><span class="token function">Invoke</span><span class="token punctuation">(</span>serviceProviderEngineScope<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><code class="language-text">RealizedServices</code> is a <code class="language-text">ConcurrentDictionary</code> that keeps track of our service types and a <code class="language-text">Func</code> to retrieve the implementation type by visiting the corresponding call sites in the dependency tree. The <code class="language-text">_createServiceAccessor</code> actually refers to <a href="https://github.com/dotnet/runtime/blob/60eff3f3766631bd4e7ee256ca17619fec90e9e6/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs#L105"><code class="language-text">CreateServiceAccessor</code></a> which does the above.</p> <p>To find the “why” of this, we need to dig into <code class="language-text">GetCallSite</code> method. This will hopefully navigate you through to <code class="language-text">CreateCallSite</code> method in <a href="https://github.com/dotnet/runtime/blob/60eff3f3766631bd4e7ee256ca17619fec90e9e6/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs#L89"><code class="language-text">CallSiteFactory.cs</code></a> class. If you find the following lines, that could give you a hint where to look at next. In our case, it will call the <code class="language-text">TryCreateExact</code> method because it’s neither an Open Generic nor an Enumerable.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token class-name"><span class="token keyword">var</span></span> callSite <span class="token operator">=</span> <span class="token function">TryCreateExact</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">,</span> callSiteChain<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token function">TryCreateOpenGeneric</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">,</span> callSiteChain<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token function">TryCreateEnumerable</span><span class="token punctuation">(</span>serviceType<span class="token punctuation">,</span> callSiteChain<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token range operator">..</span><span class="token punctuation">.</span></code></pre></div> <p>Taking a peek inside <code class="language-text">TryCreateExact</code> you will be able to see the following like which passes in the last implementation type of the service type we are looking for. There you have the answer for what we were searching for from a more granular level of detail.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABxUlEQVR42k2S3W7aQBCFeQwIMdgY4t819hpsAzblpxBi0rSlTVqpaW6bqlLf/+7r2FKkXny7mtHs2dk527G9CDtKCKoKr9rgZAtUFKN1ippOGUWKUE0FjemFXEcRRjxl4oeSi1tMa4xpmrJbdMzhgInjkcQxeeJSpB75LGQ+16RpyixJBB8/mMhhlzx2WGifKBgReBahb+G7FiPbxh7f0Ol2uwxENJppjqVBvTGp35lsyoRylbPbljwcAp5qj693HpeTw/NlxveHSGKfb/eKeuvgODfY9pjOSBY/XWA5Pn3Dotcz6F0N6Pevuep1yeYZq1VFOsvRacZsXlAsypa5jKeJM9mrskSFoQiORownLpZlYw4GgoHRYBhc9/vEOuP2/hMfHi7U548cT2c22z3b97fsGvYHdocT6/2JIFB0HBmq5fl0nTE936EvrXueRyi3hYGPym/ZXl75/OMv56ffnL784vz4Sv34h/XdC9vzC5v6J8vjM260EFPsCWNxTCWxkBCKg1preUbWmqKFYlVSVWuKoiAvcpbLJev1Wkbxll+0tUEQ0BkOh7iu27qcipAW0URoChqUfJOmsKHtWniL/88rpVr+AYTU/YGBM41KAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-8.png" title="" src="/static/3b82e94d698d0902ca5b17f4b5782a66/5a190/dotnet-core-ioc-container-8.png" srcset="/static/3b82e94d698d0902ca5b17f4b5782a66/772e8/dotnet-core-ioc-container-8.png 200w, /static/3b82e94d698d0902ca5b17f4b5782a66/e17e5/dotnet-core-ioc-container-8.png 400w, /static/3b82e94d698d0902ca5b17f4b5782a66/5a190/dotnet-core-ioc-container-8.png 800w, /static/3b82e94d698d0902ca5b17f4b5782a66/c1b63/dotnet-core-ioc-container-8.png 1200w, /static/3b82e94d698d0902ca5b17f4b5782a66/29007/dotnet-core-ioc-container-8.png 1600w, /static/3b82e94d698d0902ca5b17f4b5782a66/41870/dotnet-core-ioc-container-8.png 1788w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>A way to access multiple implementations of same service type</h3> <p>Let’s say in the rare situation where you would want to invoke both dependencies somewhere in your code; how would you achieve that?</p> <p>I have changed the <code class="language-text">MyService</code> class a little bit to give you an idea of how you can sort of do that. The key here is, we need to inject our dependencies as an <code class="language-text">IEnumerable</code> which will give us access to the service registrations of a given type.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyService</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMyService</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IMyDependency<span class="token punctuation">></span></span> _myDependencies<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MyService</span><span class="token punctuation">(</span><span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IMyDependency<span class="token punctuation">></span></span> myDependencies<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Constructed MyService"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _myDependencies <span class="token operator">=</span> myDependencies<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> dependency <span class="token keyword">in</span> _myDependencies<span class="token punctuation">)</span> <span class="token punctuation">{</span> dependency<span class="token punctuation">.</span><span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Or better yet, you can specifically select the dependency you want with a little bit of LINQ by doing something like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">_myDependencies<span class="token punctuation">.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">MyDependency</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">?.</span><span class="token function">DoWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <blockquote> <p>💡 Note that the IoC container will only resolve and inject the collection of services when the parameter type is an <code class="language-text">IEnumerable</code> and not any other type such as IList, Array etc. Otherwise, you can expect an <code class="language-text">InvalidOperationException</code>.</p> </blockquote> <p>Not the world’s best solution, but gets the job done 🙂 Another downside to this is having to manually register the services rather than making use of assembly scanning. You can also play around with <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.extensions.servicecollectiondescriptorextensions.tryaddenumerable?view=dotnet-plat-ext-3.1"><code class="language-text">TryAddEnumerable</code></a> extension method to see how you can improve this implementation depending on your use case.</p> <h3>A quick detour: how does it resolve an IEnumerable?</h3> <p>Taking a quick detour, let’s see how we got both our <code class="language-text">MyDependency</code> and <code class="language-text">MyDependency2</code> when we injected an <code class="language-text">IEnumerable</code>.</p> <p>When it does the service lookup, depending on the lifetime of the service, it will access different caches of the service provider engine. This happens in the <code class="language-text">VisitCallSite</code> in <code class="language-text">CallSiteVisitor</code> class. During this detour, you will also encounter some <a href="https://github.com/dotnet/runtime/blob/e336921010067b3a06b7afa4ab89a582790943c7/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs#L74">impressive locking code with Monitor</a> 👀. Once you run through some more hoops you will end up in <code class="language-text">VisitCallSiteMain</code> method which will call the <code class="language-text">VisitIEnumerable</code> method.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB7UlEQVR42lWS2Y6iYBCFeQ5tnZZNQFxYZEcQXBDtdW4m6Uzm/Z/i64JkJpmLLyeh/hRV55SiWSsWhoW2WmO6K+zlEtuxmVsms6XB0jTRXUdY4boumqZhGAbm8F3X/0NVVZRnXQrOmrXns9ps0U2bZ33JYlDNkIcGRVVzOB5pmoY4TgjDUDQedb/fj8RRhOOsUJ6mE1R7zeGccTv7nM8Rq8hkE6iEgUXob8gPB/r7g9PpQlHWFEU5kiQpQRDg+74Q4G1tlOlkguZsqbqa/hLRnVOc2MKMlnixSxT6xPWD69tvutcvri9fHC4/ZYBPgqLHy25Ch5ff2QURynw+w9z4JG1DlmckeYkmf3uS8XXBFvwwor329P2D6tgSZwVplhMnGYHU/GA/qmXLyvPZDGMTEMnDKIspq4q0LFh4G552axZbF0+8ai8d3fVKI14e61rWP42eBoGP5+1GdSRM5cd8jrp0UK01U11jYupMtQWqpKlrKrq6kAlCmnPHve8pxc8wikkknCGYvx4OalkWSpqmUowlpZhC1k2HFKXBbruV1He4olu5gPZ6o7/1NO2JSlJvmpa6bsakPc/7h3J/vJJkGW3T8fb+xePlFx+ff2SlG60ke3n/kLOpxLuGPM+FQhpW0qymLMtxyiHtgeF8vgFuBiLinqvBswAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-9.png" title="" src="/static/378e55bb23d235feaca138a1af141ba2/5a190/dotnet-core-ioc-container-9.png" srcset="/static/378e55bb23d235feaca138a1af141ba2/772e8/dotnet-core-ioc-container-9.png 200w, /static/378e55bb23d235feaca138a1af141ba2/e17e5/dotnet-core-ioc-container-9.png 400w, /static/378e55bb23d235feaca138a1af141ba2/5a190/dotnet-core-ioc-container-9.png 800w, /static/378e55bb23d235feaca138a1af141ba2/c1b63/dotnet-core-ioc-container-9.png 1200w, /static/378e55bb23d235feaca138a1af141ba2/29007/dotnet-core-ioc-container-9.png 1600w, /static/378e55bb23d235feaca138a1af141ba2/990be/dotnet-core-ioc-container-9.png 2748w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you carefully look at the <code class="language-text">ServiceCallSites</code> in the above screenshot (highlighted in blue) you can see it has references to our <code class="language-text">MyDependency</code> and <code class="language-text">MyDependency2</code> types under <code class="language-text">ImplementationType</code> params in their corresponding sections. Since we requested an <code class="language-text">IEnumerable</code> it knows that it needs to resolve all the dependencies we have registered under <code class="language-text">IMyDependency</code> type.</p> <p>Now, if you have a look <code class="language-text">VisitIEnumerable</code> method you would see that it visits each implementation type’s constructor in a for-loop.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 36%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABh0lEQVR42j2R227iQBBE+YuQBYwveDwzNgEbY/AVG4mEJLvRarWb/P+XnG37IQ9HNaOWStVdsyCM2JgtzkazVpo4jrHC2miWVuPqCKP1hKcVwS7G3xpMYtkKxozzCC0ztQmYOes1NnniWDUcyppSdLfPsNs9yiYEyhKEYuZt2EQxl9dP+rcvrq9f9C9/Ge6fwr+Javgtho7DRoWku4hdIvqkOB5iijzhsNfkqeWQGvLMsNsqzu3Az48/vL1/ULU9RdlwrlqORUmWF8wWiyVxorj1Cd1Zc2sNQ6VoTwFN4X/TnXzOmUdRVTw/v3C9Xun7nsvlMr3ruiZN98x8z8MLFJ4fslg6PP5wmM+XwoL544qH8S06f1iwWrqUTSfpfnG/3yVZzbmsqETzPCfLMmZjKb6OcUeVUqw1RMJ6LCQK5eiaKFKsdIhrFKe65f76zu12ox8Guq5jEB0TToaeH4iZtGZjtLViaKeWlTG4Y3rBdV1WgictHovTtOZoMCZrmkb+csuikJVT/gNAZtnrVn7u+AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-10.png" title="" src="/static/ee674d346b7ea8b423bba249b23ffbe4/5a190/dotnet-core-ioc-container-10.png" srcset="/static/ee674d346b7ea8b423bba249b23ffbe4/772e8/dotnet-core-ioc-container-10.png 200w, /static/ee674d346b7ea8b423bba249b23ffbe4/e17e5/dotnet-core-ioc-container-10.png 400w, /static/ee674d346b7ea8b423bba249b23ffbe4/5a190/dotnet-core-ioc-container-10.png 800w, /static/ee674d346b7ea8b423bba249b23ffbe4/c1b63/dotnet-core-ioc-container-10.png 1200w, /static/ee674d346b7ea8b423bba249b23ffbe4/29007/dotnet-core-ioc-container-10.png 1600w, /static/ee674d346b7ea8b423bba249b23ffbe4/d9716/dotnet-core-ioc-container-10.png 2736w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now that we are iterating through each of the implementation types we will again go through the <code class="language-text">VisitCallSite</code> method (doing the service lookup in the cache etc.) call chain as before and end up in the <code class="language-text">VisitCallSiteMain</code> method.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsElEQVR42oWSSY7bMBBFfYtutG3J8iRTlKxZpiRr9twBOk7WQYDc/w4vtJbZZPFQv0gW+VnkxHJCllvJNk4QaYp0HHzfZ6+ZS8HMdbAcgavHDWHrXOB5LjvbZj6fYRhzjYFpGqOeWBuB4+0Jwz3+Xi8UAtOyWCyXbFZrthpTF6zWa5I4Iwxi9kFEGCVEOn+hiookVaOeGAsLeag59D3qmFM1PTIveHNd7dBl63gIfUgQZbTffnH++s3t+YdB66T5SVw9R6LqO3H9ZDKbG8g0pzkdOXUB95Ni6FN2mY2dbrGTDWkaEAcuqmq43z+53e+0XUtZdyiVUx6P5EVBnCRMPqYznLTg8jjydXf58UhoW4GvFsSFRVGuKPOIPPPJ65bH45Pr9cYwDJyGnq7txvx8vpBlryubFvtjT3m5UjQ1TXdBDRfWecmb9Fh6AQsp8YJQ9ykj1+04HA7adTrGQjsry3LUYRi+NlwgVUtW16hDTKEUXVVpZzmm7/LuCd59ieV7xFFEoIvCfwiCYIyRnp+Ypokd5Wy8hOnOZqq/yofcYTo7bP0Yjv4yYrtGit1Y8D/+AmcZ/c/LdSZ4AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-ioc-container-11.png" title="" src="/static/c15d7c86521c647e14f0dd680152fcb1/5a190/dotnet-core-ioc-container-11.png" srcset="/static/c15d7c86521c647e14f0dd680152fcb1/772e8/dotnet-core-ioc-container-11.png 200w, /static/c15d7c86521c647e14f0dd680152fcb1/e17e5/dotnet-core-ioc-container-11.png 400w, /static/c15d7c86521c647e14f0dd680152fcb1/5a190/dotnet-core-ioc-container-11.png 800w, /static/c15d7c86521c647e14f0dd680152fcb1/c1b63/dotnet-core-ioc-container-11.png 1200w, /static/c15d7c86521c647e14f0dd680152fcb1/29007/dotnet-core-ioc-container-11.png 1600w, /static/c15d7c86521c647e14f0dd680152fcb1/f1f01/dotnet-core-ioc-container-11.png 2212w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Déjà vu? yet again we are visiting the <code class="language-text">VisitCallSiteMain</code> method as before because now we are resolving each dependency of type <code class="language-text">IMyDependency</code> by visiting the ctors in the above loop. In the <code class="language-text">VisitConstructor</code> method, by using reflection (<code class="language-text">ConstructorInfo.Invoke()</code> method) it will call the constructor on our implemented type and return it. Note that this will happen only during the initial call to the lookup the service and subsequent calls depends on the lifetime (by using the internal cache).</p> <p>As we can see, Microsoft’s default IoC container has all the essential capabilities but also lacks some advanced features like assembly scanning, service decorators that you would get in frameworks like <a href="https://autofac.org/">Autofac</a>, <a href="https://github.com/khellang/Scrutor">Scrutor</a> etc.</p> <h3>Summary</h3> <p>To summarise, we first looked at how we can easily bring in Microsoft’s IoC container into a console application. Then we had a look at service registration and retrieving the services using a provider. Finally, we had some fun with it by using multiple implementations of a given service type and some of the learnings along the way. This is just the tip of the iceberg though. There’s more stuff to dive into, which I am hoping to cover in the future.</p> <p>Hope you enjoyed this post and see you next time. Cheers! 👋</p> <h3>References</h3> <ol> <li> <p><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1</a></p> </li> <li> <p><a href="https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/">https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/</a></p> </li> <li> <p><a href="https://www.stevejgordon.co.uk/asp-net-core-dependency-injection-registering-multiple-implementations-interface">https://www.stevejgordon.co.uk/asp-net-core-dependency-injection-registering-multiple-implementations-interface</a></p> </li> </ol><![CDATA[Understanding .NET Generic Host Model]]>https://www.sahansera.dev/dotnet-core-generic-host/https://www.sahansera.dev/dotnet-core-generic-host/Thu, 13 Aug 2020 00:00:00 GMT<p>In this article, we will concentrate on how the Generic Host model hosts ASP.NET Core 3.x Web app and a Worker Service. We will first discuss the definition of a <em>Host</em> and its configuration. In the subsequent sections, we will dive into the implementation details from a higher level.</p> <h3>So what’s the deal with the Generic Host</h3> <p>With the separation of execution and initialisation, Generic Host provides us with a cleaner way to configure and start up our apps. By default, when you create an ASP.NET Core app now, your application will be hosted using the Generic Host model. If you create a new worker service app, it will be hosted the same way.</p> <p>Not only that, but this model also provides you standardised configuration, DI, logging, and many more. You can even create a traditional console app, beef it up and make use of Generic Host.</p> <blockquote> <p>💡 Follow along with the code from this <a href="https://github.com/sahansera/dotnet-generic-host-example">repository</a></p> </blockquote> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>The Host</h3> <p>According to the official documentation, a <code class="language-text">Host</code> is,</p> <blockquote> <p>ASP.NET Core apps configure and launch a host. The host is responsible for app startup and lifetime management. At a minimum, the host configures a server and a request processing pipeline. The host can also set up logging, dependency injection, and configuration.</p> </blockquote> <p>Let’s create a new .NET 3.1 WebAPI and a Worker Service project</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet new webapi <span class="token parameter variable">-n</span> WebApplication dotnet new worker <span class="token parameter variable">-n</span> WorkerService dotnet new sln dotnet sln <span class="token function">add</span> WebApplication WorkerService</code></pre></div> <p>If you open up the solution in an IDE, you will see the following project structure.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 598px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 122%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAABYlAAAWJQFJUiTwAAAD8UlEQVR42o1VWW/jVBT2/2AKU1Fom8R2FtvxlsXL9W7H6c6MQKpalPKEEIPQIBAS8DBDBW/DC/zXj3NPktKBdsTDl+Nrnfv5bN+J4k99ZEGOfFqgrmuUZY2qqpFlOdu6bsg2WC6PkecFDGOEyWSC2WzGkM+DwQD9fp+haJqGmT+DiBPEyzVRWVZ8Oc9LtvIchhGGwyFflnY0GrGV2JIxoa7r/NKduLBDD0EYoygKtO0STbPgiCWSJCW/EaT/fYJ/Q5E/uq7BcVz4hCSYcDQyzTTNOLo4Fpy+TE9m9D8IdfiuC2seIMrnyEWEslpsarigWs3R6/X4gkx5MOhv6ja4s/8h9FwP3sTDIjUhwoBqV1HqJdd0ne4QqqpyhJqmbqDdYVuKO0LTsuCOTaRzF8E84GZIwqKomNTzfNiOQ6VxYNsOl0ie7bFNZ/uuvtwUGbZKKdnkdHr+DEIk3JCciNak60illTVdLJZcY/nRbVnG4zFHqvT1HgYEXe3AsS0ctSdIhaBLC+52RQQl2SJL0TY1zGEfWveQ/dfoMvpal3mU3eIFntbf4YPyJT5efovRxQqzq9eIbn5HtLqFd/kK/tUt1Itf8NHRj9gl36fVy0eh7EVX2BMrfBjfYD+/htqewz1/gfzye1TXP2DyyTfwzr7GfrLCbvQ5+74LykA9xFAj9A5IVhqsyEISh8hTQalHyAhTz6E099GX6K0xlPcegCJnSKdOayo1xyRJCSKkgZZNaRYtCinDjHRO5wWpR6omJpkOhwZ1tb+ZwX/AWpYKmNPsBU2OOE24o1sNR1HMinFpTg3DhEXjZRjG40qRYzOiGfJmPhzhk+zEHZkck+08TqcznrMt3ik9GaVDw+oTRDBlLcu1dV/LTdPyupJpbrfOfbxNSF+ceB7G8zmi1N9oWe7BionnpJxut8vSewz3pLcpKH3Z9KYoj88RBiFPv2xIQUoQSYaQahnSapPrLSYlhZEgv5jfR/RsmhZHr6yZdRa7M5ZKWSKlZbAkW5Ulo6nlPkxYNYJUJOKYEQS0naKI35mmwWtQ0UYuNMNDb+jA8gXqk88QF0fI2meIimOI6hRJfYakOkHWnEEzfRxoJg77FjqMMQ51C+qGR9lpf8bO8a94cnSL3dOf0Ln4At7NG4gv/0T+1V/wVm/grv7A3sVveK99hffJd+ctvGY8OVpD6TgZDmzCOENvVqBfZpjVn6I4v0ZJmFbP4RcX6LgFDp2cIZ8fg6KT5KS0HMug3TaEF9lI44AllwmqVThHQP+M1kjH2BjANgcYyQ2ldR6EotI4iPIUaXtJKypDVay7m1BnhUiR0WBLKRYkuZpmUc6jXK4PyU5K728KzD6Nzb7RdgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-generic-host-1.png" title="" src="/static/7574402368b68f39b8626bb1f9bc8f39/0c69d/dotnet-core-generic-host-1.png" srcset="/static/7574402368b68f39b8626bb1f9bc8f39/772e8/dotnet-core-generic-host-1.png 200w, /static/7574402368b68f39b8626bb1f9bc8f39/e17e5/dotnet-core-generic-host-1.png 400w, /static/7574402368b68f39b8626bb1f9bc8f39/0c69d/dotnet-core-generic-host-1.png 598w" sizes="(max-width: 598px) 100vw, 598px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>They both have a <code class="language-text">Program.cs</code> which takes care of setting up a host. In the case of the WebApplication project, it sets up a request processing pipeline defined in a <code class="language-text">Startup.cs</code> and in the WorkerService project, sets a new hosted service which is an essentially an <code class="language-text">IHostedService</code>.</p> <p>In the WebApplication project, when you open up the Program.cs file, you will find the following boilerplate code has been added by the template:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Main</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IHostBuilder</span> <span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> args<span class="token punctuation">)</span> <span class="token operator">=></span> Host<span class="token punctuation">.</span><span class="token function">CreateDefaultBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ConfigureWebHostDefaults</span><span class="token punctuation">(</span>webBuilder <span class="token operator">=></span> <span class="token punctuation">{</span> webBuilder<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UseStartup</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Startup<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And, in the WorkerService project we have the following code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Main</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IHostBuilder</span> <span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> args<span class="token punctuation">)</span> <span class="token operator">=></span> Host<span class="token punctuation">.</span><span class="token function">CreateDefaultBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hostContext<span class="token punctuation">,</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddHostedService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Worker<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Except for the <code class="language-text">ConfigureWebHostDefaults()</code> and <code class="language-text">ConfigureServices()</code>, everything else is the same.</p> <h3>Host Configuration</h3> <p>If you look at the <code class="language-text">CreateHostBuilder</code> method in the above code, it calls a <code class="language-text">CreateDefaultBuilder</code> static method from <a href="https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs">Host</a> coming from <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.host.createdefaultbuilder?view=dotnet-plat-ext-3.1">Microsoft.Extensions.Hosting</a> namespace. It looks like that when we scaffold an ASP.NET Core app, it gives us a <em>.NET Generic Host</em> by default now. We used to have Web Host in ASP.NET Core 2.x, which was made deprecated since ASP.NET Core 3.0. For any future applications, it is recommended to use the .NET Generic Host.</p> <p>This does a few things under the covers by wrapping,</p> <ul> <li>Dependency Injection services</li> <li>HTTP Server implementation (such as Kestrel)</li> <li>Logging</li> <li>Configuration etc.</li> </ul> <p>In order to get an idea what the above methods do, I looked into the <a href="https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs">source code on Github</a>.</p> <p>We will start off with CreateDefaultBuilder method first.</p> <p><strong><a href="https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs">Host.CreateDefaultBuilder()</a></strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IHostBuilder</span> <span class="token function">CreateDefaultBuilder</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Initialize a new HostBuilder object</span> <span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HostBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Specify the content root directory</span> builder<span class="token punctuation">.</span><span class="token function">UseContentRoot</span><span class="token punctuation">(</span>Directory<span class="token punctuation">.</span><span class="token function">GetCurrentDirectory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Host Configuration : Add environment variables starting with DOTNET_</span> <span class="token comment">// and add any command line args passed</span> builder<span class="token punctuation">.</span><span class="token function">ConfigureHostConfiguration</span><span class="token punctuation">(</span>config <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// App Configuration : Add appsettings.json (depending on the env.) files</span> <span class="token comment">// and add user secrets if in development mode</span> builder<span class="token punctuation">.</span><span class="token function">ConfigureAppConfiguration</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hostingContext<span class="token punctuation">,</span> config<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token comment">// Config logging</span> <span class="token punctuation">.</span><span class="token function">ConfigureLogging</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hostingContext<span class="token punctuation">,</span> logging<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token comment">// Use default DI provider</span> <span class="token punctuation">.</span><span class="token function">UseDefaultServiceProvider</span><span class="token punctuation">(</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> options<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> builder<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, it pretty much configures a <code class="language-text">HostBuilder</code> object and returns it. There’s nothing really specific to web hosting in here. This is why it’s common to both HTTP and non-HTTP workloads.</p> <p>Taking a step further, let’s look at how the web host gets configured. We will now look through <code class="language-text">ConfigureWebHostDefaults</code> method.</p> <p><strong><a href="https://github.com/dotnet/aspnetcore/blob/4d4f2fe00ffa7c1b9ea08164f9f26a51a392d007/src/DefaultBuilder/src/GenericHostBuilderExtensions.cs#L26">GenericHostBuilderExtensions.ConfigureWebHostDefaults()</a></strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IHostBuilder</span> <span class="token function">ConfigureWebHostDefaults</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IHostBuilder</span> builder<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>IWebHostBuilder<span class="token punctuation">></span></span> configure<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> builder<span class="token punctuation">.</span><span class="token function">ConfigureWebHost</span><span class="token punctuation">(</span>webHostBuilder <span class="token operator">=></span> <span class="token punctuation">{</span> WebHost<span class="token punctuation">.</span><span class="token function">ConfigureWebDefaults</span><span class="token punctuation">(</span>webHostBuilder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">configure</span><span class="token punctuation">(</span>webHostBuilder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Remember that <code class="language-text">ConfigureWebHostDefaults</code> is used only for HTTP workloads and let’s see what we get as the default web host configuration.</p> <p><strong><a href="https://github.com/dotnet/aspnetcore/blob/4d4f2fe00ffa7c1b9ea08164f9f26a51a392d007/src/DefaultBuilder/src/WebHost.cs#L215">WebHost.ConfigureWebDefaults()</a></strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">ConfigureWebDefaults</span><span class="token punctuation">(</span><span class="token class-name">IWebHostBuilder</span> builder<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Configure static web assets if in Development mode</span> builder<span class="token punctuation">.</span><span class="token function">ConfigureAppConfiguration</span><span class="token punctuation">(</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> cb<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Configure Kestrel</span> builder<span class="token punctuation">.</span><span class="token function">UseKestrel</span><span class="token punctuation">(</span><span class="token punctuation">(</span>builderContext<span class="token punctuation">,</span> options<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token comment">// Configure the default services</span> <span class="token punctuation">.</span><span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hostingContext<span class="token punctuation">,</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token comment">// Configure IIS for Windows</span> <span class="token punctuation">.</span><span class="token function">UseIIS</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseIISIntegration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>So far, we have seen that both approaches use the same Generic Host paradigm in the two projects. If you are interested in customising the default configuration, head over to <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1#host-configuration">Microsoft Docs’ official documentation</a>.</p> <h3>Finally, how does it all run?</h3> <p>Now comes the interesting part.</p> <p>In both cases, after the configuration sections, we finally call the <code class="language-text">Run()</code> on <code class="language-text">IHost</code> object implemented in <code class="language-text">HostingAbstractionsHostExtensions</code>. This will run the app and block the calling thread until the host is shut down. This is enabled by <code class="language-text">WaitForShutdownAsync</code> which is called at the beginning of the start-up process, which can be triggered by Ctrl+C/SIGTERM or SIGINIT.</p> <p>Let’s look at how both web hosts and worker services run.</p> <p>For a worker service, remember how we registered our Worker class by passing it into <code class="language-text">ConfigureServices</code> method. This Worker class extends <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice?view=dotnet-plat-ext-3.1"><code class="language-text">BackgroundService</code></a> which in turn implements <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedservice?view=dotnet-plat-ext-3.1"><code class="language-text">IHostedService</code></a>. <code class="language-text">IHostedService</code> provides 2 methods, namely, <code class="language-text">StartAsync</code> and <code class="language-text">StopAsync</code>. So when we run our host, it must be retrieving our Worker service and invoking these methods.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVR42mMIDQ0V8vLykggKCpIPDAwU8/b2lo6KihL08/OT8vX1FQfRQDVyIDaQlgCqkQRiGQ8PD1FnZ2dhNzc3IXt7exEXFxcpf39/AYb6+nomoACLsbExK4gGamIGYRgbRGNj5+bm8gFpUZCBsbGxwiA+AwMDEwM6+P//PyOQYmTAAVatWsUMohsaGtZMnDjx/5QpU95PmDDhP9Bh8xjIASAfQeme1tbWK93d3adbWlqu1dXVNcIVlZSUKFdVVXlWV1c7lZWVuVdUVJiDxLkrL4nz1V5256696sxXe8uVo/qqHUPuLXawJqC3gSQrKA6ANDgY4AYCndw4Y8aM/0Dbvk6ePPl/Z1f3BZC4YN3lcLnuh/+Vuu78VOi691+0+dZTjqJTsiC51vb2eb29vU97enquAPGL9vb2briBTU1NzdOnT/8PNOwHMEz+19U3XAGJM5VdjuBvefBfpvXGH4m2u/+Zqm684M/ZqQjR07Kqr6/vU39//8POzs5vQG9PgRvY3NwsDxR0BNpkBXSlHdACQ7BE5TlR9trrjhyNN225ay87spRdtmKov8IGjT1Y5CFjOAAFNAcQg8KHE4h5gVgUymeEyjMSHXOCgoL87OzsKkCsxMbGpgZiA4XFgYibQdKYi0Fcj5vBvp6Fof4/EyHDAEqxu84CINF7AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dotnet-core-generic-host-2.png" title="" src="/static/62f75431b28332dbbf2b7b1d69754ff8/5a190/dotnet-core-generic-host-2.png" srcset="/static/62f75431b28332dbbf2b7b1d69754ff8/772e8/dotnet-core-generic-host-2.png 200w, /static/62f75431b28332dbbf2b7b1d69754ff8/e17e5/dotnet-core-generic-host-2.png 400w, /static/62f75431b28332dbbf2b7b1d69754ff8/5a190/dotnet-core-generic-host-2.png 800w, /static/62f75431b28332dbbf2b7b1d69754ff8/c1b63/dotnet-core-generic-host-2.png 1200w, /static/62f75431b28332dbbf2b7b1d69754ff8/29007/dotnet-core-generic-host-2.png 1600w, /static/62f75431b28332dbbf2b7b1d69754ff8/575bd/dotnet-core-generic-host-2.png 1791w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><em>Source: <a href="https://devblogs.microsoft.com/cesardelatorre/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/">Microsoft</a></em></p> <p>In the <code class="language-text">Host.cs</code> there’s a separate <code class="language-text">StartAsync</code> method and we can find the following lines inside it.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">_hostedServices <span class="token operator">=</span> Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEnumerable<span class="token punctuation">&lt;</span>IHostedService<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> hostedService <span class="token keyword">in</span> _hostedServices<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Fire IHostedService.Start</span> <span class="token keyword">await</span> hostedService<span class="token punctuation">.</span><span class="token function">StartAsync</span><span class="token punctuation">(</span>combinedCancellationToken<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>So our guess was correct. It certainly invokes the <code class="language-text">StartAsync</code> method of <code class="language-text">BackgroundService</code>, that calls <code class="language-text">ExecuteAsync</code> method in which we have ultimately implemented in our <code class="language-text">Worker</code> class.</p> <p>For a web host, there’s a little bit of abstraction on top of this before it hits the above section. A summary of how it reaches this as follows;</p> <ol> <li>In Program.cs, configure a new webhost builder object in ConfigureWebHostDefaults</li> <li>Register Startup class</li> <li><code class="language-text">GenericHostBuilderExtensions.ConfigureWebHostDefaults</code> method gets called</li> <li><code class="language-text">GenericHostWebHostBuilderExtensions.ConfigureWebHost</code> gets called</li> <li>Register a <code class="language-text">GenericWebHostService</code> service</li> </ol> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IHostBuilder</span> <span class="token function">ConfigureWebHost</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IHostBuilder</span> builder<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>IWebHostBuilder<span class="token punctuation">></span></span> configure<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> webhostBuilder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GenericWebHostBuilder</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">configure</span><span class="token punctuation">(</span>webhostBuilder<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span><span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddHostedService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GenericWebHostService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> builder<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>So what is a <code class="language-text">GenericWebHostService</code> ? It’s an <code class="language-text">IHostedService</code> 🤩. Rest of the story is as above as we looked at in the worker service scenario. Because of this nicely decoupled initialisation we are able to run both ASP.NET Core and Worker services on the Generic Host.</p> <h3>Summary</h3> <p>To summarise, we looked at what makes the Generic Host <em>generic</em> and dug deeper into the implementation details in .NET Github repo. We also looked at what makes an ASP.NET Core web application and a worker service different, configuration-wise. This post became a bit longer than I initially I thought it would be 😅 Nevertheless, hope you picked up a thing or two.</p> <p>Cheers!</p> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&#x26;tabs=visual-studio">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&#x26;tabs=visual-studio</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1</a></li> <li><a href="https://andrewlock.net/exploring-the-new-project-file-program-and-the-generic-host-in-asp-net-core-3/">https://andrewlock.net/exploring-the-new-project-file-program-and-the-generic-host-in-asp-net-core-3/</a></li> </ol><![CDATA[D3.js Creating a Bar Chart from Ground Up]]>https://www.sahansera.dev/d3-creating-bar-chart-ground-up/https://www.sahansera.dev/d3-creating-bar-chart-ground-up/Sun, 05 Jul 2020 00:00:00 GMT<p>Creating a bar chart is not that difficult, or is it? Today, we are going to dissect the basic elements of a bar chart and create it from the ground up using D3.js. It’s pretty easy to copy bits and pieces and construct a bar chart. But, the motivation behind this post is to cover the concepts behind creating a bar chart.</p> <blockquote> <p>You can clone the finished version of this tutorial - <a href="https://github.com/sahansera/D3.js-Bar-Chart">D3 Bar Chart repo</a></p> </blockquote> <p>I will be using D3.js <a href="https://github.com/d3/d3/releases/tag/v5.16.0">v5.16.0</a> which is the latest version of as of yet. Before jumping into coding, first, we need to understand what is the anatomy of a D3 chart.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Anatomy of a D3 Bar Chart</h3> <p>Again, I’m going to keep this as simple as possible. We won’t be covering stuff like calling a Web API, loading a CSV, filtering, cleaning, sorting etc. D3 uses SVG and its coordinate system under the hood - i.e. 0px, 0px are at top left corner.</p> <p>So, let’s start with a blank SVG and set its width and height.</p> <p><strong>HTML Structure</strong></p> <div class="gatsby-highlight" data-language="html"><pre class="language-html"><code class="language-html"><span class="token doctype"><span class="token punctuation">&lt;!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">html</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>D3 Playground<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"> <span class="token selector">svg</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #ccc<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">rect</span> <span class="token punctuation">{</span> <span class="token property">stroke</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">stroke-width</span><span class="token punctuation">:</span> 0.5px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><span class="token comment">// Our code goes here</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span> </code></pre></div> <p>Now we will add our initial JS code to set things up.</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">var</span> data <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">var</span> width <span class="token operator">=</span> <span class="token number">800</span><span class="token punctuation">,</span> height <span class="token operator">=</span> <span class="token number">300</span><span class="token punctuation">;</span> <span class="token keyword">var</span> margins <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token literal-property property">top</span><span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">,</span> <span class="token literal-property property">right</span><span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">,</span> <span class="token literal-property property">bottom</span><span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">,</span> <span class="token literal-property property">left</span><span class="token operator">:</span> <span class="token number">20</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Create the SVG canvas</span> <span class="token keyword">var</span> svg <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token string">'svg'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'width'</span><span class="token punctuation">,</span> width<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Following diagram shows the key elements we will be having in our bar chart.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAByklEQVR42o2TbXOaQBSF/f//IzYZRae20/ZDG2OIFRSWQBNZAUVUDGletCpm4gundx0NZhKnZeawh927z94ZzmbwH894PMb93R0WiwXi+RzL1epgbWZFi3NRtFweLEqenoDJBAJzH0V4nk63C8l2SFLger2G7/uwLAue58F13dcSc5zDvW7C63QQ2DZ804TTboM7HrjbxlXTxnR7SEa8BoMByuUy6vU6FEV5JVWpoa7W0FAVVFUV8pmM2o9TfNEsnFg+Tn4FkBQTo4eHFBiGIc5lGcwwoOk6mKbBIF2SVI3hQr9EjcZvpoPidR8lq4u8PUCRD1FwbvHJ4PgzGqXA4XAI+byyATDq8idtrugWLjQDn00XOX6DAm3OEUwSkNYN8uQLPITUilBiNgEfU2BEwO9VFRWLo8IslK585N3fkJyIOglRJECRvCQ8jamng6juTYdRv4+PX8+QZR6OmItsg+OYORtlNY4Peutdf0zKGh5yVR2PFKsX4GQ2Q0B/rddooN9sokcdd7sBgqC3kfC7730fUCM92tdhDPM4ToEveSLwNAgwo9wlFKd/PQIypoRgL+iZXTCF1qSYYDEVinzu5g9JXIZnUrJ3U/4C8XsnelGPyxcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-1" title="" src="/static/1889639235bd951248cb00f5ccc2c459/5a190/d3-creating-bar-chart-ground-up-1.png" srcset="/static/1889639235bd951248cb00f5ccc2c459/772e8/d3-creating-bar-chart-ground-up-1.png 200w, /static/1889639235bd951248cb00f5ccc2c459/e17e5/d3-creating-bar-chart-ground-up-1.png 400w, /static/1889639235bd951248cb00f5ccc2c459/5a190/d3-creating-bar-chart-ground-up-1.png 800w, /static/1889639235bd951248cb00f5ccc2c459/c1b63/d3-creating-bar-chart-ground-up-1.png 1200w, /static/1889639235bd951248cb00f5ccc2c459/29007/d3-creating-bar-chart-ground-up-1.png 1600w, /static/1889639235bd951248cb00f5ccc2c459/a7342/d3-creating-bar-chart-ground-up-1.png 3359w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Scales</h3> <p>Now we let’s set a scale to our data across x and y axes. By using scales, we can define how each data element can be <em><strong>mapped</strong></em> to pixels on the screen.</p> <p>Let’s create our scale for <strong>x</strong> axis,</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">var</span> xScale <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">scaleBand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">domain</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> width <span class="token operator">-</span> <span class="token punctuation">(</span>margins<span class="token punctuation">.</span>left<span class="token operator">+</span>margins<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><code class="language-text">scaleBand</code> is used when you have ordinal values for your axis. So, it will take any amount of ordinal values in the <code class="language-text">domain</code> function and spit out values specified in the <code class="language-text">range</code> function. The reason why we deduct the margins is that we need our bars to fit within the margins of our chart. We will now get values from 0px to 760px.</p> <p>And the scale for y axis,</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">var</span> yScale <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">scaleLinear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">domain</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span><span class="token punctuation">[</span>margins<span class="token punctuation">.</span>top<span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Since our <strong>y</strong> axis is going to have quantitative continuous values, we choose the <code class="language-text">scaleLinear</code> function to map our <strong>y</strong> values. In our dataset, the min is 1 and the max is 5. So we provide 1 and 5 as an array into the <code class="language-text">domain</code>. Now our <code class="language-text">range</code> is from 10px to 100px. Why 100px? Just bear with me on this one.</p> <p>Now let’s add some margins around in our SVG canvas. Otherwise, you will see clipping and other sorts of problems when you have data on your chart. For this we can use a SVG <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g">group element <g></a> and a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform">transformation</a>.</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">svg<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'g'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'transform'</span><span class="token punctuation">,</span> <span class="token string">'translate('</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>top <span class="token operator">+</span><span class="token string">','</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>left <span class="token operator">+</span><span class="token string">')'</span><span class="token punctuation">)</span></code></pre></div> <p>This is clearly visualised in Mike Bostock’s <a href="https://observablehq.com/@d3/margin-convention">Observable notebook</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 51%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6UlEQVR42q1SyaqEMBD0/7/M7eKCehHBgzvjjrs1VIY8dGAuMy9QlKmky+50K2VZoqoqdF2HcRyx7zvO87yB2rIs4Ho/u2KeZyjbtuHxeCAMQ0RRJDjLMuR5/ockSRDHMYqiuOkSvE+PdV2hHMchNqqqQtf1j9A07eMZYz3PE1UoLKeuaxiGAdM0vwJNgyDANE2vkvmGvxoyw2EYoDDNNE1/MmSsbdtomub/SvZ9/1WyNKRI02/Aht0M27aF4zgibfIVUrMsC67riv07Exw30WXODhvD8aHAH1CTuvzu+17c4fBemTGSOdhPVFvNcPO9EN0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-2" title="" src="/static/6d7949b0ed42e251f411cc66a82ddf42/5a190/d3-creating-bar-chart-ground-up-2.png" srcset="/static/6d7949b0ed42e251f411cc66a82ddf42/772e8/d3-creating-bar-chart-ground-up-2.png 200w, /static/6d7949b0ed42e251f411cc66a82ddf42/e17e5/d3-creating-bar-chart-ground-up-2.png 400w, /static/6d7949b0ed42e251f411cc66a82ddf42/5a190/d3-creating-bar-chart-ground-up-2.png 800w, /static/6d7949b0ed42e251f411cc66a82ddf42/c1b63/d3-creating-bar-chart-ground-up-2.png 1200w, /static/6d7949b0ed42e251f411cc66a82ddf42/29007/d3-creating-bar-chart-ground-up-2.png 1600w, /static/6d7949b0ed42e251f411cc66a82ddf42/6a878/d3-creating-bar-chart-ground-up-2.png 2846w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s add the rest of the code to draw the bars.</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js">svg<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'g'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'transform'</span><span class="token punctuation">,</span> <span class="token string">'translate('</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>top <span class="token operator">+</span><span class="token string">','</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>left <span class="token operator">+</span><span class="token string">')'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">selectAll</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">enter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'x'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">d<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">xScale</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// We only need the index. i.e. Ordinal</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'y'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">d<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">yScale</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// We need to pass in the data item</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'width'</span><span class="token punctuation">,</span> xScale<span class="token punctuation">.</span><span class="token function">bandwidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Automatically set the width</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">d<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">yScale</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'fill'</span><span class="token punctuation">,</span> <span class="token string">'lightblue'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>In the above code, we have put our bars into a <g> element to group them so that we can transform them easily. Since we are using <code class="language-text">translate</code> method, it will add 10px to x and y coordinates of each element we will be drawing inside it. The rest of the code works according to D3 <a href="https://sahansera.dev/d3-js-join-semantics/">data joins</a>.</p> <p>Let’s run this and see,</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABHElEQVR42p3S204CMRAG4H3/NzGAsIoHJEbjgWzb6bI9uAfYVcREoy/xO90AC4kX6sV30Tb9Z5pplBkLY13LWgtvTcdZOOd2rD20f2aMQV3XiM5lhgnZ1pWc45aCDDfsURKUEDskJbTqkFJQG4LPi6JAJCRvkIYm4hCDkXvGyUacLXDmmtaY11Pt8EAp7rnoHRMctA1XIkFVcqDkqsRhnVBAISWJa21x7F8x8iv2gp5bYVC+t/pslNW4NEtcsPG8gimXPwV2NHUdpFJgmnoMn1aIOTzmjnt+jf7iC4PFJ47KD1DVIArv11r/ykExvjfJSsTFG07zNYb8innFQ0mSBKHL/wiDCPeD2WyGPM8RhXHvj/8vvO9Y/mZN0+AbvHMgwQw5pn0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-3" title="" src="/static/5764a150876367773e92fb111bee7da3/5a190/d3-creating-bar-chart-ground-up-3.png" srcset="/static/5764a150876367773e92fb111bee7da3/772e8/d3-creating-bar-chart-ground-up-3.png 200w, /static/5764a150876367773e92fb111bee7da3/e17e5/d3-creating-bar-chart-ground-up-3.png 400w, /static/5764a150876367773e92fb111bee7da3/5a190/d3-creating-bar-chart-ground-up-3.png 800w, /static/5764a150876367773e92fb111bee7da3/c1b63/d3-creating-bar-chart-ground-up-3.png 1200w, /static/5764a150876367773e92fb111bee7da3/29007/d3-creating-bar-chart-ground-up-3.png 1600w, /static/5764a150876367773e92fb111bee7da3/1e5d2/d3-creating-bar-chart-ground-up-3.png 1630w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Our DOM looks likes this now,</p> <div class="gatsby-highlight" data-language="html"><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>800<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>300<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>g</span> <span class="token attr-name">transform</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>translate(20,20)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>20<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>20<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lightblue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>40<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>40<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lightblue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>304<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>60<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>60<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lightblue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>456<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lightblue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>608<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>152<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lightblue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>g</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre></div> <p>Oops, why is it like upside down? Remember that the SVG coordinates start from the top-left corner. So everything gets drawn relative to that point. Which means we need to change the range of our y values. Let’s fix this.</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">var</span> yScale <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">scaleLinear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">domain</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span><span class="token punctuation">[</span>height <span class="token operator">-</span> <span class="token punctuation">(</span>margins<span class="token punctuation">.</span>top<span class="token operator">+</span>margins<span class="token punctuation">.</span>bottom<span class="token punctuation">)</span><span class="token operator">*</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Wait, what’s this calculation? We are basically setting the max and min values for our y range. In other words, we need our max y value to go up until 220px because we need to account for the height of the bar as well.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABNklEQVR42qXSyVbCMBiGYe7/TirFgY2KDFWobeZyIOm0kEKFw8HhEj5DKirKzsVzuuift2nalk4UtDHQWv/LbDZDVVVosckYjAswxv6gvxBKj1DrMEsIQVEUaPHHEFxIcM7/EJxBcQppTVmMO2XgmxoXZoWOFYipvcfsrHDxsixtMJwcBfcD8jMUcoWeSm2ocSPm6CWpc2uFXLqH7td9B6MIXDZBwjhCJpyIclxLDS/fws838Iot+nKOhEQQlEBah9hRUA16EPYMFCN4sDvyzTPO00Z7vkQnWzvtbIO+0s0rCtH4cTxfwe6YoyszXCUlfFXA0zXObNRL17iUOQIqMGIKQ2tCmVt4ShzHzUcZLN8wWr1jZK/Dpx2CxQ731QuC5StouUBpNIrUOHmWIsuyk4z99eq6xgfMPSK2EQcFCgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-4.png" title="" src="/static/701626a121b324c8c70b4b125fd47953/5a190/d3-creating-bar-chart-ground-up-4.png" srcset="/static/701626a121b324c8c70b4b125fd47953/772e8/d3-creating-bar-chart-ground-up-4.png 200w, /static/701626a121b324c8c70b4b125fd47953/e17e5/d3-creating-bar-chart-ground-up-4.png 400w, /static/701626a121b324c8c70b4b125fd47953/5a190/d3-creating-bar-chart-ground-up-4.png 800w, /static/701626a121b324c8c70b4b125fd47953/c1b63/d3-creating-bar-chart-ground-up-4.png 1200w, /static/701626a121b324c8c70b4b125fd47953/29007/d3-creating-bar-chart-ground-up-4.png 1600w, /static/701626a121b324c8c70b4b125fd47953/7e21b/d3-creating-bar-chart-ground-up-4.png 1626w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Almost there, but the heights look weird. That’s because we changed our y scale. Now, let’s fix the height.</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">d<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> height <span class="token operator">-</span> <span class="token punctuation">(</span>margins<span class="token punctuation">.</span>top<span class="token operator">+</span>margins<span class="token punctuation">.</span>bottom<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">yScale</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div> <p>Remember, we need to deduct the top and bottom margins from the total height so that whatever the value we get from <code class="language-text">yScale</code> will not exceed that boundary.</p> <p>Cool, now we are getting somewhere 😁</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABHElEQVR42p2P2VLCQBBF8/8/ooQkxCeXJ6ssxFmSIWQlISti8CuuPaNQgDxYPpyavtVTp7utOI6RJMm/4EmB16zCgphT3Q1bWJxzCCH+BOcC7ADj8KMKdvEBh5jEHdJNA0t/lFJeJaBeyJlBsTc8hSm8bICf9pglHdxkwIxks/VIdY+8bn8LQykMSnK8SIWHZYFHjcrhrxq45R6eFpBomm7h0auzQ8LsUsiExFyGhoUIcE8Se/0JZ7032AcB4eY7Eg5XhIxB6tMEx3MQYVro6d/oDcxJPzhGOBrc/LAhZT2QzjdCL6BTVIk7wglLTKihN9HcrNpjfZn1v2PO3nEbNUirGpaqOizr3qA2LaITllVzlqOLfNpXZY3dOOILkA8j5VxF12wAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-5" title="" src="/static/cb8c46f67a6d469de1688d3994c8b813/5a190/d3-creating-bar-chart-ground-up-5.png" srcset="/static/cb8c46f67a6d469de1688d3994c8b813/772e8/d3-creating-bar-chart-ground-up-5.png 200w, /static/cb8c46f67a6d469de1688d3994c8b813/e17e5/d3-creating-bar-chart-ground-up-5.png 400w, /static/cb8c46f67a6d469de1688d3994c8b813/5a190/d3-creating-bar-chart-ground-up-5.png 800w, /static/cb8c46f67a6d469de1688d3994c8b813/c1b63/d3-creating-bar-chart-ground-up-5.png 1200w, /static/cb8c46f67a6d469de1688d3994c8b813/29007/d3-creating-bar-chart-ground-up-5.png 1600w, /static/cb8c46f67a6d469de1688d3994c8b813/1d499/d3-creating-bar-chart-ground-up-5.png 1632w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Axes</h3> <p>D3’s axes API is pretty straight forward. You can utilise that to add horizontal and vertical axes to any graph. To wrap up our bar chart, let’s add the axes.</p> <p><strong>X axis</strong></p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">svg<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'g'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'transform'</span><span class="token punctuation">,</span> <span class="token string">'translate('</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>left <span class="token operator">+</span><span class="token string">','</span><span class="token operator">+</span> <span class="token punctuation">(</span>height <span class="token operator">-</span> margins<span class="token punctuation">.</span>top<span class="token punctuation">)</span> <span class="token operator">+</span><span class="token string">')'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>d3<span class="token punctuation">.</span><span class="token function">axisBottom</span><span class="token punctuation">(</span>xScale<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Y axis</strong></p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">svg<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'g'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'transform'</span><span class="token punctuation">,</span> <span class="token string">'translate('</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>left <span class="token operator">+</span><span class="token string">','</span><span class="token operator">+</span> margins<span class="token punctuation">.</span>top <span class="token operator">+</span><span class="token string">')'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>d3<span class="token punctuation">.</span><span class="token function">axisLeft</span><span class="token punctuation">(</span>yScale<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABH0lEQVR42pXRa0/CMBQG4P3/H+JlbGNiJEGExA/qell36bZ2Drwhwr94PVsmAfEDfnhy2jR523Pq5HkOrfU/5DtSF3jSBhF5zEqYZgGHMQYhxEk45zuCRRgrA9ds4FdrXBQrMNPAaQ+llH+KiZKik0qOudK40q+41i9kiUC/IbBfGBK3+gS3i8PAeI8iD1LhLikwI23YKHuGV2/h15vOoFwdB0aM2pFxhwuJiGrUryeqgmu38Oym4/YBPwblx3FgzBmSWCKjlu7jDEH5jrDnU3uhXe/4FDDsA1oeXdDWsA2nQGGXcEY02Ju0xji1CNIGl+1Leuc0aPeE/YCc0cdEhl44SSwmicEthU7TCvPM0NzKgzpVRVdnv87391Oi6wbf6XYgYCvdyxQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-6" title="" src="/static/2d02c59db6fa26f633d41f7e4b523a16/5a190/d3-creating-bar-chart-ground-up-6.png" srcset="/static/2d02c59db6fa26f633d41f7e4b523a16/772e8/d3-creating-bar-chart-ground-up-6.png 200w, /static/2d02c59db6fa26f633d41f7e4b523a16/e17e5/d3-creating-bar-chart-ground-up-6.png 400w, /static/2d02c59db6fa26f633d41f7e4b523a16/5a190/d3-creating-bar-chart-ground-up-6.png 800w, /static/2d02c59db6fa26f633d41f7e4b523a16/c1b63/d3-creating-bar-chart-ground-up-6.png 1200w, /static/2d02c59db6fa26f633d41f7e4b523a16/29007/d3-creating-bar-chart-ground-up-6.png 1600w, /static/2d02c59db6fa26f633d41f7e4b523a16/7e21b/d3-creating-bar-chart-ground-up-6.png 1626w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Looks okay, but the axes are a bit off. So let’s fix that.</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">var</span> margins <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token literal-property property">top</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token literal-property property">right</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token literal-property property">bottom</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token literal-property property">left</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Simple! When creating a graph in D3 always remember to use variables when possible so that you can easily fix if something’s not looking good.</p> <p>And we are done!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABTUlEQVR42pWR6U7DMBCE8/7vAT1yFVFEBRJ/aIDEOahjO81RaJqWtrzEsHYoEgIJ8ePT7NjZsdexiqKAlBJKqX+TU99CKkOWCzSrFawgCBCGIaIo+hXG2BdRxBB+ElHPLBGw1Q6e7DDia8RC9YH64ziOe5LkG4zWmNljuI8z+GKNiWwxIXVEC6/cw6/2GMstUrWENZ/PKVA3JD261gfoGxHT5xIX+Ssu8xf4fAV3+WZCPFKbbqbVJz8WHVJJgQEFxuEjEhovC59wl3BMeYNrXuNqUcGRG7jlgdjTeB3cYmdCPFJbbH4GPsQpZosSN7zCLS/7Uep3uIRTHeHQG/kUqHH1e5kRDybEUVtTT+ojbDqgHznjOEtrDIhh1mDAlhgnJcZphRHpkBVGtT/VJ7TX65rzqADjAlbbtqibBlVdm9+u0V5z8nrvL1+R77oOH/dJIynMImycAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-creating-bar-chart-ground-up-7" title="" src="/static/816f006aff0212a91a70dd6a877444df/5a190/d3-creating-bar-chart-ground-up-7.png" srcset="/static/816f006aff0212a91a70dd6a877444df/772e8/d3-creating-bar-chart-ground-up-7.png 200w, /static/816f006aff0212a91a70dd6a877444df/e17e5/d3-creating-bar-chart-ground-up-7.png 400w, /static/816f006aff0212a91a70dd6a877444df/5a190/d3-creating-bar-chart-ground-up-7.png 800w, /static/816f006aff0212a91a70dd6a877444df/c1b63/d3-creating-bar-chart-ground-up-7.png 1200w, /static/816f006aff0212a91a70dd6a877444df/29007/d3-creating-bar-chart-ground-up-7.png 1600w, /static/816f006aff0212a91a70dd6a877444df/6ee58/d3-creating-bar-chart-ground-up-7.png 1640w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Great! and we are done ✅</p> <h3>References</h3> <ol> <li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g">https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g</a></li> <li><a href="https://observablehq.com/@d3/margin-convention">https://observablehq.com/@d3/margin-convention</a></li> </ol><![CDATA[D3.js Join Semantics - A Conceptual Look]]>https://www.sahansera.dev/d3-js-join-semantics/https://www.sahansera.dev/d3-js-join-semantics/Tue, 30 Jun 2020 00:00:00 GMT<p>It’s really easy to get started with D3 when you go through their excellent <a href="https://d3js.org/">documentation</a>. However, the goal of this post is to give you a sense of what patterns it uses underneath for joining DOM elements and data items.</p> <p>The key element of D3 is that it treats data you want to visualise as a database. We can also think that D3 treats the elements you have on your webpage as a database.</p> <h3>Data Join Semantics</h3> <p>Let’s say we have the following SVG elements in our DOM and a variable <code class="language-text">data</code> with some values we want to bind it with.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">x</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span> <span class="token attr-name">y</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rect</span><span class="token punctuation">></span></span><span class="token plain-text"> </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre></div> <p>Now let’s see how D3 joins the data with DOM elements. We will use the above structure as a reference.</p> <h3>Update</h3> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">d3<span class="token punctuation">.</span><span class="token function">selectAll</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre></div> <p>So we have a database of DOM elements and a database of data items. When we have a 1:1 mapping with them we call it the Update section. Once this association is formed internally in D3, we can manipulate our DOM elements using methods such as <code class="language-text">.attr()</code>.</p> <p>This situation is called “Update” stage. Think of this as a natural join between two database tables.</p> <h3>Enter</h3> <p>What if we have more data elements than our DOM elements?</p> <p>For such cases that go into a different area in the selection called <strong>Enter</strong> area. That’s when we can access that area with the <code class="language-text">.enter()</code>method in D3. We can then tell D3 to what to do with those extra data items. For instance, if we want to create new ‘rect’ DOM elements, we can do a,</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">d3<span class="token punctuation">.</span><span class="token function">selectAll</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token operator">**</span><span class="token number">6</span><span class="token operator">**</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">enter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// accessing the Enter area</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span> <span class="token comment">// telling what to do with it</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">)</span> <span class="token comment">// rest of the manipulations</span></code></pre></div> <h3>Exit</h3> <p>Finally, we have the case of having more DOM elements than our data elements.</p> <p>Imagine now we have 5 data elements but only 4 DOM elements. So these extra UI elements go into <strong>Exit</strong> area. Just like before, we just need to tell D3 what to do with those extra items? Remove you say? 👍</p> <div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx">d3<span class="token punctuation">.</span><span class="token function">selectAll</span><span class="token punctuation">(</span><span class="token string">'rect'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3>Mapping with Relational Algebra</h3> <p>Remember we are now thinking about D3 in terms of database. Let’s see how we can map this into relational algebra.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAADPUlEQVR42jWTW2gTWRzGT8ikXnJPN4kXVMQnlVKhRRYVwWXx9qJFK2KliNYHV9fF+iDrVro1ycyYlKQiSWYy0yZpJpc5SVprZ5I07dKKrkL74svWRlt924fFzZyk+iQlnkQ98PF/+/Gd7//9Afj2ap1AjWhif5kkbiK3zltxa4NlSuP55CRu1PrBPt1YcbcOFnssUHZvyRQYeybvM4rSLXNSOgD6Z4mvkJqqMasunRVRTXcUel0MkapwlWtNVEKtceQEoX+pjUJf4MKLQxHPgi2ZSVrSOe7ozN/i4eKzpBnKETPMx0yifNc2MWP/Cr0NtAqtGVAoQlAcKr9yXxUok2oG0U3sR5cq2BfqeXo+HXx3Iz30viPuW7AkJzijmGO0yUlmY+JJQC/K/jrULOYcVjirA9hZZ4VUx9Ggia1GDqbqKj/cGv58H/jHh37MHo/Sby6lBpcup4dKXTHHStuov2BMTweOFJ8nf/7rRbJFmo1iuN8EcwlLOt8JKrTGhXPj646Q1zpc9dqGy249t+YC/j+CF+e64KOV3gl2+VrW//Za5uG7Y1H3vCUlhbZnp4Z3jk+PbMoUOCPMBU3p3IgJSv1AoTRBRGnYuhT8VcWpYspUE/vZCQI3uV9enoxQpY7wwD8dEcfi6ZGB1yfDrldW+ITXY4henGSMKYnFMMYEZR7LAxRSQ9UdYjCjkCoGUWr2A4avObBDpnvubNy7fEX0li4mPEuXU4PLJ0bd80YosTi7Bqw+TQ1JI0ZR/rORoeJSC5Uhe6gabo8jnJ/C7o2t+Sxc1teWOSV43nYn6KVzArXYnaBW2oVAftfkHL9fnhVacX4Hi88EWzYfwPB4I8MP/cBQodWuyoMNceSzc8itZcuDZh7hHFcpdbCX7Xl6JPygdGa4r3Q04ppvFh9z9uxUaNtYgd+cnWJ3jE+HMEjAFaLMEBob1Vn1aG2IJH5HTlUMux1FpJrH1Qmt4vkfrRPusd0LP0W9r7amxqAhXeANeCn6lMwbRClqSE3We3jPCqVN38oNGu2usUBTodcfQBTxK86TxPL/jxvwiSKu15xEuxZOt+jEwtVmUXaYcffwAmgTzP/2Q0Y6vAfCpu+X8gVXWrV04Z8rjgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="d3-exit-update-enter.png" title="" src="/static/f38c85e6e49c23ffe87cb3dec740c73b/5a190/d3-exit-update-enter.png" srcset="/static/f38c85e6e49c23ffe87cb3dec740c73b/772e8/d3-exit-update-enter.png 200w, /static/f38c85e6e49c23ffe87cb3dec740c73b/e17e5/d3-exit-update-enter.png 400w, /static/f38c85e6e49c23ffe87cb3dec740c73b/5a190/d3-exit-update-enter.png 800w, /static/f38c85e6e49c23ffe87cb3dec740c73b/636c2/d3-exit-update-enter.png 911w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>As we can see, when we have UI elements and data with 1:1 mapping, it like a natural join. When we have more data than UI elements, it like an anti-join where we get the extra data elements in <strong>Enter</strong> stage. As with the Exit stage, we get the extra UI elements that do not map with data.</p> <p>Now we know the underpinning principles of D3 join semantics. This helps us to dive into a D3.js code without worrying too much about syntax.</p> <p>What would happen if we do a <code class="language-text">.selectAll()</code> on an empty set of DOM elements? Can we still use <code class="language-text">.enter()</code>? There’s a well-known pattern for that as well 😊 I will explain that in a separate blog post.</p> <h3>References</h3> <ol> <li><a href="https://bost.ocks.org/mike/join/">https://bost.ocks.org/mike/join/</a></li> <li><a href="https://en.m.wikipedia.org/wiki/Relational_algebra">https://en.m.wikipedia.org/wiki/Relational_algebra</a></li> </ol><![CDATA[Securing Hangfire Dashboard in ASP.NET Core with a Custom Auth Policy]]>https://www.sahansera.dev/securing-hangfire-dashboard-with-endpoint-routing-auth-policy-aspnetcore/https://www.sahansera.dev/securing-hangfire-dashboard-with-endpoint-routing-auth-policy-aspnetcore/Fri, 15 May 2020 00:00:00 GMT<p>This blog post assumes that you have a basic understanding of Hangfire. If not, you can follow their excellent <a href="https://docs.hangfire.io/en/latest/">guide</a> on how to get started. The scope of this blog post is to show you how to secure the Hangfire Dashboard and only authorise certain users to be able to access it without opening it up to the general public.</p> <p>By default, if you try to access your Hangfire dashboard, it will work perfectly fine on your <code class="language-text">localhost</code> because local requests are allowed. However, things can get a bit tricky when you want to secure your Dashboard.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>For starters, Hangfire provides us with a <a href="https://docs.hangfire.io/en/latest/configuration/using-dashboard.html"><code class="language-text">IDashboardAuthorizationFilter</code></a> to perform our custom authorisation when deployed. But, what if we want to do the authorisation at an endpoint level rather than using a custom filter?</p> <h3>Step 1 - Initial Setup</h3> <p>To get started, you can clone the repo I have put together:</p> <p><a href="https://github.com/sahansera/securing-hangfire-dashboard">Securing Hangfire Dashboard</a></p> <p>To summarise, the above solution is a .NET Core 3.1 Web API project and has the following dependencies.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVR42l2S3XaCMBCEeR4FJQIJPyIFQe0RwdjW656+/ztMM6tQTy/mbFiS5ZsJnrbfqPor2qZFsa1QFDvkeYnNJkEYRrOU+luH6rUfY73ezPs9Ve5RH09uYIcs26Jww4zJoZMUWmezpPdccx+fjcmQpgWSxMhQyiNhM9zQ7Ts0Tm17EMJJ/DI3vtL6wRrL5Qq+76r/qCQVwjCrkFc1Smd3t3O1fBNNaw4lBYlYc0fHwxwSBOGsmdB8/aCxH+hchvv2KIRU4izHsRHx8GIRCBX1OmjSlKeXqC1S4zJJ89kmSeJYI4q0WGZurModWq2UfOC/2BfCIbnDDnec+wvG0WIcLPp+xHW8wdpPXNz6fB5wOLyjrlv5C3gpUwzZMwLevBAyYBXFQsHbIpUWu1p6tEy7QfDIjCS0N2VGTdR8/wun1geQt2m+5QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./hangfire-aspnetcore-1.png" title="" src="/static/df848ee13bd4dbea4accfc08a929e68e/5a190/hangfire-aspnetcore-1.png" srcset="/static/df848ee13bd4dbea4accfc08a929e68e/772e8/hangfire-aspnetcore-1.png 200w, /static/df848ee13bd4dbea4accfc08a929e68e/e17e5/hangfire-aspnetcore-1.png 400w, /static/df848ee13bd4dbea4accfc08a929e68e/5a190/hangfire-aspnetcore-1.png 800w, /static/df848ee13bd4dbea4accfc08a929e68e/c1b63/hangfire-aspnetcore-1.png 1200w, /static/df848ee13bd4dbea4accfc08a929e68e/d777c/hangfire-aspnetcore-1.png 1555w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>We use MemoryStorage since we don’t want to persist anything for this demo. This will work just fine with SQLStorage. To keep things simple on the authentication side of things, I have used AzureAD.UI library.</p> <p>Once you have cloned the repo, do not forget to create an app registration in the Azure portal, under Active Directory. Make sure you have set the correct Redirect URIs (Note: we need to add <code class="language-text">hangfire</code> URI) and selected ID tokens.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1ElEQVR42n2T2XajMBBE+QAfswgBArGIxUBixzPJgzP//2E11YrJsT3OPNwjjJvqrRTktoFzDnVdo21bNESlGolKUZQVsjxHGEWI4hhxktwhMY8EjespZqEUg5RC6zosy4xlXVE3DQxFy0qwyAvDBAU0kwgiIN/cElhbwpgS+TWgbjqM8wn9MKHrWtJ9U5UliqKgcP4VL5VK5exgI/j4/IPT8Yi+7+GGAVnBBNYhjBOE4Z6E30TS+g0JxZJEQekCKiuQ6gzB8fSKy+WCeZ5RcY45K5gOs6/UlBaGrZasbMMY43l8znLDDqXl1mEYel/6nll1mqIta3S2RVs1qE0Fzcxa66ekjP86NffApdT9iIaVSSbNgRu2sHIxx2nECxM5ZlW+teRn+L84I+XSgq4f4DhwKTvx2TLkMg8GCiIW/4CSZA/QNg7rssBaS7/F3pPDOHJ+pbdLxfeV2OY6Q3mWWF/AtuUbArGKKMdX44qImDzT2r//X6vPqg7i7RaQiFaZpgnntzN9OJCRdhq8pW6RLuSUhYh97gVvfojgeDjg/P6O9XjGy+kXz99Y11fensWz8gaJxURUOvhHcL/fYyMMI/T04UBURDNHYmbyxNS73e7O9Bt/AWixS7b0JOuCAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./hangfire-aspnetcore-2.png" title="" src="/static/426b73dcb47cd0d47277e367632824c0/5a190/hangfire-aspnetcore-2.png" srcset="/static/426b73dcb47cd0d47277e367632824c0/772e8/hangfire-aspnetcore-2.png 200w, /static/426b73dcb47cd0d47277e367632824c0/e17e5/hangfire-aspnetcore-2.png 400w, /static/426b73dcb47cd0d47277e367632824c0/5a190/hangfire-aspnetcore-2.png 800w, /static/426b73dcb47cd0d47277e367632824c0/c1b63/hangfire-aspnetcore-2.png 1200w, /static/426b73dcb47cd0d47277e367632824c0/29007/hangfire-aspnetcore-2.png 1600w, /static/426b73dcb47cd0d47277e367632824c0/809bc/hangfire-aspnetcore-2.png 2612w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You also need to copy and paste the <code class="language-text">ClientId</code> and <code class="language-text">TenantId</code> from your app registration to <code class="language-text">appsettings.json</code></p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token property">"AzureAd"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"ClientId"</span><span class="token operator">:</span> <span class="token string">"Enter_the_Application_Id_here"</span><span class="token punctuation">,</span> <span class="token property">"TenantId"</span><span class="token operator">:</span> <span class="token string">"Enter_the_Tenant_Info_Here"</span> <span class="token punctuation">}</span></code></pre></div> <p>Once you have done this setup, you can just press F5 and head over to <a href="https://localhost:44317/hangfire"><code class="language-text">https://localhost:44317/hangfire</code></a> to see if everything is working.</p> <h3>Step 2 - Adding a custom authorisation policy</h3> <p>For our server to tell which policy to use to authorise users to access the Hangfire dashboard, we need to define a custom authorisation policy.</p> <p><a href="https://github.com/sahansera/securing-hangfire-dashboard/blob/master/SecuringHangfireDashboard/SecuringHangfireDashboard/Startup.cs">Startup.cs</a></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Add Authentication</span> <span class="token range operator">..</span><span class="token punctuation">.</span>Code removed <span class="token keyword">for</span> brevity <span class="token comment">// Add a new policy for hangfire</span> services<span class="token punctuation">.</span><span class="token function">AddAuthorization</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Policy to be applied to hangfire endpoint</span> options<span class="token punctuation">.</span><span class="token function">AddPolicy</span><span class="token punctuation">(</span>HangfirePolicyName<span class="token punctuation">,</span> builder <span class="token operator">=></span> <span class="token punctuation">{</span> builder <span class="token punctuation">.</span><span class="token function">AddAuthenticationSchemes</span><span class="token punctuation">(</span>AzureADDefaults<span class="token punctuation">.</span>AuthenticationScheme<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">RequireAuthenticatedUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token range operator">..</span><span class="token punctuation">.</span>Code removed <span class="token keyword">for</span> brevity <span class="token comment">// Bootstrap Hangfire</span> services<span class="token punctuation">.</span><span class="token function">AddHangfire</span><span class="token punctuation">(</span>configuration <span class="token operator">=></span> configuration <span class="token punctuation">.</span><span class="token function">SetDataCompatibilityLevel</span><span class="token punctuation">(</span>CompatibilityLevel<span class="token punctuation">.</span>Version_170<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseSimpleAssemblyNameTypeSerializer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseRecommendedSerializerSettings</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseMemoryStorage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3>Step 3 - Securing the Dashboard</h3> <p>Now that we have defined our custom authorisation policy, let’s configure our Hangfire dashboard endpoint to require authorisation with our <code class="language-text">HangfirePolicyName</code> policy.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Configure</span><span class="token punctuation">(</span><span class="token class-name">IApplicationBuilder</span> app<span class="token punctuation">,</span> <span class="token class-name">IWebHostEnvironment</span> env<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token range operator">..</span><span class="token punctuation">.</span>Code removed <span class="token keyword">for</span> brevity <span class="token comment">// Hangfire Settings</span> app<span class="token punctuation">.</span><span class="token function">UseHangfireServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">UseHangfireDashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Configure endpoints</span> app<span class="token punctuation">.</span><span class="token function">UseEndpoints</span><span class="token punctuation">(</span>endpoints <span class="token operator">=></span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapControllers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> endpoints<span class="token punctuation">.</span><span class="token function">MapHangfireDashboard</span><span class="token punctuation">(</span><span class="token string">"/hangfire"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DashboardOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Authorization <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>IDashboardAuthorizationFilter<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">RequireAuthorization</span><span class="token punctuation">(</span>HangfirePolicyName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//Register our background job</span> RecurringJob<span class="token punctuation">.</span><span class="token function">AddOrUpdate</span><span class="token punctuation">(</span><span class="token string">"some-id"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Cron<span class="token punctuation">.</span>Minutely<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It is important to remember that we need to pass in an empty list of <code class="language-text">IDashboardAuthorizationFilter</code> because otherwise, Hangfire will block all your requests to the dashboard when deployed (i.e. works only on <code class="language-text">localhost</code> if we don’t override it like this).</p> <p>Now head over to <a href="https://localhost:44317/hangfire"><code class="language-text">https://localhost:44317/hangfire</code></a> you will be asked to login since our new hangfire dashboard endpoint is secured with Azure AD. Once you log in, you will see the dashboard.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABDUlEQVR42qVS2W6EMAzM//9j+0rhYRsg98E0thrwsl1abYNGmoydIbajlmUBwRjzgGVZYa3Fuq4IISDG+COcc5jnGd57qGmaMI4ji7S2bcN/lhqGDwzDAK31bijxlyVzVUoJOWeUUlistTI678mSP9uzYScyIA3rVl8zxPZ4fVnyufxnexVSgM8OLlnY+I1g4JPHu37DaEbmJpojfsdtO+sYVI26fd5AoCmnnBBTZFBvDRlHz7xrMoe1tjfWQM+ab6nIiEBDkaVflXfW6UfkwYZ7v/p30bffngz38KrZLxnmklsfWi9Ke48NSaLpXcsiLvOIx5ZXazmm7KJj0DQP7tq0z5rfY1KzwSLVxIZfvQwAnWplVXIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./hangfire-aspnetcore-3.png" title="" src="/static/3bfc9ed45e360dfd36a1d276cd09c606/5a190/hangfire-aspnetcore-3.png" srcset="/static/3bfc9ed45e360dfd36a1d276cd09c606/772e8/hangfire-aspnetcore-3.png 200w, /static/3bfc9ed45e360dfd36a1d276cd09c606/e17e5/hangfire-aspnetcore-3.png 400w, /static/3bfc9ed45e360dfd36a1d276cd09c606/5a190/hangfire-aspnetcore-3.png 800w, /static/3bfc9ed45e360dfd36a1d276cd09c606/c1b63/hangfire-aspnetcore-3.png 1200w, /static/3bfc9ed45e360dfd36a1d276cd09c606/29007/hangfire-aspnetcore-3.png 1600w, /static/3bfc9ed45e360dfd36a1d276cd09c606/74d4a/hangfire-aspnetcore-3.png 2422w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Step 4 - Requiring a Specific Role</h3> <p>We can also add custom role checks inside our <code class="language-text">AddPolicy()</code> call. This is quite useful and a more secure way to protect your Hangfire Dashboard endpoint rather than granting access all authenticated users of your system.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token range operator">..</span><span class="token punctuation">.</span> services<span class="token punctuation">.</span><span class="token function">AddAuthorization</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Policy to be applied to hangfire endpoint</span> options<span class="token punctuation">.</span><span class="token function">AddPolicy</span><span class="token punctuation">(</span>HangfirePolicyName<span class="token punctuation">,</span> builder <span class="token operator">=></span> <span class="token punctuation">{</span> builder <span class="token punctuation">.</span><span class="token function">AddAuthenticationSchemes</span><span class="token punctuation">(</span>AzureADDefaults<span class="token punctuation">.</span>AuthenticationScheme<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">RequireAuthenticatedUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Your custom role check</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>env<span class="token punctuation">.</span><span class="token function">IsProduction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> builder<span class="token punctuation">.</span><span class="token function">RequireRole</span><span class="token punctuation">(</span><span class="token string">"AdministratorRoleName"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token range operator">..</span><span class="token punctuation">.</span></code></pre></div> <h3>Conclusion</h3> <p>With this approach, you can have a nicely decoupled way of protecting your Hangfire dashboard route. You can also move the authorisation logic to a custom extension method and inject your custom services as opposed to using a Hangfire’s authorisation filter.</p> <p><del>There’s a <a href="https://github.com/HangfireIO/Hangfire/pull/1663">pull request in Hangfire repository</a> to add an extension method to add authorisation policy support by a colleague of mine. Feel free to upvote it 🙂</del></p> <blockquote> <p>💡 UPDATE: The above PR has gone in and the new extension method will be available as part of <a href="https://github.com/HangfireIO/Hangfire/releases/tag/v1.7.24">1.7.24</a> release.</p> </blockquote> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp#option-2-register-and-manually-configure-your-application-and-code-sample">https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp#option-2-register-and-manually-configure-your-application-and-code-sample</a></li> <li><a href="https://docs.hangfire.io/en/latest/configuration/using-dashboard.html">https://docs.hangfire.io/en/latest/configuration/using-dashboard.html</a></li> </ol><![CDATA[Multi-stage Image Builds with Docker]]>https://www.sahansera.dev/multi-stage-image-builds-with-docker/https://www.sahansera.dev/multi-stage-image-builds-with-docker/Sun, 19 Apr 2020 00:00:00 GMT<h3>What and Why Behind Multistage Builds</h3> <p>In a Dockerfile, each statement adds up a new layer to the image. It could be counterproductive if you also build your application package when building your Docker image. This could increase the size of our docker images substantially. That’s when we need to leverage multi-stage builds feature.</p> <p>Simply put, multistage builds are useful when we want to clean up and reduce the image size without keeping unwanted artifacts lying around in our image.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>Let’s understand this through an example.</p> <h3>A Concrete Example</h3> <p>I have created a <a href="https://github.com/sahansera/docker-multi-stage-builds">sample React app</a> and 2 Dockerfiles to demonstrate this, namely, <code class="language-text">Dev.Dockerfile</code> and <code class="language-text">Prod.Dockerfile</code></p> <p>Typical Dockerfile without stages</p> <p><a href="https://github.com/sahansera/docker-multi-stage-builds/blob/master/Dev.Dockerfile">Dev.Dockerfile</a></p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> node:13.13.0-alpine</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> package.json ./</span> <span class="token instruction"><span class="token keyword">RUN</span> npm install --silent</span> <span class="token instruction"><span class="token keyword">RUN</span> npm install [email protected] -g --silent</span> <span class="token instruction"><span class="token keyword">COPY</span> . ./</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"npm"</span>, <span class="token string">"start"</span>]</span></code></pre></div> <p>Let’s build and run this Docker image:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-t</span> multi-stage-demo:dev <span class="token parameter variable">-f</span> Dev.Dockerfile <span class="token builtin class-name">.</span></code></pre></div> <p>In the lightweight version, we use the same base image to build the application, but we are using <code class="language-text">nginx</code> base image to run our application since we already have our application built.</p> <p><a href="https://github.com/sahansera/docker-multi-stage-builds/blob/master/Prod.Dockerfile">Prod.Dockerfile</a></p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token comment"># Stage 1 - the build process</span> <span class="token instruction"><span class="token keyword">FROM</span> node:13.13.0-alpine <span class="token keyword">as</span> build-deps</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> package.json ./</span> <span class="token instruction"><span class="token keyword">RUN</span> npm install --silent</span> <span class="token instruction"><span class="token keyword">RUN</span> npm install [email protected] -g --silent</span> <span class="token instruction"><span class="token keyword">COPY</span> . ./</span> <span class="token instruction"><span class="token keyword">RUN</span> npm run build</span> <span class="token comment"># Stage 2 - the deploy process</span> <span class="token instruction"><span class="token keyword">FROM</span> nginx</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build-deps</span></span> /app/build /usr/share/nginx/html</span> <span class="token instruction"><span class="token keyword">EXPOSE</span> 80</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"nginx"</span>, <span class="token string">"-g"</span>, <span class="token string">"daemon off;"</span>]</span></code></pre></div> <p>Here, you’d notice that we have two <code class="language-text">FROM</code> directives. The first one will be used for the base image for building the application and the second one for the final image. The built binaries are copied over from the first image over to the second and we expose port 80 to the host.</p> <p>Now, let’s give this a go and build it:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-t</span> multi-stage-demo:prod <span class="token parameter variable">-f</span> Prod.Dockerfile <span class="token builtin class-name">.</span></code></pre></div> <p>Time to check our image sizes</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> images <span class="token operator">|</span> <span class="token function">grep</span> multi-stage-demo</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 34%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABGElEQVR42qWO3UrDMBiGE1iEtjYtTCeuc7Zb1VXrdihMWpytwqAgBrrA9OrEixGPnPh3MWs+v4x5oOiBWnh4X940DyHXl6dnucinx6PR5CTL5Pl4LKcSv7KUxUUhi6JY9LIUUgiBWUqBfTg8kkmSyDRNJ5hXWZblhBBK4sB/7kd7EO/sVn4YQqsbwtZmCzpND+J+DIeDATQ9D4JuB3q9CKL9A/D9ANrtbbBtW1NxznXOULhKXMt8XXdcaLju3OJcGdxRrMaUyVZUvb6mTNNS+KOqMaYMw1AMd0rpousdmSOA2xOmQ7C86GF5oL6F/rB/Fj5+CN+oHgipluLfUn194S3ygNwh939A35shNyi0EMKRDaTxT7SHvgPEqHqDI9/hrgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Untitled.png" title="" src="/static/d05c43957685371320d93ce0d194306f/5a190/Untitled.png" srcset="/static/d05c43957685371320d93ce0d194306f/772e8/Untitled.png 200w, /static/d05c43957685371320d93ce0d194306f/e17e5/Untitled.png 400w, /static/d05c43957685371320d93ce0d194306f/5a190/Untitled.png 800w, /static/d05c43957685371320d93ce0d194306f/c1b63/Untitled.png 1200w, /static/d05c43957685371320d93ce0d194306f/29007/Untitled.png 1600w, /static/d05c43957685371320d93ce0d194306f/47f6c/Untitled.png 2206w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If we compare <code class="language-text">dev</code> to <code class="language-text">prod</code> image, that’s a <strong>~73% reduction</strong>! 😍This can also benefit you in reduced deployment times.</p> <p>The Github repo with code is available <a href="https://github.com/sahansera/docker-multi-stage-builds">here</a></p> <h3>Where to From Here?</h3> <p>There are some nice examples and best practices defined in the official Docker documentation.</p> <blockquote> <p>For example, if your build contains several layers, you can order them from the less frequently changed (to ensure the build cache is reusable) to the more frequently changed:</p> <ul> <li>Install tools you need to build your application</li> <li>Install or update library dependencies</li> <li>Generate your application</li> </ul> </blockquote> <p><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/">https://docs.docker.com/develop/develop-images/dockerfile_best-practices/</a></p> <h3>References</h3> <ol> <li> <p><a href="https://docs.docker.com/develop/develop-images/multistage-build/">https://docs.docker.com/develop/develop-images/multistage-build/</a></p> </li> <li> <p><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/">https://docs.docker.com/develop/develop-images/dockerfile_best-practices/</a></p> </li> </ol><![CDATA[Deploying Multi-Container Services on Azure Service Fabric]]>https://www.sahansera.dev/deploying-multi-container-services-on-azure-service-fabric/https://www.sahansera.dev/deploying-multi-container-services-on-azure-service-fabric/Mon, 13 Apr 2020 00:00:00 GMT<p>To give you a bit of context on what we will be looking at today, we will have one goal - to deploy a microservices application to Azure with Service Fabric. My preferred choice of a cloud these days would be Azure since I have been working on it for a while now.</p> <p>To keep things simple, we will be looking at the deployment of a multi-container application rather than worrying too much about what the application does.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3><strong>Prerequisites</strong></h3> <p>If you followed my <a href="https://sahansera.dev/getting-started-with-azure-service-fabric/">previous post</a>, you would already have the below mentioned installed on your developer workstation.</p> <ol> <li>Docker Desktop</li> <li>Azure account</li> <li>Azure Service Fabric SDK and tooling</li> </ol> <h3><strong>Creating a demo app</strong></h3> <p>Now that we have some basic understanding of what Azure Sevice Fabric is capable of, let’s look at what we are going to building today</p> <ol> <li>Create a containersed React app</li> <li>Create a containerised .NET Core service</li> <li>Create a Service Fabric project</li> <li>Wrap our above mentioned service in the Service Fabric project</li> <li>Build a CI/CD pipeline in Azure DevOps</li> </ol> <p>At a glance, this will be the architecture of our demo project.</p> <h3>Scaffolding our multi-container application</h3> <p>You can clone my Github repository to get started.</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/sahansera/deploying-containers-on-service-fabric.git</code></pre></div> <p>The folder structure is as follows:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 58.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAHjRm4qD//EABUQAQEAAAAAAAAAAAAAAAAAABAB/9oACAEBAAEFAmv/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAXEAEBAQEAAAAAAAAAAAAAAAABABEg/9oACAEBAAE/IUdkbHh//9oADAMBAAIAAwAAABCzz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABsQAQACAgMAAAAAAAAAAAAAAAEAESFBEGGB/9oACAEBAAE/EMWWTft8nQw5Hbz/AP/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ProjectStructure.jpg" title="" src="/static/5e180521697f43f878078db4211e7c34/4b190/ProjectStructure.jpg" srcset="/static/5e180521697f43f878078db4211e7c34/e07e9/ProjectStructure.jpg 200w, /static/5e180521697f43f878078db4211e7c34/066f9/ProjectStructure.jpg 400w, /static/5e180521697f43f878078db4211e7c34/4b190/ProjectStructure.jpg 800w, /static/5e180521697f43f878078db4211e7c34/e5166/ProjectStructure.jpg 1200w, /static/5e180521697f43f878078db4211e7c34/99aeb/ProjectStructure.jpg 1284w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>For each project, I have created a Dockerfile. What they basically do is, build each project and expose a port from their respective containers.</p> <p><em>Web - Dockerfile</em></p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token comment"># Stage 1 - the build process</span> <span class="token instruction"><span class="token keyword">FROM</span> node <span class="token keyword">as</span> build-deps</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /usr/src/app</span> <span class="token instruction"><span class="token keyword">COPY</span> web-client/package.json web-client/yarn.lock ./</span> <span class="token instruction"><span class="token keyword">RUN</span> yarn --no-bin-links</span> <span class="token instruction"><span class="token keyword">COPY</span> ./web-client/. ./</span> <span class="token instruction"><span class="token keyword">RUN</span> yarn build</span> <span class="token comment"># Stage 2 - the deploy process</span> <span class="token instruction"><span class="token keyword">FROM</span> nginx</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build-deps</span></span> /usr/src/app/build /usr/share/nginx/html</span> <span class="token instruction"><span class="token keyword">EXPOSE</span> 80</span> <span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">"nginx"</span>, <span class="token string">"-g"</span>, <span class="token string">"daemon off;"</span>]</span></code></pre></div> <p>The reason for having stages here is because we don’t want to include the source code in the container since we only need to copy the executable or the build output over to the container.</p> <p><em>ProductCatalog - Dockerfile</em></p> <p>For the .NET Core project, we have a bit more involved since we need to have the .NET Core 3.1 SDK to build the project and .NET Core 3.1 runtime to run the project.</p> <div class="gatsby-highlight" data-language="docker"><pre class="language-docker"><code class="language-docker"><span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/core/sdk:3.1 <span class="token keyword">AS</span> build-env</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token comment"># Copy csproj and restore as distinct layers</span> <span class="token instruction"><span class="token keyword">COPY</span> ./*.sln .</span> <span class="token instruction"><span class="token keyword">COPY</span> ./ProductCatalog/*.csproj ./ProductCatalog/</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet restore</span> <span class="token comment"># Copy everything else and build</span> <span class="token instruction"><span class="token keyword">COPY</span> ./ProductCatalog/. ./ProductCatalog</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app/ProductCatalog</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet publish -c Release -o app</span> <span class="token comment"># Build runtime image</span> <span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/core/aspnet:3.1 <span class="token keyword">AS</span> runtime</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build-env</span></span> /app/ProductCatalog/app .</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"dotnet"</span>, <span class="token string">"ProductCatalog.dll"</span>]</span></code></pre></div> <h3>Building and tagging images</h3> <p>We can run the following command to test out our images locally.</p> <p>If you haven’t used Docker Hub before. Here’s a <a href="https://ropenscilabs.github.io/r-docker-tutorial/04-Dockerhub.html">quick intro</a> to creating a repository and pushing images.</p> <p>In a terminal, navigate to <code class="language-text">ProductCatalog/</code> folder and run the following command</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-f</span> <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> <span class="token operator">&lt;</span>your_docker_hub_username<span class="token operator">></span>/service-fab:product-catalog-img</code></pre></div> <p>Now change directory to <code class="language-text">Web/</code> folder, let’s build and tag backend service’s Docker image</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-f</span> <span class="token builtin class-name">.</span> <span class="token operator">&lt;</span>your_docker_hub_username<span class="token operator">></span>/service-fab:web-shop-img</code></pre></div> <h3>Pushing the images to Docker Hub</h3> <p>In order for Service Fabric to fetch and build our container images, we need to first push them to a container registry. I’m using Docker Hub here as an example. But you can use any other registry such as GCR, ACR etc. These repositories can be either public or private.</p> <p>Make sure you are already logged in to Docker service from the command line,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> login <span class="token parameter variable">--username</span><span class="token operator">=</span>yourhubusername <span class="token parameter variable">--email</span><span class="token operator">=</span>[email protected]</code></pre></div> <p>Check the image ID using,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> images</code></pre></div> <p>This will come in handy in the following command.</p> <p>Now let’s push the images with,</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> push <span class="token operator">&lt;</span>image_name<span class="token operator">></span></code></pre></div> <p>We need to run this command twice for both frontend and backend images we created.</p> <h3>Configuring Service Fabric project</h3> <p>Let’s open up <code class="language-text">WebShop</code> solution in Visual Studio. You can configure the credentials in your Service Fabric project.</p> <p><strong>Frontend configuration</strong> <em>WebShopClientPkg/ServiceManifest.xml</em></p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml"><span class="token comment">&lt;!-- Code package is your service executable. --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CodePackage</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Code<span class="token punctuation">"</span></span> <span class="token attr-name">Version</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1.0.0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EntryPoint</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Follow this link for more information about deploying Windows containers to Service Fabric: https://aka.ms/sfguestcontainers --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ContainerHost</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ImageName</span><span class="token punctuation">></span></span>**index.docker.io/sahan/service-fab:web-shop-img**<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ImageName</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ContainerHost</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>EntryPoint</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Pass environment variables to your container: --></span> <span class="token comment">&lt;!-- &lt;EnvironmentVariables> &lt;EnvironmentVariable Name="VariableName" Value="VariableValue"/> &lt;/EnvironmentVariables> --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>CodePackage</span><span class="token punctuation">></span></span></code></pre></div> <p><strong>Backend service configuration</strong> <em>WebShopApiPkg/ServiceManifest.xml</em></p> <div class="gatsby-highlight" data-language="xml"><pre class="language-xml"><code class="language-xml"><span class="token comment">&lt;!-- Code package is your service executable. --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CodePackage</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Code<span class="token punctuation">"</span></span> <span class="token attr-name">Version</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1.0.0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EntryPoint</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Follow this link for more information about deploying Windows containers to Service Fabric: https://aka.ms/sfguestcontainers --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ContainerHost</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ImageName</span><span class="token punctuation">></span></span>**index.docker.io/sahan/service-fab:product-catalog-img**<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ImageName</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ContainerHost</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>EntryPoint</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Pass environment variables to your container: --></span> <span class="token comment">&lt;!-- &lt;EnvironmentVariables> &lt;EnvironmentVariable Name="VariableName" Value="VariableValue"/> &lt;/EnvironmentVariables> --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>CodePackage</span><span class="token punctuation">></span></span></code></pre></div> <p>Note how I have replaced the path to the Docker Hub registry in both configs.</p> <h3>Creating a Service Fabric cluster</h3> <p>Before deploying our services, we need to create a Service Fabric cluster in Azure. In the Azure portal, just type in <code class="language-text">service fabric</code> and click on <code class="language-text">Create service fabric cluster</code>.</p> <p>Now, fill in the cluster info as shown below.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 116.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC9ElEQVR42pVUy04bQRDkGCmR9zWPfdpeLzY26+AAQtiEQ4A7QiBOnMinJR9a6Wqzxs5Dcg6lmd2Zqanu6p6DLMuwWCwU8/lcwXnbtjg9PYVzDsYYDIdDfP16hbqu9TuOow2SJEaapijyHAdlWeDq6grX19dYrVaK5XKpI/8TvJTE/aqPQX+AKi9gjYVJzAZJFMsei4OiKPD4+IjX11e8vLzg+fkZT09Pivv7ezw8PKAsS3jjcXm+xGL+BYXL5TuFS7zCxk5GB+88DnKReXFxoUru7u5we3uLm5sbBVVeXi6RSThxHKsSKhvVDZp6hHpYY9yMZRypcu+FkPlIkkRzEIYhoijSw5wHQaDfQdBD5jO0M8nx8We00xbDUghcur4ksQJRad2akMz9fl8xGAzUACafeVuTB0idEE7nOGlPMJscK3GV92EiqyEzBRqytXaj6l+g0jzPMJ0eYTIeoxmNcHh4qG53UYVBCGPNu0IqokEMnf94EVNBBKIwE4Vni3PUVQ0vRlCZoxlv8J0p3UGS0CCWCEFyhsw1qkglX3U1QpWWyGymrjLUDnpBl0OSkmA7RIJEmxzaFMdHrZAOYWK7gyQyKIsSqU93Q6YhVVUpOUGl7wplz2CkaAS5XECijlRrkQqpjgc6NUSXO6JTyMI+rMcYSg7puDMsaKswQlypwuydkAfpVofOvXUdiilpJg4faSGnhuVEdWan/ey2wm2y38F19ulkMsHseIbZbKqPR9OMxMxEwXXv9yRUhZLP6Wym9VcP5MURNYm0YSzKCM6t21ehtJ7LKkwWKxRNCytzk1Y6dvDFED7N9w05kNs98sERXNpHr9dDIAjZ6zxLU2VupWL2DjmV0jo/+4KTeavtt85how3RlZ6+Nvuaot0ke93bQbYox+3yItf+hFqPkWK7k7b3/D+hmBOGPfkX/nXPfxHyoXDiLN02Utgm4QuebMALPcuGyU7i3cUdSH1FvY8opyucvf7A+fefKNtvCD59QBQnfyj8BYX3UnEsGYQlAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric.png" title="" src="/static/f8e46bfa1ce3dd6705ae079b26298806/5a190/service-fabric.png" srcset="/static/f8e46bfa1ce3dd6705ae079b26298806/772e8/service-fabric.png 200w, /static/f8e46bfa1ce3dd6705ae079b26298806/e17e5/service-fabric.png 400w, /static/f8e46bfa1ce3dd6705ae079b26298806/5a190/service-fabric.png 800w, /static/f8e46bfa1ce3dd6705ae079b26298806/c1b63/service-fabric.png 1200w, /static/f8e46bfa1ce3dd6705ae079b26298806/b30aa/service-fabric.png 1405w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In the cluster configuration step, you will be asked to set the Node type count. Just set it to 2 to denote frontend and backend services. In the same page, you will be asked how many nodes you want to create for each node type. Select <em>Single node cluster,</em> for now, otherwise, you will be charged for the total number of VMs. However, this is not recommended for production-grade apps.</p> <p>In order to expose our frontend and backend services let’s enter endpoints as port <code class="language-text">80</code> and <code class="language-text">81</code> respectively.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 84%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACzElEQVR42p1UW3PSYBDlnZKQ+4WE3CAkEEgITHF06tSngrTCWG2FPvgHnOmDv8cX/+hx92upttU++HBmYb98Z8/unqThh32ESYawVyCIM8IAlpfA9nuIejkhQ39YwwsH8D0fURQhyzKkaQrP6yAIAvi+D8MwYJomGnEUot9LEMcRIUaSxHSowbIMjIsR8myAYjRE1/fQarWgKApc18V4MsGoKJANMvT6fRimBdu20dA0TVSQZRnNZhNHR0eQZIlITZR0aSCUeAhDLtyDSUosx8VwXMEPYmiGDUW3KDrQLCKcTqc4OTnBYrHA8fGxiAVVZsJJVaOYTDEh4nxUIE6H0DSd1FuibdshEk0VORZmcMvL5RK73Q5XV1fY7Xe4vLwUBRRVheOFpKYLnR6WqIOWJItOLLqY0Hg6nQ4cIuUCDNEyJ7rdrpgL/+bIlZiEB59mOZ374gKfs3JuuxiNMBwOxTgOCxFLYanMfEjoui5UuHSZ1V5fXwv12+0Wm80G8/mcipHCJCUktPX4QQwXbTAJS+c/TMbgTXKsab7TqiIlOfI8Ey5waCH9JMCH5SnOzpY4Pz/Her3GarVCj5YmFB5mwOACKs2PEYQRwiihmFBRT+RakoIqC3D75TW2Hz/h5uYG+/0eFxcXwqMNVtNutx+BcwyV7KCaHWgElTbJhHzuEnlZTTGrZ8IZs9mMLFaKThuHy39H+w/8znNXuqE/FJckSUQxw5cJn4NJbMtBGqcI2Fa6DV3RRbRN+z8ISa1J1olC8qHTITd0RNRUTSzyBUJVmFvEp2cquYBfwXu7cauHUYgt/wtqW4Z6/+BTKPS+K3yu3hXkyAZv8MfgOZo0aHrYSdDWXbQox1+aOxxBVqiY24OsWnTWFHm+xy+E+Dg8Ro2qnGD+6i1W337g9PN3skSBimxS0Xk5HmHxboP3tz/xZv0VkyKnfI2yLFHXNX4BdP/KgSGMO1IAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-1.png" title="" src="/static/d19b46189464ad5deb38fff07e86a3a5/5a190/service-fabric-1.png" srcset="/static/d19b46189464ad5deb38fff07e86a3a5/772e8/service-fabric-1.png 200w, /static/d19b46189464ad5deb38fff07e86a3a5/e17e5/service-fabric-1.png 400w, /static/d19b46189464ad5deb38fff07e86a3a5/5a190/service-fabric-1.png 800w, /static/d19b46189464ad5deb38fff07e86a3a5/c1b63/service-fabric-1.png 1200w, /static/d19b46189464ad5deb38fff07e86a3a5/29007/service-fabric-1.png 1600w, /static/d19b46189464ad5deb38fff07e86a3a5/7232c/service-fabric-1.png 2063w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Next up, we need to create a key vault for our service fabric cluster. This is a very important step and make sure to select the <code class="language-text">Azure Virtual Machines for deployment</code> checkbox. You will also need to enter a name for the certificate. We will cover this in a bit. Give it some time to create the key vault and proceed to the next step.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaklEQVR42pVSy27CMBDMsZC0edhOnJDEeZGEkh5QoUKoiANw7C/0A/r/96kdZEBQED2M1l7tzu6MbYi8BvVD+DwGHwnkZY04zUEYBwsiVOMxirKC6xG4riejB+8OjN12i/X6E/P5O5I4RiEK+DQA9SgIYQhjAVHIITIfjmIQSuESchNGkiRQoLKQUYZcZAjDEEHAQZkvY4A0TSBEijzLEEWR3NS9CcO2bWgwP0BVTxD4fj+AEArOQ8RycyVHNTiOc8QlWS9ZX1SBJmCM9fCkhCgRGEsfFanalnPeK1BRDT33jyjJmlBtqIpWqxW6rsObRJbnfV5PP2/U5yvJ54Rq8n6/x2azwWKx6F9YEz4K49KDw4MEBxtc719kV4Tqn9mOezJcSdL/71FC0zRxwhDm4Ol4t55fYA4HsGTesqy70D1G27Zo2wmaeoxutsTy6weT1ynapkFVlZjtvjH92KHM097Tqqr+RCPrFdcvh9AWgVACf2UAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-2.png" title="" src="/static/578213ce8e749222afb5d68cb9405598/5a190/service-fabric-2.png" srcset="/static/578213ce8e749222afb5d68cb9405598/772e8/service-fabric-2.png 200w, /static/578213ce8e749222afb5d68cb9405598/e17e5/service-fabric-2.png 400w, /static/578213ce8e749222afb5d68cb9405598/5a190/service-fabric-2.png 800w, /static/578213ce8e749222afb5d68cb9405598/c1b63/service-fabric-2.png 1200w, /static/578213ce8e749222afb5d68cb9405598/29007/service-fabric-2.png 1600w, /static/578213ce8e749222afb5d68cb9405598/81471/service-fabric-2.png 3306w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 88.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACL0lEQVR42tWUS4/TMBSFs2GQJmlix3GaOK+2Sd8lfdF2g2AWiEFCAlSqsmCF+nP40QdfF6rp0I5gyeLTTeLk+J7jq1jtdhuTyQTj8dhUoixLBFKiRWv1FJFKwXwBEcirBDJEFCtYURQhz3NQlVpEhiFkIMCDCDeDr2iuvyNZf4M9/IJn1acHfMRN9/MZzzVWEARQSiFJEjQaDXieB845mEYVfaTtPkSYgMsEXqAgmhlkXBjo/jFGkCy2Wi1kWYY4juELH4x56JYdLBdzJCpGw7HRKnIs5jMUeaZJ4XMGrt97iEU2yTIJkTjh+0fBsqpMpqZjxo0Tytx1Xc3RyWMs+pi6I0GyTLieq627uvMO5vO5WXMcxzio9CaMMbPpRUESWy6XqOsavV7PMBwOzSFRN6vVCv3+AEVRaCc6N4+ZDkn0ouBms8HhcMB+v8dut8N2uzWVhElwvV5jNBqZQ0u05fF4ZNayNLvYpUU2qKPpdHqCBJrNJjKd7aSeYTh+AY8LxCrDfPESRbsEF1JPgjiDaywaE8rHtu0TjtMA9xw4ago2+IB08g5qco+sfg/RvQPrvIZf3Rl4+cY8E723sKPR8VAuo7MKcvC0RlwtEHUWUNUSbjzCrezBDvtn3IYDNGQB61KwhO9zM2fM08PuOic4c/Vz7ypPC/p0rYM34f+u/En+QvDf+K8F/dPBXJ+EP7k8NkKYOSxm96h3P5AOXyEIY0jVRhDnurb0TzX85eJc8Cch7M7rPDmUjQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-3.png" title="" src="/static/66a929672c9d2a1993f9b5712187ff24/5a190/service-fabric-3.png" srcset="/static/66a929672c9d2a1993f9b5712187ff24/772e8/service-fabric-3.png 200w, /static/66a929672c9d2a1993f9b5712187ff24/e17e5/service-fabric-3.png 400w, /static/66a929672c9d2a1993f9b5712187ff24/5a190/service-fabric-3.png 800w, /static/66a929672c9d2a1993f9b5712187ff24/d1d24/service-fabric-3.png 987w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once it’s up, you will see you cluster like so,</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABOklEQVR42n1Ry3KDMAzk3HQAx2+DzcOYAkmbTHpqO9N7//+Huo4bD6dqNELSSmtJFKFr3i7Xrh+OTHBlobIZmGxrKglT/ygVprje3k+vl2VdxuUmzz9k+j70X8/d58F9ZH3a6T5f+PAyh6CU4lxY11MmCDlmIYRwzv00GdMwzqMyltGibRptjNYa2RBmqXSNjjqaJJTStkWVSSKEyFAxzpvRqqwqFPVjkO0olUIF2usHRVVV8GHLsqzv8tc8z7O1Fh6jdD2dVWMFZ845ZMxjIlgMCdt1HUIMIqWMzX7wfvJYDLBzNp6dMhAhZPFLIy9j5H6JVJbycedu9OBPu23bNoaFMo6lgWE8HBLvJAeDwMGboEhcxTAM67oCQAxAa4WD5q3AmJ9NU+S/EMfmQuKSCagfku+5D/f51PwL2SA2eOQbgXsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-4.png" title="" src="/static/4e32146d33d31913ba191ce1bfc6d49c/5a190/service-fabric-4.png" srcset="/static/4e32146d33d31913ba191ce1bfc6d49c/772e8/service-fabric-4.png 200w, /static/4e32146d33d31913ba191ce1bfc6d49c/e17e5/service-fabric-4.png 400w, /static/4e32146d33d31913ba191ce1bfc6d49c/5a190/service-fabric-4.png 800w, /static/4e32146d33d31913ba191ce1bfc6d49c/c1b63/service-fabric-4.png 1200w, /static/4e32146d33d31913ba191ce1bfc6d49c/29007/service-fabric-4.png 1600w, /static/4e32146d33d31913ba191ce1bfc6d49c/86a2d/service-fabric-4.png 3027w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>It may take up to 15 minutes until your VMs start to appear under the Nodes list.</p> <h3>Installing client certificate</h3> <p>In order to be able to access the Service Fabric dashboard, you need to install the client certificate from the key vault.</p> <ol> <li>Head over to Azure Portal → ’Key Vault’ → <em>Select the key vault we created in the previous step’s Certificates</em></li> <li>Select the certificate (there should be only 1 at this stage)</li> <li>Click <em>Download PFX/PEM format</em></li> </ol> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 34.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVR42o2RzW6DMBCEeQAgQCBJsZGBEKQoUgKUnwAXDsm91176Bn3/49RjNVUrtVIPo0X2zrezxnq9D3h/e0HXX3G/3bAsC/quw9APGMcJ4zSaOk2zriOkFLBtG6vVCq7rwnEcUz3PQxAEsJR4gkok4jiGSlMIEWMdRoiFRJIkkEJAKaVBEmma4XKp0DQN6rpGpwf3fY+2bXE4HOD7PiyhG7M81yCB3W5ngME6gpBKNxUakiLLMhT7PfKi1KBnzPOE6zAYMfWkdTweTUqLk8/nM/bawDUYm+DtdoswDLHZbMxk3tHwtaaW803ep9eiuSxLY+ZFFEVmQFVVOJ1O5pvPQaDvBwZOI+tvstjMuEzKNBzAFZmY6+b6OXjOdH9BfgDZTCMfvigKk+bx5x76L4zADy4SuxGNN40yAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-5.png" title="" src="/static/9aa6725ce0e4ae1f8f421f50b79b571a/5a190/service-fabric-5.png" srcset="/static/9aa6725ce0e4ae1f8f421f50b79b571a/772e8/service-fabric-5.png 200w, /static/9aa6725ce0e4ae1f8f421f50b79b571a/e17e5/service-fabric-5.png 400w, /static/9aa6725ce0e4ae1f8f421f50b79b571a/5a190/service-fabric-5.png 800w, /static/9aa6725ce0e4ae1f8f421f50b79b571a/c1b63/service-fabric-5.png 1200w, /static/9aa6725ce0e4ae1f8f421f50b79b571a/ea964/service-fabric-5.png 1312w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once downloaded double click the certificate. You don’t need to enter anything in the Password field and click Next and complete the wizard.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 96%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACnklEQVR42p2UW2/TQBCF80sRhRcovFFa8ct4RaggnpAapKS0iZPmbjuJb7u+24cz69ppVCkgonza9Xp3di5n3Lv8MMT784Hh3dshuX0cyZvBP3NOPl0O0bu8+IWrq59c+I6XL77i1dk1Xp99I9dc6/Oiw4WnEIMfLwbo/b6NMB7HsMh4pDgqjMYa93cRRvecj3TDvT6ek/Eo7rDGCW6HIXpJUgKo4Ac7aB0gjPaIY4X/+cVxjl4cF0AN7PceLGtCDyzMF0u47ha27RhcZ4vVak02sDc2lss1NhsHRVGiLCtDVdVQKhMPC2M9zwtEUdQQRgjDEAHx/QBKa2RZ9oyyLDvquoKOEnqoU2OwyHMe9uF5nkHmPsf9fodYx6djrZtBB+pgME0zE1IYafO+ZAj8G2SeM6yWspIQD4iHB4MqaTzkou0rhCySZhZ0XiPKKkRpCZVWUFlDyHlS1McO1s2zDnVb5WbR3fl4WKwwX26wWNlI8gpp8RzxWPYfaKtc0CBLLb8kSbDdbllRhznT6HadSt2jwaJoCqvcnRhsHkpKQCQyHAwxm83InPJZYD5fULQWHMelXFaYTqaYWFMjI5HalvKSPD4JuehuCymXh4cZVjw4ny+N4el0Sn1aWDwan1Cr1mSC9Xpj5KUi1RUlTopjg57nsw0tHqAXRDyWAyKbIAjYSdo8R0TmvpFXQE3mh07pDBqlK+bQNUiIrS7FsCMd47omzxKm7LFt23RIK8Yjg5IHUxQektxI6+22Oxp2jDcSliRfyNkE7dgW5pmHYlAOSsu17WdaMAi7kE5VuzMYBE2fpmlqbmx56kn7viVOUnis6D7ghToxqVIqYmT8fNn2jrnyuh7+GwH3Ll0PX24sfP4xwI214bfzDv1+n4pY4A/blIgFGTgz1AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-6.png" title="" src="/static/aeb115e4dd6cd5c6f09e5b4d6dbc780a/5a190/service-fabric-6.png" srcset="/static/aeb115e4dd6cd5c6f09e5b4d6dbc780a/772e8/service-fabric-6.png 200w, /static/aeb115e4dd6cd5c6f09e5b4d6dbc780a/e17e5/service-fabric-6.png 400w, /static/aeb115e4dd6cd5c6f09e5b4d6dbc780a/5a190/service-fabric-6.png 800w, /static/aeb115e4dd6cd5c6f09e5b4d6dbc780a/302a4/service-fabric-6.png 1080w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now, you should be able to access the dashboard of your cluster. The link will be in Service Fabric home page in Azure Portal.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB70lEQVR42m2STW/TQBCGfWkUkkohSBDHbmLHH7v+Wttxk8ZOcZqSplVP3BEnKvU/AAKEmkMTRBES//dlvAHaoB4ezczu7Lvveqzob1bQz96h13kBvavi+bM22u2nRButVgvNRkPS+MOTeh212h5qe/fUaa3ZaGJ/vwklzGcIigX86SVYfgErKeCLFLZtQ8QxTs+WWJxfUDyX8eTVAtPyBEU5Qzk/xfFsjqPiJfLjEhH1K2L+GozE+tkc5ngJQ+SwbAddcpvlU3xYbfBxtcb7m1uKG6rX+HT7Hau7X/j67Se+bH7g8/oON1S/vbqG4jMbnjMAt00JIybFGFEk4JDLA13bQde6sEwTHmfo93qy3qLCNAwojHE8xHUdZIcpPC+g3AXnfAfGGMIwhBCCerz7PcoZRaVqeIhNz+UeRbeHnnSgQ9f1ba5vc6Pf/1dXsULTNMmOIKdnWJaNZBgiKxyIkSnd+L5P6xbCIEAQ0F4aIUliBJXTgCNNE3Q6Kn337v+CXB5MhzF8aozHBzgcpRiNjjAYDOA4DuGSsCcvr54sRCgvVdVHBCsc28UwC1EuA2RjHwH3MaQL4ljQoCI5rCwViOkbTiYTEovkuUrscUFywckFo4FUrhhN2vNpj7swabqmYcKhP8OyLdlr0GQr/gr+BkB4LKJ6L1/XAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-7.png" title="" src="/static/7f00ac47d60afac3df37c46aa5f95437/5a190/service-fabric-7.png" srcset="/static/7f00ac47d60afac3df37c46aa5f95437/772e8/service-fabric-7.png 200w, /static/7f00ac47d60afac3df37c46aa5f95437/e17e5/service-fabric-7.png 400w, /static/7f00ac47d60afac3df37c46aa5f95437/5a190/service-fabric-7.png 800w, /static/7f00ac47d60afac3df37c46aa5f95437/c1b63/service-fabric-7.png 1200w, /static/7f00ac47d60afac3df37c46aa5f95437/29007/service-fabric-7.png 1600w, /static/7f00ac47d60afac3df37c46aa5f95437/02607/service-fabric-7.png 3338w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>Deploying to Azure</h3> <p>The fast and the quickest way to deploy your app is by using Visual Studio. Just right click on the service fabric project and select <code class="language-text">Publish</code></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACcklEQVR42m2SW08TQRiG9zeI6WFP7R672+0e29J2KWW3FChKIhCrMQrBYIkWTwkX6o0X/gMVb/yzr/ONttDKxZOZzc488858n+D7Ee4kiNGJE4TH76C/+Q3z4if06TXWTn9w7p18/webs+/q9Bfk82sIuq7jLjRNR6uXwM8O4IzPsP54hs5kBnv8Es7+OaLjS4RHMwSTKxjjVzBHLxinEBRFwV3IsoyN0QCDbgeDVox+EjECZEkDWexhK/LQq5vo+S7ajoG0YWOv24RQqVT+k6mqCklW8OjhA3QPL5AczpA+vUTv5BOCs2/Qn3yBNvkM5/lXKEdXKOy/RengA+MjBIkl8eoe4jhm7xYiDCNYlsWFjm1BDfuoBim83jaa+89gHb7H/dEUxd0LlMevUWBjYWfOFEKj4aPRCGAYJkunsnQVPkqSjLIoQSoWIJUKKK6tQS0XkXcS7KQt7G60Edc01JQiHLW0QKiyx49ZNR3HZQKRiSQuI2SWUpJVNtIBCnTDwPZoF4NsCD+MYVgOTNtdQhBL5RthWeQikSVbhf7JqoZu2keW58iyDM1mC7Ztsye6QTD9BP3+JtI0ZT9rXEzU2bvexnXrCHwfw+EQeT5kwpy/+20ZF7rtDj8pSZp8QRRF8DyP9aKxhKYZsC0Tg81NHiCKYr6eQiwJdbeGgFW31WovxLqm8SuuXrla1bC1lWO0PcLe3pjNM57cNK0bYYVVlSpNUmof2kR9SJWmoswhKXVCzq6aphuIo7/pbsu40PcD3nskpDlBBximuaj0XEib2+31Ra9aK9flQlroOA5PJFLbiMttM4e+KSF1BKWjPavpSPgHySetbdzheIkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-8.png" title="" src="/static/34dee6de31af92d1c6df2d909d945a26/5a190/service-fabric-8.png" srcset="/static/34dee6de31af92d1c6df2d909d945a26/772e8/service-fabric-8.png 200w, /static/34dee6de31af92d1c6df2d909d945a26/e17e5/service-fabric-8.png 400w, /static/34dee6de31af92d1c6df2d909d945a26/5a190/service-fabric-8.png 800w, /static/34dee6de31af92d1c6df2d909d945a26/b7936/service-fabric-8.png 1155w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 74%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACAUlEQVR42o2S227TMBiA8+IcBjdoE4hH4B2Y2GhLqzGxCjRKsjaJEydOnJMPceykSdpdAMKhgKqxCj59sn5FcvyfjKdH5ssX6ckx0j4/QcfPwqNH8F/6Tx6HDx9YxvW1aduu5wVFQQlhZVn9j0o1CCWGad7EoRf4IAhgHEdlyYUotTo4JOeMc+r70EhxYuMqzKu2qaVS8jdKqfo+mqYRQjDGIAyNIolfzaLXV95tV1dSVVJqRTVQH6brujTNjTCMBM2QD+IwzFPcNXVbK60O+nWj7Rp1R51X266TJDOCMJo74dnH5dT0Zpb/fhXsvLTRxXII5jCf+9kvYf7BxRmhXddinA0vmxDPFo4V5ZApNxcekfoERLmF9HiTdN9w+3Vn2n8HfF0w3vcdxqmBEE7SzHEBYZyVusuioKwSYjMkXA9VKPnHrlYVp7rfOu3hMsQkpQKmFGACcBFkDFGZ80pWO+Suhfs2zXqz6ZOcDy97wDUXn28s0zK/OLad4ujnPO9HTxkhBABwHE/XHJOi8ACA0Ie+HwQBIVQcYLgtREFInudBgIwQxVnVgkz4hXQT7mfl/qrsoz+vmwE95+12M8xZLwplTP+MMb7Zbm8Hdudd2n6b0ZKUumylN2xo2Onp2Wg0Pj8fTSZT07RWK3u5XP2t5tPCejOavp1eXlxejceT0ejdD1OoFkUnNBHQAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-9.png" title="" src="/static/7c34c5c93dacbed146fea00a57a83407/5a190/service-fabric-9.png" srcset="/static/7c34c5c93dacbed146fea00a57a83407/772e8/service-fabric-9.png 200w, /static/7c34c5c93dacbed146fea00a57a83407/e17e5/service-fabric-9.png 400w, /static/7c34c5c93dacbed146fea00a57a83407/5a190/service-fabric-9.png 800w, /static/7c34c5c93dacbed146fea00a57a83407/c1b63/service-fabric-9.png 1200w, /static/7c34c5c93dacbed146fea00a57a83407/bc3ae/service-fabric-9.png 1268w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>After about 5 minutes you should be able to see that our services have got deploy to the cluster. You can check that from the following URL:</p> <p><code class="language-text">http://&lt;yourclustername>.&lt;yourclusterregion>.cloudapp.azure.com/</code></p> <p>Or, you can get the URL from service fabric portal and remember to delete the port number and revert it to <code class="language-text">http://</code></p> <p>Our frontend running at port 80</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 91.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKUlEQVR42p2UXU8TQRSG+1uQ7kdnP7pddtu1a7tloS3t6lYqBAxJhUQIkiBoJBg0kIgJfsQYSfTCG028MQYuvDExeusf8Ae9nhkaY8GlLRcnpzM7efqe95yZlKKNYZBQdReq4fY9lzoXYrjdyEOSdMiyAUV3Lgp0ICumAEkEsuptZKvTkJTsudBUIixjwRy/BqvWhh0vwF99iNLdPbB8gPSlTCL0LJAOckVmJUKw9Qz5m6uIP/2EO78Cr7NOewcw6NtQQJnK0v0qvFsbcG4sovHmMyrbLzB99AuljT0Ub2+hsLAm7DgNTvSQl9x8e0zqfiC4/xTxx+9CXWnzCdy5ZTgzS5DSgwB5yfTPV9Z3kZ1oIXz8mtQ9J/gRyvf2Mfvtt8ijI+p/xyjRQ//ODgrkWf3lB0TvvyLcPUTj8Au04iT8tZ1hu2yLbAZNjD96RUpjKnUfzXfH8BY3ETw4IEtyQ84hKR0dUWCGseh2cWVbjIxRnkImdxnKIECZ2b0h5jEnFDK3LKyQVRpsZvdUkgjMkMnMzFN2uplfOwcqdZxD+Fp0lUKlEN+7v3uAfCOtWvCDGqJ4FkHYQGUiwsxch3ITk/VY7NUaLVSnWmLN9+tRW5w3cp6o6IxCDmame6KE1hqpZGYBzPKgkW+MQssW/qrnma8H8lCMD90Yk/wbu96BfXWebs0SmBdCompkdnJeOtWcVN93kJ4ulVQq3C+edffi76EIrvbf6PPA/gG99FCt4an9OgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-10.png" title="" src="/static/2f8ad0935573fcd5280c1aa29b23f4b8/5a190/service-fabric-10.png" srcset="/static/2f8ad0935573fcd5280c1aa29b23f4b8/772e8/service-fabric-10.png 200w, /static/2f8ad0935573fcd5280c1aa29b23f4b8/e17e5/service-fabric-10.png 400w, /static/2f8ad0935573fcd5280c1aa29b23f4b8/5a190/service-fabric-10.png 800w, /static/2f8ad0935573fcd5280c1aa29b23f4b8/c1b63/service-fabric-10.png 1200w, /static/2f8ad0935573fcd5280c1aa29b23f4b8/f2331/service-fabric-10.png 1427w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Our backend service running at port 81</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 71.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABp0lEQVR42pVT23KjMAzlj5tekrSTJrs/2T41Xdq0M5tAwAZjfOWsZGiedpuNZs7IMuZYR5KzxfIBy4cV5ot73M2XmM/vsaD1ZvMTj48brNc/Tp6xWq2xpH/4DOP65g6z2Q1m17e4Ip955xBihGy7hFq2ULqHdf4EYx28DzDGwVHsQ4TjmPbtFDOGYUDWti0dcilgRPaRfBxOe0NaI+2NiAkhBMK4jpPPmqZJH5jUe0+EfGNPMHDJu+SNU7BeU2zxnSVCtrIssX3ZYveZ4/dhByUraCOJpIO2Eo0uoEyNnoi/JWTJLIuJX7db7PcHiIbIlKCsNS61TAhB+kfJutdjXSLLZMn2csK6rqcMJUQtqMM1hDokuR1J1LZJcr/Q0f5ow98JpRwPSMo0z3/h42OHY1XCqI6y5e5z9wI16wvxvGTOUCmFtzwHx01XwprL6zcSkmSum7V2GpswjYtNCNHTEI8jxJ7H6L8ylEKiOBT0UgocxR6qrVINef5UX53Qu/Z8U9iEqPH89IS393ccjwVcq9NFF0vmpvCT0VqjqiqSbtIwnyv+vwj/ACzxLAhh9U//AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="service-fabric-11.png" title="" src="/static/c7724ce7f9a9c5fe85594a8da7bd4f3c/5a190/service-fabric-11.png" srcset="/static/c7724ce7f9a9c5fe85594a8da7bd4f3c/772e8/service-fabric-11.png 200w, /static/c7724ce7f9a9c5fe85594a8da7bd4f3c/e17e5/service-fabric-11.png 400w, /static/c7724ce7f9a9c5fe85594a8da7bd4f3c/5a190/service-fabric-11.png 800w, /static/c7724ce7f9a9c5fe85594a8da7bd4f3c/c1b63/service-fabric-11.png 1200w, /static/c7724ce7f9a9c5fe85594a8da7bd4f3c/29007/service-fabric-11.png 1600w, /static/c7724ce7f9a9c5fe85594a8da7bd4f3c/5ba90/service-fabric-11.png 1604w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In my next post, we will look at how to set up an Azure DevOps build pipeline to deploy your services to a designated Service Fabric cluster. ✌</p> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-overview">https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-overview</a></li> </ol><![CDATA[Setting up your Raspberry PI 4 - Headless mode]]>https://www.sahansera.dev/setting-up-raspberry-pi-4-headless-mode/https://www.sahansera.dev/setting-up-raspberry-pi-4-headless-mode/Tue, 31 Mar 2020 00:00:00 GMT<p>When I first got my Raspberry PI 4 (<em>RPI</em> for the rest of the article), I was so excited to get started. However, there was one problem. How do I get started? To make matters worse it only had 2 micro-HDMI ports and I couldn’t connect it to a monitor. After a while I found myself wading through link after link.</p> <p>I am going to show you what worked best for me and a recipe you can also use to get started with your RPI 4 quickly - without an external monitor 🙂</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h3>Specs</h3> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 583px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 58.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAADGUlEQVR42mNISkqyjI+PF0hLS+MqL0/jXxW6ipmhW4978kK/jHkTmrt+Pr+j8///f0YGIACq48iqz+JJT09XBWGgHo2MjAz10NBQNgYYyMzM1MzPzxeor6/nAPHnljfbpC3pu3Di+pH/rTWV/4L93F+0l2fOOLpziR3QYJAapoykDMOYmBidlJQU45ycHP3k5GQ9oH4msIEpWVnaubnpWkAm89SW9rgps7q/V66e8n/nuTN/42c2/9Upi/5nEuX5PyTI8311bsKqHaumuwINZrl+5Ajv/1VA3wABzAdg4JGbyw6idQrCoqMzY375t+b8d5rd/Dd9evv/Mzs3/1+/ddk/50mlf43rUsAGuwU4f1o8d0bbo8uXbl48cmTdrTMnLBjQwcI9sQUr18387+Tq+scmwv2PRX/pf7vG/P89s/v+JxVl/FdV1f1vFur7X6cr869xov+/1fNm/z+wad2/JxfP/T+3b8//xdOmWUBdCvF2b2+A/ZSuhvXB5Uk/sqoK/3tPqvurMqH0r2Jn3n+vzIT/6jaW/6UTff5rhHj8n9Hd9f/ehXP/ets6/5bk5f+P8Pf5mREdrQ4yBxyO9vb2LGk+9Vwggf7V/Q6F5ek7IyvSv9dP6fkfn5v6My40+JeMn/0/K2+X/8uWzP/fVF32Pzsu8k9u+9zfiblF3xwNtRJBYQiNFEYGYNQrh64KZQY5N9cjlw8kOLm9Kjg0ym13XFT4HzdHu/+iiuK/Vq1e+uveyaP/lk2f8icxNOiPX2rx79ZJcwJADsnNzeUDJh1msIHAdAh2roeHBzsoLdbH13MAJTlBYjz8EkH+sZrbzSN0vq1atuj/86tXfvTW1//PT096VlKS4wJKfyoQfXJQzMoAS5RAJ7MAbWIH8XOhMQ8Dxrm2bqnFYYdaOsr+97TVfosJ8UuE6mGLjo7mAxpkAsRuoMQO1wQU4AdiSWDKFwNhYGIVh7kUBEBeEvURdTMLNdA6M/MMKyjXgBwBMgRkKEwN3EBQFgIaaAnERqCUD8RmwJygBjIcrBAp8QLViADlVUCGAoOMFznrAQCN0XI4lSmBZAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="1-rpi.png" title="" src="/static/bd7982b51895d08f77ca21165ffa766c/9fc4b/1-rpi.png" srcset="/static/bd7982b51895d08f77ca21165ffa766c/772e8/1-rpi.png 200w, /static/bd7982b51895d08f77ca21165ffa766c/e17e5/1-rpi.png 400w, /static/bd7982b51895d08f77ca21165ffa766c/9fc4b/1-rpi.png 583w" sizes="(max-width: 583px) 100vw, 583px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Source: <a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/">https://www.raspberrypi.org/products/raspberry-pi-4-model-b/</a></p> <h3>Step 1 - Installing an OS</h3> <p>My choice of OS for the RPI is <a href="https://www.raspberrypi.org/downloads/raspbian/">Raspbian</a> since it’s easy to get started with and comes with a lot of tools out of the box. I would recommend selecting “with Desktop” option if you are planning to use it with a window manager.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 76.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB0bbNWQR//8QAGBAAAwEBAAAAAAAAAAAAAAAAAQIQABL/2gAIAQEAAQUC4TKqiE3/xAAWEQADAAAAAAAAAAAAAAAAAAABEBH/2gAIAQMBAT8BpX//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAYEAACAwAAAAAAAAAAAAAAAAAAASAhMf/aAAgBAQAGPwLClH//xAAbEAEAAgIDAAAAAAAAAAAAAAABABEhQRCBkf/aAAgBAQABPyFZyY7dTwRWXqawLC637P/aAAwDAQACAAMAAAAQIB//xAAWEQEBAQAAAAAAAAAAAAAAAAABEQD/2gAIAQMBAT8QqRdN/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAQACAgMAAAAAAAAAAAAAAREAIRAxQcHR/9oACAEBAAE/EHShvdfuKisRRffHUjCrbFumazSoHyNZ/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2-raspbian.jpg" title="" src="/static/92a678a7b9fa6b185e7456eebeda2eee/4b190/2-raspbian.jpg" srcset="/static/92a678a7b9fa6b185e7456eebeda2eee/e07e9/2-raspbian.jpg 200w, /static/92a678a7b9fa6b185e7456eebeda2eee/066f9/2-raspbian.jpg 400w, /static/92a678a7b9fa6b185e7456eebeda2eee/4b190/2-raspbian.jpg 800w, /static/92a678a7b9fa6b185e7456eebeda2eee/4017f/2-raspbian.jpg 990w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Once you have got the ISO downloaded we need to flash it to a microSD card. I used <a href="https://www.balena.io/etcher/">balenaEtcher</a> since it was pretty straight forward to use.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 60.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0ElEQVR42q2SS0scQRSF5ze4jSuzDnFrIGYcp6cfVdVV/e6eRzuSmQkYTAgYIiKIG8GV4MJMJCC4V/BHBLIV/D/HW9XTcYZkEUgWH33r9K1TVaeqMXl/hnJwiX7/KzFdoNeb4u1kitHoCtujbyi3fu8ZDHTfBSbja4xHl2i4LACT8o94vkR3LDEYlsjKEDIRYP5iD1cKDufoD7do0QkaQmUQKjf4QWGoxxqPEyIBExm4zBf+zcP8FG1b1YYZGeWwvRCWq0z9pGujlHaS/tJqtD4/Xy9cGWozFmL34x72D47g8gh+WJgmPSnvbiPOSlNrrV4wTPqQ1FfrLo+1YQ7lx+CWiw87u9jf+wJhM0Qyhoq6pjEjw4QMdRyStEovEMQ9Y1jFNTNUPMCrd4dY+n6P5elPPLv4gaWrB6x+PofbdrC+YWNj00GTWG924NJJ2h2B103LaG9aNvV0KMOY8qYjSxGhOfyElZNbvDi9wUvi+ekd1naOwRyBlsVg2cKwSbUnYspaodX2yJgbTcNlUhnq7UoRIrEdRLZrSKkOmKRsC3Mkf/aVs1w19Xhen2VIt6XDp0nzVM8h+2v0azCG5jb/AzIqKI4UDY/rMP8dRtl2nACPza7eMUwp/IAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="3-balena.png" title="" src="/static/d4d95e78c14e00b776633a680f84ed3e/5a190/3-balena.png" srcset="/static/d4d95e78c14e00b776633a680f84ed3e/772e8/3-balena.png 200w, /static/d4d95e78c14e00b776633a680f84ed3e/e17e5/3-balena.png 400w, /static/d4d95e78c14e00b776633a680f84ed3e/5a190/3-balena.png 800w, /static/d4d95e78c14e00b776633a680f84ed3e/c1b63/3-balena.png 1200w, /static/d4d95e78c14e00b776633a680f84ed3e/1acf3/3-balena.png 1596w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>If you are using Windows, it will ask you to format the drive when opened with Windows Explorer. <strong>Don’t do that 😀</strong> Otherwise, you will find yourself doing this step over and over again.</p> <p>Now, open the microSD drive in your Windows Explorer or Finder. For us to be able to connect via ssh later, we need to create an empty file called <code class="language-text">ssh</code> at the root of the drive (without any extension).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6UlEQVR42n1T147CMBD0X9BLAFFSKYFQghQIECQQCCGk4x+O/3+c8ywK4o6Dh5Vj73rKZq0cx0Gr1UIul0M+n0epVEKxWHxEpVJBtVqVc0a5XP5VUygUHuf8VoZhYL1eo9vrwbIsuZwGc+12G7Zto1aryTqZTNDtdqXW8zwhZK7ZbIowlclkcDgcEMcxfN9/KEqDe7IzXNdFGIYIgkAEjEYj1DQpa0je7/ehstkskmSrmacwzc4L4DMwVQ2HQ1FmmhZ6GtR4qmFe1et1RNEC0+lUGP4DSwE7nQ4GusbWfec3FT8LME0TynFsrDcbxMtYGN4pTC3PZjNpDftJ67btiF0GlSvLMrHRgFEUgX/8veWy5OmEgJa2HIwC3cehgFEd26HINJ/PZfMZ8G6ZfXNdjlpbFPMO85wG7hVtLpdLUUhmXvr0U2iToFTEERoMBjI2jUZDxkkxsdvtsFgshM0wqh97GM5CDeJLre/f7ZKMM0hBd4V6BsfjsbB+ssw8LxGY31zTHC2LQgJSHf8eWchIC3+D55but697TSB5Ka73yAmgpwH5frfbLeLVSgq5f37LafCcquiESthrrmmupZ+eWGYzv283/Z71LK5iAU+S5CU4WqfTCdevK87nM47HIy6Xizxb5vf7vZz9AAxGpBn0d/IcAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="4-ssh.png" title="" src="/static/b4238449b115dbe8af80fda1d061f8bc/5a190/4-ssh.png" srcset="/static/b4238449b115dbe8af80fda1d061f8bc/772e8/4-ssh.png 200w, /static/b4238449b115dbe8af80fda1d061f8bc/e17e5/4-ssh.png 400w, /static/b4238449b115dbe8af80fda1d061f8bc/5a190/4-ssh.png 800w, /static/b4238449b115dbe8af80fda1d061f8bc/c1b63/4-ssh.png 1200w, /static/b4238449b115dbe8af80fda1d061f8bc/32b38/4-ssh.png 1471w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Safely eject, unplug the microSD card and chuck the microSD card in to the RPI’s slot.</p> <h3>Step 2 - Connecting the RPI to LAN</h3> <p>This is going to be a one-off step. We need to connect the RPI to the router with an ethernet cable (RJ45) and boot it up. Give it a minute to settle down.</p> <p>After that, we need to find the local IP of our RPI for us to be able to SSH into it. Normally you can head over to <code class="language-text">192.168.1.1</code> or <a href="http://routerlogin.net">routerlogin.net</a> if you are using a NETGEAR router.</p> <p>You need to find the connected list of devices of your router. Now, copy the IP of your RPI</p> <h3>Step 3 - SSH in to your RPI (Headless mode)</h3> <p>You can wither use <a href="https://www.putty.org/">PuTTY</a> if you are on windows or your terminal on a *nix system.</p> <p><code class="language-text">ssh pi@your_rpi_local_ip</code></p> <p>In default settings, the password would be just <code class="language-text">raspberry</code></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABv0lEQVR42tWT3U7bQBCF4/g3zu5680Pi2IhaDSIIoUp9AxAooFRKpEi5bCQuUJ+oveBB2hvcB4PFw5nFhFb0ouEOS0ezM7vz+ex63Wjg+Ta/PPp6dXZ+9uXiZDqdns5m09P5fAbNrdbrtdVyudzUnrVYLE4Qz1er1VGjftzjYu/np88faTw5MJPJIY3H+5RlGaVpSqPRiIqisPqzlo4QMU6HQ8P1PM9/MYuBUSxlKZUmqdS9SnTVbosqCMPKdd0qCILKhzzPr3zft7nreRgHdk0YRvee55HjOCWzGBgKIUqlFCVaGykRE01KJSSlRK1DnU6XNCLnPOY5lSR2nRDSuG6Tms3mC1BpXfb7O9xkuBEuGU7dXp+63R71EAfDlNpCUKvVgmKCO4JbdmbA+NuhUrrU2royAi6sA+tQEecMgBMrbB+g0NZwJIT+18CdwaDMs11CNFm+S3sfCmznCYjzpDhuQ7EVzpGbeYuAOf8G4oBLfisWG95GFEW88H/1Glgnm8kt9Q6BEZLbevIOethSdzXwdgPkS8lfju9UHbeRqb/672egj+QaxRvoO/RjS3HPDTOYxUCn/qk5Cd4ov2Y4j7k6A6hlYlKjAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./5-rpi-ssh-2.png" title="" src="/static/60ccf64567f2fc8230f09588c8c28788/5a190/5-rpi-ssh-2.png" srcset="/static/60ccf64567f2fc8230f09588c8c28788/772e8/5-rpi-ssh-2.png 200w, /static/60ccf64567f2fc8230f09588c8c28788/e17e5/5-rpi-ssh-2.png 400w, /static/60ccf64567f2fc8230f09588c8c28788/5a190/5-rpi-ssh-2.png 800w, /static/60ccf64567f2fc8230f09588c8c28788/c1b63/5-rpi-ssh-2.png 1200w, /static/60ccf64567f2fc8230f09588c8c28788/29007/5-rpi-ssh-2.png 1600w, /static/60ccf64567f2fc8230f09588c8c28788/21e8f/5-rpi-ssh-2.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>There’s only a single step remaining to get in to your RPI’s desktop environment.</p> <h3>Step 4 - Connecting to RPI with VNC</h3> <p>Now that you know how to ssh into the RPI, we need to use it in GUI mode. We are going to use a VNC provider for that. VNC is a graphical desktop sharing system that allows us to remotely control the desktop interface of our RPI (running VNC Server) from another computer or mobile device (running VNC Viewer).</p> <p>While you are connected your RPI, let’s run the following commands in the terminal</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"> <span class="token function">sudo</span> <span class="token function">apt</span> update <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> realvnc-vnc-server realvnc-vnc-viewer</code></pre></div> <p>You can enable VNC Server at the command line using <a href="https://www.raspberrypi.org/documentation/configuration/raspi-config.md">raspi-config</a>:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"> <span class="token function">sudo</span> raspi-config</code></pre></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACsUlEQVR42p2Ty08TURTGIQYlccHWlcS4q4ua6EKjUYhdKwQFNDGCiYmQEEEFWRGD0BIToS3xEdCV8R9gmikmjBsUEHlJC5GFEPoY6kyhpYW2M3Onn/cOHSjBDd7ky7nnm3t+58zcTEEBXdctJyxXzx+3nSktKaMqN2WlOne6pPyKtdQQ21tLD6js7KkS22XLSUtBbh2pvd/KN74cRNUTJ6l52o+aViY3qh67UNniRm37O9RQVba4UNHs3Kcbj3pJdfsAbHdbvYzFgMX3Oj8JvaPbsPNBzcEHsj3eYLbnczjr4INZ6mW7PauG7N5Q1jFM/Vy0U3UNh7Xer1uo7/woMBYDHvMMDQlYX0fK7yfavA+azwfVvwDi84OwuG/vhzo/D3VuDsrsLJLTMwRrEXCUsQvkPR4BKwGIo2NEmviBxE8K/bWEyPgkpO9TiE5OU01BnphEjEZ1cYk2XYRCz2Vm5ggiEjiOywN6vYIGYDkskrAchbyZQHx7GyFZRkiSEYj8AfNF+hYhGqV4HPFUCilVRSKdJrQUHjrUHpDnBU0j+L28QiQKiMfiSNOCQCCIYCiMcFjE6mrAiKK4BlmKYiu5hayuQ1NUA8jlA710woyqsWKSSCaxmUhCpd0ZTKTfZ41OGJEkyFE6ZSSC9Y0NxGKbSKXS0HX9IJBNyExFUYimaWAirDshMHPDM3IChTYzvX8COc5jAOkBytHZIWQyCjKKAjMnZCeyyZnP4LncAA7tu+XchGa3nT1BXoo9n8G1/PzApRTXNXeMPHd+QNuLPrWt26UztTv69Wd2t27mppjHnu16XU6149UAqusbRnaBtqa3Qv37Rdzu+0buuMZxKDnHSN3gAq41ur+YwCJr+a0Hl242DVyoePj6YmXDm8OI1bBaxmAsBizM/dQsOfqfKsoxCv8Cw+D48Jjzzt8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./6-rpi-config.png" title="" src="/static/c4941a1f6bcb50adc44073550e6f3d5b/5a190/6-rpi-config.png" srcset="/static/c4941a1f6bcb50adc44073550e6f3d5b/772e8/6-rpi-config.png 200w, /static/c4941a1f6bcb50adc44073550e6f3d5b/e17e5/6-rpi-config.png 400w, /static/c4941a1f6bcb50adc44073550e6f3d5b/5a190/6-rpi-config.png 800w, /static/c4941a1f6bcb50adc44073550e6f3d5b/c1b63/6-rpi-config.png 1200w, /static/c4941a1f6bcb50adc44073550e6f3d5b/29007/6-rpi-config.png 1600w, /static/c4941a1f6bcb50adc44073550e6f3d5b/21e8f/6-rpi-config.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now, enable VNC Server by doing the following:</p> <ul> <li>Navigate to <strong>Interfacing Options</strong>.</li> <li>Scroll down and select <strong>VNC > Yes</strong>.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACi0lEQVR42p2TS0wTURSGIQYlccHWlcS4q4ua6EKjMRC6VkgEH9EICxMxISKJPDZE5VE3TUuND0BXukbsNHVDjVhayqtqDMaEtD4K0+e0pQIznd6Z33unLS1iTOpJ/rnn/Cf3u2dyZyoqaDTrDugaju83HKmtqdPX1tT/qTP6Wk3HDu/uUdUdPVRjOK07qKvIx56mK12O1sFRnO00k8bbIyhVU5cVF/tGcaH3qZY3dlp29M/dspCWvnEYrt55w1gMWH3t/kunyZnE0GQgO/xqRTVO+lXj64C2DjNNrKhDEzn/Qd7PKaAOTvqzpikBbfdeOBmLAffZOc4JGhuSRNSsDJKRkM1kkBW3kJVoLoqaR2Tqab1cnpFEiLJM2F6OsxWBnI1zEv83hKdnSObzMiIeL4LTLqy5PAjPzIKf8WDtvZv6c+BdbgTfuSDMLyJE+3GPl2CNp0CuBEgL9ecq0h8+EbL8FfGFJYS88wjPLSK26ANP89iCT6sF30dE5hY08W4v9ZcIkuuw2+1FoI0CJaIgJCToE1iNROEPruI7H0IwHMGPUBh8LK7VobiAaGodiV8biK2nIaTTuVcuBTI6M6PRGNnc2EQymYIgJJBIJGieRCqV0pSkSlOIuLUFhR2tqnQlu4EOh0MDKoqiNcuJwp4dQC4/oUxvjB3IJNLbZSrUBUnUy9AvoFDL+Vu22Wz/npCl6l8GzvnKrglLL6W6tbN/6q7lOboHzHL30IjC1Gt8qPQMW5VCXfStSg/tbXuDFrnfNIaWtvapbaCh44mz7dkXXDK7yeWRWZQli4e0ji+j4ab1bQFYpa9vvn7qfMfYicYbj042tT8uR2wP28sYjMWAlfmfmhV7/1NVeUblbwkW+DZ6kIWWAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./7-rpi-vnc.png" title="" src="/static/b558e929cb12bd4e7af4e0887937e176/5a190/7-rpi-config-2.png" srcset="/static/b558e929cb12bd4e7af4e0887937e176/772e8/7-rpi-config-2.png 200w, /static/b558e929cb12bd4e7af4e0887937e176/e17e5/7-rpi-config-2.png 400w, /static/b558e929cb12bd4e7af4e0887937e176/5a190/7-rpi-config-2.png 800w, /static/b558e929cb12bd4e7af4e0887937e176/c1b63/7-rpi-config-2.png 1200w, /static/b558e929cb12bd4e7af4e0887937e176/29007/7-rpi-config-2.png 1600w, /static/b558e929cb12bd4e7af4e0887937e176/21e8f/7-rpi-config-2.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>For our changes to take effect, let’s reboot the RPI</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"> <span class="token function">sudo</span> <span class="token function">reboot</span></code></pre></div> <p>While it’s rebooting, let’s download the <a href="https://www.realvnc.com/en/connect/download/viewer/">RealVNC viewer</a>,</p> <p>Once the viewer is installed on your local machine, let’s open it up and enter the IP address of your RPI, username &#x26; password.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 76.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABG0lEQVR42u2TwUrDMBiAs8PwIAhuj+JL9CVyKhTKNrBP4HP4Aj6ALzD1oBcrlSmsDU0D6mGuSjea0Nqkv8m2g6UXi57EH75A/j/5/hz+oNMjpx9cnePp7PY4JPGYkHAShk2CIJj4vt/K7xhHUeTN54/YcZw+cl33ME1TuhQA72uhyqKAuq4bKKVAStnKm8hzrlarNWRZRo0LjUZ4SBaCPuUAUQoVL0Hpc12ojFgIEdu2PUAnnj14eObsfgFw9yJV/rFpDNv+3wojBc55shF6WsiWOYvfFJDXUhUSukZTaBa9YV+L/8I/KqS7YtVxqNuDjTEe/tYLjQtZlnWgP/cZY+w6SZILzWUXKKVTfffGOIwL6ehp9jT7P8Q4ep+BHedy6WfdwQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./8-rpi-vnc-2.png" title="" src="/static/fb63696dd8c2aa81015a45bdf9a7bf34/5a190/8-rpi-vnc-2.png" srcset="/static/fb63696dd8c2aa81015a45bdf9a7bf34/772e8/8-rpi-vnc-2.png 200w, /static/fb63696dd8c2aa81015a45bdf9a7bf34/e17e5/8-rpi-vnc-2.png 400w, /static/fb63696dd8c2aa81015a45bdf9a7bf34/5a190/8-rpi-vnc-2.png 800w, /static/fb63696dd8c2aa81015a45bdf9a7bf34/c1b63/8-rpi-vnc-2.png 1200w, /static/fb63696dd8c2aa81015a45bdf9a7bf34/29007/8-rpi-vnc-2.png 1600w, /static/fb63696dd8c2aa81015a45bdf9a7bf34/a4f52/8-rpi-vnc-2.png 2024w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Don’t forget to connect the RPI to your home’s Wifi network. <strong>Now you can unplug the RJ45 from the RPI and keep it anywhere you want</strong>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVR42gGfAmD9AJWKfa2Nb8CWcdWleuKreOWtdt+qeNSle8aefLyZfbeYfrSXf7GWf7GYgLOZgbWbgbyhhKiRdquYfrOegQC4f0jNiEjcjUHwmzv7rkH9uEv6sUfznz/gkUTNiUzAhU61gE+tflGpfVGofVKrgVO1h1TCkVjEk1a8j1UAu31I35NG+bBM/85n/+un//jN/+ed/9iA/8tq/b1f9K9a8qxd76xg66lh6alh6ali5Khh5ahh5Khg3qRfANyMQfqjQP+5R//SZv/vrf7tuP/plf/SZfyzUOmcTdKNT8eIUsiIUrmBU6t7U7WBVa1/V5x3VY1vUY9wUQC2dD/FejjgijH3pDT8uD7JgxnjnC3wnDXhi0DGfkisckqTZkiNZk2CY1B7YFJ4Xk9qVUhVRj87MzQ3MjQAh1IqmVsntmsl2IAnynUZdj4EnFgYwXEppGMtlFwxfE0ua0UuY0MyX0M1XEM3VkA4TDw2NCkmRjo4QTg2AIVQI5pbIpZXGq5mGYJLD1o0El42E6hjIrNrLJteLJheNYlWMXpOL3tTOXBNOVo/LkczJiMXEFpHP11MRQBMKwpWMAxfNApfNAVSLwlGKQ89JA1bNQ9yQhBkOhBZNRFQLxFHKxBCKRM0IQ82Ig8qGw0dEgo0JhxDNCcAOCENQicOUzQTSCwPPCIKMx4JMB0IMR0HOSIHTS4PTS8SOCINJxcIJhcIJxgKIxYKHBIHFw4HGBAKFxAKAB0SByUXCDcmETMhDiUVByUWByMXBSEUBB0RAykbCEErEjYiDSoaCyMWCh4TCRoRCBkQCBkRChwVDhcTDAATCgIXDQQjGAsdEgUcEQccEwYXDQIXDQIaDwQXDwMdEwYmFwkiFAgeEwgZEAYZEQgYEQkVEAgYFQwTEAjx4++AGuuHuQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./9-rpi-desktop.png" title="" src="/static/a21eff908e935ad3bed8f31b80e1bb9f/5a190/9-rpi-desktop.png" srcset="/static/a21eff908e935ad3bed8f31b80e1bb9f/772e8/9-rpi-desktop.png 200w, /static/a21eff908e935ad3bed8f31b80e1bb9f/e17e5/9-rpi-desktop.png 400w, /static/a21eff908e935ad3bed8f31b80e1bb9f/5a190/9-rpi-desktop.png 800w, /static/a21eff908e935ad3bed8f31b80e1bb9f/c1b63/9-rpi-desktop.png 1200w, /static/a21eff908e935ad3bed8f31b80e1bb9f/29007/9-rpi-desktop.png 1600w, /static/a21eff908e935ad3bed8f31b80e1bb9f/29114/9-rpi-desktop.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Hope this article helped you. In my next article, we will see how you can configure your own Kubernetes cluster on your RPI!</p> <p>Enjoy your new Raspberry PI 🥧</p> <h3>References</h3> <ol> <li> <p><a href="https://www.raspberrypi.org/documentation/remote-access/vnc/">https://www.raspberrypi.org/documentation/remote-access/vnc/</a></p> </li> <li> <p><a href="https://hackernoon.com/raspberry-pi-headless-install-462ccabd75d0">https://hackernoon.com/raspberry-pi-headless-install-462ccabd75d0</a></p> </li> <li> <p><a href="https://www.imore.com/how-take-screenshot-raspberry-pi">https://www.imore.com/how-take-screenshot-raspberry-pi</a></p> </li> </ol><![CDATA[Getting Started with Azure Service Fabric]]>https://www.sahansera.dev/getting-started-with-azure-service-fabric/https://www.sahansera.dev/getting-started-with-azure-service-fabric/Mon, 23 Mar 2020 00:00:00 GMT<blockquote> <p>tl;dr - if you only want to get started quickly, just jump ahead to “Setting up a Development Environment” section</p> </blockquote> <p>I have been experimenting with container orchestrators to help me deploy a microservices-style hobby project I have been working on (more on this in a separate blog post) to the cloud. If there’s one thing I learnt from this project, there are a lot of moving parts to it and might not be easy to manage a distributed cluster with hundreds of services that are fast, reliable and scaling.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>While I was on the journey in finding a platform/framework with a good balance of container orchestration, managing and monitoring infrastructure, I came across Azure Service Fabric which looked promising.</p> <p>The following definition is the one that caught my eye,</p> <blockquote> <p>The aim of Service Fabric is to solve the hard problems of building and running a service and to use infrastructure resources efficiently, so teams can solve business problems by using a microservices approach. Service Fabric is agnostic about how you build your service, and you can use any technology. But it does provide built-in programming APIs that make it easier to build microservices.</p> </blockquote> <p>For starters, you can have a quick look at <a href="https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-overview">Microsoft’s official page</a> to learn more about Azure Service Fabric.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACwUlEQVR42k2TWWgTURSG501BRarikyA+uD64oPjgirijT+qL4lJUCm51qwouKFpURLEuiLgUpIhbiyjuVhtF0NatsbWN2lo1mYlZSpLJZJKZTPJ5JqXqhX/unHvP+e4w579KJpOhW+l012yJkskkCV3/Kz0pkjkWi8ueXsh18yzLkhrrb63iPuxMmqyVIZe1SbtgiTUtyO+gRmckQjQc5ncojKaqqAE/0WgER/JNgRiGgSnK2ZlCreKeEktnaY3muOM3OOR1eOu32F5eSWPAoFnPc7YlQ50GJy7e5npdM7LNxa9JrnZAzb1XFJ9/yDcglzZRcGwafsZZsO0yEys8rPdC1dtOapsCPG7S8HTo3O9I8cgX4+7HADXvA3jaYhz25dnlMbjySmV5zSdONXwXpCNAGdefNtB35iamlF1lclkVQ1aeYOK604xffZRp2yuZv/8WY0tOMX7DOUavOsy4kpPMPXifEcUnGbbmDEMX7+VC9XMX1QW8cPslfeftpN/cbRTN2crwpQfpPb2UnjO20GPqBvrM2sKQJfvoNW0j/SVvwPwdFM0uZfCiPRTN2owyaT3llQ/+AW8+e4cyoYSBC3dz1/OaOs9jSiuqUUauYFzxEVo6NHw/g0xYewxl1CrKKm5wvDHMgRdtjFlWjjJmHWeqPV1At8uOdPfBm8+8/6KSM4No7a8xU0mqnnlpVyN0j7ZfmvyeeumqTnPC4qsJ/vZ6ntRew7b+s43rpctNDt6II63KSmwTTGbxxqExmidhiK1si4BYqfbjdzo7o+RzORwzRL55EdQPIuu/hJhFbCOwVCrFlx8aP/wahhjalrWInqH1VxQtZhb8ZQtQFeCHljZU8aNlO+gxlbDvJiHfLeKhVlnLdX2hCzT0BIlEvPDuHmKJuVOyZom3um9SKBQScwcIBoOF2Exb6CmnoJRpFW7OHze+3sqIPHeWAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./1-service-fabric-intro.png" title="" src="/static/14ab6211e40e118b64600d6c5e73557a/5a190/1-service-fabric-intro.png" srcset="/static/14ab6211e40e118b64600d6c5e73557a/772e8/1-service-fabric-intro.png 200w, /static/14ab6211e40e118b64600d6c5e73557a/e17e5/1-service-fabric-intro.png 400w, /static/14ab6211e40e118b64600d6c5e73557a/5a190/1-service-fabric-intro.png 800w, /static/14ab6211e40e118b64600d6c5e73557a/c1b63/1-service-fabric-intro.png 1200w, /static/14ab6211e40e118b64600d6c5e73557a/29007/1-service-fabric-intro.png 1600w, /static/14ab6211e40e118b64600d6c5e73557a/c2d13/1-service-fabric-intro.png 2560w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Source: <a href="https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-overview">https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-overview</a></p> <h3>Options in Azure for Microservices</h3> <p>Here’s a quick summary of different options you can use to build microservices type applications in Azure,</p> <ul> <li><strong>AKS</strong>: Microsoft’s Managed Kubernetes service with Docker as runtime</li> <li><strong>Service Fabric</strong>: Another step forward from AKS. All about microservices and, - Service communication - Service discovery - Monitoring and Telemetry - Provision and upgrade - Testing locally - Scalability - Manage downtimes <ul> <li>Focus on business objectives and infrastructure. In AKS you need to build your infrastructure. Build and deploy from day1, but still be able to customize.</li> <li>Proven tech - Many azure services run on ASF. Cosmos DB, Azure SQL, Power BI, Skype, IoT Hub, Cortana, ACR, Azure DevOps</li> </ul> </li> <li><strong>Azure Functions</strong> - Mini version of microservices. Event-driven (triggers, scheduled). Easier to get started than Azure Service Fabric and Azure Kubernetes Service</li> </ul> <h3>Supported Environments</h3> <ul> <li> <p><strong>Public Clouds</strong></p> <p>Service Fabric is designed to run on any cloud platform other than Azure, such as AWS, GCP etc. However, as you might have already guessed, Azure has first-class support.</p> </li> <li> <p><strong>On Premise</strong></p> <p>If you want to manage your own premise cluster, ASF supports Windows Server 2012 R2, 2016 or higher, Linux Ubuntu 16.04 and 18.04. However, when it comes to Macs, you are kind of on your own since you will have to rely on an emulator for that.</p> </li> <li> <p><strong>Developer Workstation</strong></p> <p>Service Fabric does not have a dependency on Visual Studio since it’s designed to be modular. This is great because you can have a similar setup to your production environment in your development machine. It also brings you much good stuff such as simulating real-life disasters and fixing issues before they go into production!</p> </li> </ul> <h3>Programming Models</h3> <p>Azure service fabric provides us with different ways to wrap our services in. Here’s a summary of programming models used in Service Fabric.</p> <ul> <li><a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-quick-start">Reliable Service</a>: Used to address single or multiple business concerns. <ul> <li>Stateless - Acts like a console application</li> <li>Stateful - With its own transactional replicated storage</li> </ul> </li> <li><a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-actors-introduction">Reliable Actors</a>: Service Fabric uses virtual actors to handle communication between servers. Here’s an <a href="https://mattferderer.com/what-is-the-actor-model-and-when-should-you-use-it">excellent article on Actor Model</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-guest-executables-introduction">Guest Executables</a>: run any executable without any code changes</li> <li><a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-containers-overview">Containers</a>: Separate blog post will be published on this (I promise!)</li> </ul> <h3>Setting up a Development Environment</h3> <p>To set up your <em>local</em> environment, we need to first install the necessary tooling. You can also have a look at Service Fabric’s <a href="https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-get-started">official documentation</a> on getting started.</p> <p>Make sure you follow these steps,</p> <ol> <li> <p>Install Visual Studio 2019 - Community edition is fine. In fact, you can use an editor such as Visual Studio Code. But it would be easier to use VS to get something up and running.</p> </li> <li> <p>Install Service Fabric tooling</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 54%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkElEQVR42o1S2W7jMAz0//9fsDl9x4llO5Iv+Uowy2ERoCi23T4MRInkkBwqiOIYpqpwvd4Qpxnya4HHw8IYI+cDzjmUZYm6rjGOI6ZpUtD23qu9LIvCuRbB3dQYphl2XFD3M5qeASsmCZ7nWQOZRHtdV2zbpmeWpQjDEEmSwFqrfhYIdqnF3VTSpcEwDJrwfBJPxcf9ky2YpWCYl0pWFIVOwaLOdQj+kPB2R5HnqISYlZj4HUjolw1J49E0tZKNo1eftQ5BfExRHo647w+wopX/BeFEwmqQBgwq0b9tW53AWtEwudVSqYETHajb+gPZV8JGFsXFsTPq13Ipu8yhkJFzGZlOOn5DGJseD2mE3VE/dth3PQLvR10CN8eNvjf5Pw1jM6iGfd9r3uv1QkdCPvBC0vdG38kk/4pN4GXLqYzMv0m5OBnzlDDLMn2sqloCGkGFWoTm3+I3YsF/oe0+zq7rdGzGMifY7/e4nM84HE84nC44ib07RjhdQpzFVlw+Qe5pmsLIj8hE9yiKNOdt/wVBTkncz2gMxwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./2-installing-tools.png" title="" src="/static/788ccd5f8c7520349977417626da1be3/5a190/2-installing-tools.png" srcset="/static/788ccd5f8c7520349977417626da1be3/772e8/2-installing-tools.png 200w, /static/788ccd5f8c7520349977417626da1be3/e17e5/2-installing-tools.png 400w, /static/788ccd5f8c7520349977417626da1be3/5a190/2-installing-tools.png 800w, /static/788ccd5f8c7520349977417626da1be3/c1b63/2-installing-tools.png 1200w, /static/788ccd5f8c7520349977417626da1be3/29007/2-installing-tools.png 1600w, /static/788ccd5f8c7520349977417626da1be3/b0bb3/2-installing-tools.png 2344w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> </li> <li> <p>Install Service Fabric SDK - If you run Visual Studio and try to create a new Service Fabric project it will ask you whether you want to install the Service Fabric. However, you can also install the SDK with the <a href="https://www.microsoft.com/web/downloads/platform.aspx">Microsoft Web Platform Installer</a> (don’t forget to scroll down to <em>Download Web PI</em> section)</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 68.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABPUlEQVR42u2TTU/CQBCG9/+fjIkmGvy48Qs8eDGtKWrws6ZUa9S6EoQNCAi0pdt2X2dLCqhIPHjw4CRP3unM9G13k2HBoI8wDBFGEaSUSNMUMpmoAr5Fh1Jqip5PkgRsOByi024jItN4PIaMY0TBKNdlobIMGRko+ngSR5RLxJIMy5aN8pGD1QMHm2YNpYqHknVLeNg6vsfuGcfO6RO2q/4HVvbPsW66eX/NcLGhc5pjtn2Ba8eG9dCG6fg4dDkMUrNGUF65a+DkUeDqpY/LRm+K5dWp3srrxg3HXtWBScroX5EzOchM1ecb+3LoWU9l5JFSkoGp4t1l96UWMFefD4Zfjn/Dv2iY0QrN7+RP0XtboPe4gIlODw3RRb31il5/gID2eDRajO7p+U73Dc+cw3Vd+L6PZrMJTs9CCLwDgbU8hjYmLsEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./3-installing-sdk.png" title="" src="/static/163e9e86a1414f7d91029db8177044f9/5a190/3-installing-sdk.png" srcset="/static/163e9e86a1414f7d91029db8177044f9/772e8/3-installing-sdk.png 200w, /static/163e9e86a1414f7d91029db8177044f9/e17e5/3-installing-sdk.png 400w, /static/163e9e86a1414f7d91029db8177044f9/5a190/3-installing-sdk.png 800w, /static/163e9e86a1414f7d91029db8177044f9/c1b63/3-installing-sdk.png 1200w, /static/163e9e86a1414f7d91029db8177044f9/29007/3-installing-sdk.png 1600w, /static/163e9e86a1414f7d91029db8177044f9/e92cd/3-installing-sdk.png 1778w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> </li> </ol> <h3>Verifying your local setup</h3> <p>Once you have installed the prerequisites, you can open up Visual Studio and select <em><strong>Create a new project</strong></em></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 72%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVR42pWSa3KjMBCEOUuMHggjzFuAFyMHO7t7//v0jgYnsV1OnP3RSFVIPd+MOsqyHFXVoCzqdS0rUg0h1EPFsWQ9+ielRlQUFYZ+RNc5ONejbR3vH10KF4zZwiTplwWjdJujrB222x2ZaAhp6EeCJLW01zdkShnuwtrdTcFQSF7ORtmuQT+dUbUTyovCviAJmdCh9WJMqybDcfSY5xOW1984Ht/wejxjGCbktuQikVIJV7yez+M5vRM6GtFExge0zYAuqB2Q59VqGD6bjfhyJveGh8MCT4RHIvP+xITT3jNhOBM9N7o1LMsOvdsT5S+M1Krr9mjq/rPlnxqGw1obnPzMVN6fcVr+YD/O3PpHyz8mjBXCvNvGMVHb9GzUUEIKelibFfxw/0WoiHD2C5O9nf+y/LxQ6zM9VvdJ+F3674OdGot8V8EWDto2SG3NdMZkaw5DGwklX16F+BlpSMUm7CmnMd27BoqCWZ4XPPDr1D+TEJrnukpfRIah2stLzFXvg/2dhJSQilYlILVcpST+ARjspHkxlb5HAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./4-new-project.png" title="" src="/static/278d780b5e2f79684e4308e249eb10d4/5a190/4-new-project.png" srcset="/static/278d780b5e2f79684e4308e249eb10d4/772e8/4-new-project.png 200w, /static/278d780b5e2f79684e4308e249eb10d4/e17e5/4-new-project.png 400w, /static/278d780b5e2f79684e4308e249eb10d4/5a190/4-new-project.png 800w, /static/278d780b5e2f79684e4308e249eb10d4/c1b63/4-new-project.png 1200w, /static/278d780b5e2f79684e4308e249eb10d4/29007/4-new-project.png 1600w, /static/278d780b5e2f79684e4308e249eb10d4/e53e8/4-new-project.png 1982w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In the <em><strong>Create a new project</strong></em> dialog, filter by typing “fabric” and select <em><strong>Service Fabric Application</strong></em></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 68%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB5UlEQVR42sWTy2oUQRSGC3QXUZMJXgLOpG/V3dXX6p7qq23iZOPWRxBc6cYElUAiuMpjCJIXyCKBIG5dCuLGx/mtUxMnKlkkblx8Tdepqr/OlYVhhCTJEEWJIdbQmuyu6yMIBHz/IiJwHsDz/Dku17YQTMoSGxuP0bY9uu4h+n7AbLaFslRwHA9N1aNpBijVo1Id6rpHfWaLohTcmwt5YaaFAzAhYqRpbrzKMok4TmFZjhGjl/Nph2k1oKk7xJmC6mao2k1t75HKGqrZRKEGyLI1HjOZ5ahVjaKYgryl1wIdrggFLC7An+7A2j1GeXCKB3ufcP/tCW68OsGt7WPDbc3S689Y23oG35qA5bJAVdXI88KEaXKmEZRD7eHN5x9xb+cI4xcfkLw5hHXwBWz/O66/+4ZrxN5XsPc/cOfJS/jr47lgOVWLkClMEhWCisJhZy3cpAKPJCZ+hrVQYRLXGGsmaQtbDrCLR3BFbgrD6LKprs4d/VNlKRcm0VqcO7bGgWs7WB2NsLxyxvIK7q6O4NO+va7FXHOe0Yc8+cWiDRYEC+gxxxAYaH2+758LXhb+FxeduZLgZWC/u/vfBan5CddxDUbwj3m8Elz3bolcVkiLxkyNGb1/FaSmj2M9sqlElJYGEvwJL+yslxS5d8EAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./5-new-project-2.png" title="" src="/static/0580c0ec92d28357ada2b784eda6d6e0/5a190/5-new-project-2.png" srcset="/static/0580c0ec92d28357ada2b784eda6d6e0/772e8/5-new-project-2.png 200w, /static/0580c0ec92d28357ada2b784eda6d6e0/e17e5/5-new-project-2.png 400w, /static/0580c0ec92d28357ada2b784eda6d6e0/5a190/5-new-project-2.png 800w, /static/0580c0ec92d28357ada2b784eda6d6e0/c1b63/5-new-project-2.png 1200w, /static/0580c0ec92d28357ada2b784eda6d6e0/29007/5-new-project-2.png 1600w, /static/0580c0ec92d28357ada2b784eda6d6e0/ae858/5-new-project-2.png 2075w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Enter a project name. You don’t need to worry about Framework version unless you are working on a classic ASP application.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQElEQVR42qWU63KCMBCFfRaBXCEJEC4KVqnj+z/S6W7sOG3tiKM/vplAhpOzezZshFSwtiQqGGNvXN9dEUI+hVIaG60tjqcF5/MF83zANM3Y7/aYaL0snzgcPlAU4mk2rFqHAO8dQqjhnIMUAlorGK2RbbfIsiyR5/mKoCSH1mE+XlC3O/hmIMZ/CbRf+kiixYqgKTFOJwQSa+IuEVikHv4woiJBKfVjQS556Ef044QYO8S2o7I9SgrpjrKitrQrghRK1w0I/QxtOGkifezv4L3VHrLDpokIvkbbdGndkksWUMpSOOUNfl4PRZvk0LmQnBnqKcPjpJT5xeP+/XAYY0/OOlSVhy0duXOvl1wUxfec5Wkk1nhCUOBV+IAsvw48m0o35R0xvuccIg+8p8GXFNpbgvwTCaGBrejq1h2ENPgCiWyfdPR0FvcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./6-new-project-3.png" title="" src="/static/5a7284bb4101f0153bbf2f03ef75ce23/5a190/6-new-project-3.png" srcset="/static/5a7284bb4101f0153bbf2f03ef75ce23/772e8/6-new-project-3.png 200w, /static/5a7284bb4101f0153bbf2f03ef75ce23/e17e5/6-new-project-3.png 400w, /static/5a7284bb4101f0153bbf2f03ef75ce23/5a190/6-new-project-3.png 800w, /static/5a7284bb4101f0153bbf2f03ef75ce23/c1b63/6-new-project-3.png 1200w, /static/5a7284bb4101f0153bbf2f03ef75ce23/29007/6-new-project-3.png 1600w, /static/5a7284bb4101f0153bbf2f03ef75ce23/f24d4/6-new-project-3.png 2053w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>You will get another popup to select a project type. For this demo, we will select <em><strong>Stateless Service</strong></em> and enter a name for our project.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABoElEQVR42o1T2VLDMBDrt9DEiR3nvo9m2sIMvPD/nyNWm5RrgPKgbtKsZa1kH6IoRppmghRZlglyhXMJwtB8QRCECI4BQqk/wZgIBxI2TaskSeLhffojIZuTJEVRVEh8BhNZRLH7AvYdjInx/PyqWNcnLMsV43jGNJ0xzxepG06nRwzTiuXygm6+omxPqLoV5Y5C3o2QHrjzspyF7Iq+n9F1E+q639FprapWaivfB5RFLVNkopbI5fkDOjJllmWtCzgSLbDWIo4F1snoTp5jaTZo+1GUTKjKClVNmwr55qQ/UYThTlgUpcI5r6SsVMEdNQiBFfJavA7lP7PjPagdu4eRjFRLMI0orURpo6F8DuQoyVIxp7gt/A2qsOt68XFVYipj0kycSW9+eQU3+xdhnpd6Fjnq5snNlw9/SM4jc59QR27QikqGc1usClXldjZvvv5F9u5hP4xy9iZVxJSZ1mez6SH70jS/T8gfq8dDVNGzxGsA3xt5Aaj0++2hAEMRuxAd+WgzPBAux0Pst7t5R0kQBOr9MMwomhG93J7IerwBrmiGphaNnu8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./7-new-project-4.png" title="" src="/static/69747af08d2b8a3c54c32e3e0e33c377/5a190/7-new-project-4.png" srcset="/static/69747af08d2b8a3c54c32e3e0e33c377/772e8/7-new-project-4.png 200w, /static/69747af08d2b8a3c54c32e3e0e33c377/e17e5/7-new-project-4.png 400w, /static/69747af08d2b8a3c54c32e3e0e33c377/5a190/7-new-project-4.png 800w, /static/69747af08d2b8a3c54c32e3e0e33c377/c1b63/7-new-project-4.png 1200w, /static/69747af08d2b8a3c54c32e3e0e33c377/29007/7-new-project-4.png 1600w, /static/69747af08d2b8a3c54c32e3e0e33c377/ca98b/7-new-project-4.png 1968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>In your solution explorer you will find 2 projects like so, you can also double check with my <a href="https://github.com/sahansera/service-fabric-stateless.git">repo</a> if you managed to get this far.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 583px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 112.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEBklEQVR42o1US28cRRAeOeIAEhzIASXYzu7Ozuxrdt4zO7vzfuzTONkQ/IgPliIkUOQDEsHGNgLjGAREQgrwSzhyAAkBB0BcITFSzD/5qG5bIQc/OHyqmp7qr6urvmph7uERxG/+QfnrI8w9fEL+EUpfPcFLn/8NYf8xLt1/jJn7h5ghf2b/0TEOyP/4L/IZ/qSYQx5z6eAQQnVpG7Vlwso25OUdtNZ2Yd3Zg0RrV6abuPbGDl4lO39rG+XlDyCufoirN95Dhez82qeYW/sMV6dbhE0OwW23YBOsVh2TLMY4y7A6HSO0VKiyiFEcwFHqyHoeJmmE5cUJ3HYDt1+/iTgukOdjaDUZeq3KITgubeiEhABeL0aPYDo+bFpzvRCdLv2jGObbrg/L7hJ6MMjqnRRGt4Bhek8hJGmOfn+IwI8QhgmSJEcUpQgC8mPyaS3wY77WJXJdt6FpJgzDgWUSDBuW1XkKwTF8DPJFuN0ebaSsXA+qqqPoD+hKKdyOR0Q+OrQehhGtsYNSIjYgSTJqtTpk+T8IpurBM3oYd1QsTCaYTm9SNhGlb8K2HdqoUzYmNFWjwzrwvC4cx0WrpaBalTjpsxAUVYVraOi3qxgPh1i8McVksoB6vc4DRLF6AgmViohyucLtaWSckAW02xqabZXqFcL3A6pfiDwr0KMysM0skBEwUql6glPIOCE7Xdd0NFsqNlYCLA4CpFmfE2YkoTwnaRQFt6PRGMPBkOra4QfJcu10QlaferONu6sRFooe0rSPougjSzPehJRslucYjyf8u9Fonn1lTqhpaFCGby0FeK0fUoePMxmQnBjxcDiiLru8KZZln3ndZwh1TrixGhIh1ZA0lyUJ1dCH3wsQRzFEyqjEGkLx5xJWRZGurNI1FGzcjvmVkyTDgOrG/pWulVAulcmvUs1kDkmSzgRlKHJZlEQZaT6A5Xi8AWlOjaG6hX6Py4nZSrkEWaIMq+KZEF7c/RmX730LY/0diHvfofngd9S++A3ql39g9uBXPLf1I57f+Qkv7P6CmXe/x6V7P5wL4ZXpNipLWzAWbqG9/hHstx9Av/MJrDcPUF8/wOzqPmZX9vDy4iYuX3//QgjVuSvQmzU06w0spB0UgYtRliALacaTEKMkQOa7aFTmUSvNXgihItWhGRYaio67awmuT1IkxRjFYIT+cIK0GEK3XIhyA1JdIZAGa2eDy6ZNY6fS+DXMFr2FDhc0e9LiJIVDD0S5dDy/DOJFsmEBKk2KRk+WYiswbZsLmnW6IOlEpEE22+yV6dJs2/T/WDrnEWonhI4Cx3Nphgsu5pQyDIksjhI+JWyGTcs6n5DNpKK0ORpqA5ppUHZ9enkiPsMsQ5YVe5XE/3HlfwEwHAKAxF3bwQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./8-solution-explorer.png" title="" src="/static/3cecf4b6e5d94001a7b1a4b84aecaabc/9fc4b/8-solution-explorer.png" srcset="/static/3cecf4b6e5d94001a7b1a4b84aecaabc/772e8/8-solution-explorer.png 200w, /static/3cecf4b6e5d94001a7b1a4b84aecaabc/e17e5/8-solution-explorer.png 400w, /static/3cecf4b6e5d94001a7b1a4b84aecaabc/9fc4b/8-solution-explorer.png 583w" sizes="(max-width: 583px) 100vw, 583px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>The first project is just a wrapper around the Verification Project. If you open up <code class="language-text">Verification.cs</code> you will see that in <code class="language-text">RunAsync</code> method is the main entry point of our stateless console app. Hit F5 and see whether your local cluster is working fine. It may take about 2 - 3 mins initially.</p> <p>Once everything is up and running, you will see the following output in Visual Studio. Put a breakpoint in your code and have a play around it.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABXUlEQVR42lVR2VLDMAzMpxSa5nCO5qwdu4mTtDCUAYb//5lFUmgHHnZkHV6tpODz4xtvty8s8yumacU0zqiqFtaOGMcFdd1Ba0v5K+b5gvPZo2s1LNUv5HedQVHUKMtGEEzjhNPJwPsFw+CQJBmyrKRkTfGBiisoVSBNc4FSOTVskDUaSZwgThTFswcCrQ3atqNOPalyyPMC+30o1rkzkecIwwMOh+gB8cMQURT/izOCeV6EaCSlt9u7kO92T6TwiOv1BWVRSoM70Ya/7/8I1vUiZH7yQshKIxqlrhshPB4rxOQnqaJ1pIS73aAortQ2NtcEExGxQu89LX3Fsqzo+5MQLqRemvn5Ac7zH37zdCygaVqZjP8ElvY0DJYWXctYrOaZRuTdnSlnaMfObjXut9bS8QxZPiLfwDonoozWCPpeCzsf4b58tnmeS5F8ICtEbrPcxJhBiMWntzGb/QFU3P7+DYdcpQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./9-solution-diagnostics.png" title="" src="/static/afd72e1ffb2a56b86f7912604d95ec7d/5a190/9-solution-diagnostics.png" srcset="/static/afd72e1ffb2a56b86f7912604d95ec7d/772e8/9-solution-diagnostics.png 200w, /static/afd72e1ffb2a56b86f7912604d95ec7d/e17e5/9-solution-diagnostics.png 400w, /static/afd72e1ffb2a56b86f7912604d95ec7d/5a190/9-solution-diagnostics.png 800w, /static/afd72e1ffb2a56b86f7912604d95ec7d/c1b63/9-solution-diagnostics.png 1200w, /static/afd72e1ffb2a56b86f7912604d95ec7d/9de76/9-solution-diagnostics.png 1423w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Now open up a browser and navigate to <code class="language-text">http://localhost:19080/</code></p> <p>You will be able to see that your application is running in a healthy state in your local cluster.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 77%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACX0lEQVR42p1Tu3LbMBBUa41kqeNLEkmQBAmC74dkUlLkeJImKTKTD0iXJlX+JD+Qf90cQGtiT2wXKXYOwAF7d3uHmfHtF4zvv2H1n2Cu5jBMC4ZhYL1e4+bmBvP5XNsJ03p5e4tbwnK5xGKx0Ha1WmFBmOUyRJlxZFIgq1rwOEEcx4g4h+vu4DgOPNfFZrPR2G63sG0bO7LKp/ZXn2WamAWffyD88hPW8BWybBHLHL7ngTEfURjC931wImeMIU1TlGWhA+oz8gVBoBHS3e1uSxnWHQ7HC/wwwuHQYBgGxMn04CnUg7ZtMY4j8qL4x68QRRFmIuaQIqEMPIznDl3XQkqps7hmcrV5nqMgsvgJydM7KuhMsf4tSUCIlDRTpTB9FhB8L6DyGPkEUiFJCg6fPfofS1ZQ+9k1kjrgnKLFAVxmwrIcOPYELzBJdBumacOyDe3fOFODLMt6htmU7kSYiBjdKUT3zkN1UJmFyJsQzdFDf6HmJAGqKkc9+hhGgXF/0J1+gTDW3ayqEn27h7ulBj14NDoM5/MJnGUouwBF75KOBco+wP4coW7K1wlV/UqjukshMh/9vQtGOrZ9hbqVlJWLrPYRch/dewdpwlHI7GVCTiWHVHKaJBSZ4/iRo2xouBmnADGGhwh3l0Rr3DQ1fYAIlw8Cp+HuFUI1Q0RYZzn2+5qGt0JGaxlxtETQ0F7JEQYRZK50ZsjrEFku3yo5QCzoG97tSBtqRFHBpsuGaRBMbdUDU1nbhGmotflal2M9Z6qcqucY70OcjoP+ny89egvPCBOhtBIoKkklZ/9F+AfkSKoNN8ICkgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="./10-sf-explorer.png" title="" src="/static/1e3b4d123a8da6ababf672206565dfc9/5a190/10-sf-explorer.png" srcset="/static/1e3b4d123a8da6ababf672206565dfc9/772e8/10-sf-explorer.png 200w, /static/1e3b4d123a8da6ababf672206565dfc9/e17e5/10-sf-explorer.png 400w, /static/1e3b4d123a8da6ababf672206565dfc9/5a190/10-sf-explorer.png 800w, /static/1e3b4d123a8da6ababf672206565dfc9/c1b63/10-sf-explorer.png 1200w, /static/1e3b4d123a8da6ababf672206565dfc9/85053/10-sf-explorer.png 1225w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Congratulations for making it this far 🙌! Stay tuned for my next post on <strong><a href="https://sahansera.dev/deploying-multi-container-services-on-azure-service-fabric/">deploying a multi-container application to Azure with Service Fabric</a>.</strong></p> <h3>References</h3> <ol> <li><a href="https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-overview">https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-overview</a></li> <li><a href="https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-architecture">https://docs.microsoft.com/en-au/azure/service-fabric/service-fabric-architecture</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/service-fabric">https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/service-fabric</a></li> </ol><![CDATA[Simple In-Memory Caching in .Net Core with IMemoryCache]]>https://www.sahansera.dev/in-memory-caching-aspcore-dotnet/https://www.sahansera.dev/in-memory-caching-aspcore-dotnet/Wed, 15 Jan 2020 00:00:00 GMT<p>Caching is the process of storing the data that’s frequently used so that data can be served faster for any future requests.</p> <blockquote> <p>💡 <strong>UPDATE on April 2025:</strong> I have recently migrated this project to .NET 9.0 with modern C# features, improved architecture, and a comprehensive test suite. You can find the previous updates at the bottom of this post.</p> </blockquote> <p>Suppose we have a very lightweight process which talks to another server whose data is not going to change frequently; “Our service” and “Users Service” (which returns an array of users) respectively.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 762px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACeElEQVR42nVSW0iTYRj+dCc1l1jUCIoolpRJF9FhNyIIlSHepVAG84Ci0lDJdaDDH3kxh3TAXXio0NQumlpqGJrippIs51LD48rpVLbMuskip///P33fXGFgLzz8fO/zP8/3fu/7EgDB5lmcK7Rj8pFTeE7P2wkN+g0im0UgT/nI0jHBUOTAvPUL8gNkEOn3Cr33PwHFjjXx6XvA8w1nGWUGJAELeQCK7OxsGUv8XBU1L9zwGJxAqQNoGxVm6AV+jtROAFW9y8L1SpdQVDGHYdevMxsMgyliKA5THFGr1TsY1+3mtVVM177kKzC5UWr20JIg9Rs+sEHQ1y0i6cqweLliFktOew7La7VcSHJyXnhCQurWP9DrHysptaVh6HvWDStQaHLySTc/orxx6jPw5oDf8FoXxNQyDwqutmG6/KI42VMN71BTo7myJEKjSQ7V6XQKi8UipRVI7pnNoUzzZMCXnv4SSDNMCRWcSVyoTxPH3rWu/hiqziLnG4H4O4tis6kYLXVGoaAVmGm+BE9PiZZqJUZ9hpI+dTcN9bHY2D3M8OFbPl1TCWTctvMj9XliyTMbX/baBb7jwjTJacVC4t15lNX0rri6MkWuvhPGltE1eF+dYGKO41hv9rE+qlT7d7Jc3eBa4umqVTH11jjf113r62/KFXUNc2ix2mxs/Kq+MV/KhxUcxEhmra3TOJFvQW5gwpuszvraTH3FSbsTp5a9g/EYSLHVtFut+nEcZRxrtIIihOxFSFxcYFr/mgVtAIsw//+EsFUJJbsQxkWPyteXTC6PCg/fdkgmk8XI1tdD+f/q/uZUUqn0uFKpjGI6eo4mJCKSMb8Bgz5IR0OR7jkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="caching 1" title="" src="/static/e47263e9a64bbcbc0680ffcd381712c8/a016c/caching-1.png" srcset="/static/e47263e9a64bbcbc0680ffcd381712c8/772e8/caching-1.png 200w, /static/e47263e9a64bbcbc0680ffcd381712c8/e17e5/caching-1.png 400w, /static/e47263e9a64bbcbc0680ffcd381712c8/a016c/caching-1.png 762w" sizes="(max-width: 762px) 100vw, 762px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Without any caching in place, we would be making multiple requests which will ultimately result in timeouts or making the remote server unnecessarily busy.</p> <h2>Walkthrough video</h2> <p>If you like to watch a video walkthrough instead of this article with a thorough explanation, you can follow along on my Youtube channel too 😊</p> <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%;"> <iframe src="https://www.youtube.com/embed/LvjiCEMb88k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe> </div> <h2>Introduction to IMemoryCache</h2> <p>Let’s have a look at how we can improve the performance of these requests by using a simple caching implementation. .NET Core provides 2 cache implementations under <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory?view=dotnet-plat-ext-3.1"><code class="language-text">Microsoft.Extensions.Caching.Memory</code> </a>library:</p> <ol> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.imemorycache">IMemoryCache</a> - Simplest form of cache which utilises the memory of the web server.</li> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache">IDistributedCache</a> - Usually used if you have a server farm with multiple app servers.</li> </ol> <p>In this example we will be using the <code class="language-text">IMemoryCache</code> along with latest version of .NET (Core) as of yet, which is version <code class="language-text">7</code>.</p> <p>These are the steps we are going to follow:</p> <ol> <li>Create/Clone a sample .NET Core app</li> <li>Naive implementation</li> <li>Refactoring our code to use locking</li> </ol> <h2>1. Create/Clone a sample .NET Core app</h2> <p>You can simply clone <a href="https://github.com/sahansera/InMemoryCacheNetCore">In-memory cache sample code repo</a> I have made for the post. If not, make sure that you scaffold a new ASP.NET Core MVC app to follow along.</p> <p>First, we need to inject the in-memory caching service into the constructor of the <code class="language-text">HomeController</code></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HomeController</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Controller</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IMemoryCache</span> _cache<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HomeController</span><span class="token punctuation">(</span><span class="token class-name">ILogger<span class="token punctuation">&lt;</span>HomeController<span class="token punctuation">></span></span> logger<span class="token punctuation">,</span> <span class="token class-name">IMemoryCache</span> memoryCache<span class="token punctuation">)</span> <span class="token punctuation">{</span> _cache <span class="token operator">=</span> memoryCache<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Note: With .Net Core 3.1 you don’t need to specifically register the memory caching service. However, if you are using a prior version such as 2.1, then you will need to add the following line in the <code class="language-text">Startups.cs</code>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Startup</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token function">AddMemoryCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Add this line for .NET 2.1</span> services<span class="token punctuation">.</span><span class="token function">AddMvc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">SetCompatibilityVersion</span><span class="token punctuation">(</span>CompatibilityVersion<span class="token punctuation">.</span>Version_2_1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <h2>2. Naive implementation</h2> <p>For the sake of this tutorial, we’ll use a free external API such as <a href="https://reqres.in/api/users">reqres.in/api/users</a>. Let’s imagine we want to cache the response of the API. For simplicity, I have used the example code provided by <a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1#systemruntimecachingmemorycache">Microsoft Docs</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token comment">// Code removed for brevity</span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token comment">// Look for cache key.</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>_cache<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>CacheKeys<span class="token punctuation">.</span>Entry<span class="token punctuation">,</span> <span class="token keyword">out</span> cacheEntry<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Key not in cache, so get data.</span> cacheEntry <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">;</span> <span class="token comment">// Set cache options.</span> <span class="token class-name"><span class="token keyword">var</span></span> cacheEntryOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MemoryCacheEntryOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Keep in cache for this time, reset time if accessed.</span> <span class="token punctuation">.</span><span class="token function">SetSlidingExpiration</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Save data in cache.</span> _cache<span class="token punctuation">.</span><span class="token function">Set</span><span class="token punctuation">(</span>CacheKeys<span class="token punctuation">.</span>Entry<span class="token punctuation">,</span> cacheEntry<span class="token punctuation">,</span> cacheEntryOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre></div> <h3>Explanation</h3> <p>The code is pretty straightforward. We first check whether we have the value for the given key present in our in-memory cache store. If not, we do the request to get the data and store in our cache. What <code class="language-text">SetSlidingExpiration</code> does is that as long as no one accesses the cache value, it will eventually get deleted after 10 seconds. But if someone accesses it, the expiration will get renewed.</p> <p>Suppose we want to get a list of users as per our use case. Here’s my implementation with a bit of code re-structuring:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> users <span class="token operator">=</span> _cacheProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetFromCache</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEnumerable<span class="token punctuation">&lt;</span>User<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>users <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> users<span class="token punctuation">;</span> <span class="token comment">// Key not in cache, so get data.</span> users <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cacheEntryOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MemoryCacheEntryOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SetSlidingExpiration</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _cacheProvider<span class="token punctuation">.</span><span class="token function">SetCache</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">,</span> users<span class="token punctuation">,</span> cacheEntryOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> users<span class="token punctuation">;</span></code></pre></div> <p><code class="language-text">await func()</code> will wait and return the response from our external API endpoint (provided that it’s passed into our method) so that we can use that value to store in our cache.</p> <p>Have a look at the full implementation of my <a href="https://github.com/sahansera/InMemoryCacheNetCore/blob/fb6fed98a14c0396a4b307516a0a4a14caf8f588/InMemoryCachingSample/Services/CachedUserService.cs#L35">GetCachedResponse() in CachedUserService.cs</a> for a more generic solution to handle any type of data.</p> <p>This gets the job done for a very simple workload. But how can we make this more reliable if there are multiple threads accessing our cache store? Let’s have a look in our next step.</p> <h2>3. Refactoring our code to use locking</h2> <p>Now, let’s assume that we have several users accessing our service which means there could be multiple requests accessing our in-memory cache. One way to make sure that no two different users get different results is by utilising .Net locks. Refer the following scenario:</p> <blockquote> <p>💡 Note: I need to mention that the IMemoryCache is thread-safe by design. However, if you are using doing computationally heavy task when creating the initial cache entry it’s advisable to use a Semaphore to synchronize the threads accessing the same logic, as shown in my examples.</p> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEklEQVR42mNgQAP///9nBNGPHz9WffHihd/Lly/dgdjj2atXnv8+PbNfffZDefu6h+v/r2JghqmH6QGDUAaIBJKBrCD68+fPLf8h4BeI+PcPzP777PP//6s27bt3fn25AkxP/ZX/bFv+/xcEczRMA4QZGIxZ5eXtOUA23bp1ix0k/vHjx1aYgX+Ahn3/gzD8y5dPNSA1amr2IiAHzL32d2v34Z93gGw2hgcf/1/MqF8kxsAgzLt/fz1L96JF3CDFr968awdp/vHj56+zr/7+3/Dw3//77/+ADXz4+HETUAmLnAiXZM3iDepN2z7/6phx+sf/J6sNGe5/+HM2tGq6NAODHndofT1bfW+vEMjAZy9edII0v/v8/decc///Lzn66f/CrY/BBr778AHkQiYbAwlRr/3/JRKmPvmyde3y//v2bb7GcPn1f1+gJAfQywKrQkOZ6+vrBcAufP22DaT5568/P7bc+P93wvpnf3ccuP8DJPbt27cKkBpn01jhVf//M+dNe+Cze31X48z1u7MY3v3+/6t0wmZVBgYhvvpVq9gmTpzIhx4p379////p85f/v3/9Arvw06dP1dAIZAF5HYi5oJiJ4cyDD9kMHrnsxmlprCA8f/58DpDid+/exX39+vUY0LAtQLz1z6+fW4Au2wxkH3v79m0E1EBm5OQG9B0TiM3GQBjwALEoELPikGeEYgYAfJFjCvAwZxUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="caching 2" title="" src="/static/cdb29d808387ee5950e05e406bc96237/5a190/caching-2.png" srcset="/static/cdb29d808387ee5950e05e406bc96237/772e8/caching-2.png 200w, /static/cdb29d808387ee5950e05e406bc96237/e17e5/caching-2.png 400w, /static/cdb29d808387ee5950e05e406bc96237/5a190/caching-2.png 800w, /static/cdb29d808387ee5950e05e406bc96237/c1b63/caching-2.png 1200w, /static/cdb29d808387ee5950e05e406bc96237/29007/caching-2.png 1600w, /static/cdb29d808387ee5950e05e406bc96237/21e8f/caching-2.png 1684w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Let’s breakdown the sequence of requests and responses:</p> <ol> <li><strong>User A</strong> makes a request to our web service</li> <li>In-memory cache doesn’t have a value in place, it enters in to lock state and makes a request to the <strong>Users Service</strong></li> <li><strong>User B</strong> makes a request to our web service and waits till the lock is released</li> <li>This way, we can reduce the number of calls being made to the external web service. returns the response to our web service and the value is cached</li> <li>Lock is released, <strong>User A</strong> gets the response</li> <li><strong>User B</strong> enters the lock and the cache provides the value (as long it’s not expired)</li> <li><strong>User B</strong> gets the response</li> </ol> <p>The above depiction is a very high-level abstraction over all the awesome stuff that happens under the covers. Please use this as a guide only. Let’s implement this!</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token class-name"><span class="token keyword">var</span></span> semaphore <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SemaphoreSlim</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> users <span class="token operator">=</span> _cacheProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetFromCache</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEnumerable<span class="token punctuation">&lt;</span>User<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>users <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> users<span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> semaphore<span class="token punctuation">.</span><span class="token function">WaitAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Recheck to make sure it didn't populate before entering semaphore</span> users <span class="token operator">=</span> _cacheProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetFromCache</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEnumerable<span class="token punctuation">&lt;</span>User<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>users <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> users<span class="token punctuation">;</span> users <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cacheEntryOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MemoryCacheEntryOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SetSlidingExpiration</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> _cacheProvider<span class="token punctuation">.</span><span class="token function">SetCache</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">,</span> users<span class="token punctuation">,</span> cacheEntryOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token comment">// It's important to do this, otherwise we'll be locked forever</span> semaphore<span class="token punctuation">.</span><span class="token function">Release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> users<span class="token punctuation">;</span> </code></pre></div> <h3>Explanation</h3> <p>Same as in our previous example we first check our cache for the presence of the value for a key provided. if not, we then asynchronously wait to enter the Semaphore. Once our thread has been granted access to the Semaphore, we recheck if the value has been populated previously for safety. If we still don’t have a value, we then call our external service and store the value in the cache.</p> <p>Have a look at the <a href="https://github.com/sahansera/InMemoryCacheNetCore/blob/fb6fed98a14c0396a4b307516a0a4a14caf8f588/InMemoryCachingSample/Services/CachedUserService.cs#L45">CachedUserService.cs</a> for the full implementation.</p> <h2>Demo</h2> <img src="/c266c4f60cb4c0d324f716c2df55551b/screen-capture.gif" style="width: 100%;height: 100%;margin: 0;"> <p>Hope you enjoyed this tutorial. Happy to know your thoughts! 🙂</p> <h2>Updates</h2> <blockquote> <p>💡 <strong>UPDATE on March 2023:</strong> I have recently migrated this project to .NET 7 and refactored the code to make it more readable. You can find the previous updates at the bottom of this post. You can still access the .NET Core 3.1 version from <a href="https://github.com/sahansera/InMemoryCacheNetCore/tree/dotnet6">this branch</a></p> </blockquote> <blockquote> <p>💡 <strong>UPDATE on Aug 2022:</strong> I have recently migrated this project to .NET 6 also with its minimal hosting model 🎉. You can still access the .NET Core 3.1 version from <a href="https://github.com/sahansera/InMemoryCacheNetCore/tree/dotnet3.1">this branch</a>.</p> </blockquote> <blockquote> <p>💡 <strong>UPDATE on Feb 2021:</strong> Head over to my blog post on <a href="https://sahansera.dev/distributed-caching-aspnet-core-redis/">IDistributedCache</a> if you are interested to see how to integrate with Redis as a distributed caching solution.</p> </blockquote> <h2>References</h2> <ol> <li> <p><a href="https://www.blexin.com/en-US/Article/Blog/In-memory-caching-in-ASPNET-Core-45">https://www.blexin.com/en-US/Article/Blog/In-memory-caching-in-ASPNET-Core-45</a></p> </li> <li> <p><a href="https://www.infoworld.com/article/3230129/how-to-use-in-memory-caching-in-aspnet-core.html">https://www.infoworld.com/article/3230129/how-to-use-in-memory-caching-in-aspnet-core.html</a></p> </li> <li> <p><a href="https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/">https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/</a></p> </li> </ol><![CDATA[Understanding Nullable Value Types in C#]]>https://www.sahansera.dev/nullable-value-types-in-csharp/https://www.sahansera.dev/nullable-value-types-in-csharp/Sat, 28 Dec 2019 00:00:00 GMT<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACsUlEQVR42qWTW3OTQBiGcbxWp+o4NoGQQHOgNWmahlRryKnkfCCBEAjaWm09jt6o46U/x/GPNDf6C9Rq/B0Krx+k1lHHK5l5Zj9g99mX3YWxhvW2bvYc2zYnjmPRZVq6rllar2f1CF3T6X6Bpml/0e/3J9TPobrJMMxZZte2Pk9uOdANx53YuzDGDgbaGJ12F+bYhKHrGA6HGI1GMAwD2mAAGozpdArbtv1nrmmaIPEHlmUvMqPx4fzg/nM4ew++j8Z7Xr1leLXGwLtZVLxSRfXavaHX6XS9UrnilSsVr7C15eU2855cKHh52Uf+Lhe2oKrqJ0EQrjC39x7MX756jb39R641vQvbOUR/YGJFFMFGooglViGk1hCXqF2Jg4sKWGY5LIfDuBoKcJfDLLaLxeMbmcwV5s7+w/mTpy+wf++xS+Dg/jNMrF1IqxL4aBQCCcV0DmlZRr5wHRubMlbT6xDiKfCCiDAXcSPRGIqKcpzxheWd/rzRHkNtjly1qYM+GeVqB5IkIcJTQjGOrFxANp9HOpOBKCWRTK8h6U8oxHxcjiY+FdZbo3l3MEWra7rt3gSdvo2deh+pVArRmAB/9pV4AvFkKoCnpYjG4xCJCIn42B9CtTmc+5JmZ+ySFO2ehRoJk8lEkJCN8AhxEYRo3XxYqsMn+O9Yjnc56re9fbKGfwsnQcJEIgGO54NB3In4H/wu3GkMv/ipSPhtIZ24VbXriqLohljW38FTFjsapvbXM0r9zU9Lx+djIKRUXzVjFz1t6vaHdKhHt9DuGpBpV9c3cshkfTaCOptb1NncZsC1zDqS0qorrV2DUiotEt4slt4qpcq7Yql8VFTKs58oijKr1WqnqPX6rNVqzegAB22j0QhqOuxH1Wr1PfV/s7S0dIF+P+YccZm49B/4488TZ34AacZ0bgGAKjcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="nullable values ide" title="" src="/static/516d52281c83ec212c41b011dada9e79/5a190/nullable-values-ide.png" srcset="/static/516d52281c83ec212c41b011dada9e79/772e8/nullable-values-ide.png 200w, /static/516d52281c83ec212c41b011dada9e79/e17e5/nullable-values-ide.png 400w, /static/516d52281c83ec212c41b011dada9e79/5a190/nullable-values-ide.png 800w, /static/516d52281c83ec212c41b011dada9e79/c1b63/nullable-values-ide.png 1200w, /static/516d52281c83ec212c41b011dada9e79/29007/nullable-values-ide.png 1600w, /static/516d52281c83ec212c41b011dada9e79/ba715/nullable-values-ide.png 2210w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>I recently started picking out different topics that I like to learn more about. One such thing I wanted to have a closer look at is “Nullable <strong>value types</strong> in C#“.</p> <p>We first need to understand the ‘why’ behind nullable value types.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>Let’s have a look at this class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> DaysSinceLastLogin <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> RegistrationTimestamp <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> DaysSinceLastLogin <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// Magic Number</span> RegistrationTimestamp <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>MinValue<span class="token punctuation">;</span> <span class="token comment">// Magic Number</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>When designing a class like this, we often come across situations where we want to default a value type to a <code class="language-text">null</code> or tell the program that there’s no actual value set.</p> <p>In the above example, since <code class="language-text">RegistrationTimestamp</code> and <code class="language-text">DaysSinceLastLogin</code> are value types, we can’t represent that an actual value exists, we need to default them to the magic numbers we chose. Two major drawbacks of this approach are, we wouldn’t know what the actual magic numbers represent and if we decide to change the value we need to do some plumbing to get around it.</p> <p>For a much cleaner implementation we can make use of C#‘s out of the box Nullable Value Types.</p> <h2>Definition of the Nullable Value Types</h2> <p>The MSDN definition goes as follows:</p> <blockquote> <p>“A nullable value type T? represents all values of its underlying <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/value-types">value type</a> T and an additional null value.”</p> </blockquote> <p>Let’s think of <code class="language-text">DaysSinceLastLogin</code> from the above example. Its type is <a href="https://docs.microsoft.com/en-us/dotnet/api/system.int32">Int32</a> which spans from -2,147,483,648 to 2,147,483,647. Obviously, in our context we don’t need negative numbers to represent how many days have elapsed since the last login of the user (we could have used <code class="language-text">uint</code> instead, but let’s not get bogged down with such details). For the sake of the argument, let’s look at how to can convert it to a Nullable value Type.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAECBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAHo1oSDLCv/xAAaEAEBAAIDAAAAAAAAAAAAAAABAgARAxIT/9oACAEBAAEFAvR3dpcO56Tjxiho/8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/AYV//8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/AaF//8QAGBABAQADAAAAAAAAAAAAAAAAABEBEDH/2gAIAQEABj8C1cuKj//EABkQAAIDAQAAAAAAAAAAAAAAAAABESExUf/aAAgBAQABPyF1q3g5JOhy9C+Yk2aLiYf/2gAMAwEAAgADAAAAEPM//8QAFxEAAwEAAAAAAAAAAAAAAAAAACExAf/aAAgBAwEBPxBdMjP/xAAXEQADAQAAAAAAAAAAAAAAAAAAATEh/9oACAECAQE/ENIOn//EABoQAQEAAwEBAAAAAAAAAAAAAAERACExQXH/2gAIAQEAAT8QvJ1g25fuBuAE1fMfyq3U9xbp23E4qsJ1jlz/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="nullable diagram" title="" src="/static/66c46039d1e72481382675e4f40707e8/4b190/nullable-diagram.jpg" srcset="/static/66c46039d1e72481382675e4f40707e8/e07e9/nullable-diagram.jpg 200w, /static/66c46039d1e72481382675e4f40707e8/066f9/nullable-diagram.jpg 400w, /static/66c46039d1e72481382675e4f40707e8/4b190/nullable-diagram.jpg 800w, /static/66c46039d1e72481382675e4f40707e8/e5166/nullable-diagram.jpg 1200w, /static/66c46039d1e72481382675e4f40707e8/b17f8/nullable-diagram.jpg 1600w, /static/66c46039d1e72481382675e4f40707e8/1b43a/nullable-diagram.jpg 1892w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>What this does is, essentially, it wraps our value type of <code class="language-text">int</code> in a <a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1?view=netcore-3.1">Nullable struct</a>; which allows us to assign it a integer value or a <code class="language-text">null</code></p> <h2>Refactoring our code with Magic Numbers</h2> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Nullable<span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span> DaysSinceLastLogin <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Nullable<span class="token punctuation">&lt;</span>DateTime<span class="token punctuation">></span></span> RegistrationTimestamp <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> DaysSinceLastLogin <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> RegistrationTimestamp <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Now we can simply assign <code class="language-text">null</code>s to our <code class="language-text">DaysSinceLastLogin</code> and <code class="language-text">RegistrationTimestamp</code>. But, C# is known for its nice syntactic sugar. Let’s sugarcoat it like so:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> DaysSinceLastLogin <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime<span class="token punctuation">?</span></span> RegistrationTimestamp <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This is beautiful, and behind the scenes it still wraps then in a <code class="language-text">Nullable&lt;T></code> struct. Another good thing is that, now we don’t need to explicitly specify <code class="language-text">null</code> to <code class="language-text">DaysSinceLastLogin</code> and <code class="language-text">RegistrationTimestamp</code>. We all know that <code class="language-text">null</code>s are a <a href="https://en.wikipedia.org/wiki/Tony_Hoare">billion dollar mistake</a>, but still, could have been worse 😁</p> <h2>Convenience properties and methods of Nullable<T></h2> <ul> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1.hasvalue?view=netcore-3.1"><code class="language-text">.HasValue</code></a> - <code class="language-text">true</code> if it has a value; false if null</li> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1.value?view=netcore-3.1"><code class="language-text">.Value</code></a> - Gives us the underlying value. However, this will throw an <code class="language-text">InvalidOperationException</code> if you try to access a value whose <code class="language-text">.Value</code> property is <code class="language-text">false</code></li> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1.getvalueordefault?view=netstandard-2.1"><code class="language-text">.GetValueOrDefault()</code></a> - Underlying value or default. Eg: for an integer whose value has not been set, will return <code class="language-text">0</code></li> </ul> <p>-<a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1.getvalueordefault?view=netstandard-2.1#System_Nullable_1_GetValueOrDefault__0_"><code class="language-text">.GetValueOrDefault(T)</code></a> - Value or a specified default value.</p> <p>In my next post we will look at how we can access and check for null values in C#. Until then ✌️</p> <h2>References</h2> <ol> <li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1?view=netcore-3.1">https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1?view=netcore-3.1</a></li> </ol><![CDATA[Deploying an ASP.NET Core App on Google Kubernetes Engine]]>https://www.sahansera.dev/deploying-aspcore-gcp/https://www.sahansera.dev/deploying-aspcore-gcp/Wed, 25 Dec 2019 00:00:00 GMT<p>I recently started playing around with the Google Cloud Platform. Since ASP.NET Core now runs on virtually any major OS, I wanted to give it a go with GCP’s Google Kubernetes Engine offering to see whether we can use it to host a .NET Core application.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>So here are the steps we are going to follow:</p> <ol> <li>Create/Clone a sample ASP.NET Core app</li> <li>Build the docker image</li> <li>Push the containerized app to Google Container Registry (GCR)</li> <li>Deploy the app to Google Kubernetes Engine (GKE)</li> </ol> <p>First things first, what are the prerequisites?</p> <ul> <li>VS Code or Visual Studio 2019</li> <li><a href="https://docs.microsoft.com/en-us/dotnet/core/install/sdk">.NET Core SDK v3.1+</a></li> <li><a href="https://cloud.google.com/sdk/docs/#install_the_latest_cloud_tools_version_cloudsdk_current_version">GCloud CLI Tools v274.0.0</a></li> <li>Docker and Kubectl</li> </ul> <h2>1. Create/Clone a sample ASP.NET Core app</h2> <p>You can grab the ASP.NET Sample app from my Github Repo:</p> <p><a href="https://github.com/sahansera/FoodAppCore" target="_blank">ASP.NET Core Food App</a></p> <p>This app has nothing to do with food though 😁 Let’s just say I was hungry when creating it.</p> <p>You can clone the above repo into your local machine and navigate to the <code class="language-text">FoodApp</code> folder and publish the app to <code class="language-text">app/</code> folder from a command line:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> FoodAppCore/FoodApp dotnet restore dotnet publish <span class="token parameter variable">-o</span> app </code></pre></div> <h2>2. Build the docker image</h2> <blockquote> <p>Don’t forget to replace <code class="language-text">PROJ_ID</code> with your GCP project’s ID</p> </blockquote> <p>Let’s build the docker image:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-t</span> gcr.io/PROJ_ID/food-app <span class="token builtin class-name">.</span></code></pre></div> <p>If you want to test out your docker build locally, try it by doing so:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">--rm</span> <span class="token parameter variable">-p</span> <span class="token number">8080</span>:8080 gcr.io/PROJ_ID/food-app:latest</code></pre></div> <h2>3. Push the containerized app to Google Container Registry (GCR)</h2> <p>Let’s push our image to GCR:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> push gcr.io/PROJ_ID/food-app</code></pre></div> <p>If you haven’t configured gcloud CLI to authenticate to GCR you need to run the following command (you need to this only once):</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">gcloud auth configure-docker</code></pre></div> <h2>4. Deploy the app to Google Kubernetes Engine (GKE)</h2> <p>Next, we need to create a cluster in GKE to deploy our app to:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">gcloud container clusters create foodapp-cluster --num-nodes<span class="token operator">=</span><span class="token number">3</span></code></pre></div> <p>Finally, let’s deploy it to GKE:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl create deployment food-app <span class="token parameter variable">--image</span><span class="token operator">=</span>gcr.io/PROJ_ID/food-app:latest</code></pre></div> <p>If the above command gives you an error, don’t forget to install <code class="language-text">kubectl</code> for <strong>gcloud</strong>:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">gcloud components <span class="token function">install</span> kubectl</code></pre></div> <p>Wait, now the app is deployed, how can our clients access it? This final command will expose our app through a load balancer binding port <code class="language-text">8080</code> to <code class="language-text">80</code></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">kubectl expose deployment food-app <span class="token parameter variable">--type</span><span class="token operator">=</span><span class="token string">"LoadBalancer"</span> <span class="token parameter variable">--port</span><span class="token operator">=</span><span class="token number">80</span> --target-port<span class="token operator">=</span><span class="token number">8080</span></code></pre></div> <p>Well, that’s it! you have your app running in GKE! If you are wondering how to access it, run a <code class="language-text">kubectl get service</code> and grab its external IP and paste it in a browser.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsklEQVR42qVSW27VMBTMokBFQnxUSIAKiC2wFB4VEoIK9Zt9sAz4obQqtwLuhTq5cRI78Ts3GY4dTP+QAEujc+z4TGYmKW4eMtx+WeHRcY27R1s8eEP1dYV9Ort3VOOA9jeel7j2pMT1p1QJe7S/9aLEnVdbHBwzPHzLcf/Zezw+fIfiw8kKp5/XOLvYLFhtwOoeP7YSH8+/4mJdQugR37nDp28arKW6tlhdGnBpcVlL1MKi6QPBowhOw2oJoyTmncc0emAKCN5h6Ds4q+GtgXeW+lgNHPXeWoR4RvPzPCGvgjUem8rgCzNJiVABYgiwzmMYBoQwwvmAcRypD/Bh6T2dLfBEOGMmMiooGqHAuwFl3WHLBZRx0JaGdrs02DQN2qZFVVXo+z6RRiARzNjRPSkltNaLQmM0XZQ0xGGNIRIaIEQbrTDopELJqzTUdRSBc0l5fFFd14mIMZaUJkIhRLqcHy52FhtKGwxKoTU9Wc0RhKTKGJPI455zfqUw2sik2U7ENF0F/TersPS1IrsiJVFZxm/CX4H/aaWPMs85Q5PCjplE8mz5nxVGwkiQf4v/tfwTxVJAXwMEeXQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="GKE Cluster" title="" src="/static/15d16f0c98232782956fdf212f487141/5a190/gke-cluster.png" srcset="/static/15d16f0c98232782956fdf212f487141/772e8/gke-cluster.png 200w, /static/15d16f0c98232782956fdf212f487141/e17e5/gke-cluster.png 400w, /static/15d16f0c98232782956fdf212f487141/5a190/gke-cluster.png 800w, /static/15d16f0c98232782956fdf212f487141/c1b63/gke-cluster.png 1200w, /static/15d16f0c98232782956fdf212f487141/29007/gke-cluster.png 1600w, /static/15d16f0c98232782956fdf212f487141/19ab6/gke-cluster.png 1664w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>References</h3> <ol> <li><a href="https://cloud.google.com/kubernetes-engine/docs/tutorials/hello-app">https://cloud.google.com/kubernetes-engine/docs/tutorials/hello-app</a></li> <li><a href="https://cloud.google.com/kubernetes-engine/docs/troubleshooting">https://cloud.google.com/kubernetes-engine/docs/troubleshooting</a></li> </ol><![CDATA[Demystifying Async & Await in C#]]>https://www.sahansera.dev/demystifying-csharp-tasks/https://www.sahansera.dev/demystifying-csharp-tasks/Mon, 02 Dec 2019 00:00:00 GMT<p>You might have come across the two keywords <code class="language-text">async</code> and <code class="language-text">await</code> many times while working on .NET projects. Although it’s a fascinating thing to use, it’ll also be beneficial in knowing what happens under the hood when dealing with such code.</p> <p>Before getting bogged down with in-depth details what’s what, we need to clarify some common misconceptions about Tasks and Threads used in C#.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <h2>🤔 Tasks Vs Threads</h2> <p>Simply put, Tasks are not Threads. If you have ever worked with <a href="https://en.wikipedia.org/wiki/Futures_and_promises">Promises</a> in other languages such as JavaScript, you will be quite comfortable in using them. Tasks are essentially Promises, which would be completed in a later point in time which lets you deal with the result returned from an asynchronous action. This also means that Tasks can be faulted and queried to know whether they are completed or not. Threads, on the other hand, are a more lower-level implementation of OS-level code executions.</p> <p>It is really important to understand that tasks are <strong>not an abstraction over threads</strong>, and we should think of tasks as an abstraction over some work that’s intended to be happening asynchronously. In summary:</p> <ul> <li> <p>Tasks are not Threads</p> </li> <li> <p>Tasks are similar to Promises providing you with a clean API to handle async code</p> </li> <li> <p>Tasks do not guarantee parallel execution</p> </li> <li> <p>Tasks can be explicitly requested to run on a separate thread via the Task.Run API.</p> </li> <li> <p>Tasks are scheduled to be run by a TaskScheduler</p> </li> </ul> <h2>🔮 The Magic of the ‘await’ keyword</h2> <p>As <a href="https://blog.stephencleary.com/2012/02/async-and-await.html">Stephen Cleary</a> clearly mentions in his excellent blog post, async keyword only enables await. So, an async method would simply run like any other synchronous method until it sees an await keyword.</p> <p>The await keyword is where the magic happens. It gives back control to the caller of the method that performed await and it ultimately allows an IO-bound (eg. calling a web service) or a CPU bound task (eg: such as a CPU-heavy calculation) to be responsive. All async and await does is providing us with some nice syntactic sugar to write cleaner code.</p> <p>Let’s consider the following simple example:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> <span class="token function">DownloadString</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> url<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> request <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> download <span class="token operator">=</span> <span class="token keyword">await</span> request<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> download<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The same code can be equivalent to what gets unfolded into under the hood:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>String<span class="token punctuation">></span></span> <span class="token function">DownloadString</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> url<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> request <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> download <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">ContinueWith</span><span class="token punctuation">(</span>http <span class="token operator">=></span> http<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> download<span class="token punctuation">.</span><span class="token function">Unwrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, it pretty much creates a chain of tasks that needs to execute and gets queued up with the TaskScheduler. The above code is somewhat okay, but it is also beneficial to always use the language constructs provided by C# without doing everything manually!</p> <p>The sequence of how this gets executed is as follows:</p> <ol> <li> <p>Calling <code class="language-text">client.GetAsync(url)</code> will create a request behind the scenes by calling lower-level .NET libraries.</p> </li> <li> <p>Some part of its underlying code may run synchronously until it delegates its work from the networking APIs to the OS</p> </li> <li> <p>At this point, a Task gets created and gets bubbled up to the original caller of the asynchronous code. This is still an unfinished Task!</p> </li> <li> <p>During this time, the caller may query the status of the Task</p> </li> <li> <p>Once the network request is completed by the OS level, the response gets returned via an IO completion port and CLR gets notified by a CPU interrupt that the work is done</p> </li> <li> <p>The response gets scheduled to be handled by the next available thread to unwrap the data</p> </li> <li> <p>The remainder of your async method continues running synchronously</p> </li> </ol> <p>The key takeaway here is that there won’t be any dedicated threads to complete a Task. Which also means that your task continuation/completion isn’t guaranteed to run on the same thread that started it.</p> <h2>☠️ Common gotchas when using async and await</h2> <p>You would be tempted to parallelise everything that you see in your code when you start using async and await (well, I did 😁). But there are some pitfalls that you should avoid in your code.</p> <ul> <li><strong>Making methods void methods async void</strong></li> </ul> <p>This is the first mistake I did when I got my hands dirty with async-await! I had a void method which called an async method within itself. Since you can’t use await without making the calling method to be async, I made the caller method async void !</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GetResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">SomeAsyncMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The problem here is, the caller of the GetResult method doesn’t have any kind of control over the result of SomeAsyncMethod() This also causes other side-effects such as lack of a call stack to debug if something goes wrong, application crashes if an exception occurs etc.</p> <p>However, there are times that you can’t avoid this though. If you open up AsyncWpfApp in our code <a href="https://github.com/sahansera/c-sharp-tasks">example</a> you would find code like the one below:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AsyncParallelBtn_Click</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> sender<span class="token punctuation">,</span> <span class="token class-name">RoutedEventArgs</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token class-name"><span class="token keyword">var</span></span> output <span class="token operator">=</span> <span class="token keyword">await</span> RunAsyncParallelDemo<span class="token punctuation">.</span><span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token range operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s not necessarily a problem if you find code like the one above since you can’t change the method signatures of the event handlers in WPF.</p> <blockquote> <p>Nevertheless, it’s recommended to avoid using async void where possible.</p> </blockquote> <ul> <li><strong>Ignoring or Forgetting to ‘await’</strong></li> </ul> <p>It is an easy mistake to forget to await on an async task since the compiler would happily compile your code without complaining. In the below example we simply call an async function without awaiting on it.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Caller</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Before"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Deliberately forgetting to await</span> <span class="token function">DoSomeBackgroundWorkAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"After"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><code class="language-text">DoSomeBackgroundWorkAsync()</code> method will return a Task (either a Task or a Task<T> depending on the implementation) to the caller method without actually executing it.</p> <blockquote> <p>Therefore, be mindful when you are dealing with async methods (especially with third party libraries) not to forget to use await</p> </blockquote> <ul> <li><strong>Blocking async tasks with .Result() and .Wait()</strong></li> </ul> <p>This is another common trap developers jump into (unknowingly 😋) when they need to run some async code in a synchronous method. Let’s consider the below example:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DoSomeWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">DoAsyncWork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Result</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>At a glance, it may look pretty convenient to use the .Result() method. However, this could cause serious <a href="https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html">deadlock problems</a>. In order to avoid this, you should usually make your caller method async. This could be lot of work as it creates a cascading effect on your changes. But that’s usually preferable.</p> <p>More ways to write non-blocking code as clearly depicted in MSDN:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe3YNA//xAAWEAEBAQAAAAAAAAAAAAAAAAABIDH/2gAIAQEAAQUCJM//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAaEAEBAAIDAAAAAAAAAAAAAAABABBBITFR/9oACAEBAAE/IU+22Hi3Pbi//9oADAMBAAIAAwAAABCzz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABkQAQADAQEAAAAAAAAAAAAAAAEAESExof/aAAgBAQABPxBD6yOLjQvINKPYLTY2m3jNC9n/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tasks list table" title="" src="/static/04af68439cf5b79b4ff6f78ebe9278ff/4b190/tasks-list-table.jpg" srcset="/static/04af68439cf5b79b4ff6f78ebe9278ff/e07e9/tasks-list-table.jpg 200w, /static/04af68439cf5b79b4ff6f78ebe9278ff/066f9/tasks-list-table.jpg 400w, /static/04af68439cf5b79b4ff6f78ebe9278ff/4b190/tasks-list-table.jpg 800w, /static/04af68439cf5b79b4ff6f78ebe9278ff/b81d4/tasks-list-table.jpg 1090w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <blockquote> <p>It’s recommended to make the caller async where you would end up making a lot of cascading changes to your codebase although it could be painful if you are working on a legacy project.</p> </blockquote> <h3><strong>Conclusion</strong></h3> <p>I hope you enjoyed this article as much as I did writing it. The bottom line is that Task is almost always the best option; it provides a much more powerful API and avoids wasting OS threads.</p> <p>Ready for more stuff? Head over to my Github repo to work through some sample code.</p> <p><a href="https://github.com/sahansera/c-sharp-tasks">https://github.com/sahansera/c-sharp-tasks</a></p> <h3>Suggestions or Found a Bug?</h3> <p>If you think there’s a misunderstanding in any of the things I explained, please feel free to comment down below :) Also, if you found a bug or have any suggestions, please open a pull request in my Github repo. Cheers!</p> <h3>References</h3> <ul> <li> <p><a href="https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl">https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl</a></p> </li> <li> <p><a href="https://blog.stephencleary.com/2012/02/async-and-await.html">https://blog.stephencleary.com/2012/02/async-and-await.html</a></p> </li> <li> <p><a href="https://markheath.net/post/async-antipatterns">https://markheath.net/post/async-antipatterns</a></p> </li> <li> <p><a href="https://msdn.microsoft.com/en-us/magazine/jj991977.aspx">https://msdn.microsoft.com/en-us/magazine/jj991977.aspx</a></p> </li> </ul><![CDATA[How to Pre-Configure Services Before Registering with ASP.NET Core's Built-in Service Container]]>https://www.sahansera.dev/preconfiguring-aspcore-services/https://www.sahansera.dev/preconfiguring-aspcore-services/Sun, 01 Dec 2019 00:00:00 GMT<p>In ASP.NET, if you are using the default DI service container, registering services is done in the <code class="language-text">ConfigureServices(IServiceCollection)</code> method in your <code class="language-text">Startup.cs</code> class.</p> <p>In some special situations, you may want to take control of the instantiation of some service. The idea here is to inject any other dependencies that a service would require before registering with your DI container; ASP.NET Core’s default service container in our example.</p> <div data-ea-publisher="sahanseradev" data-ea-type="text"></div> <p>Here is an example:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IComputerVisionClient<span class="token punctuation">,</span> ComputerVisionClient<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>serviceProvider<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name">ConfigService</span> configService <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ConfigService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ComputerVisionClient</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ApiKeyServiceClientCredentials</span><span class="token punctuation">(</span>configService<span class="token punctuation">.</span>SubscriptionKey<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Http<span class="token punctuation">.</span>DelegatingHandler<span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Endpoint <span class="token operator">=</span> configService<span class="token punctuation">.</span>Endpoint <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The above code sits in your <code class="language-text">ConfigureServices(IServiceCollection services)</code> method when registering your service.</p> <p>In simple terms, it instantiates <code class="language-text">ComputerVisionClient</code> by using another component. We can obtain a reference to another service <code class="language-text">ConfigService</code> which is used to keep our configurations in.</p> <p>Now you should be able to inject that service in your controllers/services 😊</p>