<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kosala (KP) Perera</title>
    <description>The latest articles on DEV Community by Kosala (KP) Perera (@kosperera).</description>
    <link>https://dev.to/kosperera</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F750305%2F9a2689e6-aac6-4e54-888c-482ebecb9b32.JPG</url>
      <title>DEV Community: Kosala (KP) Perera</title>
      <link>https://dev.to/kosperera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kosperera"/>
    <language>en</language>
    <item>
      <title>Migrating Service Fabric apps to .NET 6</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Mon, 10 Apr 2023 09:11:46 +0000</pubDate>
      <link>https://dev.to/kosperera/migrating-service-fabric-apps-to-net-6-2bee</link>
      <guid>https://dev.to/kosperera/migrating-service-fabric-apps-to-net-6-2bee</guid>
      <description>&lt;p&gt;Migrating Service Fabric apps to the .NET 6 suck! Not only is there no opensource community engagement, but there's &lt;a href="https://youtu.be/LjClUEu9duk?t=2207" rel="noopener noreferrer"&gt;no same-day support for new .NET versions&lt;/a&gt; at all. What a SHAM! There's nothing I can do about that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv8p6k8jle40w9kw6rt0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv8p6k8jle40w9kw6rt0.gif" alt="2013-06-13_frustration_obliviousness_sales personnel_software_third party library_new version_windows_engineering" width="900" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Alertbox Inc. (@&lt;a href="https://dev.to/alertbox"&gt;alertbox&lt;/a&gt;) pipelines page is open on my work PC and one of the CI pipelines is broken. For the record, Alertbox has ~60 microservices. They require Service Fabric / .NET Core combo to build and release. I'm going to go ahead and say that they use &lt;code&gt;windows-latest&lt;/code&gt; on hosted agents. And the &lt;a href="https://devblogs.microsoft.com/devops/upgrade-of-net-agent-for-azure-pipelines" rel="noopener noreferrer"&gt;Azure Pipelines team is upgrading the .NET used by agents&lt;/a&gt;, so they have no way of running pipelines without &lt;a href="https://dev.to/kosalanuwan/selecting-what-net-version-is-to-use-14n1"&gt;adding the UseDotNet task to select what .NET version is to use&lt;/a&gt; in the pipeline agents. Maybe that's a plausible fix, but they recommend keeping the source code up-to-date with the current LTS version of .NET. So what does each microservice have that must be upgraded for .NET 6?&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Fabric app
&lt;/h3&gt;

&lt;p&gt;The first thing we're going to need is the &lt;a href="https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-get-started" rel="noopener noreferrer"&gt;latest update for Service Fabric&lt;/a&gt;. And that means runtime, SDK, and the tools to &lt;a href="https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-quick-start#create-a-stateless-service" rel="noopener noreferrer"&gt;create, build, and run reliable service apps on a local cluster&lt;/a&gt;. Unfortunately, &lt;a href="https://blogs.iis.net/iisteam/web-platform-installer-end-of-support-feed" rel="noopener noreferrer"&gt;the IIS team recently sunset the Web PI&lt;/a&gt;, so the Service Fabric team bundled the latest releases with Visual Studio, and I'm going to have to update Visual Studio to download them for me.&lt;/p&gt;

&lt;p&gt;The other thing we're going to need is a &lt;em&gt;global.json&lt;/em&gt; file since there are several .NET versions installed already in my work PC, so it's definitely necessary to break out that &lt;a href="https://dev.to/kosalanuwan/selecting-what-net-version-is-to-use-14n1"&gt;&lt;code&gt;dotnet&lt;/code&gt; driver and create a &lt;em&gt;global.json&lt;/em&gt; file to select &lt;code&gt;net6.0&lt;/code&gt; SDK version&lt;/a&gt; that is in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
&lt;span class="c"&gt;# Show installed SDKs.&lt;/span&gt;
dotnet &lt;span class="nt"&gt;--list-sdks&lt;/span&gt;
&lt;span class="c"&gt;# Add a global.json file&lt;/span&gt;
dotnet new globaljson &lt;span class="nt"&gt;--sdk-version&lt;/span&gt; 6.0.403
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm a bit of a control freak, not gonna lie. I like the idea of knowing what's happening underneath. It's slightly obnoxious that &lt;a href="https://github.com/microsoft/service-fabric/issues/1290#issuecomment-1489868105" rel="noopener noreferrer"&gt;Service Fabric project templates are not opensourced&lt;/a&gt; yet. I can &lt;a href="https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-quick-start#create-a-stateless-service" rel="noopener noreferrer"&gt;create a new reliable service app on .NET 6&lt;/a&gt; to figure out what's new in the scaffolding project.&lt;/p&gt;

&lt;p&gt;The first on the list is the entry project &lt;em&gt;.sfproj&lt;/em&gt;. They use a &lt;a href="https://github.com/microsoft/service-fabric/issues/885" rel="noopener noreferrer"&gt;non-SDK-style project template&lt;/a&gt; with a bunch of &lt;em&gt;.xml&lt;/em&gt; files for configuration but with no C# code. They require &lt;a href="https://www.nuget.org/packages/Microsoft.VisualStudio.Azure.Fabric.MSBuild" rel="noopener noreferrer"&gt;&lt;em&gt;Fabric.MSBuild&lt;/em&gt; nuget&lt;/a&gt; to build and package Service Fabric apps. Unfortunately, the &lt;code&gt;dotnet add package&lt;/code&gt; command won't update dependencies since they only support &lt;code&gt;&amp;lt;PackageReference /&amp;gt;&lt;/code&gt; and non-SDK-style project template uses the &lt;em&gt;package.config&lt;/em&gt; file to manage dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- packages.config --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.VisualStudio.Azure.Fabric.MSBuild"&lt;/span&gt;
         &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.7"&lt;/span&gt;
         &lt;span class="na"&gt;targetFramework=&lt;/span&gt;&lt;span class="s"&gt;"net48"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can update the &lt;em&gt;package.config&lt;/em&gt; to use the latest versions but there's more to do. The &lt;em&gt;.sfproj&lt;/em&gt; still points to the older build tools. And you do want to use the latest version to build and package.&lt;/p&gt;

&lt;p&gt;Updating the &lt;em&gt;.sfproj&lt;/em&gt; is extraordinarily uncomplicated. All we need to do is figure out the references to the &lt;em&gt;Fabric.MSBuild&lt;/em&gt; package and replace the version numbers, say, find all and replace the version number to &lt;code&gt;1.7.7&lt;/code&gt; from the version that is already been used. Then I can replace the &lt;code&gt;&amp;lt;TargetFrameworkVersion /&amp;gt;&lt;/code&gt; version with the .NET Framework 4.8 and we're done.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;.xml&lt;/em&gt; configurations need no changes, so we can ignore them for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services and dependencies
&lt;/h3&gt;

&lt;p&gt;Next on the list are reliable services. This covers a lot of ground since Alertbox uses multiple stateless services bundled into a single Service Fabric app, say, Web APIs and Console apps, and they target &lt;code&gt;netcoreapp3.1&lt;/code&gt; to build and run, so we're going to have to upgrade them to &lt;code&gt;net6.0&lt;/code&gt;. I can replace &lt;code&gt;&amp;lt;DefaultNetCoreTragetFramework /&amp;gt;&lt;/code&gt; in the &lt;em&gt;Directory.Build.props&lt;/em&gt; file like they do on &lt;a href="https://github.com/search?q=org%3Adotnet+%3CTargetFrameworks%3E+language%3AXML&amp;amp;type=code&amp;amp;l=XML" rel="noopener noreferrer"&gt;@dotnet repos on GitHub&lt;/a&gt;, but there's more to it. They need to upgrade dependencies.&lt;/p&gt;

&lt;p&gt;Figuring out dependencies to upgrade for all the &lt;em&gt;.csproj&lt;/em&gt; projects that are in the code repo is basically the same as listing installed SDKs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
&lt;span class="c"&gt;# First restore packages&lt;/span&gt;
dotnet restore &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="c"&gt;# Then scan for outdated packages&lt;/span&gt;
dotnet list package &lt;span class="nt"&gt;--outdated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can check and review each package to decide whether or not to upgrade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
dotnet add package Azure.Messaging.ServiceBus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's slightly obnoxious that they only let you check for the status, so I'm going to have to &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-add-package#examples" rel="noopener noreferrer"&gt;upgrade outdated packages&lt;/a&gt; one at a time, say, migrating Web APIs and the Automapper configurations to the latest versions by going thru what's new and migration docs. It's taking a few tries to get the unit tests green since it demands highly intense concentration on breaking changes of each package upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;Visual Studio can build from source but it keeps failing to deploy the Service Fabric app onto the local cluster. I'm going to go ahead and say that they use &lt;em&gt;Deploy-FabricApplication.ps1&lt;/em&gt; powershell script in the &lt;em&gt;.sfproj&lt;/em&gt; to deploy, so replacing it from the script in that new boilerplate code to take care of it seems like the right answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwcfylounkt0mxm25ddj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwcfylounkt0mxm25ddj.gif" alt="2010-08-21_product safety testing_angry_rodney_swear_ship_bandage" width="900" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I can build and run the app from source and Visual Studio will do the rest for me. But there are more problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/microsoft/service-fabric/issues/1356" rel="noopener noreferrer"&gt;The Visual Studio debugger won't attach.&lt;/a&gt; This may be difficult. I'll come back to this later.&lt;/p&gt;

&lt;p&gt;This may not be an issue with Service Fabric since it works on my colleague's PC, so it's definitely necessary to diff both development environments to figure out what SDK version to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/microsoft/service-fabric/issues/1356#issuecomment-1362622324" rel="noopener noreferrer"&gt;Service Fabric creates dynamic proxy classes for service remoting,&lt;/a&gt; but the .NET runtime team has &lt;a href="https://github.com/dotnet/runtime/issues/62977#issuecomment-1186149824" rel="noopener noreferrer"&gt;turned off metadata generation for dynamic classes&lt;/a&gt; in .NET 6, so it would be impossible to tell Visual Studio debugger how to load metadata for dynamic proxy classes that Service Fabric creates. The .NET team has a &lt;a href="https://github.com/dotnet/runtime/pull/77322" rel="noopener noreferrer"&gt;release that always generates metadata&lt;/a&gt;, so a little &lt;code&gt;winget install&lt;/code&gt; magic is all that's necessary to update the .NET 6 installed locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
&lt;span class="c"&gt;# Display the status of the installed SDK.&lt;/span&gt;
dotnet sdk check
&lt;span class="c"&gt;# Install/upgrade the SDK via Windows Package Manager.&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;Microsoft.DotNet.SDK.6
&lt;span class="c"&gt;# Show installed SDKs.&lt;/span&gt;
dotnet &lt;span class="nt"&gt;--list-sdks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there's more to do. We need to update the &lt;em&gt;global.json&lt;/em&gt; with the new SDK version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;global.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6.0.407"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowPrerelease"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollForward"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latestMinor"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can build and run from source and Visual Studio debugger will do the rest for me. And if version &lt;code&gt;&amp;gt;=6.0.407&lt;/code&gt; is not installed locally, nothing gets compiled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipelines
&lt;/h3&gt;

&lt;p&gt;I'm not exactly sure when the Azure Pipelines team is going to remove .NET 6 once it reaches end-of-life support, so I'm going to have to update the build steps to &lt;a href="https://dev.to/kosalanuwan/selecting-what-net-version-is-to-use-14n1"&gt;always select the .NET version used in the code repo&lt;/a&gt;. All I need to do is push all changes to a feature branch to raise a pull request and the branch policies will trigger the CI pipeline for me. Excellent!&lt;/p&gt;

&lt;p&gt;/KP&lt;/p&gt;

</description>
      <category>azure</category>
      <category>servicefabric</category>
      <category>dotnet</category>
      <category>upgrade</category>
    </item>
    <item>
      <title>Selecting .NET version to use</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Tue, 04 Apr 2023 06:58:12 +0000</pubDate>
      <link>https://dev.to/kosperera/selecting-what-net-version-is-to-use-14n1</link>
      <guid>https://dev.to/kosperera/selecting-what-net-version-is-to-use-14n1</guid>
      <description>&lt;p&gt;I'm a little procrastinated, not gonna lie. The Alertbox Inc. (@&lt;a href="https://dev.to/alertbox"&gt;alertbox&lt;/a&gt;) pipelines page is open on my work PC and one of the CI pipelines is broken. What a SHAM!&lt;/p&gt;

&lt;p&gt;It's a little weird since the pipeline logs show the target .NET version &lt;code&gt;net5.0&lt;/code&gt; isn't available, but the changeset seems to work locally. Not only are there no tests failing, but there are no errors at all. For the record, Alertbox has several hundred git repos and &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&amp;amp;tabs=yaml#use-a-microsoft-hosted-agent" rel="noopener noreferrer"&gt;pipelines that they run quite often on hosted agents&lt;/a&gt;. The truth is they have not upgraded the .NET used by the code repos to the &lt;a href="https://versionsof.net/core/" rel="noopener noreferrer"&gt;latest version&lt;/a&gt;, so they require several .NET versions installed locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jxvegzmuvw6li5l06cr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jxvegzmuvw6li5l06cr.gif" alt="2017-06-10_budget_money_spending_projects_upgrades_technology_software_engineering" width="900" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe they don't have &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core#lifecycle" rel="noopener noreferrer"&gt;.NET end-of-life support versions&lt;/a&gt; on hosted agents anymore? Yup! &lt;a href="https://devblogs.microsoft.com/devops/upgrade-of-net-agent-for-azure-pipelines/" rel="noopener noreferrer"&gt;The Azure Pipeline team is upgrading the .NET used by pipeline agents&lt;/a&gt;. I almost want to upgrade the .NET used by the code repo to the latest LTS version, say, replacing &lt;code&gt;TargetFramework&lt;/code&gt; to &lt;code&gt;net6.0&lt;/code&gt; from &lt;code&gt;net5.0&lt;/code&gt; and running &lt;code&gt;dotnet list package --outdated&lt;/code&gt; to see what packages are to upgrade. You'd be amazed how problematic is it to have several .NET versions running locally since &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/versions/selection#the-sdk-uses-the-latest-installed-version" rel="noopener noreferrer"&gt;the &lt;code&gt;dotnet&lt;/code&gt; driver selects the latest SDK installed locally&lt;/a&gt; for commands and not from the entry project / solution that's in, so it would be difficult to figure out what packages are to upgrade without going thru all of them one at a time completely. And .NET Core 3.1 will be end-of-life support soon, so that means more code repos to upgrade.&lt;/p&gt;

&lt;p&gt;It's not such a great idea and probably not even pragmatic to go thru all of that &lt;em&gt;Jazz&lt;/em&gt; and upgrade each one since you can only boil so many oceans at the same time, so it's definitely necessary to break out that pipeline and modify the YAML script to install any end-of-life support .NET versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#.azuredevops/steps/build.yaml&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Install .NET from global.json&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;packageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sdk'&lt;/span&gt;
    &lt;span class="na"&gt;useGlobalJson&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All I need to do is add a &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/use-dotnet-v2?view=azure-pipelines" rel="noopener noreferrer"&gt;UseDotNet task&lt;/a&gt; as a prerequisite for the build step and it will install .NET on pipelines for me. But there are more problems. They require a &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#globaljson-schema" rel="noopener noreferrer"&gt;&lt;em&gt;global.json&lt;/em&gt; file&lt;/a&gt; to figure out what SDK version is to install.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;global.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5.0.408"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowPrerelease"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollForward"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latestMinor"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can run &lt;code&gt;dotnet --list-sdks&lt;/code&gt; to get the SDK version that is running on my work PC, and then run &lt;code&gt;dotnet new globaljson&lt;/code&gt; to create the &lt;em&gt;global.json&lt;/em&gt; file to mention that SDK version. Then I can &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/manage-pipelines-with-azure-cli?view=azure-devops#example" rel="noopener noreferrer"&gt;run the pipeline&lt;/a&gt; and the &lt;code&gt;UseDotNet@2&lt;/code&gt; task will take care the rest for me. Excellent! Moving right along.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trigger the pipeline&lt;/span&gt;
az pipelines run &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--branch&lt;/span&gt; kp/fix-23-dotnet-not-found &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--folder-path&lt;/span&gt; ci &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--name&lt;/span&gt; ca-cocktails &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--output&lt;/span&gt; table &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for build status&lt;/span&gt;
az pipelines runs list &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--top&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--status&lt;/span&gt; all &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--branch&lt;/span&gt; kp/fix-23-dotnet-not-found &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's taking a few tries to &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/runs?view=azure-devops#example" rel="noopener noreferrer"&gt;get the pipeline green&lt;/a&gt; since &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/breaking-changes" rel="noopener noreferrer"&gt;some of the dependencies and C# features&lt;/a&gt; don't seem to recognize anymore since &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-5#c-updates" rel="noopener noreferrer"&gt;.NET 5 only supports C# 9&lt;/a&gt; and not latest version. This may be difficult. I'll come back to this later!&lt;/p&gt;

&lt;p&gt;/KP&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Development containers with Docker</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Wed, 22 Mar 2023 08:54:30 +0000</pubDate>
      <link>https://dev.to/kosperera/homogenize-your-dev-environments-with-docker-gio</link>
      <guid>https://dev.to/kosperera/homogenize-your-dev-environments-with-docker-gio</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Docker Team has deprecated native dev environments feature. They &lt;a href="https://github.com/docker/awesome-compose/pull/488" rel="noopener noreferrer"&gt;removed references&lt;/a&gt; and archived the &lt;a href="https://github.com/docker/dev-environments" rel="noopener noreferrer"&gt;@docker/dev-environments&lt;/a&gt; repo.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I need to build an eng-practice wiki for Alertbox Inc. (@&lt;a href="https://dev.to/alertbox"&gt;alertbox&lt;/a&gt;) and most of these contents are pretty outdated word docs and slide decks. It's not such a great idea and probably not even fun to begin with since, Alertbox has strict security policies on work PC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2f800qoyh4lygq3hqld.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2f800qoyh4lygq3hqld.gif" alt="Teaching capitalism for the communist mud planning pigs from the Elbonia - Dilbert by Scott Adams" width="1200" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the record, not only are there no administrative rights for developers, but there's no permission at all. The PC is a &lt;em&gt;Brick&lt;/em&gt;, as in barely anything you can do with it. The truth is, I'm way too lazy to go thru their intense approval process to install and configure a dev environment. I need to keep the &lt;em&gt;Hacktivation Effort&lt;/em&gt; to minimal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.pluralsight.com/library/courses/docker-web-development/table-of-contents" rel="noopener noreferrer"&gt;Dan Wahlin's Docker for Web Developers course&lt;/a&gt; is open on my work PC and he is showing how to map the source code into a docker container. Good idea Mr. Wahlin!&lt;/p&gt;

&lt;p&gt;I'm not exactly sure how the Alertbox's wiki is going to fit into this whole thingy, but I like the idea of developing inside a container. It gives the whole thing a very &lt;em&gt;Homogenize&lt;/em&gt; feel since each teammate's work PC will be more implicit than, say, installing all the prerequisite dev tools on their work PC like we typically do. Easy enuf, except, I need to bake in development environments as part of source repos.&lt;/p&gt;

&lt;p&gt;The first thing we're going to need is a markdown parser to generate the site like they do in &lt;a href="https://learn.microsoft.com/en-us/docs/" rel="noopener noreferrer"&gt;Microsoft Docs&lt;/a&gt; sites. And that means &lt;a href="https://docsify.js.org/#/quickstart" rel="noopener noreferrer"&gt;Docsify&lt;/a&gt;. They are basically similar to &lt;a href="https://jekyllrb.com/docs/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;, but with no build steps. All I need to do is fork their &lt;a href="https://github.com/docsifyjs/docsify-template" rel="noopener noreferrer"&gt;docsify-template&lt;/a&gt; repo and we're good to go!&lt;/p&gt;

&lt;p&gt;The other thing we're going to need is a dead simple &lt;a href="https://hub.docker.com/_/node/tags?page=1&amp;amp;name=bullseye" rel="noopener noreferrer"&gt;node container&lt;/a&gt; to run docsify locally, so a little &lt;code&gt;docker run&lt;/code&gt; magic is all that's necessary to &lt;a href="https://docs.docker.com/engine/reference/commandline/run/#volume" rel="noopener noreferrer"&gt;sync the repo contents&lt;/a&gt; and spawn the container. Then I can &lt;a href="https://www.npmjs.com/package/serve" rel="noopener noreferrer"&gt;run &lt;code&gt;npx serve&lt;/code&gt; to spin up an HTTP server&lt;/a&gt; to preview on localhost and docsify will parse the markdown to generate the site on the fly for me. Kids' stuff!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; stoic_goldstine &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# clean up&lt;/span&gt;
           &lt;span class="nt"&gt;-w&lt;/span&gt; /workspaces &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# working directory&lt;/span&gt;
           &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/workspaces"&lt;/span&gt; &lt;span class="c"&gt;# sync immediate file changes to container&lt;/span&gt;
           &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# expose port 3000&lt;/span&gt;
           node:lts-bullseye &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# node image&lt;/span&gt;
           npx serve &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--listen&lt;/span&gt; 3000 &lt;span class="c"&gt;# run the http server in port 3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, docker runs in the foreground and requires another Terminal to stop to and remove the container. It would be impossible to run in &lt;a href="https://docs.docker.com/engine/reference/run/#detached--d" rel="noopener noreferrer"&gt;detached &lt;code&gt;-d&lt;/code&gt; mode&lt;/a&gt; without giving away docker &lt;a href="https://docs.docker.com/engine/reference/run/#clean-up---rm" rel="noopener noreferrer"&gt;clean up &lt;code&gt;--rm&lt;/code&gt; option&lt;/a&gt; completely. And you do want docker to automatically clean up when the container exit. It's time to switch to &lt;a href="https://docs.docker.com/desktop/dev-environments/" rel="noopener noreferrer"&gt;Docker Dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuywsfv1zfuwbwdpli77j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuywsfv1zfuwbwdpli77j.png" alt="Docker dev environment in action - Docker Desktop" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I prefer &lt;code&gt;docker dev&lt;/code&gt; over &lt;code&gt;docker&lt;/code&gt;. They're technically the same as &lt;code&gt;docker compose&lt;/code&gt;, except &lt;code&gt;docker dev&lt;/code&gt; is specifically for dev environments. It also requires &lt;a href="https://docs.docker.com/get-started/08_using_compose/#create-the-compose-file" rel="noopener noreferrer"&gt;a compose YAML file to configure the container&lt;/a&gt;, so you don't need to memorize which image to pull, what ports to expose, the working directory to map, args to override, et al. All I need to do is break out the same &lt;code&gt;docker&lt;/code&gt; command that I use to spawn the container in detached mode to &lt;a href="https://docs.docker.com/desktop/dev-environments/set-up/" rel="noopener noreferrer"&gt;create a &lt;code&gt;compose-dev.yaml&lt;/code&gt; file&lt;/a&gt; and we're all set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./compose-dev.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:lts-bullseye&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stoic_goldstine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/workspaces&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./:/workspaces&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/sh -c "npx serve . --listen 3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can use &lt;code&gt;docker dev&lt;/code&gt; commands to spawn the dev environment and docker will do the rest for me. Excellent!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
&lt;span class="c"&gt;# Spin up the dev environment&lt;/span&gt;
docker dev create &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Stop the dev environment&lt;/span&gt;
docker dev stop devcontainers-try-docsify-hungry_elion

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/desktop/dev-environments/dev-cli/" rel="noopener noreferrer"&gt;Docker Dev CLI&lt;/a&gt; is handy if you would want to stick to the Terminal. Use &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; or the &lt;a href="https://github.com/docker/dev-envs-extension" rel="noopener noreferrer"&gt;Browser Extension&lt;/a&gt;, to create and &lt;a href="https://docs.docker.com/desktop/dev-environments/create-dev-env/" rel="noopener noreferrer"&gt;launch dev environments&lt;/a&gt; otherwise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/kosalanuwan/hello-world-39di-temp-slug-8551015"&gt;I use Typora for markdown&lt;/a&gt; but we're going to have to change the default theme. They require intellisense and syntax highlighting to work with HTML/CSS and JavaScript. I can use &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; with a few &lt;a href="//./.vscode/extensions.json"&gt;extensions&lt;/a&gt; but there are more problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8hql4fpn9rznysaddbj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8hql4fpn9rznysaddbj.gif" alt="They write high-quality code for six cents a day - Dilbert by Scott Adams" width="1200" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not only is there no language recognition, but no intellisense at all. I'm going to go ahead and say that they don't have access to the node runtime inside the container, so they have no way of knowing what syntax highlighting and intellisense to show. And you do want to them to work. Time to install the &lt;a href="https://aka.ms/vscode-remote/download/containers" rel="noopener noreferrer"&gt;Remote - Containers&lt;/a&gt; extnesion and &lt;code&gt;Attach to Running Container&lt;/code&gt;. Then I can install the required extensions for node and JavaScript. It's slightly obnoxious that VS Code doesn't install recommended extensions from the &lt;code&gt;.vscode/extensions.json&lt;/code&gt; automatically, so I'm going to have to install &lt;code&gt;recommendations&lt;/code&gt; manually.&lt;/p&gt;

&lt;p&gt;Unfortunately, there's no simpler way of converting word docs and slide decks into markdown so I'm going to have to copy all the contents from the individual directories that are in. And there's no way I'm going to go to 100 pages and slides to create markdown content one at a time, so it's definitely necessary to push this initial content to a git repo and let the teammates contribute.&lt;/p&gt;

&lt;p&gt;/KP&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; For the complete work, see &lt;a href="https://github.com/alertbox/devcontainers-try-docsify/tree/devenvs-try-docsify#readme" rel="noopener noreferrer"&gt;my devcontainers-try-docsify repo on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>docker</category>
      <category>devenvironment</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Non-opinionated code review guidelines</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Fri, 02 Sep 2022 05:53:17 +0000</pubDate>
      <link>https://dev.to/kosperera/non-opinionated-code-review-guidelines-16h1</link>
      <guid>https://dev.to/kosperera/non-opinionated-code-review-guidelines-16h1</guid>
      <description>&lt;p&gt;I'm not going to lie, opinions are like Arseholes, everyone has one. It would have been acceptable but the objectives of code reviews are simple. In general, not only is to get the best possible pull request, but also improving skills of the fellow contributors. Easy enuf, except we need a set of ground rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etiquette
&lt;/h3&gt;

&lt;p&gt;The first thing we're going to need is a contributor etiquette. I like the idea of &lt;a href="https://en.wikipedia.org/wiki/Law_of_Jante#Definition" rel="noopener noreferrer"&gt;Jante Law&lt;/a&gt;. It gives everyone a very &lt;em&gt;Egalitarian&lt;/em&gt; feel since &lt;em&gt;you are not to think you're anyone special or you're better than everyone else&lt;/em&gt;, as they teach in Scandinavian schools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;The next is the release engineering strategy. All we need is a version control tool and &lt;a href="https://git-scm.io/docs/" rel="noopener noreferrer"&gt;git&lt;/a&gt; is the mainstream thingy for that. But &lt;a href="https://atlassian.com/docs/git/gitflow/" rel="noopener noreferrer"&gt;some of the branching strategies are pretty horrendous&lt;/a&gt; so you have to keep the branch integrations to minimum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2Fddf1fa20315201378d0e005056a9545d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2Fddf1fa20315201378d0e005056a9545d" alt="Using Git - Dilbert by Scott Adams" width="900" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://trunkbaseddevelopment.com/" rel="noopener noreferrer"&gt;Trunk-based development&lt;/a&gt; seems extraordinarily uncomplicated but there are more to consider.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Branch policies for protecting the default branch.&lt;/li&gt;
&lt;li&gt;Naming conventions for branches.&lt;/li&gt;
&lt;li&gt;Pipelines for release engineering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Changesets
&lt;/h3&gt;

&lt;p&gt;Some organizations often call this a &lt;em&gt;Pull request&lt;/em&gt; or a &lt;em&gt;Changelist&lt;/em&gt;. And it has to be small-enuf to one self-contained change. Unfortunately, no playbook has a recipe for this so I'm going to have to let individual contributor decide the size of the pull request.&lt;/p&gt;

&lt;p&gt;Once the pull request is ready to review, they have to describe it, specifically &lt;em&gt;what&lt;/em&gt; has been done and &lt;em&gt;why&lt;/em&gt; it was made as-is. This can be tricky, so a &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#summary" rel="noopener noreferrer"&gt;conventional-based description&lt;/a&gt; seems like a great way of adding structure and meaning to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design and Health
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2F268f57809f72012f2fe600163e41dd5b" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2F268f57809f72012f2fe600163e41dd5b" alt="Its the holy grail of technology - Dilbert by Scott Adams" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This covers a lot of ground and can be challenging enuf. Unfortunately, you can only boil so many oceans at the same time, so what does each reviewer should look for in a pull request? In general, &lt;em&gt;readability and maintainability&lt;/em&gt; would hint a few mistakes here and there, but there are more to review.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intended functionality and the design.&lt;/li&gt;
&lt;li&gt;Over-engineering and complexity.&lt;/li&gt;
&lt;li&gt;Clear naming and conformance to style guide.&lt;/li&gt;
&lt;li&gt;Parallel programming and thread safety.&lt;/li&gt;
&lt;li&gt;Sufficient exception handling and logging et al.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Style Guide
&lt;/h3&gt;

&lt;p&gt;Yes, a homegrown standards and conventions isn't such a great idea. The major issue is that it would demand an intense concentration to keep up with each tech stack without giving away the &lt;em&gt;egalitarian culture&lt;/em&gt; completely since you would want to &lt;em&gt;Dictate-It-All&lt;/em&gt;. And you do want contributors to maintain consistancy of the code. This may be difficult. I need a community-adept style guide to start with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster Reviews
&lt;/h3&gt;

&lt;p&gt;A pull request won't get reviewed itself. And a contributor must intervene at some point. I can review shortly after it comes in. But that seems a little unproductive since it takes time to get back to &lt;em&gt;The Zone&lt;/em&gt; after been interrupted. So how fast should code reviews be? &lt;em&gt;One business day or 24 hours&lt;/em&gt; sounds reasonable but there is more to this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Professional courtesy when pointing out problems.&lt;/li&gt;
&lt;li&gt;Code analytics for obvious smelly codes, anti-patterns, conformance to standards, et al.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2Fdc424c1016c20134707c005056a9545d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.amuniversal.com%2Fdc424c1016c20134707c005056a9545d" alt="Too Dumb To Understand - Dilbert by Scott Adams" width="900" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All we need is non-opinionated guidelines for all kinds of contributors, so in doing a code review, you should value for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Technical facts and data over opinions and personal preferences.&lt;/li&gt;
&lt;li&gt;
Style guides over personal preferences.&lt;/li&gt;
&lt;li&gt;
Software design principles over opinionated-design.&lt;/li&gt;
&lt;li&gt;
Consistency of the code over personal preferences.&lt;/li&gt;
&lt;li&gt;
Team velocity over personal velocity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;/KP&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; You can find more about the topic in my curated list of standards, practices, guides, and discussions on &lt;a href="https://github.com/kosalanuwan/keep-on-truckin/discussions/#readme" rel="noopener noreferrer"&gt;my awesome list #33&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>codenewbie</category>
      <category>review</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Writing pipes and filters for fluent OR-mapping</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Mon, 15 Aug 2022 09:13:37 +0000</pubDate>
      <link>https://dev.to/kosperera/writing-pipes-and-filters-for-fluent-or-mapping-18hh</link>
      <guid>https://dev.to/kosperera/writing-pipes-and-filters-for-fluent-or-mapping-18hh</guid>
      <description>&lt;p&gt;I like the idea of &lt;a href="https://martinfowler.com/bliki/FluentInterface.html" rel="noopener noreferrer"&gt;fluent interfaces&lt;/a&gt;. It gives the whole thing a very &lt;em&gt;Ubiquitous&lt;/em&gt; feel since the code becomes more understandable, say, two months from now like &lt;a href="https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29" rel="noopener noreferrer"&gt;Uncle Bob says in the Clean Code&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// An example LINQ expression to filter all active products&lt;/span&gt;
&lt;span class="c1"&gt;// that belongs to a specified supplier.&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Discontinued&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SupplierId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simplifying query expressions
&lt;/h3&gt;

&lt;p&gt;The first thing we are going to need is a lot of &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions#lambdas-with-the-standard-query-operators" rel="noopener noreferrer"&gt;query expressions&lt;/a&gt;. I can &lt;a href="https://sourcemaking.com/refactoring/simplifying-conditional-expressions" rel="noopener noreferrer"&gt;decompose or consolidate&lt;/a&gt; query expressions so a little &lt;a href="https://sourcemaking.com/refactoring/composing-methods" rel="noopener noreferrer"&gt;extract method&lt;/a&gt; magic is all that is necessary to get started. Kids' stuff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Better readability.&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;WithSupplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Better resuability.&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Suppliers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That seems a little better since it allows reusing the query expressions. &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/linq/linq-in-csharp" rel="noopener noreferrer"&gt;LINQ&lt;/a&gt; may look like the best thing that happened for .NET, but it's getting all kinds of help from our friends at C#. It's the &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods" rel="noopener noreferrer"&gt;extension methods&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chaining query expressions
&lt;/h3&gt;

&lt;p&gt;The other thing we're going to need is chaining. And that means &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods" rel="noopener noreferrer"&gt;extension methods&lt;/a&gt;. It's slightly obnoxious that extension methods require non-nested non-generic static classes. So what does each predicate have that can be used for chaining that the fluent interface requires? &lt;code&gt;IQueryable&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use filters to create the pipes.&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WithSupplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Extension methods for `IQueryable&amp;lt;Product&amp;gt;`.&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IQueryableOfProduct&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Discontinued&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;WithSupplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SupplierId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can pass in the &lt;code&gt;IQueryable&amp;lt;&amp;gt;&lt;/code&gt; domain entity type as the first parameter on which the method must operate. All I have to do is add the &lt;code&gt;this&lt;/code&gt; modifier to the parameter and the C# compiler will do the rest for me. I'm not going to lie, you'd be amazed how tricky it is and if you return the wrong type, chaining breaks and nothing gets compiled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describing pipes and filters
&lt;/h3&gt;

&lt;p&gt;Naming things is hard. But there are more problems. Not only are the extension methods should be reusable, but they must be understandable. And you do want the code to scream what kind of business there is. Using a &lt;a href="https://martinfowler.com/bliki/DomainSpecificLanguage.html" rel="noopener noreferrer"&gt;domain-specific language&lt;/a&gt; to describe methods seems like the right answer. Indeed.&lt;/p&gt;

&lt;p&gt;/KP&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>entityframework</category>
      <category>linq</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Hello world</title>
      <dc:creator>Kosala (KP) Perera</dc:creator>
      <pubDate>Sat, 13 Aug 2022 06:17:39 +0000</pubDate>
      <link>https://dev.to/kosperera/hello-world-2p85</link>
      <guid>https://dev.to/kosperera/hello-world-2p85</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; used to be my unicorn and now it is &lt;em&gt;my bitch&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I interact with GitHub more often than &lt;a href="https://twitter.com/kosalanuwan" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/kosalanuwan" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, so why not use it for everything else, say, todos, bookmarks, links, ahaa thoughts, notes for future me, journaling, and the like? It makes more sense, at least for me. I'm that nerd 🤓&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghchart.rshah.org%2FAF0000%2Fkosalanuwan%3Fauto%3Dcompress" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghchart.rshah.org%2FAF0000%2Fkosalanuwan%3Fauto%3Dcompress" alt="Kosala Nuwan's GitHub contributions" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The truth is I tried out all those widespread productivity and notes-taking apps from Evernote to OneNote, Workflowy, Trello, Todoist, Asana, and even Notion. For the record, they all seemed great apps but this is just my personal preference.&lt;/p&gt;

&lt;h3&gt;
  
  
  About the site
&lt;/h3&gt;

&lt;p&gt;In case you were wondering, these journal notes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Written on a Mac in Colombo, Sri Lanka.&lt;/li&gt;
&lt;li&gt;Written in &lt;a href="https://support.hashnode.com/docs/markdown-guidelines" rel="noopener noreferrer"&gt;Markdown&lt;/a&gt; with &lt;a href="https://www.typora.io/" rel="noopener noreferrer"&gt;Typora app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Served with &lt;a href="https://github.com/kosalanuwan" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; in a &lt;a href="https://github.com/kosalanuwan/keep-on-truckin/#readme" rel="noopener noreferrer"&gt;public repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Published on &lt;a href="https://keepontruckin.hashnode.dev/hello-world" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Synced to &lt;a href="https://dev.to/kosalanuwan"&gt;DEV.to&lt;/a&gt; via RSS&lt;/li&gt;
&lt;li&gt;Originally, a flavor of my &lt;a href="https://opensource.com/article/19/4/what-developer-journal" rel="noopener noreferrer"&gt;Developer Journal&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find more about my work on &lt;a href="https://kosalanuwan.github.io/" rel="noopener noreferrer"&gt;my GitHub Page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For comments or questions, feel free to &lt;a href="https://github.com/kosalanuwan/ama" rel="noopener noreferrer"&gt;ask me on GitHub&lt;/a&gt;, &lt;a href="https://twitter.com/kosalanuwan" rel="noopener noreferrer"&gt;on Twitter&lt;/a&gt;, or via email at kosala dot nuwan at gmail dot com.&lt;/p&gt;

&lt;p&gt;/KP&lt;/p&gt;

</description>
      <category>helloworld</category>
      <category>devjournal</category>
      <category>github</category>
      <category>blog</category>
    </item>
  </channel>
</rss>
