<?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: Nils Diekmann</title>
    <description>The latest articles on DEV Community by Nils Diekmann (@kinneko-de).</description>
    <link>https://dev.to/kinneko-de</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%2F1707808%2Fdd6cb3bc-709e-43e4-ac5b-0bc4a2ae9ed5.jpeg</url>
      <title>DEV Community: Nils Diekmann</title>
      <link>https://dev.to/kinneko-de</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kinneko-de"/>
    <language>en</language>
    <item>
      <title>Mediator and CQRS with MediatR</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Wed, 23 Oct 2024 22:11:43 +0000</pubDate>
      <link>https://dev.to/kinneko-de/mediator-and-cqrs-with-mediatr-278c</link>
      <guid>https://dev.to/kinneko-de/mediator-and-cqrs-with-mediatr-278c</guid>
      <description>&lt;p&gt;ultimate beautiful code&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%2Fsq6ex8jvssqdpublefjq.jpg" 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%2Fsq6ex8jvssqdpublefjq.jpg" alt="Solution to save money — Generated by Image3" width="650" height="407"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;Today I am talking about a coding dojo that my younger and older self are carrying out against each other. A developer is active in the coding dojo all the time. The active developer is free to decide what to develop. But he must explain his thoughts to the other developer.&lt;/p&gt;

&lt;p&gt;The company they work for has developed a donation system. People can donate some money and see the total amount of all donations. The challenge for the Coding Dojo is now to develop this system further. &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;The sample application is available on GitHub&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;
        sample-codingdojo-donation
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Read my articles to find out how my [younger](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/344fe6e8e4f6) and [older](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/0de8a4351da2) selves solved this coding dojo.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donationimg/header.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donationimg%2Fheader.png" alt="Donation app"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This is a sample exercise for a coding dojo in C#.&lt;/p&gt;
&lt;p&gt;The application consists of a user interface part and a server part with a database. Everything is in one application to avoid unnecessary complexity. API calls are reduced to method calls.&lt;/p&gt;
&lt;p&gt;Read my articles to find out how my &lt;a href="https://medium.com/@kinneko-de/344fe6e8e4f6" rel="nofollow noopener noreferrer"&gt;younger&lt;/a&gt; and &lt;a href="https://medium.com/@kinneko-de/0de8a4351da2" rel="nofollow noopener noreferrer"&gt;older&lt;/a&gt; selves solved this coding dojo.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Challenge&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Develop the application further. You are completely free in your decisions&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture in a real world example&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;In a real world the UI would run on the client. The UI would access the server over an API.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donationimg/codingdojo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donationimg%2Fcodingdojo.png" alt="C4 mode"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Sources&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The headliner image was generated using Gemini's image generation capabilities.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Gemini is a large language model developed by Google AI. It can generate realistic and diverse images based on text prompts. &lt;a href="https://www.gemini.google.com" rel="nofollow noopener noreferrer"&gt;Learn more about Gemini&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;My younger self will take on the role of the active developer in this article. &lt;a href="https://dev.to/kinneko-de/businessvalue-with-cqs-51p2"&gt;Read my other article to find out what my older self would do instead.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding Dojo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Younger Self&lt;/strong&gt; [&lt;em&gt;excited&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; Oh my God, today we had a training session with an external consultant. He talked about what we should use in our project. He told us to use patterns to improve the code. There were a lot of patterns, I can’t remember them all, but there was one very special pattern called &lt;a href="https://en.wikipedia.org/wiki/Mediator_pattern" rel="noopener noreferrer"&gt;Mediator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self&lt;/strong&gt; [&lt;em&gt;relaxed&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; Can you explain me what the mediator pattern does and when to use it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Youger Self&lt;/strong&gt; [&lt;em&gt;irritated&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; There’s a mediator between different parts of the code that greatly improves readability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; If you don’t know what the mediator pattern is, how are you going to implement it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; Of course, I am not going to implement it myself. There's a framework called &lt;a href="https://github.com/jbogard/MediatR" rel="noopener noreferrer"&gt;MediatR&lt;/a&gt;. It has almost the same name, so I cannot be wrong if I use it. This framework will do everything for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self&lt;/strong&gt; [&lt;em&gt;sceptical&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; Are you absolutely sure?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self&lt;/strong&gt; [&lt;em&gt;self-confident&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; Yes, of course. &lt;a href="https://www.jimmybogard.com/you-probably-dont-need-to-worry-about-mediatr/#comment-5862880139" rel="noopener noreferrer"&gt;There is a comment from a guy who uses it everywhere because he totally likes it and is totally satisfied by the framework&lt;/a&gt;. So I am going to use it everywhere now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; Okay, so where do we start?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; The service adds a number to a value in a database. I have heard that this is a command. Then the service queries the result. If I separate the command from the query, I can also implement CQRS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; Sounds ambitious, but let us first introduce the mediator pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; I first need to add a nuget package for MediatR and register it in my &lt;code&gt;Program.cs&lt;/code&gt;. That is all I have to do to get all my code injected by dependency Injection.&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="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMediatR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterServicesFromAssemblyContaining&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I create a command that does the update in the database. It needs the same properties as I had before as parameters. I put the command in its own class.&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddDonationCommand&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Donation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&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;Then I create a handler for the command. It will get the database injected as the service before. Each handler has a &lt;code&gt;Handle&lt;/code&gt; method. There I do my database query.&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;// ReSharper disable once UnusedMember.Global&lt;/span&gt;
&lt;span class="c1"&gt;// MediatR will automatically find and use this class somehow, do not remove it!&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddDonationCommandHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AddDonationCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AddDonationCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Database&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AddDonationCommand&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDonation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Donation&lt;/span&gt;&lt;span class="p"&gt;);&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;Then I do the same with the query. In the service, I just call the mediator and send the command and the query to it.&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateDonation&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;donation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AddDonationCommand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Donation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;donation&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetTotalDonationsQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Send&lt;/span&gt;&lt;span class="p"&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;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation-mediatr" rel="noopener noreferrer"&gt;
        sample-codingdojo-donation-mediatr
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Read my articles to find out how my [younger](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/344fe6e8e4f6) and [older](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/0de8a4351da2) selves solved this coding dojo. 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donation-mediatrimg/header.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donation-mediatrimg%2Fheader.png" alt="Donation app"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This is a sample exercise for a coding dojo in C#. It was copied from the &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;donation template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the coding dojo solution of my &lt;a href="https://medium.com/@kinneko-de/344fe6e8e4f6" rel="nofollow noopener noreferrer"&gt;younger&lt;/a&gt; selve.&lt;/p&gt;

&lt;p&gt;Read my article to find out how my &lt;a href="https://medium.com/@kinneko-de/0de8a4351da2" rel="nofollow noopener noreferrer"&gt;older&lt;/a&gt; selve solved this coding dojo.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Challenge&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Develop the application further. You are completely free in your decisions&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-codingdojo-donation-mediatr" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; The code now looks much more complex to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; It’s more simpler because I had don’t have to worry about how the objects are created. The framework does it for me. Now I can just focus on my command and not have to look at the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; Where do you see CQRS in this specific example?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; I separated my query from my command by putting a mediator between them. So its now segregated right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self:&lt;/strong&gt; There is an R in CQRS. It stands for responsibility. The responsibility in your code is not segregated. It is still one method, even with all the layers of abstraction in between.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self&lt;/strong&gt; [&lt;em&gt;thoughtful&lt;/em&gt;]&lt;strong&gt;:&lt;/strong&gt; Hmmmmm [&lt;em&gt;pause&lt;/em&gt;] I have the perfect solution for that. We do two teams, one is responsible for the query and one is responsible for the command. Then we do a &lt;a href="https://files.gotocon.com/uploads/slides/conference_12/515/original/gotoberlin2018-modular-monoliths.pdf" rel="noopener noreferrer"&gt;modular monolith&lt;/a&gt;. We can also extract the query to an separate microservice that I call synchronously from the microservice that does the addition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older Self&lt;/strong&gt; [&lt;em&gt;surrenders&lt;/em&gt;]: I am at a loss for words. Product owner, we are done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product owner:&lt;/strong&gt; What is the benefit to our company of doing your approach?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger Self:&lt;/strong&gt; Hmm Hmm Hmm&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When I was still young, I tried to use as many patterns as possible. Later I realized that I was just adding a lot more complexity to my code. Nowadays, I only use patterns to solve problems that already exist.&lt;/p&gt;

&lt;p&gt;I also realized that beautiful code and my personal satisfaction do not pay the bills. So I focus more on business value and the bigger picture than on just code.&lt;/p&gt;

&lt;p&gt;If you totally disagree with experience and totally like the approach of my younger self, then stop here. Otherwise, &lt;a href="https://dev.to/kinneko-de/businessvalue-with-cqs-51p2"&gt;I am happy to share my older self’s aproach with you.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And don’t forget to send me a reaction at this article.&lt;/p&gt;

</description>
      <category>mediatr</category>
      <category>mediator</category>
      <category>cqrs</category>
      <category>csharp</category>
    </item>
    <item>
      <title>BusinessValue with CQS</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Wed, 23 Oct 2024 22:10:57 +0000</pubDate>
      <link>https://dev.to/kinneko-de/businessvalue-with-cqs-51p2</link>
      <guid>https://dev.to/kinneko-de/businessvalue-with-cqs-51p2</guid>
      <description>&lt;p&gt;simple is harder&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%2Ffnn8xhin3qd3szfdjev4.jpg" 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%2Ffnn8xhin3qd3szfdjev4.jpg" alt="Solution to save money — Generated by Image3&amp;lt;br&amp;gt;
" width="644" height="529"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;Today I am talking about a coding dojo that my younger and older self are carrying out against each other. A developer is active in the coding dojo all the time. The active developer is free to decide what to develop. But he must explain his thoughts to the other developer.&lt;/p&gt;

&lt;p&gt;The company they work for has developed a donation system. People can donate some money and see the total amount of all donations. The challenge for the Coding Dojo is now to develop this system further. &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;The sample application is available on GitHub&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;
        sample-codingdojo-donation
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Read my articles to find out how my [younger](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/344fe6e8e4f6) and [older](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/0de8a4351da2) selves solved this coding dojo.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donationimg/header.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donationimg%2Fheader.png" alt="Donation app"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This is a sample exercise for a coding dojo in C#.&lt;/p&gt;
&lt;p&gt;The application consists of a user interface part and a server part with a database. Everything is in one application to avoid unnecessary complexity. API calls are reduced to method calls.&lt;/p&gt;
&lt;p&gt;Read my articles to find out how my &lt;a href="https://medium.com/@kinneko-de/344fe6e8e4f6" rel="nofollow noopener noreferrer"&gt;younger&lt;/a&gt; and &lt;a href="https://medium.com/@kinneko-de/0de8a4351da2" rel="nofollow noopener noreferrer"&gt;older&lt;/a&gt; selves solved this coding dojo.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Challenge&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Develop the application further. You are completely free in your decisions&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture in a real world example&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;In a real world the UI would run on the client. The UI would access the server over an API.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donationimg/codingdojo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donationimg%2Fcodingdojo.png" alt="C4 mode"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Sources&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The headliner image was generated using Gemini's image generation capabilities.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Gemini is a large language model developed by Google AI. It can generate realistic and diverse images based on text prompts. &lt;a href="https://www.gemini.google.com" rel="nofollow noopener noreferrer"&gt;Learn more about Gemini&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;My older self will take on the role of the active developer in this article. &lt;a href="https://dev.to/kinneko-de/mediator-and-cqrs-with-mediatr-278c"&gt;Read my other article to find out what my younger self would do instead.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding Dojo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Older self:&lt;/strong&gt; Let’s analyze what the service does first.&lt;br&gt;
It adds the donated money to the project. Then it selects the total amount of donated money. So it does two things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order self:&lt;/strong&gt; Not in general. Maybe you address this because of the the &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle" rel="noopener noreferrer"&gt;single responsibility principle&lt;/a&gt;. In my opinion it adress more code design. In this service two methods are called after each other. But the service and the code must serve the business first, not the other way around.&lt;/p&gt;

&lt;p&gt;But I don’t think this service meets the customer’s needs. The donor has to donate money before they can see what other people have donated before them. Maybe it would be better to first show them the money that has already been donated, even without them having to donate money. This might motivate them to donate more money. A second use case for this new query option would be for the project owner to see how much money has already been donated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger self:&lt;/strong&gt; Which framework do you want to use to solve this problem?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older self:&lt;/strong&gt; I don’t need a framework to do this. It is just a matter of rearranging the current code. This is a simple task.&lt;br&gt;
I remove the query part of the service method and add a second method that just selects the total amount of money. I now have two methods in the service.&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetTotalDonations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalDonations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UpdateDonation&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;donation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDonation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;donation&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 also need to modify the user interface. The user interface can now initially fetch the amount of money donated.&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetTotalDonations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;GetTotalDonations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalDonations&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;After the user has donated some money, we also need to update the amount. The user needs immediate feedback to feel that their money has been received.&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Donate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateDonation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDonation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;[...]&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetTotalDonations&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;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation-businessvalue" rel="noopener noreferrer"&gt;
        sample-codingdojo-donation-businessvalue
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Read my articles to find out how my [younger](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/344fe6e8e4f6) and [older](https://medium.com/&lt;a class="mentioned-user" href="https://dev.to/kinneko-de"&gt;@kinneko-de&lt;/a&gt;/0de8a4351da2) selves solved this coding dojo. 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/KinNeko-De/sample-codingdojo-donation-businessvalueimg/header.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FKinNeko-De%2Fsample-codingdojo-donation-businessvalueimg%2Fheader.png" alt="Donation app"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This is a sample exercise for a coding dojo in C#. It was copied from the &lt;a href="https://github.com/KinNeko-De/sample-codingdojo-donation" rel="noopener noreferrer"&gt;donation template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the coding dojo solution of my &lt;a href="https://medium.com/@kinneko-de/0de8a4351da2" rel="nofollow noopener noreferrer"&gt;older&lt;/a&gt; selve.&lt;/p&gt;

&lt;p&gt;Read my article to find out how my &lt;a href="https://medium.com/@kinneko-de/344fe6e8e4f6" rel="nofollow noopener noreferrer"&gt;younger&lt;/a&gt; selve solved this coding dojo.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Challenge&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Develop the application further. You are completely free in your decisions&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-codingdojo-donation-businessvalue" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Younger self:&lt;/strong&gt; This will introduce more overhead when we deploy this as a real service. Each call introduces network overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older self:&lt;/strong&gt; Yes, you’re right. But separating the functionality into two methods makes our system more flexible. It is more likely to fit business use cases just by combining the calls. So it improves maintainability. If it were really performance relevant, we could just copy the code and both methods could ask for the total amount of the donation. But I don’t think performance is relevant for us in this use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger self:&lt;/strong&gt; I see that we separated the query and the command that updates the donations. This is CQRS, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older self:&lt;/strong&gt; Not really. &lt;a href="https://microservices.io/patterns/data/cqrs.html" rel="noopener noreferrer"&gt;CQRS&lt;/a&gt; is for more complex services where we have multiple services with different databases. And also in these cases you add it only in a later stage when you really want to optimize for more complex queries. But for small projects in early stages it is overkill.&lt;br&gt;
There’s a pattern called &lt;a href="https://martinfowler.com/bliki/CommandQuerySeparation.html" rel="noopener noreferrer"&gt;CQS&lt;/a&gt;, but I did this to add business value to the application and not just to use a pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Younger self:&lt;/strong&gt; Interesting. Product owner, we are done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product owner:&lt;/strong&gt; I listened to your discussion and it was really interesting. The approach may be interesting. But I want to discuss it with our customers first. We should keep the code in a feature branch for now. Maybe we can also add some more motivational words based on the amount of money already donated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I realize more and more every day that implementation is not about personal satisfaction. It is about delivering business value to the company, and that is what the company pays me for.&lt;/p&gt;

&lt;p&gt;After years of developing and maintaining existing code, I realized that maintenance is the most time-consuming and expensive part of software development. That’s why I prefer simple solutions these days. This includes refining concepts to keep the code as simple as possible. Because simple is harder.&lt;/p&gt;

&lt;p&gt;Don’t forget to leave a reaction at this article and then hopefully &lt;a href="https://dev.to/kinneko-de/mediator-and-cqrs-with-mediatr-278c"&gt;read about my younger self’s approach&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>cqs</category>
      <category>senior</category>
      <category>codingdojo</category>
    </item>
    <item>
      <title>Angular, Typescript, and CD to GitHub Pages (2024)</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Thu, 19 Sep 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/kinneko-de/angular-typescript-and-cd-to-github-pages-2024-35o9</link>
      <guid>https://dev.to/kinneko-de/angular-typescript-and-cd-to-github-pages-2024-35o9</guid>
      <description>&lt;p&gt;How to set up a typescript-based Angular application and deploy it directly to GitHub Pages using a CD GitHub action. A practical guide for 2024.&lt;/p&gt;




&lt;h1&gt;
  
  
  Motivation
&lt;/h1&gt;

&lt;p&gt;Setting up a new Angular repository in GitHub is not rocket science. The introduction of a CD pipeline is easy too. In this tutorial I will give you a quick and easy step-by-step guide.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create Angular App
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/docs/nodejs/angular-tutorial" rel="noopener noreferrer"&gt;Install the following prerequisites: Git and Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular CLI
&lt;/h2&gt;

&lt;p&gt;I will use &lt;a href="https://angular.dev/tools/cli/setup-local" rel="noopener noreferrer"&gt;Angular CLI&lt;/a&gt; to create the initial project setup. I need to install the CLI globally using Node. The CLI version I am using is &lt;code&gt;18.2.2&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I want to create a template with typescript. I googled that and this leads my to an old and much older link. The older link takes me to a &lt;a href="https://angular.dev/reference/configs/typescript-configuration" rel="noopener noreferrer"&gt;dead page&lt;/a&gt;. The trick is, that Angular is generated with typescript by default! So I open a terminal in the root folder where all my git repositories are. The CLI will create a subfolder with the name of my application.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new sample-angular-typescript-githubpages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I have some options when I create the application. I choose CSS and no server-side rendering. The template includes a .gitignore and I can push it directly to GitHub.&lt;/p&gt;

&lt;p&gt;I can run &lt;code&gt;npm install&lt;/code&gt; without any vulnerabilities. But when I run &lt;code&gt;npm ci&lt;/code&gt; I get warnings about three deprecated packages. The &lt;a href="https://github.com/angular/angular/issues/57274" rel="noopener noreferrer"&gt;dependencies are caused by the karma test runner&lt;/a&gt;. So I &lt;a href="https://stackoverflow.com/questions/76853271/angular-vulnerability-inflight1-0-6-deduped" rel="noopener noreferrer"&gt;override the version of glob and rimraf&lt;/a&gt; in my package.json below &lt;code&gt;devDependencies&lt;/code&gt;.&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="nl"&gt;"overrides"&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;"glob"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^9.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rimraf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.0.0"&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;But I rejoice too soon. Now the tests (&lt;code&gt;ng test&lt;/code&gt;) are no longer running. So I roll back the changes and hope for the best. The issue is only one month old.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run tests locally
&lt;/h2&gt;

&lt;p&gt;When I run the test command a Chrome instance pops up. I have to select a default search engine every time I start the tests. As this is annoying, follow the &lt;a href="https://stackoverflow.com/questions/78860458/how-to-disable-search-engine-choice-screen" rel="noopener noreferrer"&gt;advice of clever people&lt;/a&gt;. First, follow the advice of the second comment, generate a &lt;code&gt;karma.conf.js&lt;/code&gt; and insert the snippet from the first comment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Build and continuous deployment
&lt;/h2&gt;

&lt;p&gt;I create a &lt;a href="https://docs.github.com/en/actions/writing-workflows/about-workflows" rel="noopener noreferrer"&gt;.github/workflows folder and a builddeploy.yaml with the following content in it&lt;/a&gt;. This time I select the right NodeJs version, as &lt;a href="https://github.com/nodejs/Release" rel="noopener noreferrer"&gt;22 is not LTS yet&lt;/a&gt;.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Angular to GitHub Pages&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt; &lt;span class="c1"&gt;# name of the branch you are pushing to&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build -- --configuration production --base-href /${{ github.event.repository.name }}/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-pages-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dist/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.repository.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/browser/.'&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pages&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.repository_owner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.github.io/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.repository.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/'&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Pages&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/configure-pages@v5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/deploy-pages@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This workflow uses the GitHub action &lt;a href="https://github.com/actions/deploy-pages" rel="noopener noreferrer"&gt;actions/deploy-pages&lt;/a&gt; to deploy the artifact from &lt;a href="https://github.com/actions/upload-pages-artifact" rel="noopener noreferrer"&gt;actions/upload-pages-artifact&lt;/a&gt; to GitHub pages. I can also view and download the &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages/actions/runs/8949805152/attempts/1" rel="noopener noreferrer"&gt;produced artifact&lt;/a&gt; in the workflow run overview.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5e9t14pjwar8c2q6ti7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5e9t14pjwar8c2q6ti7.png" alt="Generated artifact in the workflow run" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is one improvement compared to the &lt;a href="https://medium.com/@kinneko-de/react-typescript-and-cd-to-github-pages-2024-92d4f19d71d7" rel="noopener noreferrer"&gt;react tutorial&lt;/a&gt;. I pass two parameters to the npm run build. &lt;a href="https://stackoverflow.com/questions/52812626/npm-run-build-production-does-not-distribute-the-production" rel="noopener noreferrer"&gt;These parameters are then passed to ng build&lt;/a&gt;. The first parameter declares that I want the production configuration and the second is the base-href for my application. It must be the name of the GitHub repository framed by slashes. You can also &lt;a href="https://dev.to/danielcaballero88/how-to-deploy-angular-website-to-github-pages-jhb"&gt;use a full URL&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;When I push the file, a workflow run starts automatically . It fails because the GitHub's default configuration is currently to deploy GitHub pages from a specific branch. So I need to change the configuration of my GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwb4vyd3jp3if6gt53g7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwb4vyd3jp3if6gt53g7.png" alt="Initial setup of a GitHub Repository" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I need to change the selection of the drop down box to 'GitHub Actions'. As you can see 'Deploy from a branch' is declared as 'classic', which is usually a nicer word for 'legacy'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfuppd8tbhd4vger64i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfuppd8tbhd4vger64i6.png" alt="Change to " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I need to re-run the failed jobs of my GitHub Action workflow. GitHub will only execute the 'deploy' job. The already built artifact 'github-pages' will be reused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1wifozxb9gy1g52qp1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1wifozxb9gy1g52qp1q.png" alt="Re-run failed jobs only" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F524pyow4kk6jn8w1h3tw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F524pyow4kk6jn8w1h3tw.png" alt="Artifact from build stge is reused" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the job re-run succeed, I finally see my &lt;a href="https://kinneko-de.github.io/sample-angular-typescript-githubpages/" rel="noopener noreferrer"&gt;GitHub Page&lt;/a&gt;. You can see the full code in my &lt;a href="https://github.com/KinNeko-De/sample-angular-typescript-githubpages" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywn742b7xwnk4qfzg9m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywn742b7xwnk4qfzg9m8.png" alt="Deployed Angular App" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The setup of an Angular app is more straightforward than in my &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages" rel="noopener noreferrer"&gt;React tutorial&lt;/a&gt;. Angular, GitHub and the community have done a great job making life easy for developer's . Thank you for that ❤.&lt;/p&gt;
&lt;h2&gt;
  
  
  Versions used
&lt;/h2&gt;

&lt;p&gt;I will try to update this guide to the latest version to keep up with breaking changes in the tools. If you find something that does not work for you, please leave a comment. For a better overview, I list all versions used while writing this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20.17.0&lt;/li&gt;
&lt;li&gt;NPM: 10.8.2&lt;/li&gt;
&lt;li&gt;Angular CLI: 18.2.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Samples
&lt;/h2&gt;

&lt;p&gt;I created a &lt;a href="https://github.com/KinNeko-De/sample-angular-typescript-githubpages" rel="noopener noreferrer"&gt;sample application on GitHub&lt;/a&gt;:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-angular-typescript-githubpages" rel="noopener noreferrer"&gt;
        sample-angular-typescript-githubpages
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application how to setup a Angular app with typescript and deploy it to GitHub Pages.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motivation&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Sample application of how to set up a Angular app with typescript and deploy it to GitHub Pages.&lt;/p&gt;

&lt;p&gt;See my &lt;a href="https://medium.com/@kinneko-de/bc71d3a6eeff" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt; how to do this yourself.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-angular-typescript-githubpages" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;a href="https://kinneko-de.github.io/sample-angular-typescript-githubpages/" rel="noopener noreferrer"&gt;Here you can see the deployed version&lt;/a&gt;:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://kinneko-de.github.io/sample-angular-typescript-githubpages/" rel="noopener noreferrer"&gt;
      kinneko-de.github.io
    &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>angular</category>
      <category>tutorial</category>
      <category>githubactions</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Gamechanger Protobuf</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Thu, 29 Aug 2024 16:33:30 +0000</pubDate>
      <link>https://dev.to/kinneko-de/gamechanger-protobuf-5ha3</link>
      <guid>https://dev.to/kinneko-de/gamechanger-protobuf-5ha3</guid>
      <description>&lt;p&gt;Protobuf is Google's answer to the need for an effective, efficient, and flexible serialization format as an alternative to JSON or XML. Its benefits pave the way for groundbreaking solutions to the problems faced by modern enterprises in creating a digital landscape in the cloud.&lt;/p&gt;

&lt;p&gt;🌎 &lt;a href="https://dev.to/kinneko-de/gamechanger-protobuf-3lib"&gt;Lies diesen Artikel in deutsch&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cloud-native means rethinking
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucme800l33540g4r39tu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucme800l33540g4r39tu.jpg" alt="half-timbered house" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Constructing a half-timbered house using traditional methods and tools certainly has its charm and is an achievement not to be sneezed at. However, modern zero-emission houses with a smart home connection cannot be built this way. And let's be honest, the number of customers desperately looking for a half-timbered house is very small.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4qg6twwg3exy2pul0m3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4qg6twwg3exy2pul0m3.jpg" alt="modern house" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloud-based applications present new and unfamiliar challenges to today's software architects. They need modern processes that are specifically designed to meet the new challenges of the cloud, so that applications can run reliably while conserving resources. Whether it is CPU, memory or disk space, anything that is used less saves money in running the cloud and is also a step towardss &lt;a href="https://en.wikipedia.org/wiki/Green_computing" rel="noopener noreferrer"&gt;green computing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numerous challenges
&lt;/h2&gt;

&lt;p&gt;I cannot cover all the challenges in the necessary depth in this article. The text would be so long and complicated that no one would finish reading it. Instead, I will focus on serialization. Serialization is the conversion of structured data into a format suitable for transmission or storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21pnolfb3k8goy2b4d5c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21pnolfb3k8goy2b4d5c.gif" alt="Serialization for transmission" width="603" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In modern distributed systems, the choice of a fast and reliable serialisation format is critical. Serialization itself is not complex, but it is widely used. Choosing the optimal format therefore has a major impact on the overall design of the cloud application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzb4l0lkuceqrwy3d2c3t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzb4l0lkuceqrwy3d2c3t.gif" alt="Serialization for storage" width="603" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A well-chosen format ensures that data is transferred over the network quickly and with minimal overhead. In addition, a reliable format is essential to avoid errors during data reconstruction and thus ensuring data integrity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation as a success factor
&lt;/h2&gt;

&lt;p&gt;JSON is currently the serialization format of choice for many. It has overtaken XML over the years. However, the advantage of JSON, as a format that is easy for humans to create and read, does not really come into play when it is used for communication between microservices. Although data and processes are triggered by human interaction, they only run downstream between systems and react to events rather than users.&lt;/p&gt;

&lt;p&gt;The argument that developers need to read the serialization format is also no longer valid. Cloud applications run in containers, which are themselves monitored and controlled by systems such as Kubernetes. Manual intervention is not the method of choice for analyzing and resolving problems.&lt;/p&gt;

&lt;p&gt;In addition, the application is updated at short intervals via a continuous delivery pipeline to meet market demands for rapid and constant adaptation. There is no alternative to automation in this context, as it ensures secure and, in times of skills shortages, cost-effective operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gamechanger Protobuf
&lt;/h2&gt;

&lt;p&gt;In this article, I will briefly discuss the terms and functionality of Protobuf. I will then explain its novel concepts and features and how they address the shortcomings of JSON. Finally, I will explain why Protobuf is a vast improvement over JSON and the next step in the evolution of serialization formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe and secure thanks to precise definition and strong typing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to serialize data, it must first be described in a structured way. With Protobuf, the data structures are defined in files with a .proto extension. In this proto definition, an object is described by a message. A message may contain any number of fields. A field has an identifier, a type and a number that is unique for this message. The type of a field can in turn be a message, creating a tree structure similar to JSON and XML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;Page&lt;/span&gt; &lt;span class="na"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;The code example above describes a simplified data structure for a book. The book has a title of type string, which can contain any character string. The book also has an arbitrary number of ("&lt;em&gt;repeated&lt;/em&gt;") pages, each of which is stored as a message of type "&lt;em&gt;Page&lt;/em&gt;". The pages have the text they contain stored in the "&lt;em&gt;content&lt;/em&gt;" field. The explicit specification of types in the proto definition helps to avoid errors and ensures consistency across system boundaries. Protobuf supports &lt;a href="https://protobuf.dev/programming-guides/proto3/#scalar" rel="noopener noreferrer"&gt;common programming language data types&lt;/a&gt;. Google also provides a collection of extended types, called &lt;a href="https://cloud.google.com/dotnet/docs/reference/Google.Protobuf/latest/Google.Protobuf.WellKnownTypes" rel="noopener noreferrer"&gt;&lt;em&gt;WellKnownTyps&lt;/em&gt;&lt;/a&gt;. You can also create your own data types using self-defined messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High productivity with code generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With other serialization formats, a lot of valuable development time is spent creating the data structures again as objects in the programming language and continually updating them as changes are made. The work is time consuming, but not very challenging, which means that errors creep in quickly. Generic mappers promise to reduce the effort, but they are difficult to configure in all cases where you deviate from the normal use case.&lt;/p&gt;

&lt;p&gt;Protobuf relies on automatic and continuous code generation and supports multiple programming languages. The data structure code is generated directly from the proto definition, eliminating much of the effort of other approaches. It is also guarantees that the sender and receiver always use the same implementation as long as they have the same version of the proto schema. The generated source code can also be flexibly extended with additional methods to make the use of custom data types more convenient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strictly defined but extendable contract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The proto definition is an interface contract between the sender and the receiver. Protobuf uses only simple structural elements. For example, the keyword "&lt;em&gt;repeated&lt;/em&gt;" refers to a list of a messages without specifying its exact number. On the one hand, this means that the sender cannot read possible restrictions on the receiver side from the contract. On the other hand, this can also be seen as an opportunity to make the contract more flexible and therefore easier to extend. The ability to easily change the contract while maintaining the integrity of the application is invaluable in a constantly evolving system landscape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple schema evolution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Protobuf was designed with compatibility in mind. A change is backward compatible if the new version of a receiver can process messages from from an old version of a sender. Conversely, a change is forward compatible if the new version of a sender still works with the old version of the receiver.&lt;/p&gt;

&lt;p&gt;By following a few simple rules, Protobuf provides both downward and upward compatibility. For example, the type and number of a field must never be changed. A message and a field cannot be deleted. The proto3 syntax also does not define any mandatory fields, as extensions to a contract must always be optional in order to maintain compatibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compatibility for zero downtime deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backward and forward compatibility is particularly important if it cannot be guaranteed that the sender and receiver and all messages can be updated to a new version of an interface description at the same time. This is an important feature for cloud applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpzxi6z8003kqvm3kk5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpzxi6z8003kqvm3kk5r.png" alt="Rolling update" width="650" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full compatibility makes it possible to roll out a sender and a receiver independently of each other, eliminating the need for time-consuming coordination of the sequence and making continuous delivery possible in the first place. Using a modern software solution for managing containerised applications, such as Kubernetes, also enables automatic rolling updates. This means that a new version of the application can be rolled out without any downtime. Users are unaware of this, which allows for uninterrupted service and an optimal customer experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outstanding efficiency as binary and minimalist&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Protobuf serializes messages in binary format. Only the number and value of the field in a message are stored. The proto definition is therefore needed to be able to read the message later. Serialization deliberately avoids unnecessary ballast that is not necessary for the actual benefit. Because of the small amount of data, the creation and reading of messages is very fast.&lt;/p&gt;

&lt;p&gt;However, the minimal message storage and high compatibility can lead to unexpected behaviour in the event of errors during development. Under certain circumstances, messages can be deserialized by a foreign proto definition without an error occurring. It is therefore essential to ensure the use of protobuf with automated tests. These can also be used to ensure compatibility with older versions.&lt;/p&gt;

&lt;p&gt;Manual error analysis of messages on a case-by-case basis is easily possible by converting the binary information to an object. Messages can also be easily converted to a JSON format. The code required to do this is fully generated according to the proto definition. This means that legacy systems that can only work with JSON can be connected to modern applications. The benefits of Protobuf can be at least partially exploited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;No serialization format, neither Protobuf nor JSON, can be used as a "&lt;a href="https://archive.org/details/antipatternsrefa0000unse/page/110/mode/2up" rel="noopener noreferrer"&gt;golden hammer&lt;/a&gt;". Protobuf is certainly not an alternative to JSON for all use cases, nor was it designed to be. The strengths of Protobuf make it a flexible and at the same time particularly efficient serialization format. The focus of the application area is primarily on data exchange between software systems. It can therefore be easily used wherever automated processes are created as part of digitization.&lt;/p&gt;

&lt;p&gt;The initial adoption of Protobuf may require some training and learning, but this is quickly recouped through the consistent use of the serialisation format across as many use cases as possible. The high degree of automation and code generation allows the development team to focus on what they do best - solving problems.&lt;/p&gt;

&lt;p&gt;Now there are no limits to your creativity in designing solutions for your domain based on Protobuf. I look forward to your ideas and suggestions. After all, aren't we all striving to continuously improve our software through first-class design and to make it a success?&lt;/p&gt;

&lt;p&gt;In future articles, I will give some concrete examples of how I have used Protobuf in the past. It's best to follow me so that you don't miss any of my future articles on Protobuf. ❤&lt;/p&gt;

</description>
      <category>protobuf</category>
      <category>cloudnative</category>
      <category>cloud</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Gamechanger Protobuf</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Thu, 29 Aug 2024 16:32:26 +0000</pubDate>
      <link>https://dev.to/kinneko-de/gamechanger-protobuf-3lib</link>
      <guid>https://dev.to/kinneko-de/gamechanger-protobuf-3lib</guid>
      <description>&lt;p&gt;Protobuf ist Googles Antwort auf die Anforderungen an ein effektives, effizientes und zugleich flexibles Serialisierungsformat als Alternative zu JSON oder XML. Seine Vorteile ebnen den Weg für bahnbrechende Lösungen für die Probleme, mit denen moderne Unternehmen bei der Schaffung einer digitalen Systemlandschaft in der Cloud konfrontiert werden.&lt;/p&gt;

&lt;p&gt;🌎 &lt;a href="https://dev.to/kinneko-de/gamechanger-protobuf-5ha3"&gt;Read this article in english&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cloud native bedeutet neu zu denken
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmr2tsbs9ggj4ru9mq53.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmr2tsbs9ggj4ru9mq53.jpg" alt="Fachwerkhaus" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Die Konstruktion eines Fachwerkhauses mit traditionellen Methoden und Werkzeugen hat gewiss seinen Scharm und ist eine nicht zu verachtende Leistung. Moderne Null-Emissions-Häuser mit Smart-Home-Anbindung lassen sich damit allerdings nicht errichten. Und mal ehrlich, die Anzahl der Kunden, die händeringend ein Fachwerkhaus suchen, ist sehr gering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5tk2qj1jwh4yl7ewr1p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5tk2qj1jwh4yl7ewr1p.jpg" alt="Modernes Haus" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloudbasierte Anwendungen stellen die Softwarearchitekten von heute vor neue und ungewohnte Herausforderungen. Sie benötigen moderne Verfahren die speziell auf die neuen Herausforderungen der Cloud ausgerichtet sind, damit die Anwendungen zuverlässig und zugleich ressourcenschonend operieren. Ob CPU, Arbeits- oder Festplattenspeicher, alles was weniger gebraucht wird, spart Kosten im Betrieb der Cloud und ist zugleich ein Schritt hin zu einer &lt;a href="https://de.wikipedia.org/wiki/Green_IT" rel="noopener noreferrer"&gt;Green-IT&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zahlreiche Herausforderungen
&lt;/h2&gt;

&lt;p&gt;Ich kann nicht alle Herausforderungen in der erforderlichen Tiefe in diesem Artikel behandeln. Der Text würde so lang und kompliziert, dass niemand ihn zu Ende lesen würde. Stattdessen konzentriere ich mich auf die Serialisierung. Unter Serialisierung versteht man die Umwandlung von strukturierten Daten in ein für die Übertragung oder Speicherung geeignetes Format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55lpjaaig9g7h6ykvkkt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55lpjaaig9g7h6ykvkkt.gif" alt="Serialisierung zur Übertragung" width="603" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In modernen verteilten Systemen ist es entscheidend, ein schnelles und zuverlässiges Serialisierungsformat zu wählen. Die Serialisierung selbst ist nicht aufwändig. sie wird aber häufig benutzt. Die Entscheidung für ein optimales Format hat also einen großen Einfluss auf das Gesamtdesign der Cloudanwendung.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcbj9qfssojsngbisg2p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcbj9qfssojsngbisg2p.gif" alt="Serialisierung zur Speicherung" width="603" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ein gut gewähltes Format stellt sicher, dass Daten schnell und mit minimalem Overhead über das Netzwerk übertragen werden. Darüber hinaus ist ein zuverlässiges Format entscheidend um Fehler bei der Datenrekonstruktion zu vermeiden und damit die Datenintegrität sicherzustellen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatisierung als Erfolgsfaktor
&lt;/h2&gt;

&lt;p&gt;Aktuell ist JSON das von vielen präferierte Serialisierungsformat. Es hat im Laufe der Jahre XML den Rang abgelaufen. Der Vorteil von JSON, als für Menschen einfach zu erstellendes und lesbares Format, kommt aber gerade beim Einsatz für die Kommunikation zwischen Microservices nicht wirklich zum Tragen. Daten und Prozesse werden zwar durch menschliche Interaktion angestoßen, laufen danach aber ausschließlich nachgelagert zwischen Systemen ab und reagieren dabei eher auf Events als auf Benutzer.&lt;/p&gt;

&lt;p&gt;Auch das Argument, dass Entwickler das Serialisierungsformat lesen müssen ist nicht mehr griffig. Cloud-Anwendungen laufen in Containern, die selbst durch Systeme, wie zum Beispiel Kubernetes, überwacht und kontrolliert werden. Manuelle Eingriffe sind dabei nicht das Mittel der Wahl um Probleme zu analysieren und zu beheben.&lt;/p&gt;

&lt;p&gt;Auch das Aktualisieren der Anwendung erfolgt durch eine Continuous-Delivery-Pipeline in kurzen Intervallen, um so den Anforderungen des Marktes nach schnellen und ständigen Anpassungen gerecht zu werden. Die Automatisierung ist in diesem Rahmen alternativlos, gewährleistet sie doch den sicheren und, in Zeiten von Fachkräftemangel, günstigen Betrieb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gamechanger Protobuf
&lt;/h2&gt;

&lt;p&gt;Ich werde in diesem Artikel kurz auf die Begriffe und Funktionsweise von Protobuf eingehen. Dann erläutere ich seine neuartigen Konzepte und Eigenschaften und inwiefern diese die Mängel von JSON beheben. Damit begründe ich, warum Protobuf eine überragende Verbesserung im Vergleich zu JSON ist und damit die nächste Stufe in der Entwicklung der Serialisierungsformate darstellt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sicher durch präzise Definition und starke Typisierung&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Um Daten zu serialisieren, müssen sie zunächst einmal strukturiert beschrieben werden. Bei Protobuf werden die Datenstrukturen in Dateien mit der Endung .proto definiert. In dieser Proto-Definition wird ein Objekt durch eine Nachricht („Message") beschrieben. Eine Message kann eine beliebige Anzahl von Feldern („Fields") beinhalten. Ein Field hat einen Bezeichner, einen Typ und eine, für diese Message, eindeutige Nummer. Der Typ eines Feldes kann wiederum eine Message sein, wodurch eine Baumstruktur entsteht wie sie aus JSON und XML bekannt ist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;Page&lt;/span&gt; &lt;span class="na"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;Das obrige Codebespiel beschreibt eine vereinfachte Datenstruktur für ein Buch (&lt;em&gt;„Book"&lt;/em&gt;). Das Buch hat einen Titel von Typ &lt;em&gt;string&lt;/em&gt;, der eine beliebige Zeichenkette beinhalten kann. Das Buch verfügt darüber hinaus über beliebig viele (&lt;em&gt;„repeated"&lt;/em&gt;) Seiten, die als je eine Message vom Typ &lt;em&gt;„Page"&lt;/em&gt; hinterlegt werden. Die Seiten wiederum haben den Text der auf ihnen steht im Field &lt;em&gt;„content"&lt;/em&gt; hinterlegt. Die explizite Angabe von Typen in der Proto-Definition hilft Fehler zu vermeiden und stellt die Konsistenz über Systemgrenzen hinweg sicher. Protobuf unterstützt dabei die gängigen &lt;a href="https://protobuf.dev/programming-guides/proto3/#scalar" rel="noopener noreferrer"&gt;Datentypen von Programmiersprachen&lt;/a&gt;. Zudem stellt Google eine Sammlung von erweiterten Typen, die sogenannten &lt;a href="https://cloud.google.com/dotnet/docs/reference/Google.Protobuf/latest/Google.Protobuf.WellKnownTypes" rel="noopener noreferrer"&gt;&lt;em&gt;WellKnownTyps&lt;/em&gt;&lt;/a&gt;, bereit. Darüber hinaus können eigene Datentypen über selbst definierte Messages erstellt werden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hohe Produktivität durch Codegenerierung&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bei anderen Serialisierungsformaten wird viel wertvolle Entwicklungszeit damit verbracht die Datenstrukturen als Objekte in der Programmiersprache noch einmal anzulegen und bei Änderungen diese kontinuierlich nachzuziehen. Die Arbeit ist aufwändig, aber nicht sehr herausfordernd, was dazu führt, dass sich schnell Fehler einschleichen. Generische Mapper versprechen zwar den Aufwand zu reduzieren, sie sind allerdings in allen Fällen, wo man vom Normalanwendungsfall abweicht, selbst schwer zu konfigurieren.&lt;/p&gt;

&lt;p&gt;Protobuf setzt daher auf eine automatische und kontinuierliche Codegenerierung und unterstützt dabei gleich mehrere Programmiersprachen. Aus der Proto-Definition wird direkt der Code für die Datenstruktur generiert, womit ein Großteil des Aufwands anderer Ansätze entfällt. Außerdem ist garantiert, dass Sender und Empfänger immer die gleiche Implementierung nutzen solange sie die selbe Version des Protoschemas haben. Der generierte Sourcecode kann zudem flexibel um zusätzliche Methoden ergänzt werden, um so die Benutzung von eigenen Datentypen komfortabler zu machen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streng definierter aber erweiterbarer Vertrag&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Die Proto-Definition ist ein Schnittstellenvertrag (&lt;em&gt;„Contract"&lt;/em&gt;) zwischen dem Sender und dem Empfänger. In Protobuf wird ausschließlich auf einfache Strukturelemente gesetzt. So bezeichnet das Keyword repeated eine Liste einer Message ohne Aussagen über deren genaue Anzahl zumachen[vgl. Abb. 1]. Einerseits führt dies dazu, dass der Sender mögliche Einschränkungen auf Empfängerseite nicht aus dem Contract herauslesen kann. Andererseits kann dieses auch als Chance gesehen werden den Vertrag flexibler und damit leichter erweiterbar zu gestalten. Die Möglichkeit den Vertrag leicht ändern zu können und gleichzeitig die Integrität der Anwendung beizubehalten, ist von einem unschätzbaren Wert für eine sich ständig weiterentwickelnde Systemlandschaft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Einfache Schemaevolution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bei dem Design von Protobuf wurde ein hoher Wert auf Kompatibilität gelegt. Eine Änderung ist dann abwärtskompatibel, wenn die neue Version eines Empfängers Nachrichten eines Senders einer alten Version verarbeiten kann. Andersherum ist eine Änderung dann vorwärtskompatibel wenn die neue Version eines Senders weiterhin mit der alten Version des Empfängers funktioniert.&lt;/p&gt;

&lt;p&gt;Durch die Einhaltung von nur wenigen einfachen Regeln erhält man mit Protobuf sowohl Abwärts- als auch Aufwärtskompatibilität. So darf der Typ und die Nummer eines Fields nie geändert werden. Eine Message und ein Field können nicht gelöscht werden. In der Syntax proto3 wird zudem auch auf die Definition von Pflichtfeldern verzichtet, da Erweiterungen eines Vertrags immer optional sein müssen, um die Kompatibilität zu erhalten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kompatibilität für Zero-Downtime&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Abwärts- und Vorwärtskompatibilität sind besonders dann wichtig, wenn man nicht gewährleisten kann, dass Sender und Empfänger und alle Nachrichten gleichzeitig auf eine neue Version einer Schnittstellenbeschreibung aktualisiert werden können. Dies ist für Cloudanwendungen eine wichtige Eigenschaft.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o6z1e0n7f7fy9vdplfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o6z1e0n7f7fy9vdplfg.png" alt="Rolling update" width="650" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Volle Kompatibilität ermöglicht es einen Sender und einen Empfänger unabhängig voneinander auszurollen, wobei auf eine aufwändige Koordinierung der Reihenfolge verzichtet und Continuous-Delivery somit erst ermöglicht wird. Der Einsatz einer modernen Softwarelösung für das Management von containerisierten Anwendungen, wie zum Beispiel Kubernetes, ermöglicht zudem ein automatisches &lt;a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/" rel="noopener noreferrer"&gt;Rolling Update&lt;/a&gt;. Dabei wird eine neue Version der Anwendung ohne Downtime im laufenden Betrieb ausgerollt. Die Benutzer bekommen davon nichts mit, was einen unterbrechungsfreien Service und ein optimales Kundenerlebnis erlaubt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Überragende Effizienz da binär und minimalistisch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bei Protobuf werden die Nachrichten im Binärformat serialisiert. Dabei werden nur die Nummer und der jeweilige Wert des Feldes einer Nachricht gespeichert. Um die Nachricht später auslesen zu können, benötigt man deshalb die Proto-Definition. Damit verzichtet die Serialisierung bewusst auf unnötigen Ballast, die für den eigentlichen Nutzen nicht notwendig sind. Durch die geringe Datenmenge ist das Erzeugen und Lesen von Nachrichten somit sehr schnell.&lt;/p&gt;

&lt;p&gt;Allerdings kann es durch die minimale Speicherung der Nachricht und die hohe Kompatibilität bei Fehlern in der Entwicklung zu unerwartetem Verhalten kommen. So können Nachrichten unter Umständen durch eine fremde Proto-Definition deserialisiert werden, ohne dass ein Fehler auftritt. Deshalb ist es zwingend notwendig die Benutzung von Protobuf durch automatisierte Tests abzusichern. Mit diesen kann auch die Kompatibilität zu alten Versionen sichergestellt werden.&lt;/p&gt;

&lt;p&gt;Eine manuelle Fehleranalyse von Nachrichten im Einzelfall ist einfach möglich, indem man die Binärinformationen in ein Objekt umwandelt. Zudem lassen sich Nachrichten einfach in ein JSON-Format konvertieren. Der dafür notwendige Code wird entsprechend der Proto-Definition vollständig generiert. Es können so auch LegacySysteme, die ausschließlich mit JSON arbeiten können, an moderne Anwendungen angeschlossen werden. Die Vorteile von Protobuf können hierbei zumindest teilweise genutzt werden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazit
&lt;/h2&gt;

&lt;p&gt;Kein Serialisierungsformat, weder Protobuf noch JSON, darf als „&lt;a href="https://archive.org/details/antipatternsrefa0000unse/page/110/mode/2up" rel="noopener noreferrer"&gt;Goldener Hammer&lt;/a&gt;" verwendet werden. Protobuf ist daher sicher keine Alternative zu JSON für alle Anwendungsfälle und wurde auch nicht dafür entwickelt. Die Stärken von Protobuf machen es zu einem flexiblen und zugleich besonders effizienten Serialisierungsformat. Der Fokus des Einsatzgebietes liegt dabei primär auf dem Datenaustausch zwischen Softwaresystemen. Es kann daher besonders einfach überall dort eingesetzt werden, wo im Rahmen der Digitalisierung automatisierte Prozesse geschaffen werden.&lt;/p&gt;

&lt;p&gt;Die initiale Einführung von Protobuf mag einen Einarbeitungs- und Lernaufwand bedeuten, der sich aber durch die einheitliche Verwendung des Serialisierungsformats für möglichst viele Anwendungsfälle schnell wieder auszahlt. Durch den hohen Automatisierungsgrad und die Codegenerierung kann sich die Entwicklung besser auf ihre eigentliche Aufgabe - das Lösen von Problemen - konzentrieren.&lt;/p&gt;

&lt;p&gt;Jetzt sind deiner Kreativität keine Grenzen mehr gesetzt, um basierend auf Protobuf Lösungen für deine Domäne zu entwerfen. Ich bin sehr gespannt auf deine Ideen und Vorschläge. Denn streben wir nicht letztlich alle danach, unsere Software durch ein erstklassiges Design kontinuierlich zu verbessern und damit zum Erfolg zu führen?&lt;/p&gt;

&lt;p&gt;In zukünftigen Artikeln werde ich selbst einige konkrete Beispiele liefern, wie ich Protobuf in der Vergangenheit eingesetzt habe. Am besten folgst du mir, um keinen meiner zukünftigen Artikel zum Thema Protobuf zu verpassen.&lt;/p&gt;

</description>
      <category>protobuf</category>
      <category>cloudnative</category>
      <category>cloud</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Spring cleanup: Outdated Git tags</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Wed, 31 Jul 2024 14:25:00 +0000</pubDate>
      <link>https://dev.to/kinneko-de/spring-cleanup-outdated-git-tags-3fai</link>
      <guid>https://dev.to/kinneko-de/spring-cleanup-outdated-git-tags-3fai</guid>
      <description>&lt;p&gt;Imagine your repository is flooded with auto-generated Git tags. Most of them are from feature branches you no longer remember anymore. Just as you strive to keep your code clean, you should also regularly clean up these Git tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feb3noof0th53m6rt8w8f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feb3noof0th53m6rt8w8f.jpg" alt="Spring cleanup" width="640" height="424"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently I started programming with Go. I have written some modules and in order to use them in other projects, I need to &lt;a href="https://go.dev/blog/publishing-go-modules" rel="noopener noreferrer"&gt;publish&lt;/a&gt; them. Go uses Git tags to mark a specific version. The tags should use semantic versioning. I am already familiar with semantic versioning. In C#, I used this simple approach to give my services and NuGet packages a descriptive version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7sg9ruzn4l9tprru3eo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7sg9ruzn4l9tprru3eo.jpg" alt="Git version tag" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am a lazy person and I like to automate all my work. So I invested blood and tears in automatically tagging my code with corresponding semantic versions. Having even managed to handle multiple modules in one repository, I am now facing the next challenge.&lt;/p&gt;

&lt;p&gt;I use feature branches to develop and pre-release my code. The pipeline generates new Git tags with each check-in, which is useful during development. But how do I get rid of them, after I merge the feature branch? I don't want to waste my life manually deleting things.&lt;/p&gt;

&lt;p&gt;The full code for the article is available on GitHub. The repository also has an open feature branch for demonstration purposes.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-git-tag" rel="noopener noreferrer"&gt;
        sample-git-tag
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application how to cleanup outdated Git tags.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;sample-git-tag&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Sample application how to cleanup outdated Git tags.&lt;/p&gt;
&lt;p&gt;See my &lt;a href="https://medium.com/@kinneko-de/7d17fa85c175" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt; how to create this by yourself.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-git-tag" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Step 0: Producing semantic versioning tags
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;Semantic versioning&lt;/a&gt; is a popular versioning scheme that uses a format like "major.minor.patch" to indicate the version of the code base. My GitHub workflow has an environment variable 'MAJOR_MINOR_PATCH' which you must manually update according to the &lt;a href="https://semver.org/#semantic-versioning-specification-semver" rel="noopener noreferrer"&gt;rules of semantic versioning&lt;/a&gt; when you develop the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9kdzvyx8oyg3imnrl2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9kdzvyx8oyg3imnrl2g.png" alt="Semantic version git tag" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;main&lt;/code&gt; branch, the semantic version is determined by the environment variable. For feature branches, a &lt;a href="https://semver.org/#spec-item-9" rel="noopener noreferrer"&gt;pre-release version&lt;/a&gt; is defined by a suffix containing the name of the branch and the build run number: &lt;code&gt;v0.1.0-feature-branch.1&lt;/code&gt;. The workflow then pushes the semantic version with &lt;a href="https://github.com/actions/github-script" rel="noopener noreferrer"&gt;actions/github-script&lt;/a&gt; as a Git tag. Adding the build run number makes the Git tag unique without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Delete outdated tags with a script
&lt;/h2&gt;

&lt;p&gt;When I need to automate my task, I start with writing a script that I can run and debug locally. The first step is to have a good design. I want to delete the git tag from the remote repository. Outdated git tags are defined by git tags, that contain a semantic prerelease version and where the corresponding feature branch was already deleted.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I need to update my local version to the remote version.&lt;/li&gt;
&lt;li&gt;I have to fetch all git tags.&lt;/li&gt;
&lt;li&gt;I have to fetch all branches.&lt;/li&gt;
&lt;li&gt;Determine for each git tag if it is outdated.&lt;/li&gt;
&lt;li&gt;If so, delete the git tag on the remote repository.&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;git fetch --tags --prune --prune-tags&lt;/code&gt; will fetch branches and tags from the remote version and delete any local branches and tags that no longer exist on the remote.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git branch -r&lt;/code&gt; retrieves all remote branches. Then the names are normalized by removing slashes and whitespaces with &lt;code&gt;sed 's/^*//;s/ *$//&lt;/code&gt;. Finally, I filter out my &lt;code&gt;main&lt;/code&gt; branch with &lt;code&gt;egrep -v "^main"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git tag - list 'v[0–9]*\.[0–9]*\.[0–9]*-*&lt;/code&gt; gives me a list of all git tags, where the name starts with a &lt;code&gt;v&lt;/code&gt; followed by the major, minor, and patch version numbers. It also must be followed by a hyphen &lt;code&gt;-&lt;/code&gt; and any character that indicates a pre-release version.&lt;/p&gt;

&lt;p&gt;I have the convention that all my feature branches start with &lt;code&gt;feature&lt;/code&gt; followed by the name of the branch. But the git tag only contains the name of the branch. An example &lt;code&gt;feature/branchname&lt;/code&gt; for the feature branch and the corresponding git tag is then &lt;code&gt;v1.2.0-branchname&lt;/code&gt;.&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="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^v[0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0-9]&lt;span class="k"&gt;*&lt;/span&gt;-&lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="nv"&gt;featurebranchname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_REMATCH&lt;/span&gt;&lt;span class="p"&gt;[1]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For each of the git tags I cut the feature branch name out (see code block above) and check if a feature branch with a name according to my conversion exists (see code block below).&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;$existingfeaturebranches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="s2"&gt;"feature/&lt;/span&gt;&lt;span class="nv"&gt;$featurebranchname&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If not so, then the git tag is first deleted on the origin &lt;code&gt;git push origin --delete $tag&lt;/code&gt; and then locally &lt;code&gt;git tag -d $tag&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Integrate the script into a CI pipeline
&lt;/h2&gt;

&lt;p&gt;Automating tasks with scripts is a valuable first step, but manual execution is time-consuming. Integrating the script into my CI pipeline, triggered on every code push, would be a more convenient approach.&lt;/p&gt;

&lt;p&gt;To create this step for my workflow, I ask GitHub Copilot to translate the bash script into a step for my GitHub workflow. Like me, GitHub Copilot prefers to use &lt;a href="https://github.com/actions/github-script" rel="noopener noreferrer"&gt;actions/github-script&lt;/a&gt;. Unexpectedly, GitHub Copilot generated JavaScript code instead of Bash. It seems that JavaScript is the preferred language for GitHub actions.&lt;/p&gt;

&lt;p&gt;The logic of how the code fetches the branches has changed. GitHub Copilot missed that I was filtering out the &lt;code&gt;main&lt;/code&gt; branch. My Git tags for the &lt;code&gt;main&lt;/code&gt; branch have the format &lt;code&gt;v1.2.0&lt;/code&gt; and do not include the name of the &lt;code&gt;main&lt;/code&gt; branch itself. Other than the fact that the &lt;code&gt;main&lt;/code&gt; branch is now logged as an existing feature branch, the change does not affect the result.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingFeatureBranches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listBranches&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Another change is that Git tags are no longer filtered to feature branch tags only. Aside from the longer runtime, this does not change the logic, since the other tags do not match the feature branch regular expression anyway. I keep the code as it is and consider this a limitation of the GitHub Rest API.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listMatchingRefs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tags/v&lt;/span&gt;&lt;span class="dl"&gt;'&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;The workflow with the script is triggered every time I push code. I do this a lot to break problems into smaller steps. Since deleting git tags takes time, I do not want to run the script on every push. My repository is configured to automatically delete the feature branch when a pull request completes. After that, the workflow is triggered for the &lt;code&gt;main&lt;/code&gt; branch. If I run the action on every run of the workflow for the &lt;code&gt;main&lt;/code&gt; branch, it will fit my needs perfectly.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Delete Git Tags&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To achieve this goal, I introduce a conditional check within the pipeline using the &lt;code&gt;if&lt;/code&gt; keyword. The branch that triggered the current run of the workflow is defined by &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context" rel="noopener noreferrer"&gt;&lt;code&gt;github.ref&lt;/code&gt;&lt;/a&gt;. In my case, the name of my &lt;code&gt;main&lt;/code&gt; branch is &lt;code&gt;refs/heads/main&lt;/code&gt;. &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions" rel="noopener noreferrer"&gt;The step will be now only executed if the two variables have the same value&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, you can also take a look at my first GitHub action that replicates the functionality of the step inside this sample pipeline. The GitHub action is implemented using TypeScript. It's important to note that this GitHub action is specifically tailored to my conventions and lacks flexibility. Under these miserable conditions, I recommend understanding the underlying concepts before directly using my GitHub action in your projects. Instead, feel free to copy and modify the code to suit your needs.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/cleanup-outdated-tag-action" rel="noopener noreferrer"&gt;
        cleanup-outdated-tag-action
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GitHub action to cleanup outdated Git tags.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Create a GitHub Action Using TypeScript&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/super-linter/super-linter" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/actions/typescript-action/actions/workflows/linter.yml/badge.svg" alt="GitHub Super-Linter"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/actions/typescript-action/actions/workflows/ci.yml/badge.svg"&gt;&lt;img src="https://github.com/actions/typescript-action/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/actions/typescript-action/actions/workflows/check-dist.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/actions/typescript-action/actions/workflows/check-dist.yml/badge.svg" alt="Check dist/"&gt;&lt;/a&gt;
&lt;a href="https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml/badge.svg" alt="CodeQL"&gt;&lt;/a&gt;
&lt;a href="https://github.com/KinNeko-De/cleanup-outdated-tag-action./badges/coverage.svg" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iXbL8xwS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/KinNeko-De/cleanup-outdated-tag-action./badges/coverage.svg" alt="Coverage"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Use this template to bootstrap the creation of a TypeScript action. 🚀&lt;/p&gt;
&lt;p&gt;This template includes compilation support, tests, a validation workflow
publishing, and versioning guidance.&lt;/p&gt;
&lt;p&gt;If you are new, there's also a simpler introduction in the
&lt;a href="https://github.com/actions/hello-world-javascript-action" rel="noopener noreferrer"&gt;Hello world JavaScript action repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Create Your Own Action&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To create your own action, you can use this repository as a template! Just
follow the below instructions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the &lt;strong&gt;Use this template&lt;/strong&gt; button at the top of the repository&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Create a new repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select an owner and name for your new repository&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clone your new repository&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Make sure to remove or update the &lt;a href="https://github.com/KinNeko-De/cleanup-outdated-tag-action./CODEOWNERS" rel="noopener noreferrer"&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/a&gt; file! For
details on how to use this file, see
&lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners" rel="noopener noreferrer"&gt;About code owners&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Initial Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;After you've cloned the repository to your local machine or codespace, you'll
need to perform some initial setup steps before you can…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/cleanup-outdated-tag-action" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I'm excited to share my experience and encourage others to explore the potential of GitHub Actions for my automation needs. Thank you for reading my story until the end. ❤&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>git</category>
      <category>go</category>
      <category>semanticversioning</category>
    </item>
    <item>
      <title>OpenTelemetry Metrics meets Azure</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Sat, 20 Jul 2024 20:19:15 +0000</pubDate>
      <link>https://dev.to/kinneko-de/opentelemetry-metrics-meets-azure-3o6o</link>
      <guid>https://dev.to/kinneko-de/opentelemetry-metrics-meets-azure-3o6o</guid>
      <description>&lt;p&gt;Tutorial featuring &lt;a href="https://devblogs.microsoft.com/dotnet/azure-monitor-opentelemetry-distro/" rel="noopener noreferrer"&gt;Azure Monitor OpenTelemetry Distro&lt;/a&gt; for C# and Grafana&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is the future of observability. It is being pushed by Google, Microsoft and Amazon to provide a vendor-neutral solution for traces, metrics and logs. The OpenTelemetry API implementation is available for many programming languages. I will only use metrics and the implementation for the language C# for this tutorial. Let's see how far apart reality and desire are.&lt;/p&gt;

&lt;p&gt;I will use the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-enable?tabs=aspnetcore" rel="noopener noreferrer"&gt;Azure Monitor OpenTelemetry Distro&lt;/a&gt; to publish the metrics to the Azure cloud. The way the metrics are collected in the application is independent of this decision. It affects the way how the data is processed by the cloud components. Using the &lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry Collector&lt;/a&gt; as an alternative is also &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-configuration?tabs=aspnetcore#enable-the-otlp-exporter" rel="noopener noreferrer"&gt;not officially supported by Azure&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, I want to aggregate, transform, and visualize the raw metrics. I will use &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview" rel="noopener noreferrer"&gt;Log Analytics&lt;/a&gt;, Azure's default log analysis solution. Additionally, I will also use &lt;a href="https://grafana.com/grafana/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; to create a beautiful dashboard. &lt;a href="https://azure.microsoft.com/en-US/products/managed-grafana" rel="noopener noreferrer"&gt;Grafana is available as a managed service on Azure&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  OPS side - Infrastructure
&lt;/h2&gt;

&lt;p&gt;The application will push the metrics from the running application to Application Insights. It will use the C# SDK for the connection to Azure Monitor. The data will be stored in the table '&lt;em&gt;customMetrics&lt;/em&gt;' of the Logs database. Log Analytics can query that table. Grafana queries the data from Log Analytics and displays it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl23w1qx6nxuwe68oirbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl23w1qx6nxuwe68oirbu.png" alt="Azure Components needed" width="742" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DEV side - Application
&lt;/h2&gt;

&lt;p&gt;My goal is to provide the smallest possible sample to get you started with OpenTelemetry Metrics and Azure. The full &lt;a href="https://github.com/KinNeko-De/sample-opentelemetry-azure" rel="noopener noreferrer"&gt;source code is also available on GitHub&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-opentelemetry-azure" rel="noopener noreferrer"&gt;
        sample-opentelemetry-azure
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application how to publish OpenTelemetry metrics to Azure
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motiviation&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Sample application how to publish OpenTelemetry metrics to Azure&lt;/p&gt;
&lt;p&gt;See my &lt;a href="https://medium.com/@kinneko-de/c28fc0c7d7d9" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt; how to create this by yourself.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-opentelemetry-azure" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-instrumentation" rel="noopener noreferrer"&gt;Metrics in C# are instrumented&lt;/a&gt; with classes from the &lt;em&gt;System.Diagnostics.Metrics&lt;/em&gt; namespace. The namespace provides an abstraction to separate the instrumentation from the way the metrics &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-collection" rel="noopener noreferrer"&gt;are later collected&lt;/a&gt;. OpenTelemetry uses a &lt;a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/" rel="noopener noreferrer"&gt;generic data model&lt;/a&gt; to define these metrics. For this example, I will use a &lt;em&gt;counter&lt;/em&gt; as the simplest type. The metric will track the number of items ordered as an integer counter that only ever goes up.&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Meter&lt;/span&gt; &lt;span class="n"&gt;Meter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"restaurant-order-svc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ItemsOrderedTotal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
      &lt;span class="n"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
         &lt;span class="s"&gt;"restaurant-order-svc-items-ordered"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"Items ordered total"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I define my metrics in a specific class called '&lt;em&gt;Metric&lt;/em&gt;'. It is injected as a &lt;a href="https://en.wikipedia.org/wiki/Singleton_pattern" rel="noopener noreferrer"&gt;singleton&lt;/a&gt;. The counter is declared as a static field. I prefer not to access the field directly, but to provide a wrapper method instead. With this approach, the class encapsulates how business data is transferred to generic metric data types. Using another layer of abstraction for a simple counter seems like overkill. The advantage becomes more apparent when I use &lt;a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram-bucket-inclusivity" rel="noopener noreferrer"&gt;histograms with buckets&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ItemsOrdered&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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ItemsOrderedTotal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;The metric is simulated with an infinite &lt;a href="https://github.com/KinNeko-De/sample-opentelemetry-azure/blob/main/Sample.Metrics/Orders/ItemsOrdered.cs" rel="noopener noreferrer"&gt;background service&lt;/a&gt;. It sends a random number of ordered items at each interval. In a real application, I would instead increase the metric after the order has been successfully stored in the database.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;itemsOrdered&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomNumberGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ItemsOrdered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemsOrdered&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To publish the collected data, I have to configure the application to use OpenTelemetry. I do this within my &lt;code&gt;Program.cs&lt;/code&gt; with the service collection extension &lt;code&gt;AddOpenTelemetry()&lt;/code&gt;. The extension is available via the NuGet package &lt;a href="https://www.nuget.org/packages/OpenTelemetry.Extensions.Hosting" rel="noopener noreferrer"&gt;&lt;em&gt;OpenTelemetry.Extensions.Hosting&lt;/em&gt;&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&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;To publish the data to Azure, I use the service collection extension &lt;code&gt;UseAzureMonitor()&lt;/code&gt; service collection extension. This extension is available via the NuGet package &lt;a href="https://www.nuget.org/packages/Azure.Monitor.OpenTelemetry.AspNetCore" rel="noopener noreferrer"&gt;&lt;em&gt;Azure.Monitor.OpenTelemetry.AspNetCore&lt;/em&gt;&lt;/a&gt;. This package has a dependency on &lt;a href="https://www.nuget.org/packages/OpenTelemetry.Extensions.Hosting" rel="noopener noreferrer"&gt;&lt;em&gt;OpenTelemetry.Extensions.Hosting&lt;/em&gt;&lt;/a&gt; so I only need this package as a dependency in my application. Within the AzureMonitor configuration, I need to use the ConnectionString to my Application Insights resource.&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;applicationInsightsConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="s"&gt;"InstrumentationKey=00000000-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;UseAzureMonitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationInsightsConnectionString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metricBuilder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;metricBuilder&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Operations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsoleExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
               &lt;span class="n"&gt;ConsoleExporterOutputTargets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;)&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 also added an OpenTelemetry console exporter to see all the data being generated. It takes some time for the data to appear in Application Insights. Metrics can generally be lost if the application crashes. If you are running the application and you want to be sure that the data is pushed, you should shut down the application gracefully. Locally, you can do this by pressing CTRL+C in the application console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Azure Infrastructure
&lt;/h2&gt;

&lt;p&gt;Firstly, I need to create an &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/azure-monitor-workspace-overview" rel="noopener noreferrer"&gt;Azure Monitor workspace&lt;/a&gt;. I am not sure, if I need it for publishing the metrics. As far as I understood the documentation, it is needed for Grafana to access the stored metrics.&lt;/p&gt;

&lt;p&gt;I have also created an &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview" rel="noopener noreferrer"&gt;Application Insights&lt;/a&gt; resource. I need the connection string for the C# SDK. When I create the Application Insights, an associated &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-workspace-overview" rel="noopener noreferrer"&gt;Log Analytics Workspace&lt;/a&gt; is automatically generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frri8e6ikp7r8vt2pe9bl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frri8e6ikp7r8vt2pe9bl.png" alt="Creating a new application Insights" width="750" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had the problem that most of the time I could not query any data through this generated Log Analytics Workspace. The effect was quite random and I was totally confused. It seemed to be solved by creating a new Log Analytics namespace and linking it to the Applications Insights resource. If you encounter the same problem, please leave a comment.&lt;/p&gt;

&lt;p&gt;Finally, I created an &lt;a href="https://azure.microsoft.com/en-us/products/managed-grafana" rel="noopener noreferrer"&gt;Azure Managed Grafana&lt;/a&gt; resource. I change the pricing tier from '&lt;em&gt;Standard&lt;/em&gt;' to "&lt;em&gt;Essential&lt;/em&gt;". This pricing tier is &lt;a href="https://techcommunity.microsoft.com/t5/azure-observability-blog/azure-managed-grafana-adds-new-sku-and-features/ba-p/3980412#:~:text=These%20users%20don't%20require,for%20development%20and%20testing%20uses." rel="noopener noreferrer"&gt;suggested for my use case&lt;/a&gt;, but it also has some &lt;a href="https://learn.microsoft.com/en-us/azure/managed-grafana/known-limitations" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; and a pitfall that I will describe later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize with Log Analytics
&lt;/h2&gt;

&lt;p&gt;The first challenge I have to overcome is querying the raw data. To do this, I need to go to Application Insight. In the 'Monitor' section, I select the 'Logs' item. The most obvious choice would be 'Metrics', but I cannot find the OpenTelemetry metrics here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr018d7zcu2rem71allnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr018d7zcu2rem71allnm.png" alt="Monitor section" width="152" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are several tables within the Logs section. The OpenTelemetry metrics are stored in the 'customMetrics' table. I can now query the data using the &lt;a href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/" rel="noopener noreferrer"&gt;Kusto Query Language (KQL)&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;customMetrics&lt;/span&gt; 
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'restaurant-order-svc-items-ordered'&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each metric must have a &lt;a href="https://opentelemetry.io/docs/specs/semconv/general/metrics/#general-guidelines" rel="noopener noreferrer"&gt;unique name&lt;/a&gt;. In this first query, I filter my metric by name and then sort by timestamp. The result of this query is a table where each row is a reported counter value. Because I only increment the value every 15 minutes, the column value is exactly the number of one order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3gmfymmh4gxxde881uh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3gmfymmh4gxxde881uh.png" alt="Query raw data using KQL" width="456" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also possible to create a time chart from the raw data. I simply add a &lt;a href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/render-operator?pivots=azuredataexplorer" rel="noopener noreferrer"&gt;&lt;code&gt;render&lt;/code&gt; command&lt;/a&gt; to the KQL query. There are &lt;a href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/render-operator?pivots=azuredataexplorer#visualization" rel="noopener noreferrer"&gt;various types of charts&lt;/a&gt; available to visualize the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;customMetrics&lt;/span&gt; 
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'restaurant-order-svc-items-ordered'&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;timechart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a time chart that looks very nice. The reason for this is not my query itself, but rather the fact that the application has published exactly one data point every 15 minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyzzs4njpgymba472sb2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyzzs4njpgymba472sb2.png" alt="Time chart with every datapoint" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To visualize the data, I need to aggregate the values over a specific interval. In this query, I choose a fixed interval of one hour. The &lt;a href="https://learn.microsoft.com/de-de/azure/data-explorer/kusto/query/bin-function" rel="noopener noreferrer"&gt;timestamp of each data point is rounded to this interval&lt;/a&gt;. The values for all &lt;a href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/summarize-operator" rel="noopener noreferrer"&gt;data points are summarized&lt;/a&gt; to one value per hour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;customMetrics&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'restaurant-order-svc-items-ordered'&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;summarize&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;timechart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a graph showing the total number of items ordered each hour.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv72b2y5j63tyljg07x1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv72b2y5j63tyljg07x1j.png" alt="Items ordered per hour" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize with Grafana
&lt;/h2&gt;

&lt;p&gt;Once I have created the Grafana resource, I can already enter it and create new dashboards. Within the Azure Portal, I can find the URL to access the Grafana resource.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvbb4d0l0rfb0x1z83h5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvbb4d0l0rfb0x1z83h5.png" alt="Grafana URL" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to give Grafana access, I need to link Grafana to the Azure Monitor Workspace. It looked like I could also set the link in Azure Managed Grafana unter ‘Azure Monitor workspaces’, but here I got an error message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F749pisq35uqe3n8igb21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F749pisq35uqe3n8igb21.png" alt="Linking Grafana to Azure Monitor Workspace" width="690" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After connecting the Grafana to Azure Monitor, I have a new data source ‘Azure Monitor’ in Grafana. This data source is already configured correctly. Azure also generated a lot of additional dashboards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jwqeoofnpvxev5ds3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jwqeoofnpvxev5ds3u.png" alt="generated datasource ‘’Azure Monitor”" width="693" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grafana was created with the ‘&lt;em&gt;Essential&lt;/em&gt;’ pricing tear because it costs less. The pricing tier has a &lt;a href="https://learn.microsoft.com/en-us/azure/managed-grafana/known-limitations#throttling-limits-and-quotas" rel="noopener noreferrer"&gt;limit on the number of dashboards&lt;/a&gt;, I can only have 20. With the newly created dashboards, I exceed this limit. Whenever I try to save a new dashboard, I get an error message that my quota has been exceeded. I can now change the pricing tier to ‘&lt;em&gt;Standard&lt;/em&gt;’, but I can never go back to ‘&lt;em&gt;Essential&lt;/em&gt;’. I have fallen into this trap. A much cheaper solution would be to delete all the other dashboards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmt1wff4hmufwqxbfyfzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmt1wff4hmufwqxbfyfzu.png" alt="First steps: Datasource ‘Azure Monitor’" width="297" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am now ready to start my first visualization. In the ‘&lt;em&gt;Query&lt;/em&gt;’ tab, I first select the correct data source to retrieve data from the ‘&lt;em&gt;customMetric&lt;/em&gt;’ table. The database source is ‘&lt;em&gt;Azure Monitor&lt;/em&gt;’ (1st in the previous picture). I need to change the Service value to ‘&lt;em&gt;Logs&lt;/em&gt;’. The value describes the database I want to query from (2nd in the previous picture). I then select as the Resource the Application Insight resource (3rd in the previous picture) on which I previously ran my KQL queries (see the following picture).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4peyv6gukt830s043hh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4peyv6gukt830s043hh5.png" alt="Second steps: Select Resource Application Insights resource" width="674" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want to select the number of items ordered. To do this, I simply summarize all the values. This is not the total number of items ever ordered, as metrics data is usually purged after some time. It just gives me an idea of the most recent orders and that the application is running smoothly. If the number of orders is lower than expected, it may be an indication that something is wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;customMetrics&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'restaurant-order-svc-items-ordered'&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;summarize&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Summarising by timestamp and value is required in Grafana, otherwise, every single entry in the table will be displayed as a single point in Grafana. Ordering by timestamp is not logically necessary, but the ‘Stat’ graph type I chose displays a nice background image of the individual data points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ad9nuzcfdym1hf0skl4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ad9nuzcfdym1hf0skl4.png" alt="Items ordered in total (Stat)" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My second chart should show the aggregated number of orders in a given interval. To do this, I first declare an interval variable for the dashboard with an enumeration of values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13bmk3tgp0o4sqep6hc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13bmk3tgp0o4sqep6hc5.png" alt="Custom defined variable" width="486" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The user can select the interval in the frontend. The selected value is then passed to the query as a parameter. The query then aggregates the timestamp according to the interval and then summarises all the values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;customMetrics&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'restaurant-order-svc-items-ordered'&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;summarize&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph I have chosen to display the data is ‘Time series’. In the top left hand corner of the following image, you can see the selectable interval. The legend of this diagram is not aligned with the selected interval. There are 30 minutes between the legend values, but one hour between the data points. I can change this later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4t014obkkcs1m41f8g8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4t014obkkcs1m41f8g8u.png" alt="Items ordered per hour (time series with interval 1h)" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The variable &lt;em&gt;$interval&lt;/em&gt; is defined in Grafana and from the result of the diagram, I see that it works, but KQL’s syntax highlighting does not recognize the variable. It appears as an unknown variable. I did a bit of googling and I think I am not alone with this and I should ignore it for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffupvgzmiszwtcxdprp1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffupvgzmiszwtcxdprp1b.png" alt="KQL does not detect the variables defined in Grafana" width="415" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry is easy to use. I already have experience with metrics and &lt;a href="https://opentracing.io/" rel="noopener noreferrer"&gt;OpenTracing&lt;/a&gt;. I am also a bit biased because I really like the concept of metrics. Do you find it easy to use? Please leave a comment below. I really want to know your opinion.&lt;/p&gt;

&lt;p&gt;Integrating OpenTelemetry with Azure is fairly straightforward if you know what you need to do. However, what components you need and how they communicate with each other is not very clearly documented. For example, the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-configuration?tabs=aspnetcore" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; tells you to use “Azure Monitor”, but then the SDK needs a connection string to Application Insight and not to the Azure Monitor workspace. This, and the fact that there are metrics that are different from ‘&lt;em&gt;customMetrics&lt;/em&gt;’, is confusing.&lt;/p&gt;

&lt;p&gt;It is time-consuming to put all the pieces together for the first time. The problem with the non-queryable results in Application Insights also took me several hours. When things went wrong, I could not find detailed logs to figure out which component was causing the problem. I had previously used an OpenTelemetry Exporter on my local host, which at least gave me a log of the data received.&lt;/p&gt;

&lt;p&gt;The next pain point is the additional cost of the Azure components. If you already have a Prometheus for your Kubernetes cluster, why not store the metrics there? I guess the Azure Monitor OpenTelemetry Distro is aimed at people who don’t have Prometheus and just want to use observability for &lt;a href="https://azure.microsoft.com/en-us/products/container-apps" rel="noopener noreferrer"&gt;Azure Container Apps&lt;/a&gt;. I think for them the Distro gives an easy first impression of the subject matter. And to make it even easier, I have created this tutorial for you.&lt;/p&gt;

&lt;p&gt;Follow me to read more about OpenTelemetry metrics meets Google Cloud later. More claps will motivate me to write it ❤&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>azure</category>
      <category>metrics</category>
      <category>grafana</category>
    </item>
    <item>
      <title>e1889d6d-945d-436b-b94f-68f268285e7c — WTF? I’m only human</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Sat, 13 Jul 2024 12:42:20 +0000</pubDate>
      <link>https://dev.to/kinneko-de/e1889d6d-945d-436b-b94f-68f268285e7c-wtf-im-only-human-7a6</link>
      <guid>https://dev.to/kinneko-de/e1889d6d-945d-436b-b94f-68f268285e7c-wtf-im-only-human-7a6</guid>
      <description>&lt;h1&gt;
  
  
  Designing human-friendly identifiers is easy - follow these simple guidelines.
&lt;/h1&gt;

&lt;p&gt;If you are just interested in a quick solution, download the tutorial of my C# and Go examples on GitHub. Read on for the motivation behind the solution.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-humanfriendly-id-csharp" rel="noopener noreferrer"&gt;
        sample-humanfriendly-id-csharp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample how to create a human-friendly identifier in C#
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;sample-humanfriendly-id-csharp&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Sample how to create a human-friendly identifier in C#&lt;/p&gt;
&lt;p&gt;&lt;a href="https://codecov.io/gh/KinNeko-De/sample-humanfriendly-id-csharp" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ef2cfc33068b1a6e684b7fee4178b6eaf8677ed9e577bfb2fa7c3dacdc1976a6/68747470733a2f2f636f6465636f762e696f2f67682f4b696e4e656b6f2d44652f73616d706c652d68756d616e667269656e646c792d69642d6373686172702f67726170682f62616467652e7376673f746f6b656e3d755547416e5456666b34" alt="codecov"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Generate a human friendly id&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;dotnet run --project Sample.HumanFriendlyId/Sample.HumanFriendlyId.csproj&lt;/code&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Motivation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The motivation behind this project is to described in an &lt;a href="https://medium.com/@kinnekode/e1889d6d-945d-436b-b94f-68f268285e7c-wtf-i-m-only-human-d7818de44397" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-humanfriendly-id-csharp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-humanfriendly-id-go" rel="noopener noreferrer"&gt;
        sample-humanfriendly-id-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample how to create a human-friendly identifier in Go
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;sample-humanfriendly-id-go&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Sample how to create a human-friendly identifier in Go&lt;/p&gt;
&lt;p&gt;&lt;a href="https://codecov.io/gh/KinNeko-De/sample-humanfriendly-id-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0cbc4fc8dcdd1a98a42f7049f575adec5a2c343177d3df430406e7b7170ac934/68747470733a2f2f636f6465636f762e696f2f67682f4b696e4e656b6f2d44652f73616d706c652d68756d616e667269656e646c792d69642d676f2f67726170682f62616467652e7376673f746f6b656e3d47327a35524661746941" alt="codecov"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Generate a human friendly id&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;go run cmd/humanfriendly-id/main.go&lt;/code&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Motivation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The motivation behind this project is to described in an &lt;a href="https://medium.com/@kinnekode/e1889d6d-945d-436b-b94f-68f268285e7c-wtf-i-m-only-human-d7818de44397" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-humanfriendly-id-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;h2&gt;
  
  
  Accept the differences - Humans are not systems and vice versa
&lt;/h2&gt;

&lt;p&gt;The debate is as old as mankind. Some argue that values in systems must be human-readable because the user is human. Others want to force the user to deal with technical values because they are already used by the system.&lt;/p&gt;

&lt;p&gt;Have you ever typed a UUID from a letter? If so, I have great respect and at the same time pity for you. UUIDs are designed for systems, not for people. On the other hand, business identifiers, like invoice numbers, are not designed for reliable relationships in systems, especially not in modern distributed microservices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6udxgrzz2tocilji8sx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6udxgrzz2tocilji8sx.jpg" alt="generated by https://deepai.org/machine-learning-model/text2img" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In computer science, we have the principle of '&lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Separation_of_concerns" rel="noopener noreferrer"&gt;separation of concerns&lt;/a&gt;&lt;/em&gt;'. It says that you should divide your program into distinct sections. If you follow this principle, you benefit from more freedom of use.&lt;/p&gt;

&lt;p&gt;It can also save us unnecessary headaches when designing the solution. We can still use UUIDs for internal system IDs. In the rare cases where we need to communicate with a human, use a specially designed human-friendly identifier. I will now give a guideline on how to design it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements - select what needs to be met
&lt;/h2&gt;

&lt;p&gt;Requirements always depend on the context. I will provide a list of requirements based on my experience. If some of the requirements are not necessary for you, feel free to leave some out. If you have additional requirements, please leave a comment. I would appreciate it if you explain why you have these requirements so that I can learn from you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;(R)&lt;/strong&gt; It must be easy to read by humans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(P)&lt;/strong&gt; Errors of perception must be avoided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(W)&lt;/strong&gt; It must be easy to spell, hand-write, and type by humans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(O)&lt;/strong&gt; It must be easy to read by optical character recognition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(S)&lt;/strong&gt; It must be hard to guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(U)&lt;/strong&gt; It must be unique for the related entity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;R&lt;/em&gt;&lt;/strong&gt; This ensures that users can quickly and accurately interpret the identifier without confusion or difficulty. By prioritizing readability, we aim to improve the user experience and streamline the use of the identifier in different contexts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;P&lt;/em&gt;&lt;/strong&gt; It is important to design the identifier in a way that minimizes misinterpretation. By prioritizing clarity and distinctiveness, we aim to reduce the risk of misreading the identifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;W&lt;/em&gt;&lt;/strong&gt; This ensures ease of use across multiple communication channels and input methods. We aim to make communication and data entry seamless for users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;O&lt;/em&gt;&lt;/strong&gt; This ensures that the identifier can be accurately scanned and processed by machines, increasing efficiency and automation in various applications. By designing the identifier with OCR readability in mind, we ensure compatibility with modern technology and data processing workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;S&lt;/em&gt;&lt;/strong&gt; It is important to make the identifier sufficiently complex and unpredictable to prevent unauthorized access or manipulation. Our goal is to enhance security and protect the integrity of the identifier and associated data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;U&lt;/em&gt;&lt;/strong&gt; By enforcing uniqueness, we maintain data integrity and facilitate accurate referencing and retrieval of information. Each entity is uniquely identified without any overlap or duplication.&lt;/p&gt;

&lt;p&gt;From time to time in this article, I will refer to these requirements with the letter at the beginning. This means that I have taken this particular requirement into account when designing a human-friendly identifier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Characters - make the identifier easy to read
&lt;/h2&gt;

&lt;p&gt;To generate the identifier, we need a specific set of characters. We have to choose this collection carefully because it affects readability &lt;strong&gt;(R)&lt;/strong&gt;, perception &lt;strong&gt;(P)&lt;/strong&gt;, and writing &lt;strong&gt;(W)&lt;/strong&gt;. Ensuring the suitability of these characters is essential to their effectiveness in interacting with humans.&lt;/p&gt;

&lt;p&gt;In addition to the characters, the choice of font we use is important in terms of readability &lt;strong&gt;(R)&lt;/strong&gt;. Have a look at the appearance of the following characters in print. As you can see in the image below, it is hard to tell the difference even when you see them side by side. Imagine how difficult it would be if you could only see one character. We need to choose a font that makes it easier to distinguish between the characters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyy1n7nyhpf8ot60e87u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyy1n7nyhpf8ot60e87u.png" alt="big i, small l, and the number 1 in the font: Calibri, Arial, Times New Roman, and Courier New" width="800" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The use of both lowercase and uppercase letters also raises the question of whether the reader needs to use a case-sensitive identifier in a written or typed response. A program cannot easily normalize the input to avoid errors. If the character set contains only uppercase letters, we can easily convert any input to uppercase. This choice also removes the small &lt;em&gt;"l"&lt;/em&gt; problem.&lt;/p&gt;

&lt;p&gt;We need as many characters as possible to ensure uniqueness &lt;strong&gt;(U)&lt;/strong&gt; and to keep the identifier short &lt;strong&gt;(R)&lt;/strong&gt;. By adding numbers, we increase the number of possible characters. On the other hand, it creates a problem with the uppercase &lt;em&gt;"O"&lt;/em&gt; and the zero, as they look very similar in any font.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngvs6x0ws1583v780rzc.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngvs6x0ws1583v780rzc.PNG" alt="big o and zero in the font: Calibri, Arial, Times New Roman, and Courier New" width="800" height="129"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can remove one of them from the set. As a reader of the identifier, you do not know that we omitted only one character and which one. I have seen several times that there is a note describing whether both zero and &lt;em&gt;"O"&lt;/em&gt; or only one of them is used. If we remove both characters, we avoid any kind of confusion for the human &lt;strong&gt;(R)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you live in a country like Germany, where they even print out electronic receipts and still use real paper letters (to the rest of the world: this article was written in 2024!). It is also important to consider that someone will reply handwritten &lt;strong&gt;(W)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To OCR &lt;strong&gt;(O)&lt;/strong&gt;, a handwritten &lt;em&gt;"1"&lt;/em&gt; may look like an &lt;em&gt;"I"&lt;/em&gt;. Removing just one of them, preferably the &lt;em&gt;"I"&lt;/em&gt;, is useful in this case because we can normalize the input to replace a recognized &lt;em&gt;"I"&lt;/em&gt; with a &lt;em&gt;"1"&lt;/em&gt;. The number one is more likely to be recognized differently from other characters. By excluding the &lt;em&gt;"I"&lt;/em&gt;, we also make it easier to recognize the characters as there is no conflict with the small &lt;em&gt;"l"&lt;/em&gt; &lt;strong&gt;(R)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This leaves us with the following charset:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ABCDEFGHJKLMNPQRSTUVWXYZ12345679&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Length - how much do you really need?
&lt;/h2&gt;

&lt;p&gt;In general, it is a good idea to use a wide set of characters as it affects the number of unique identifiers you can generate &lt;strong&gt;(U)&lt;/strong&gt;. When you use a narrow character set because of the reason above, you have to increase the length of your identifier to allow more recombination.&lt;/p&gt;

&lt;p&gt;The number of possible combinations is calculated as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsh6q81618twe07f2xvd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsh6q81618twe07f2xvd.png" alt="n is the number of characters in the set, k is the length of the identifier" width="45" height="36"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have to keep the identifier reasonably short. Long identifiers can be difficult to remember and read &lt;strong&gt;(R)&lt;/strong&gt;. If you ever run out of combinations because your business scales up like hell, just increase the length for new identifiers. You can keep the old one instead of setting yourself up for success which will never happen if you use too long identifiers.&lt;/p&gt;

&lt;p&gt;With a length of only 8, there are 1,099,511,627,776 combinations, which is enough for most of the time. That is 140 times the number of people in the world. Look at this example, close your eyes, and try to remember it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AKL1U9ZW&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the other hand, sometimes an identifier needs to be hard to guess &lt;strong&gt;(S)&lt;/strong&gt; when used in a security context. Using a length that is too small makes it more likely that a valid identifier will be found by brute-force attacks. It also makes it more likely that a typo will lead to another valid identifier &lt;strong&gt;(U)&lt;/strong&gt;&lt;strong&gt;(W)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using the character set from the example above and a length of 16 we get the astronomically large number of 1,208,925,819,614,629,174,706,176 combinations. Try to remember the following example as you did it before. Not so easy anymore, right?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AKL1U9ZWKWA6QPAB&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Display - the best way to recognize it
&lt;/h2&gt;

&lt;p&gt;The identifier itself is a random, uninterrupted combination of characters. It has no obvious meaning or context. The human brain tends to look for familiar patterns. Since there are none here, it is very difficult to recognize each of the characters.&lt;/p&gt;

&lt;p&gt;Remember the &lt;a href="https://en.wikipedia.org/wiki/Tally_marks" rel="noopener noreferrer"&gt;tally marks&lt;/a&gt; we used when we were kids? After drawing four vertical lines, the fifth line is drawn diagonally across the previous four lines. This groups five of the strokes together to make counting easier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiftt77z2arl77be4vhhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiftt77z2arl77be4vhhj.png" alt="Both times twenty elements" width="674" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grouping items helps people to understand and organize information more quickly and efficiently. This is also known as the "&lt;a href="https://en.wikipedia.org/wiki/Principles_of_grouping" rel="noopener noreferrer"&gt;law of proximity&lt;/a&gt;". We cannot stroke our characters, but we can use a separator between them. We can use dashes, minuses, hyphens, or slashes, as UUIDs use minus. But when you type or spell the identifier, don't you always wonder if you can leave out the separators?&lt;/p&gt;

&lt;p&gt;As children, we would never write the tally marks with a minus or even a slash between them. Such characters were not in our minds at that time. Children always use the simplest and most obvious solution. After five strokes, we leave a little space to group them even more. In the following example the second line is easier to read, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx5fu63cxvqmpib6wffx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx5fu63cxvqmpib6wffx.png" alt="Grouping by distance" width="640" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do the same simple trick with our identifier and use spaces as separators &lt;strong&gt;(P)&lt;/strong&gt;. These separators are not even visible to humans. When we process the input for a computer we can normalize it by removing all the spaces between the characters. This makes it as easy as possible for humans to type the identifier. And when you spell the identifier hopefully you will not even notice that there is a hidden separator.&lt;/p&gt;

&lt;p&gt;After how many elements should we use a separator? According to "&lt;a href="https://en.wikipedia.org/wiki/Miller's_law" rel="noopener noreferrer"&gt;Miller's Law&lt;/a&gt;", people can remember about seven elements. The exact number depends on the person, so we will use the lower limit to include everyone &lt;strong&gt;(P)&lt;/strong&gt;. Other publications tend to define this lower bound as only four elements. This is exactly the number we used with the tally marks. See the result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AKL1 U9ZW HKAL BCND&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you may have noticed, we are not the only ones using this rule. IBAN or credit card numbers are grouped the same way. I am pretty sure there are more real-life examples in your cultural context to convince you that this idea is widely adopted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Random - at least pseudo and safe
&lt;/h2&gt;

&lt;p&gt;The identifier should be randomly generated using a cryptographically secure algorithm &lt;strong&gt;(S)&lt;/strong&gt;. This makes it easier for you to pass a pen test, especially in a security-critical scenario. Proactively demonstrating that you have taken this into account during design increases confidence in your overall solution.&lt;/p&gt;

&lt;p&gt;Even in non-security scenarios, you want to avoid using auto-incrementing numbers. The recipient of an invoice with invoice number &lt;em&gt;"5"&lt;/em&gt; does not need to know that you have only written five invoices this year. Hiding this information is also in the best interest of the business.&lt;/p&gt;

&lt;p&gt;Note that the identifier is not globally unique like UUIDs. The generation algorithm is pseudorandom, and the length is shorter so that we have fewer combinations. To make it unique for a given entity, you must ensure this using a shared state between concurrent processes, such as a unique constraint on a database &lt;strong&gt;(U)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use the character set ABCDEFGHJKLMNPQRSTUVWXYZ12345679&lt;/li&gt;
&lt;li&gt;Generate randomly an identifier with a length of 4 or 8&lt;/li&gt;
&lt;li&gt;To display it to humans insert a space after four characters&lt;/li&gt;
&lt;li&gt;On human input remove spaces and replace &lt;em&gt;"I"&lt;/em&gt; with &lt;em&gt;"1"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do you know all that shit? Are you a real programmer?
&lt;/h2&gt;

&lt;p&gt;Thank you to the &lt;a href="https://cs.uni-paderborn.de/studium/studienangebot/informatik" rel="noopener noreferrer"&gt;University of Paderborn&lt;/a&gt; for teaching me that software engineering is more than just coding. Now it is my time to give something back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code examples
&lt;/h2&gt;

&lt;p&gt;Last but not least check out the code examples in &lt;a href="https://github.com/KinNeko-De/sample-humanfriendly-id-csharp" rel="noopener noreferrer"&gt;C#&lt;/a&gt; and &lt;a href="https://github.com/KinNeko-De/sample-humanfriendly-id-go" rel="noopener noreferrer"&gt;Go&lt;/a&gt; on my GitHub page. I would be happy if you could give them a star. Thanks for reading my article ❤&lt;/p&gt;

</description>
      <category>identifier</category>
      <category>humanfriendly</category>
      <category>uuid</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Launch React from Visual Studio Code</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Sun, 07 Jul 2024 19:33:50 +0000</pubDate>
      <link>https://dev.to/kinneko-de/launch-react-from-visual-studio-code-24do</link>
      <guid>https://dev.to/kinneko-de/launch-react-from-visual-studio-code-24do</guid>
      <description>&lt;p&gt;How to launch a React application from Visual Studio Code including Firefox and Chrome using React Script 5.0.1 and Windows&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kzgysfnmw6jkoigltm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kzgysfnmw6jkoigltm5.png" alt="Visual Studio Code meets React" width="494" height="284"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I want to launch my React application from Visual Studio Code with a single click. I hate typing a lot in the terminal to launch an application. I prefer to use scripts. There has been a change to &lt;em&gt;react-scripts&lt;/em&gt;, so other solutions on the web will no longer work. So here is my current working solution!&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Create different scripts in your package.json to start react-script with different options depending on your current needs. First, set the browser to &lt;em&gt;none&lt;/em&gt; to prevent &lt;em&gt;react-scripts&lt;/em&gt; from launching the browser itself. Then launch the browser of your choice, followed by starting the application. In your launch.json from Visual Studio Code call the appropriate npm scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start in Firefox&lt;/strong&gt;&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="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startfirefox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"set &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;BROWSER=none&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; start firefox http://localhost:3000 &amp;amp;&amp;amp; react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Start in Firefox's private Window&lt;/strong&gt;&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="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startfirefoxprivate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"set &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;BROWSER=none&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; start firefox -private-window http://localhost:3000 &amp;amp;&amp;amp; react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Start in Chrome&lt;/strong&gt;&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="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startchrome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"set &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;BROWSER=none&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; start chrome http://localhost:3000 &amp;amp;&amp;amp; react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Start in Chrome incognito&lt;/strong&gt;&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="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startchromeincognito"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"set &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;BROWSER=none&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; start chrome http://localhost:3000 /incognito &amp;amp;&amp;amp; react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Launch from Visual Studio Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let Visual Studio Code create a &lt;em&gt;launch.json&lt;/em&gt; for you. Replace the generated configurations with the following JSON:&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="nl"&gt;"configurations"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start using Default browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeExecutable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeArgs"&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="s2"&gt;"run-script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"start"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start using Chrome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeExecutable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeArgs"&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="s2"&gt;"run-script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startchrome"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start using Firefox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeExecutable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeArgs"&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="s2"&gt;"run-script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startfirefox"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start using Chrome Incognito"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeExecutable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeArgs"&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="s2"&gt;"run-script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startchromeincognito"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start using Firefox private"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeExecutable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeArgs"&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="s2"&gt;"run-script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startfirefoxprivate"&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="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This solution works for Windows and the current version of react-script. Since it only uses OS-specific commands, you can easily adapt it to your OS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version used&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tutorial was created based on&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;react-scripts: 5.0.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Samples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created a sample application on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-react-vscode" rel="noopener noreferrer"&gt;
        sample-react-vscode
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application how to start a React app from Visual Studio Code in different browsers
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motiviation&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Sample application how to start a React app from Visual Studio Code in different browsers.&lt;/p&gt;

&lt;p&gt;See my &lt;a href="https://medium.com/@kinneko-de/cb9292c7e23a" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt; how to create this by yourself.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-react-vscode" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Credits goes to this comment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/facebook/create-react-app/issues/11873#issuecomment-1030708183" rel="noopener noreferrer"&gt;https://github.com/facebook/create-react-app/issues/11873#issuecomment-1030708183&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>vscode</category>
      <category>tutorial</category>
      <category>chrome</category>
    </item>
    <item>
      <title>React, Typescript, and CD to GitHub Pages (2024)</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Thu, 04 Jul 2024 19:44:28 +0000</pubDate>
      <link>https://dev.to/kinneko-de/react-typescript-and-cd-to-github-pages-2024-3n3h</link>
      <guid>https://dev.to/kinneko-de/react-typescript-and-cd-to-github-pages-2024-3n3h</guid>
      <description>&lt;p&gt;How to set up a typescript-based React application and deploy it directly to GitHub Pages using a CD GitHub action. A practical guide for 2024.&lt;/p&gt;




&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I was new to React, Typescript, and GitHub Pages when I started this. I fell into some traps, mainly due to outdated suggestions, while creating my proof of concept. To avoid this for you, I will give you a quick and easy walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create React app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/docs/nodejs/reactjs-tutorial" rel="noopener noreferrer"&gt;Install the following prerequisites: Git and Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the React app&lt;/strong&gt;&lt;br&gt;
I will use &lt;a href="https://create-react-app.dev/docs/getting-started/" rel="noopener noreferrer"&gt;create-react-app&lt;/a&gt; to create the initial project setup. I start a terminal in the root folder where all my git repositories are located. A subfolder with the name of your application will be created.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-react-app sample-react-typescript-githubpages --template typescript&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/65505209/create-react-app-with-template-typescript-not-creating-tsx-files" rel="noopener noreferrer"&gt;The &lt;em&gt;cra-template-typescript&lt;/em&gt; package does not need to be installed globally&lt;/a&gt;. I have uninstalled it for this tutorial. &lt;a href="https://xfor.medium.com/setting-up-your-create-react-app-project-with-typescript-vscode-d83a3728b45e" rel="noopener noreferrer"&gt;There is also no more switch &lt;em&gt;typescript&lt;/em&gt; anymore for &lt;em&gt;create-react-app&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The folder now contains a &lt;em&gt;.git&lt;/em&gt; folder. I am removing it to push directly from Visual Studio Code to GitHub. I also renamed my ‘&lt;em&gt;master&lt;/em&gt;’ to ‘&lt;em&gt;main&lt;/em&gt;’ branch which has implications for the GitHub workflow I will introduce later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vulnerability warnings from npm&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I get a notice that vulnerabilities have been found.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;8 vulnerabilities (2 moderate, 6 high)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Npm suggests that I should run ‘&lt;em&gt;npm audit fix&lt;/em&gt;’ to fix this. But this only introduces more vulnerabilities. &lt;a href="https://github.com/facebook/create-react-app/issues/11174" rel="noopener noreferrer"&gt;Instead, I move the ‘react-scripts’ dependency in the &lt;em&gt;package.json&lt;/em&gt; to ‘devDependencies’&lt;/a&gt;. After this step, I can run npm audit without the dependencies that are only used to develop the page.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm audit --omit=dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build and continuous deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I create a &lt;a href="https://docs.github.com/en/actions/using-workflows/about-workflows" rel="noopener noreferrer"&gt;.github/workflows folder and a &lt;em&gt;builddeploy.yaml&lt;/em&gt; with the following content in it&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy React to GitHub Pages

on:
  push:
    branches:
      - main # name of the branch you are pushing to

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
      - name: Install Dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: 'build/.'
  deploy:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: 'https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/'
    steps:
      - name: Setup Pages
        uses: actions/configure-pages@v5
      - name: Deploy
        uses: actions/deploy-pages@v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This workflow uses the GitHub action &lt;a href="https://github.com/actions/deploy-pages" rel="noopener noreferrer"&gt;&lt;em&gt;actions/deploy-pages&lt;/em&gt;&lt;/a&gt; to deploy the artifact from &lt;a href="https://github.com/actions/upload-pages-artifact" rel="noopener noreferrer"&gt;&lt;em&gt;actions/upload-pages-artifact&lt;/em&gt;&lt;/a&gt; to GitHub pages. I can also view and download the &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages/actions/runs/8949805152/attempts/1" rel="noopener noreferrer"&gt;generated artifact&lt;/a&gt; in the workflow run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntbyzyesnzqg15brj85p.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntbyzyesnzqg15brj85p.PNG" alt="Generated artifact in the workflow run" width="631" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I push the file, a workflow run automatically starts. It fails because the default configuration of GitHub is currently to deploy GitHub pages from a specific branch. So I need to change the configuration of my GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqutipw0kp4k6w7pucy6.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqutipw0kp4k6w7pucy6.PNG" alt="Initial setup of a GitHub Repository" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I need to change the selection of the drop down box to ‘GitHub Actions’. As you can see ‘Deploy from a branch’ is declared as ‘classic’, which is usually a nicer word for ‘legacy’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6umr9f6cvepnmh552vxw.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6umr9f6cvepnmh552vxw.PNG" alt="Select GitHub Actions" width="332" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I need to re-run the failed jobs of my GitHub Action workflow. GitHub will only execute the job ‘deploy’ again. The already built artifact ‘github-pages’ will be reused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4x2llnisynrbrvo5m96v.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4x2llnisynrbrvo5m96v.PNG" alt="Re-run workflow" width="787" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline is now green and I see a link in my execution that I can click to go to my deployed GitHub page. The page is there because I am not getting a 404 error. But the page shows nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activating npm publish&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason for this is a &lt;a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json#private" rel="noopener noreferrer"&gt;configuration in the &lt;em&gt;package.json&lt;/em&gt;&lt;/a&gt; in my React app that prevents the page from being published. I have to change it to see my page. I also have to add the link to my GitHub Page, even though GitHub Copilot says I do not have to and the problem is somewhere else.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
...
"private": false,
"homepage": "https://kinneko-de.github.io/sample-react-typescript-githubpages/",
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After pushing and deploying I finally see &lt;a href="https://kinneko-de.github.io/sample-react-typescript-githubpages/" rel="noopener noreferrer"&gt;my GitHub Page&lt;/a&gt;. You can see the full code in my &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9956oo7hi0lx2bg1ay5v.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9956oo7hi0lx2bg1ay5v.PNG" alt="Deployed page" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Even with the traps, it took me only two hours to finally deploy my first React app. React, GitHub and the community have done a great job making the developer's life easy. Thank you for that ❤&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versions used&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I will try to update this guide to the latest version to keep up with breaking changes in the tools. If you find something that does not work for you, please leave a comment. For a better overview, I list all versions used while writing this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20.12.2&lt;/li&gt;
&lt;li&gt;NPM: 9.8.1&lt;/li&gt;
&lt;li&gt;cra-template-typescript: 1.2.0&lt;/li&gt;
&lt;li&gt;create-react-app: 5.0.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Samples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created a &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages" rel="noopener noreferrer"&gt;sample application on GitHub&lt;/a&gt;:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/KinNeko-De" rel="noopener noreferrer"&gt;
        KinNeko-De
      &lt;/a&gt; / &lt;a href="https://github.com/KinNeko-De/sample-react-typescript-githubpages" rel="noopener noreferrer"&gt;
        sample-react-typescript-githubpages
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application how to setup a React app with typescript and deploy it to GitHub Pages.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Motiviation&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Sample application how to setup a React app with typescript and deploy it to GitHub Pages.&lt;/p&gt;

&lt;p&gt;See my &lt;a href="https://medium.com/@kinneko-de/92d4f19d71d7" rel="nofollow noopener noreferrer"&gt;article&lt;/a&gt; how to create this by yourself.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/KinNeko-De/sample-react-typescript-githubpages" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Here you can see the &lt;a href="https://kinneko-de.github.io/sample-react-typescript-githubpages/" rel="noopener noreferrer"&gt;deployed version&lt;/a&gt;:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://kinneko-de.github.io/sample-react-typescript-githubpages/" rel="noopener noreferrer"&gt;
      kinneko-de.github.io
    &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>react</category>
      <category>tutorial</category>
      <category>typescript</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Install Module in Powershell without Install-Module</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Tue, 02 Jul 2024 21:26:27 +0000</pubDate>
      <link>https://dev.to/kinneko-de/install-module-in-powershell-without-install-module-5ae7</link>
      <guid>https://dev.to/kinneko-de/install-module-in-powershell-without-install-module-5ae7</guid>
      <description>&lt;h2&gt;
  
  
  The term ‘Install-Module’ is not recognized as the name of a cmdlet.
&lt;/h2&gt;

&lt;p&gt;Have you ever found yourself in trouble because your Powershell is not working anymore after a colleague told you to just delete every module of Windows Powershell? No problem… you can just use Install-Module to reinstall everything. Oops… Install-Module is part of the module you just deleted. Here is the guide on how to escape this chicken and egg problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fmeffvuoks88cjikxrdmb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fmeffvuoks88cjikxrdmb.jpg" alt="Chicken - Egg - Problem"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to restore Install-Module manually
&lt;/h2&gt;

&lt;p&gt;To restore functionality, you need two modules. &lt;em&gt;PowerShellGet&lt;/em&gt; and its dependency &lt;em&gt;PackageManagement&lt;/em&gt;. You can get them from the &lt;a href="https://www.powershellgallery.com/" rel="noopener noreferrer"&gt;Powershell Gallery&lt;/a&gt;. Search for the modules and click on &lt;em&gt;‘Manual Download’&lt;/em&gt;. There you will also find a helpful &lt;a href="https://learn.microsoft.com/en-us/powershell/gallery/how-to/working-with-packages/manual-download?view=powershellget-3.x" rel="noopener noreferrer"&gt;description of how to install these Nuget packages manually&lt;/a&gt;. Follow the instructions step by step. Only in step 5, you should first create a subfolder containing the version of the module. Restart your PowerShell and you can install all other modules now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are you a developer and too lazy to do things manually?
&lt;/h2&gt;

&lt;p&gt;Since you already use Powershell, why not just use Powershell?&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Finally, copy the two folders &lt;em&gt;PowerShellGet&lt;/em&gt; and &lt;em&gt;PackageManagement&lt;/em&gt; to your Powershell Module folder. You will get a warning that the files are marked as untrusted. If anyone finds a solution, please leave a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are simply smart and pragmatic
&lt;/h2&gt;

&lt;p&gt;Use the Windows Recycle Bin to restore the PowerShellGet and PackageManagement modules.&lt;/p&gt;




&lt;p&gt;Please also leave a comment if anyone else is experiencing the same problem. Together we are strong 💪♥️❤♥️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fnepjx9ua6o1vm1jqn1mp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnepjx9ua6o1vm1jqn1mp.jpg" alt="Love for all"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Foto von &lt;a href="https://unsplash.com/de/@timmarshall?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Tim Marshall&lt;/a&gt; auf &lt;a href="https://unsplash.com/de/fotos/hande-die-zusammen-mit-roter-herzfarbe-geformt-wurden-cAtzHUz7Z8g?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>help</category>
      <category>windows</category>
      <category>installmodul</category>
    </item>
    <item>
      <title>Don’t trust AI, trust tests</title>
      <dc:creator>Nils Diekmann</dc:creator>
      <pubDate>Mon, 01 Jul 2024 23:03:52 +0000</pubDate>
      <link>https://dev.to/kinneko-de/dont-trust-ai-trust-tests-1noj</link>
      <guid>https://dev.to/kinneko-de/dont-trust-ai-trust-tests-1noj</guid>
      <description>&lt;p&gt;In my very first story, I talked about my experience with AI in the form of GitHub Copilot. It betrayed me again. But I was gently caught by my true lover: UnitTest&lt;/p&gt;




&lt;p&gt;I am currently working on code that receives a file using a &lt;a href="https://grpc.io/docs/languages/go/basics/"&gt;grpc stream&lt;/a&gt;. The file is sent in byte chunks. Go has a &lt;a href="https://pkg.go.dev/net/http#DetectContentType"&gt;nice functionality where you can determine the media type&lt;/a&gt; of a file from the first 512 bytes. I do not want to keep all the bytes sent in memory so my goal is to have a byte array of exactly 512 bytes at the end to sniff the media type. All other bytes should be written to a physical file storage and then discarded.&lt;/p&gt;

&lt;p&gt;I am not that experienced in working with arrays and slices in Golang, nor in other languages. For my test cases, I choose to test chunks smaller than 512 bytes, exactly 512 bytes, and larger than 512 bytes. If you wonder why, check out what &lt;a href="https://en.wikipedia.org/wiki/Boundary-value_analysis"&gt;boundary tests&lt;/a&gt; are. I have a lot of experience in writing tests.&lt;/p&gt;

&lt;p&gt;Not surprisingly, the test with only 4 bytes failed. It took me some time to get deeper into the go standard libraries. I (mis)use tests for this because it is so easy to write, execute, and debug small snippets of code. Here is my learning example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;//target2 := [6]int{}&lt;/span&gt;
 &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
 &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;size&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;AI helps me with explanations and gives me a better understanding of how to use slices in Go. It is always a pleasure for an old man to learn something from the youth full of new ideas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbzf4jg4pdvvok46sz1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbzf4jg4pdvvok46sz1n.png" alt="Helpful explanation" width="566" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the help of GitHub Copilot, my first and second tests pass. Here is the code I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;sniff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;totalFileSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test for more than 512 bytes failed because my slice was out of range. Maybe it is time for me to admit to myself that I still have a lot to learn. GitHub Copilot came up with the following solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;totalFileSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&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;In my arrogance as an old wise man, I thought I could do better. In my defense, &lt;em&gt;chunkMessage.Chunk&lt;/em&gt; cannot be modified because all bytes must be copied in the final file. I implemented a shorter version that worked well, at least in my eyes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;missingBytes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt;
  &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;totalFileSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;missingBytes&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 suggested this shorter version to the AI and asked for its opinion on my code. The AI was very pleased with my solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7s49cqz0ss6aj0jabss.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7s49cqz0ss6aj0jabss.PNG" alt="Right, ..." width="557" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…but when I reran the tests, the scales fell from my eyes. GitHub Copilot is right, I do not copy more than 512 bytes. But in the test case where I have less than 512 bytes, this code does not work. The AI chose an answer to please me and avoided pointing out what I’d done wrong. I ended up with the code below. This is the best of both worlds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;missingBytes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt;
  &lt;span class="n"&gt;remaingBytesInChunk&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remaingBytesInChunk&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;missingBytes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;missingBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remaingBytesInChunk&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;totalFileSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;chunkMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;missingBytes&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 strongly believe that a software engineer has to write tests. Tests are sometimes hard to write, it is stupid boring work and you have to spend time to maintain them. But like a mother, they secure your life and take care of you. With them, I can sleep like a baby without worries. Now the AI does the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30ud7cvdt1yaz4c8ug9l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30ud7cvdt1yaz4c8ug9l.jpg" alt="Mummy loves baby" width="640" height="427"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Foto von &lt;a href="https://unsplash.com/de/@isaacquesada?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Isaac Quesada&lt;/a&gt; auf &lt;a href="https://unsplash.com/de/fotos/frau-im-weissen-t-shirt-mit-rundhalsausschnitt-tragt-baby-DMcNqigMn1c?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sleep well, AI. UnitTest loves and protects you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubcopilot</category>
      <category>unittest</category>
      <category>go</category>
    </item>
  </channel>
</rss>
